Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions apps/server/scripts/record-codex-app-server-replay-fixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as NodeOS from "node:os";

import { CODEX_THREAD_CONFIG } from "../src/orchestration-v2/Adapters/CodexAdapterV2.ts";
import { revertCodexThread } from "../src/provider/CodexThreadRevert.ts";
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
Expand Down Expand Up @@ -119,8 +120,11 @@ interface ReplayRun {
readonly steps: ReadonlyArray<ReplayStep>;
readonly turnDefaults?: Omit<TurnStartParams, "input" | "threadId">;
readonly interactionMode?: "plan";
/** Config overrides for `thread/start`; replay ignores them when matching frames. */
readonly threadConfig?: CodexSchema.V2ThreadStartParams["config"];
/**
* Recorder-only `thread/start` config merged over the adapter's. Its keys go in the
* transcript header as `recorderThreadConfigKeys`, which replay leaves out of matching.
*/
readonly threadConfig?: Readonly<Record<string, boolean | number | string>>;
}

type ReplayStep =
Expand Down Expand Up @@ -585,8 +589,6 @@ function scenarios(): ReadonlyArray<ReplayScenario> {
name: "todo-list",
description: "Default-mode turn that should surface turn/plan/updated notifications.",
prompt: TODO_LIST_PROMPT,
// Codex 0.156 only registers update_plan when this is enabled.
threadConfig: { "tools.update_plan.enabled": true },
turnDefaults: {
approvalPolicy: "never",
sandboxPolicy: readOnlyFullAccessSandbox(),
Expand Down Expand Up @@ -947,6 +949,11 @@ function scenarios(): ReadonlyArray<ReplayScenario> {
];
}

function recorderThreadConfigMetadata(scenario: ReplayScenario) {
const keys = [...new Set(scenario.runs.flatMap((run) => Object.keys(run.threadConfig ?? {})))];
return keys.length === 0 ? {} : { recorderThreadConfigKeys: keys };
}

function makeRecorder({
outPath,
scenario,
Expand Down Expand Up @@ -992,6 +999,7 @@ function makeRecorder({
fileName: scenario.fileName,
description: scenario.description,
model: scenarioModel(scenario),
...recorderThreadConfigMetadata(scenario),
},
},
...outputRecords,
Expand Down Expand Up @@ -1209,7 +1217,7 @@ function runReplaySession({
approvalGate ? Deferred.await(approvalGate) : Effect.void;
const model = scenarioModel(scenario);
// Same runtime params the adapter sends on thread/start, thread/resume and thread/fork.
const threadRuntimeParams = { cwd: process.cwd(), model };
const threadRuntimeParams = { cwd: process.cwd(), model, config: CODEX_THREAD_CONFIG };
const lastTurnIdByThread = new Map<string, string>();
const runningCommandProcessIds = new Map<string, Map<string, string>>();

Expand Down Expand Up @@ -1358,7 +1366,7 @@ function runReplaySession({
const client = yield* initializeClient;
const thread = yield* client.request("thread/start", {
...threadRuntimeParams,
...(run.threadConfig === undefined ? {} : { config: run.threadConfig }),
config: { ...threadRuntimeParams.config, ...run.threadConfig },
});
let activeThreadId = thread.thread.id;
const threadIds = new Map<string, string>([["source", thread.thread.id]]);
Expand Down
34 changes: 28 additions & 6 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
canReuseCodexContextUsage,
CODEX_DEFAULT_INSTANCE_ID,
CODEX_DRIVER_KIND,
CODEX_THREAD_CONFIG,
codexBackgroundCommandDetail,
codexFileChangeApprovalPrompt,
codexProviderTurnTokenUsage,
Expand Down Expand Up @@ -639,6 +640,7 @@ describe("CodexAdapterV2 process spawning", () => {
cwd: "/workspace/thread-codex-mcp",
model: "gpt-5.4",
config: {
"tools.update_plan.enabled": true,
mcp_servers: {
"t3-code": {
url: "http://127.0.0.1:43123/mcp",
Expand Down Expand Up @@ -1492,7 +1494,7 @@ function codexReplayPreamble(input: {
{
type: "expect_outbound",
label: "thread/start",
frame: { id: 2, method: "thread/start", params: {} },
frame: { id: 2, method: "thread/start", params: { config: CODEX_THREAD_CONFIG } },
},
{
type: "emit_inbound",
Expand Down Expand Up @@ -2348,7 +2350,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
frame: {
id: 3,
method: "thread/resume",
params: { threadId: nativeThreadId, excludeTurns: true },
params: {
threadId: nativeThreadId,
excludeTurns: true,
config: CODEX_THREAD_CONFIG,
},
},
},
{
Expand Down Expand Up @@ -2393,7 +2399,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
frame: {
id: 3,
method: "thread/resume",
params: { threadId: nativeThreadId, excludeTurns: true },
params: {
threadId: nativeThreadId,
excludeTurns: true,
config: CODEX_THREAD_CONFIG,
},
},
},
{
Expand Down Expand Up @@ -6485,7 +6495,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
{
type: "expect_outbound",
label: "thread/fork",
frame: { id: 3, method: "thread/fork", params: { threadId: nativeThreadId } },
frame: {
id: 3,
method: "thread/fork",
params: { threadId: nativeThreadId, config: CODEX_THREAD_CONFIG },
},
},
{
type: "emit_inbound",
Expand Down Expand Up @@ -6620,7 +6634,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
{
type: "expect_outbound",
label: "thread/fork",
frame: { id: 3, method: "thread/fork", params: { threadId: nativeThreadId } },
frame: {
id: 3,
method: "thread/fork",
params: { threadId: nativeThreadId, config: CODEX_THREAD_CONFIG },
},
},
{
type: "emit_inbound",
Expand Down Expand Up @@ -6718,7 +6736,11 @@ describe("CodexAdapterV2 post-settle continuation", () => {
frame: {
id: 3,
method: "thread/fork",
params: { threadId: nativeThreadId, lastTurnId: "native-turn-first" },
params: {
threadId: nativeThreadId,
lastTurnId: "native-turn-first",
config: CODEX_THREAD_CONFIG,
},
},
},
{
Expand Down
22 changes: 15 additions & 7 deletions apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1141,24 +1141,32 @@ export class CodexAppServerClientFactory extends Context.Service<
CodexAppServerClientFactoryShape
>()("t3/orchestration-v2/Adapters/CodexAdapterV2/CodexAppServerClientFactory") {}

/**
* Config overrides sent with every `thread/start`, `thread/resume` and `thread/fork`.
* Codex 0.152 made the `update_plan` checklist tool opt-in; T3 renders it as the
* todo list. Codex layers these above the user's and project's `config.toml`.
*/
export const CODEX_THREAD_CONFIG = { "tools.update_plan.enabled": true } as const;

export function codexThreadRuntimeParams(input: {
readonly threadId: ThreadId | null;
readonly modelSelection?: { readonly model: string };
readonly runtimePolicy?: ProviderAdapterV2RuntimePolicy;
}): {
readonly cwd?: string;
readonly model?: string;
readonly config?: Readonly<Record<string, Schema.Json>>;
readonly config: Readonly<Record<string, Schema.Json>>;
} {
const mcpSession =
input.threadId === null ? undefined : McpProviderSession.readMcpProviderSession(input.threadId);
return {
...(input.runtimePolicy?.cwd == null ? {} : { cwd: input.runtimePolicy.cwd }),
...(input.modelSelection === undefined ? {} : { model: input.modelSelection.model }),
...(mcpSession === undefined
? {}
: {
config: {
config: {
...CODEX_THREAD_CONFIG,
...(mcpSession === undefined
? {}
: {
mcp_servers: {
"t3-code": {
url: mcpSession.endpoint,
Expand All @@ -1167,8 +1175,8 @@ export function codexThreadRuntimeParams(input: {
},
},
},
},
}),
}),
},
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.120.0 (Mac OS 26.4.1; arm64) dumb (t3code_desktop; 0.1.0)","codexHome":"/home/replay-user/.codex","platformFamily":"unix","platformOs":"macos"}}}
{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
{"type":"expect_outbound","label":"parent.thread/start","frame":{"id":2,"method":"thread/start","params":{}}}
{"type":"expect_outbound","label":"parent.thread/start","frame":{"id":2,"method":"thread/start","params":{"config":{"tools.update_plan.enabled":true}}}}
{"type":"emit_inbound","label":"parent.thread/start","frame":{"id":2,"result":{"thread":{"id":"019f1000-0000-7000-8000-000000000001","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739467,"updatedAt":1776739467,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-parent.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/home/replay-user/t3code","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":[],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}}
{"type":"expect_outbound","label":"parent.turn/start","frame":{"id":3,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"Keep this parent turn active while orchestration tools are tested.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000001","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"emit_inbound","label":"parent.thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019f1000-0000-7000-8000-000000000001","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739467,"updatedAt":1776739467,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-parent.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}}
{"type":"emit_inbound","label":"parent.turn/start","frame":{"id":3,"result":{"turn":{"id":"019f1000-0000-7000-8000-000000000002","items":[],"status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}}
{"type":"emit_inbound","label":"parent.thread/status/changed","frame":{"method":"thread/status/changed","params":{"threadId":"019f1000-0000-7000-8000-000000000001","status":{"type":"active","activeFlags":[]}}}}
{"type":"emit_inbound","label":"parent.turn/started","frame":{"method":"turn/started","params":{"threadId":"019f1000-0000-7000-8000-000000000001","turn":{"id":"019f1000-0000-7000-8000-000000000002","items":[],"status":"inProgress","error":null,"startedAt":1776739467,"completedAt":null,"durationMs":null}}}}
{"type":"expect_outbound","label":"child.thread/start","frame":{"id":4,"method":"thread/start","params":{}}}
{"type":"expect_outbound","label":"child.thread/start","frame":{"id":4,"method":"thread/start","params":{"config":{"tools.update_plan.enabled":true}}}}
{"type":"emit_inbound","label":"child.thread/start","frame":{"id":4,"result":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]},"model":"gpt-5.4","modelProvider":"openai","serviceTier":"fast","cwd":"/home/replay-user/t3code","approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":[],"readOnlyAccess":{"type":"fullAccess"},"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"reasoningEffort":"xhigh"}}}
{"type":"expect_outbound","label":"child.original.turn/start","frame":{"id":5,"method":"turn/start","params":{"approvalPolicy":"never","cwd":"<workspace>","input":[{"text":"Inspect the delegated API boundary and return the result.","type":"text"}],"model":"gpt-5.4","sandboxPolicy":{"type":"dangerFullAccess"},"threadId":"019f1000-0000-7000-8000-000000000003","approvalsReviewer":"user","summary":"detailed"}}}
{"type":"emit_inbound","label":"child.thread/started","frame":{"method":"thread/started","params":{"thread":{"id":"019f1000-0000-7000-8000-000000000003","forkedFromId":null,"preview":"","ephemeral":false,"modelProvider":"openai","createdAt":1776739468,"updatedAt":1776739468,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/delegated-task-status-child.jsonl","cwd":"/home/replay-user/t3code","cliVersion":"0.120.0","source":"vscode","agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"turns":[]}}}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{"type":"expect_outbound","label":"initialize","frame":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true,"optOutNotificationMethods":["turn/diff/updated"]},"clientInfo":{"name":"t3code_desktop","title":"T3 Code Desktop","version":"0.1.0"}}}}
{"type":"emit_inbound","label":"initialize","frame":{"id":1,"result":{"userAgent":"t3code_desktop/0.156.1 (Debian 13.0.0; x86_64) unknown (t3code_desktop; 0.1.0)","codexHome":"/home/replay-user/.codex","platformFamily":"unix","platformOs":"linux"}}}
{"type":"expect_outbound","label":"initialized","frame":{"method":"initialized"}}
{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{"cwd":"<workspace>","model":"gpt-6-luna"}}}
{"type":"expect_outbound","label":"thread/start","frame":{"id":2,"method":"thread/start","params":{"config":{"tools.update_plan.enabled":true},"cwd":"<workspace>","model":"gpt-6-luna"}}}
{"type":"emit_inbound","label":"configWarning","frame":{"method":"configWarning","params":{"summary":"Codex could not find bubblewrap on PATH. Install bubblewrap with your OS package manager. See the sandbox prerequisites: https://developers.openai.com/codex/concepts/sandboxing#prerequisites. Codex will use the bundled bubblewrap in the meantime.","details":null},"emittedAtMs":1790295500250}}
{"type":"emit_inbound","label":"remoteControl/status/changed","frame":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"replay-host","installationId":"00000000-0000-4000-8000-000000000000","environmentId":null},"emittedAtMs":1790295500250}}
{"type":"emit_inbound","label":"thread/start","frame":{"id":2,"result":{"thread":{"id":"01a0d5ed-65ea-7d42-b8e9-d06f045cfa86","environments":[{"environmentId":"local","cwd":"/home/replay-user/t3code/packages/effect-codex-app-server","runtimeWorkspaceRoots":["/home/replay-user/t3code/packages/effect-codex-app-server"]}],"extra":null,"sessionId":"01a0d5ed-65ea-7d42-b8e9-d06f045cfa86","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-6-luna","reasoningEffort":null,"createdAt":1790295500,"updatedAt":1790295500,"recencyAt":1790295500,"status":{"type":"idle"},"path":"/home/replay-user/.codex/sessions/2026/09/24/rollout-2026-09-24T17-18-20-01a0d5ed-65ea-7d42-b8e9-d06f045cfa86.jsonl","cwd":"/home/replay-user/t3code/packages/effect-codex-app-server","cliVersion":"0.156.1","originator":"t3code_desktop","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/home/replay-user/t3code/packages/effect-codex-app-server","runtimeWorkspaceRoots":["/home/replay-user/t3code/packages/effect-codex-app-server"],"instructionSources":["/home/replay-user/.codex/AGENTS.md","/home/replay-user/t3code/AGENTS.md"],"approvalPolicy":"on-request","approvalsReviewer":"user","sandbox":{"type":"workspaceWrite","writableRoots":[],"networkAccess":false,"excludeTmpdirEnvVar":false,"excludeSlashTmp":false},"activePermissionProfile":{"id":":workspace","extends":null},"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}}
Expand Down
Loading
Loading