fix(codex): resolve Windows app-server shims

This commit is contained in:
Peter Steinberger
2026-04-24 05:01:36 +01:00
parent b1d0c14d38
commit 5b34082106
3 changed files with 117 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Codex harness: route native `request_user_input` prompts back to the originating chat, preserve queued follow-up answers, and honor newer app-server command approval amendment decisions.
- Codex harness/Windows: resolve npm-installed `codex.cmd` shims through PATHEXT before starting the native app-server, so `codex/*` models work without a manual `.exe` shim. Fixes #70913.
- Slack/groups: classify MPIM group DMs as group chat context and suppress verbose tool/plan progress on Slack non-DM surfaces, so internal "Working…" traces no longer leak into rooms. Fixes #70912.
- Agents/replay: stop OpenAI/Codex transcript replay from synthesizing missing tool results while still preserving synthetic repair on Anthropic, Gemini, and Bedrock transport-owned sessions. (#61556) Thanks @VictorJeon and @vincentkoc.
- Telegram/media replies: parse remote markdown image syntax into outbound media payloads on the final reply path, so Telegram group chats stop falling back to plain-text image URLs when the model or a tool emits `![...](...)` instead of a `MEDIA:` token. (#66191) Thanks @apezam and @vincentkoc.

View File

@@ -0,0 +1,72 @@
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import type { CodexAppServerStartOptions } from "./config.js";
import { resolveCodexAppServerSpawnInvocation } from "./transport-stdio.js";
const tempDirs: string[] = [];
async function createTempDir(): Promise<string> {
const dir = await mkdtemp(path.join(os.tmpdir(), "openclaw-codex-spawn-"));
tempDirs.push(dir);
return dir;
}
afterEach(async () => {
for (const dir of tempDirs.splice(0)) {
await rm(dir, { recursive: true, force: true });
}
});
function startOptions(command: string): CodexAppServerStartOptions {
return {
transport: "stdio",
command,
args: ["app-server", "--listen", "stdio://"],
headers: {},
};
}
describe("resolveCodexAppServerSpawnInvocation", () => {
it("keeps non-Windows Codex app-server invocation unchanged", () => {
const resolved = resolveCodexAppServerSpawnInvocation(startOptions("codex"), {
platform: "darwin",
env: {},
execPath: "/usr/local/bin/node",
});
expect(resolved).toEqual({
command: "codex",
args: ["app-server", "--listen", "stdio://"],
shell: undefined,
windowsHide: undefined,
});
});
it("resolves Windows npm .cmd Codex shims through Node instead of raw spawn", async () => {
const binDir = await createTempDir();
const entryPath = path.join(binDir, "node_modules", "@openai", "codex", "bin", "codex.js");
const shimPath = path.join(binDir, "codex.cmd");
await mkdir(path.dirname(entryPath), { recursive: true });
await writeFile(entryPath, "console.log('codex')\n", "utf8");
await writeFile(
shimPath,
'@ECHO off\r\n"%~dp0\\node_modules\\@openai\\codex\\bin\\codex.js" %*\r\n',
"utf8",
);
const resolved = resolveCodexAppServerSpawnInvocation(startOptions("codex"), {
platform: "win32",
env: { PATH: binDir, PATHEXT: ".CMD;.EXE;.BAT" },
execPath: "C:\\node\\node.exe",
});
expect(resolved).toEqual({
command: "C:\\node\\node.exe",
args: [entryPath, "app-server", "--listen", "stdio://"],
shell: undefined,
windowsHide: true,
});
});
});

View File

@@ -1,7 +1,43 @@
import { spawn } from "node:child_process";
import {
materializeWindowsSpawnProgram,
resolveWindowsSpawnProgram,
} from "openclaw/plugin-sdk/windows-spawn";
import type { CodexAppServerStartOptions } from "./config.js";
import type { CodexAppServerTransport } from "./transport.js";
type CodexAppServerSpawnRuntime = {
platform: NodeJS.Platform;
env: NodeJS.ProcessEnv;
execPath: string;
};
const DEFAULT_SPAWN_RUNTIME: CodexAppServerSpawnRuntime = {
platform: process.platform,
env: process.env,
execPath: process.execPath,
};
export function resolveCodexAppServerSpawnInvocation(
options: CodexAppServerStartOptions,
runtime: CodexAppServerSpawnRuntime = DEFAULT_SPAWN_RUNTIME,
): { command: string; args: string[]; shell?: boolean; windowsHide?: boolean } {
const program = resolveWindowsSpawnProgram({
command: options.command,
platform: runtime.platform,
env: runtime.env,
execPath: runtime.execPath,
packageName: "@openai/codex",
});
const resolved = materializeWindowsSpawnProgram(program, options.args);
return {
command: resolved.command,
args: resolved.argv,
shell: resolved.shell,
windowsHide: resolved.windowsHide,
};
}
export function createStdioTransport(options: CodexAppServerStartOptions): CodexAppServerTransport {
const env = {
...process.env,
@@ -10,9 +46,16 @@ export function createStdioTransport(options: CodexAppServerStartOptions): Codex
for (const key of options.clearEnv ?? []) {
delete env[key];
}
return spawn(options.command, options.args, {
const invocation = resolveCodexAppServerSpawnInvocation(options, {
platform: process.platform,
env,
execPath: process.execPath,
});
return spawn(invocation.command, invocation.args, {
env,
detached: process.platform !== "win32",
shell: invocation.shell,
stdio: ["pipe", "pipe", "pipe"],
windowsHide: invocation.windowsHide,
});
}