From c8f542fbe5a05b6e8285f3cbd2d5a46e2ef41f3a Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 24 Dec 2025 20:31:48 +0700 Subject: [PATCH] Fix Codex SDK shell command hydration status --- .../src/server/agent/providers/codex-agent.ts | 129 ++++++++++++------ plan.md | 3 +- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/packages/server/src/server/agent/providers/codex-agent.ts b/packages/server/src/server/agent/providers/codex-agent.ts index 1c362054b..305e17fe1 100644 --- a/packages/server/src/server/agent/providers/codex-agent.ts +++ b/packages/server/src/server/agent/providers/codex-agent.ts @@ -1591,17 +1591,9 @@ function handleRolloutFunctionCall( } if (SHELL_FUNCTION_NAMES.has(name)) { - const args = parseJsonOrObject>(payload.arguments); - const command = formatCommand(args); - const cwd = - args && - typeof args === "object" && - typeof (args as { workdir?: unknown }).workdir === "string" - ? ((args as { workdir?: unknown }).workdir as string) - : undefined; + const { command, cwd } = buildCommandContext(payload.arguments); if (typeof payload.call_id === "string") { - const commandValue = command ?? "Command"; - commandCalls.set(payload.call_id, { command: commandValue, cwd }); + commandCalls.set(payload.call_id, { command, cwd }); events.push({ type: "timeline", provider: "codex", @@ -1612,7 +1604,7 @@ function handleRolloutFunctionCall( callId: payload.call_id, displayName: buildCommandDisplayName(command), kind: "execute", - input: { command: commandValue, cwd }, + input: { command, cwd }, }), }); } @@ -1659,40 +1651,7 @@ function finalizeRolloutFunctionCall( if (!command) { return; } - const result = parseJsonOrObject(payload.output); - const parsedOutput = - typeof payload.output === "string" - ? parseCommandOutputText(payload.output) - : null; - const exitCode = result?.metadata?.exit_code ?? parsedOutput?.exitCode; - const status = - exitCode === undefined || exitCode === 0 ? "completed" : "failed"; - const stdout = - result?.stdout ?? - (typeof result?.output === "string" ? result.output : undefined) ?? - parsedOutput?.stdout; - const metadata = - result?.metadata ?? - (typeof exitCode === "number" ? { exit_code: exitCode } : undefined); - - // Build structured command output - let output: unknown; - if (stdout !== undefined) { - output = { - type: "command" as const, - command: command.command, - output: stdout, - exitCode, - cwd: command.cwd, - metadata, - }; - } else if (result && typeof result === "object") { - output = { ...result, metadata: metadata ?? result.metadata }; - } else if (metadata) { - output = { metadata }; - } else { - output = result; - } + const { status, output } = buildCommandOutput(command, payload.output); events.push({ type: "timeline", @@ -1748,6 +1707,27 @@ function handleRolloutCustomToolCall( return; } + if (payload?.name && SHELL_FUNCTION_NAMES.has(payload.name)) { + const commandContext = buildCommandContext(payload.input); + const { status, output } = buildCommandOutput(commandContext, payload.output); + events.push({ + type: "timeline", + provider: "codex", + item: createToolCallTimelineItem({ + server: "command", + tool: "shell", + status: payload.status ?? status, + callId: + typeof payload.call_id === "string" ? payload.call_id : undefined, + displayName: buildCommandDisplayName(commandContext.command), + kind: "execute", + input: { command: commandContext.command, cwd: commandContext.cwd }, + output, + }), + }); + return; + } + if (typeof payload?.name === "string") { events.push({ type: "timeline", @@ -1810,6 +1790,7 @@ type RolloutCustomToolCallPayload = { type: "custom_tool_call"; name?: string; status?: string; + call_id?: string; input?: string | Record; output?: unknown; }; @@ -2158,6 +2139,64 @@ function parseCommandOutputText( }; } +function buildCommandContext(input: unknown): { command: string; cwd?: string } { + const args = parseJsonOrObject>(input); + const commandFromArgs = formatCommand(args); + let command = commandFromArgs; + if (!command && typeof input === "string") { + const trimmed = input.trim(); + if (trimmed && !trimmed.startsWith("{") && !trimmed.startsWith("[")) { + command = trimmed; + } + } + const cwd = + args && + typeof args === "object" && + typeof (args as { workdir?: unknown }).workdir === "string" + ? ((args as { workdir?: unknown }).workdir as string) + : undefined; + return { command: command ?? "Command", cwd }; +} + +function buildCommandOutput( + command: { command: string; cwd?: string }, + rawOutput: unknown +): { status: "completed" | "failed"; output: unknown } { + const result = parseJsonOrObject(rawOutput); + const parsedOutput = + typeof rawOutput === "string" ? parseCommandOutputText(rawOutput) : null; + const exitCode = result?.metadata?.exit_code ?? parsedOutput?.exitCode; + const status = + exitCode === undefined || exitCode === 0 ? "completed" : "failed"; + const stdout = + result?.stdout ?? + (typeof result?.output === "string" ? result.output : undefined) ?? + parsedOutput?.stdout; + const metadata = + result?.metadata ?? + (typeof exitCode === "number" ? { exit_code: exitCode } : undefined); + + let output: unknown; + if (stdout !== undefined) { + output = { + type: "command" as const, + command: command.command, + output: stdout, + exitCode, + cwd: command.cwd, + metadata, + }; + } else if (result && typeof result === "object") { + output = { ...result, metadata: metadata ?? result.metadata }; + } else if (metadata) { + output = { metadata }; + } else { + output = result; + } + + return { status, output }; +} + function formatCommand(args: unknown): string | null { if (!args || typeof args !== "object") { return null; diff --git a/plan.md b/plan.md index 7357b2019..faa815361 100644 --- a/plan.md +++ b/plan.md @@ -259,7 +259,8 @@ Build a new Codex MCP provider side‑by‑side with the existing Codex SDK prov - If failures: add follow-up fix tasks immediately after this item. - **Done (2025-12-24 20:25)**: Ran `npm run test --workspace=@paseo/server`; failures in Codex SDK persisted shell_command hydration and multiple Codex MCP mapping/persistence/permission checks; `agent-mcp.e2e.test.ts` hung and was interrupted. -- [ ] **Fix**: Codex SDK persisted shell_command hydration still missing completed status. +- [x] **Fix**: Codex SDK persisted shell_command hydration still missing completed status. + - **Done (2025-12-24 20:31)**: Mapped shell_command custom_tool_call entries to command tool calls and normalized output/status during rollout hydration. - [ ] **Fix**: Codex MCP command output should include exit codes for command tool calls (missing in timeline mapping).