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";
77
88import {
99 buildCodexInitializeParams,
10 CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS,
1011 CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS,
1112 CodexAppServerManager,
1213 classifyCodexStderrLine,
......@@ -346,6 +347,36 @@ describe("sendTurn", () => {
346347 });
347348 });
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
349380 it("rejects empty turn input", async () => {
350381 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
257257Only produce at most one \`<proposed_plan>\` block per turn, and only when you are presenting a complete spec.
258258</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
260273function mapCodexRuntimeMode(runtimeMode: RuntimeMode): {
261274 readonly approvalPolicy: "on-request" | "never";
262275 readonly sandbox: "workspace-write" | "danger-full-access";
......@@ -326,7 +339,7 @@ function buildCodexCollaborationMode(input: {
326339 readonly effort?: string;
327340}):
328341 | {
329 mode: "plan";
342 mode: "default" | "plan";
330343 settings: {
331344 model: string;
332345 reasoning_effort: string;
......@@ -334,16 +347,19 @@ function buildCodexCollaborationMode(input: {
334347 };
335348 }
336349 | undefined {
337 if (input.interactionMode !== "plan") {
350 if (input.interactionMode === undefined) {
338351 return undefined;
339352 }
340353 const model = normalizeCodexModelSlug(input.model) ?? "gpt-5.3-codex";
341354 return {
342 mode: "plan",
355 mode: input.interactionMode,
343356 settings: {
344357 model,
345358 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,
347363 },
348364 };
349365}
......@@ -644,7 +660,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
644660 effort?: string;
645661 serviceTier?: string;
646662 collaborationMode?: {
647 mode: "plan";
663 mode: "default" | "plan";
648664 settings: {
649665 model: string;
650666 reasoning_effort: string;
apps/web/src/store.test.ts+8-2
......@@ -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";
28import { describe, expect, it } from "vitest";
39
410import { markThreadUnread, syncServerReadModel, type AppState } from "./store";
......@@ -139,6 +145,6 @@ describe("store read model sync", () => {
139145
140146 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);
143149 });
144150});