authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 22:43:16-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
log551aebc6046f23eabcf0ea6dddab73619b83b2da
tree981b63bc822bd1e087e0be9d16fd49a9dc13f59d
parent9de85ea16e5228ae68ceb3b52b03ab66f8f1b17c

Add default collaboration mode preset for Codex turn starts

- Send `collaborationMode` for both `default` and `plan` interactions - Add default-mode developer instructions and preserve plan-mode behavior - Update server/web tests for default collaboration mode and model constant usage

3 files changed, 60 insertions(+), 7 deletions(-)

apps/server/src/codexAppServerManager.test.ts+31
...@@ -7,6 +7,7 @@ import { ApprovalRequestId, ThreadId } from "@t3tools/contracts";...@@ -7,6 +7,7 @@ import { ApprovalRequestId, ThreadId } from "@t3tools/contracts";
77
8import {8import {
9 buildCodexInitializeParams,9 buildCodexInitializeParams,
10 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
10 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,11 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
11 CodexAppServerManager,12 CodexAppServerManager,
12 classifyCodexStderrLine,13 classifyCodexStderrLine,
...@@ -346,6 +347,36 @@ describe("sendTurn", () => {...@@ -346,6 +347,36 @@ describe("sendTurn", () => {
346 });347 });
347 });348 });
348349
350 it("passes Codex default mode as a collaboration preset on turn/start", async () => {
351 const { manager, context, sendRequest } = createSendTurnHarness();
352
353 await manager.sendTurn({
354 threadId: asThreadId("thread_1"),
355 input: "PLEASE IMPLEMENT THIS PLAN:\n- step 1",
356 interactionMode: "default",
357 });
358
359 expect(sendRequest).toHaveBeenCalledWith(context, "turn/start", {
360 threadId: "thread_1",
361 input: [
362 {
363 type: "text",
364 text: "PLEASE IMPLEMENT THIS PLAN:\n- step 1",
365 text_elements: [],
366 },
367 ],
368 model: "gpt-5.3-codex",
369 collaborationMode: {
370 mode: "default",
371 settings: {
372 model: "gpt-5.3-codex",
373 reasoning_effort: "medium",
374 developer_instructions: CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
375 },
376 },
377 });
378 });
379
349 it("rejects empty turn input", async () => {380 it("rejects empty turn input", async () => {
350 const { manager } = createSendTurnHarness();381 const { manager } = createSendTurnHarness();
351382
apps/server/src/codexAppServerManager.ts+21-5
...@@ -257,6 +257,19 @@ Do not ask "should I proceed?" in the final output. The user can easily switch o...@@ -257,6 +257,19 @@ Do not ask "should I proceed?" in the final output. The user can easily switch o
257Only produce at most one \`<proposed_plan>\` block per turn, and only when you are presenting a complete spec.257Only produce at most one \`<proposed_plan>\` block per turn, and only when you are presenting a complete spec.
258</collaboration_mode>`;258</collaboration_mode>`;
259259
260export const CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS = `<collaboration_mode># Collaboration Mode: Default
261
262You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
263
264Your active mode changes only when new developer instructions with a different \`<collaboration_mode>...</collaboration_mode>\` change it; user requests or tool descriptions do not change mode by themselves. Known mode names are Default and Plan.
265
266## request_user_input availability
267
268The \`request_user_input\` tool is unavailable in Default mode. If you call it while in Default mode, it will return an error.
269
270In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message.
271</collaboration_mode>`;
272
260function mapCodexRuntimeMode(runtimeMode: RuntimeMode): {273function mapCodexRuntimeMode(runtimeMode: RuntimeMode): {
261 readonly approvalPolicy: "on-request" | "never";274 readonly approvalPolicy: "on-request" | "never";
262 readonly sandbox: "workspace-write" | "danger-full-access";275 readonly sandbox: "workspace-write" | "danger-full-access";
...@@ -326,7 +339,7 @@ function buildCodexCollaborationMode(input: {...@@ -326,7 +339,7 @@ function buildCodexCollaborationMode(input: {
326 readonly effort?: string;339 readonly effort?: string;
327}):340}):
328 | {341 | {
329 mode: "plan";342 mode: "default" | "plan";
330 settings: {343 settings: {
331 model: string;344 model: string;
332 reasoning_effort: string;345 reasoning_effort: string;
...@@ -334,16 +347,19 @@ function buildCodexCollaborationMode(input: {...@@ -334,16 +347,19 @@ function buildCodexCollaborationMode(input: {
334 };347 };
335 }348 }
336 | undefined {349 | undefined {
337 if (input.interactionMode !== "plan") {350 if (input.interactionMode === undefined) {
338 return undefined;351 return undefined;
339 }352 }
340 const model = normalizeCodexModelSlug(input.model) ?? "gpt-5.3-codex";353 const model = normalizeCodexModelSlug(input.model) ?? "gpt-5.3-codex";
341 return {354 return {
342 mode: "plan",355 mode: input.interactionMode,
343 settings: {356 settings: {
344 model,357 model,
345 reasoning_effort: input.effort ?? "medium",358 reasoning_effort: input.effort ?? "medium",
346 developer_instructions: CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,359 developer_instructions:
360 input.interactionMode === "plan"
361 ? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS
362 : CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
347 },363 },
348 };364 };
349}365}
...@@ -644,7 +660,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve...@@ -644,7 +660,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
644 effort?: string;660 effort?: string;
645 serviceTier?: string;661 serviceTier?: string;
646 collaborationMode?: {662 collaborationMode?: {
647 mode: "plan";663 mode: "default" | "plan";
648 settings: {664 settings: {
649 model: string;665 model: string;
650 reasoning_effort: string;666 reasoning_effort: string;
apps/web/src/store.test.ts+8-2
...@@ -1,4 +1,10 @@...@@ -1,4 +1,10 @@
1import { ProjectId, ThreadId, TurnId, type OrchestrationReadModel } from "@t3tools/contracts";1import {
2 DEFAULT_MODEL_BY_PROVIDER,
3 ProjectId,
4 ThreadId,
5 TurnId,
6 type OrchestrationReadModel,
7} from "@t3tools/contracts";
2import { describe, expect, it } from "vitest";8import { describe, expect, it } from "vitest";
39
4import { markThreadUnread, syncServerReadModel, type AppState } from "./store";10import { markThreadUnread, syncServerReadModel, type AppState } from "./store";
...@@ -139,6 +145,6 @@ describe("store read model sync", () => {...@@ -139,6 +145,6 @@ describe("store read model sync", () => {
139145
140 const next = syncServerReadModel(initialState, readModel);146 const next = syncServerReadModel(initialState, readModel);
141147
142 expect(next.threads[0]?.model).toBe("gpt-5.3-codex");148 expect(next.threads[0]?.model).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
143 });149 });
144});150});