From 588c16af15003f344edc03b8d20cc22273879fdc Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 18:15:20 +0000 Subject: [PATCH] test(codex): express patch approval through provider actions Keep protocol framing in the fake app-server and precompute the approval title. Streamed completion already requires an item ID; expose that narrowing so completion cleanup stays inline without repeating its guard. --- .../providers/codex-app-server-agent.test.ts | 22 ++++++---------- .../agent/providers/codex-app-server-agent.ts | 25 ++++++++----------- .../codex/test-utils/fake-app-server.ts | 22 ++++++++++++++++ 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts index 54bf967cac..9b5263e474 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.test.ts @@ -236,21 +236,13 @@ describe("Codex file-change approvals", () => { ["file-alpha", "/workspace/project/alpha.txt", "@@ -1 +1 @@\n-blue\n+green\n"], ["file-beta", "/workspace/project/beta.txt", "@@ -1 +1 @@\n-small\n+large\n"], ]) { - appServer.child.stdout.write( - `${JSON.stringify({ - method: "item/started", - params: { - threadId: "thread-1", - turnId: "native-A", - item: { - type: "fileChange", - id, - status: "inProgress", - changes: [{ path: filePath, kind: { type: "update", move_path: null }, diff }], - }, - }, - })}\n`, - ); + appServer.startsFileChange({ + threadId: "thread-1", + turnId: "native-A", + callId: id, + path: filePath, + diff, + }); } const permission = waitForNextPermission(session); appServer.requestFileChangeApproval({ diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index a3e87087bb..f349978996 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -6781,10 +6781,6 @@ export class CodexAppServerAgentSession implements AgentSession { return !this.closed && this.activeForegroundTurnId === turnId; } - private forgetPendingToolCallDetail(itemId: string | null | undefined): void { - if (itemId) this.pendingToolCallDetails.delete(itemId); - } - private async handleItemCompletedNotification( parsed: Extract, ): Promise { @@ -6796,7 +6792,9 @@ export class CodexAppServerAgentSession implements AgentSession { if (shouldIgnoreMirroredLifecycleItem(parsed.source, parsed.item)) { return; } - this.forgetPendingToolCallDetail(parsed.item.id); + if (parsed.item.id) { + this.pendingToolCallDetails.delete(parsed.item.id); + } this.receiveAsyncQuestion(parsed.threadId, parsed.item); if (this.isUserMessageItem(parsed.item)) { await this.handleUserMessageItem(parsed); @@ -6844,10 +6842,8 @@ export class CodexAppServerAgentSession implements AgentSession { if (timelineItem.type === "assistant_message") { this.pendingAssistantMessageBoundary = true; } - if (itemId) { - this.emittedItemCompletedIds.add(itemId); - this.emittedItemStartedIds.delete(itemId); - } + this.emittedItemCompletedIds.add(itemId); + this.emittedItemStartedIds.delete(itemId); await this.replayPendingSubAgentNotifications(registeredChildThreadIds); return; } @@ -6883,7 +6879,7 @@ export class CodexAppServerAgentSession implements AgentSession { private consumeStreamedTextCompletion( timelineItem: AgentTimelineItem, itemId: string | null | undefined, - ): boolean { + ): itemId is string { if (!itemId) { return false; } @@ -7280,15 +7276,16 @@ export class CodexAppServerAgentSession implements AgentSession { input: { reason: parsed.reason ?? null }, output: null, }; + const title = + detail.type === "edit" && detail.filePath + ? `Apply file changes: ${detail.filePath}` + : "Apply file changes"; const request: AgentPermissionRequest = { id: requestId, provider: CODEX_PROVIDER, name: "CodexFileChange", kind: "tool", - title: - detail.type === "edit" && detail.filePath - ? `Apply file changes: ${detail.filePath}` - : "Apply file changes", + title, description: parsed.reason ?? undefined, detail, metadata: { diff --git a/packages/server/src/server/agent/providers/codex/test-utils/fake-app-server.ts b/packages/server/src/server/agent/providers/codex/test-utils/fake-app-server.ts index 99e6a73050..ec14a773e4 100644 --- a/packages/server/src/server/agent/providers/codex/test-utils/fake-app-server.ts +++ b/packages/server/src/server/agent/providers/codex/test-utils/fake-app-server.ts @@ -43,6 +43,13 @@ interface FakeTerminalInput { processId: string; text: string; } +interface FakeFileChange { + threadId: string; + turnId: string; + callId: string; + path: string; + diff: string; +} interface FakeLegacyPatch { threadId: string; callId: string; @@ -88,6 +95,7 @@ export interface FakeCodexAppServer { completesSubAgentActivity(params: FakeSubAgentActivity): void; completesCompaction(params: { threadId: string; itemId: string }): void; runsLegacyCommand(params: FakeLegacyCommand): void; + startsFileChange(params: FakeFileChange): void; appliesLegacyPatch(params: FakeLegacyPatch): void; completesCommand(params: FakeLegacyCommand): void; completesSilentCommand(params: FakeSilentCommand): void; @@ -467,6 +475,20 @@ export function createFakeCodexAppServer( success: true, }); }, + startsFileChange(params) { + writeNotification("item/started", { + threadId: params.threadId, + turnId: params.turnId, + item: { + type: "fileChange", + id: params.callId, + status: "inProgress", + changes: [ + { path: params.path, kind: { type: "update", move_path: null }, diff: params.diff }, + ], + }, + }); + }, appliesLegacyPatch(params) { const changes = [ {