Handle Windows shell wrappers in Codex command summaries (#931)

This commit is contained in:
ezra
2026-05-12 15:07:37 +08:00
committed by GitHub
parent 1a8fdcd388
commit db0d63dd90
2 changed files with 75 additions and 7 deletions

View File

@@ -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",

View File

@@ -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(" ");
}