fix(codex): include patch details in file change approvals

This commit is contained in:
Okan YILDIRIM
2026-11-02 18:04:00 +00:00
committed by paseo-bot[bot]
parent 7495c20f78
commit 930ded5e1a
2 changed files with 69 additions and 1 deletions
@@ -227,6 +227,67 @@ function deferred<T>() {
return { promise, resolve };
}
describe("Codex file-change approvals", () => {
test("file-change approval includes the matching started patch", async () => {
const appServer = createFakeCodexAppServer();
const { session } = await startPublicSteeringSession(appServer);
try {
for (const [id, filePath, diff] of [
["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"],
]) {
const started = waitForTimelineToolCall(session, id);
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`,
);
await started;
}
const permission = waitForNextPermission(session);
appServer.requestFileChangeApproval({
itemId: "file-alpha",
threadId: "thread-1",
turnId: "native-A",
reason: "Apply the patch",
});
expect((await permission).request).toEqual({
id: "permission-file-alpha",
provider: "codex",
name: "CodexFileChange",
kind: "tool",
title: "Apply file changes",
description: "Apply the patch",
detail: {
type: "edit",
filePath: "alpha.txt",
unifiedDiff: "@@ -1 +1 @@\n-blue\n+green\n",
},
metadata: { itemId: "file-alpha", threadId: "thread-1", turnId: "native-A" },
});
await session.respondToPermission("permission-file-alpha", { behavior: "allow" });
await expect(appServer.waitForApprovalDecision("file-alpha")).resolves.toEqual({
decision: "accept",
});
appServer.assertNoErrors();
} finally {
await session.close();
}
});
});
describe("Codex active-turn steering admission", () => {
test("steers an accepted turn whose native id arrives after startTurn returns", async () => {
const appServer = createFakeCodexAppServer({
@@ -3483,6 +3483,7 @@ export class CodexAppServerAgentSession implements AgentSession {
private pendingReasoning = new Map<string, string[]>();
private pendingCommandOutputDeltas = new Map<string, string[]>();
private pendingFileChangeOutputDeltas = new Map<string, string[]>();
private pendingToolCallDetails = new Map<string, ToolCallTimelineItem["detail"]>();
private pendingAssistantMessageBoundary = false;
private terminalCommandByProcessId = new Map<string, string>();
// The root thread's still-running command process ids, keyed by the native turn that
@@ -5251,6 +5252,7 @@ export class CodexAppServerAgentSession implements AgentSession {
async close(): Promise<void> {
this.closed = true;
this.clearPendingPermissions();
this.pendingToolCallDetails.clear();
this.pendingSubAgentNotificationsByThreadId.clear();
this.subscribers.clear();
this.startupEvents = null;
@@ -6416,6 +6418,7 @@ export class CodexAppServerAgentSession implements AgentSession {
this.pendingReasoning.clear();
this.pendingCommandOutputDeltas.clear();
this.pendingFileChangeOutputDeltas.clear();
this.pendingToolCallDetails.clear();
this.pendingAssistantMessageBoundary = false;
this.warnedIncompleteEditToolCallIds.clear();
this.pendingRootCompactionItemIds.clear();
@@ -6789,6 +6792,9 @@ export class CodexAppServerAgentSession implements AgentSession {
if (shouldIgnoreMirroredLifecycleItem(parsed.source, parsed.item)) {
return;
}
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);
@@ -6984,6 +6990,7 @@ export class CodexAppServerAgentSession implements AgentSession {
if (!timelineItem || timelineItem.type !== "tool_call") {
return;
}
this.pendingToolCallDetails.set(timelineItem.callId, timelineItem.detail);
const registeredChildThreadIds = this.registerSubAgentToolCall({
timelineItem,
rawItem: parsed.item,
@@ -7270,7 +7277,7 @@ export class CodexAppServerAgentSession implements AgentSession {
kind: "tool",
title: "Apply file changes",
description: parsed.reason ?? undefined,
detail: {
detail: this.pendingToolCallDetails.get(parsed.itemId) ?? {
type: "unknown",
input: {
reason: parsed.reason ?? null,