From db0d63dd909a9d0536af6be8ffe214c9b9384b24 Mon Sep 17 00:00:00 2001 From: ezra Date: Tue, 12 May 2026 15:07:37 +0800 Subject: [PATCH] Handle Windows shell wrappers in Codex command summaries (#931) --- .../providers/codex/tool-call-mapper.test.ts | 33 +++++++++++++ .../agent/providers/codex/tool-call-mapper.ts | 49 ++++++++++++++++--- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts index 6871ec04c..ac14899fd 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.test.ts @@ -64,6 +64,39 @@ describe("codex tool-call mapper", () => { }); }); + it("unwraps pwsh wrapper strings for commandExecution on Windows", () => { + const item = mapCodexToolCallFromThreadItem({ + type: "commandExecution", + id: "codex-call-wrapper-pwsh-string", + status: "running", + command: + '"C:\\Users\\example\\AppData\\Local\\Microsoft\\WindowsApps\\pwsh.exe" -NoLogo -NoProfile -Command "echo hello"', + cwd: "C:\\repo", + }); + + expect(item?.detail).toEqual({ + type: "shell", + command: "echo hello", + cwd: "C:\\repo", + }); + }); + + it("unwraps cmd wrapper arrays for commandExecution on Windows", () => { + const item = mapCodexToolCallFromThreadItem({ + type: "commandExecution", + id: "codex-call-wrapper-cmd-array", + status: "running", + command: ["cmd.exe", "/c", "echo hello"], + cwd: "C:\\repo", + }); + + expect(item?.detail).toEqual({ + type: "shell", + command: "echo hello", + cwd: "C:\\repo", + }); + }); + it("keeps only command output body when commandExecution output is wrapped in shell envelope", () => { const item = mapCodexToolCallFromThreadItem({ type: "commandExecution", diff --git a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts index 776bea34f..2c21288b7 100644 --- a/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts +++ b/packages/server/src/server/agent/providers/codex/tool-call-mapper.ts @@ -200,21 +200,48 @@ const CodexThreadItemSchema = z.discriminatedUnion("type", [ function maybeUnwrapShellWrapperCommand(command: string): string { const trimmed = command.trim(); - const wrapperMatch = trimmed.match(/^(?:\/bin\/)?(?:zsh|bash|sh)\s+-(?:lc|c)\s+([\s\S]+)$/); - if (!wrapperMatch) { + const unixWrapperMatch = trimmed.match(/^(?:\/bin\/)?(?:zsh|bash|sh)\s+-(?:lc|c)\s+([\s\S]+)$/); + if (unixWrapperMatch) { + const candidate = unixWrapperMatch[1]?.trim() ?? ""; + if (!candidate) { + return trimmed; + } + return stripMatchingEdgeQuotes(candidate); + } + const windowsWrapperMatch = trimmed.match( + /^(?:"[^"]*\\)?(?:pwsh|powershell|cmd)(?:\.exe)?"?\s+((?:-[A-Za-z]+(?:\s+[^-\s][^\s]*)?\s+)*)((?:-Command|-c|\/c)\s+[\s\S]+)$/i, + ); + if (!windowsWrapperMatch) { return trimmed; } - const candidate = wrapperMatch[1]?.trim() ?? ""; + const wrappedCommand = windowsWrapperMatch[2]?.trim() ?? ""; + if (!wrappedCommand) { + return trimmed; + } + const commandMatch = wrappedCommand.match(/^(?:-Command|-c|\/c)\s+([\s\S]+)$/i); + if (!commandMatch) { + return trimmed; + } + const candidate = commandMatch[1]?.trim() ?? ""; if (!candidate) { return trimmed; } + return stripMatchingEdgeQuotes(candidate); +} + +function stripMatchingEdgeQuotes(value: string): string { if ( - (candidate.startsWith('"') && candidate.endsWith('"')) || - (candidate.startsWith("'") && candidate.endsWith("'")) + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) ) { - return candidate.slice(1, -1); + return value.slice(1, -1); } - return candidate; + return value; +} + +function isWindowsShellCommand(command: string): boolean { + const normalized = command.replace(/^["']|["']$/g, ""); + return /(?:^|\\)(?:pwsh|powershell|cmd)(?:\.exe)?$/i.test(normalized); } function normalizeCommandExecutionCommand(value: unknown): string | undefined { @@ -236,6 +263,14 @@ function normalizeCommandExecutionCommand(value: unknown): string | undefined { const unwrapped = parts[2]?.trim(); return unwrapped && unwrapped.length > 0 ? unwrapped : undefined; } + if ( + parts.length >= 3 && + isWindowsShellCommand(parts[0] ?? "") && + /^(-command|-c|\/c)$/i.test(parts[1] ?? "") + ) { + const unwrapped = parts.slice(2).join(" ").trim(); + return unwrapped.length > 0 ? stripMatchingEdgeQuotes(unwrapped) : undefined; + } return parts.join(" "); }