mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 17:20:45 +00:00
fix(codex): resolve managed package binary fallback
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Codex/app-server: resolve the managed `@openai/codex` package bin when package installs do not provide a nearby `.bin/codex` shim, avoiding false missing-binary startup failures.
|
||||
- Plugins/source checkout: discover source-only plugins such as Codex from the `extensions/*` workspace while using npm package excludes as the packaged-core boundary, removing the stale core-bundle metadata path.
|
||||
- Plugins/ClawHub: install ClawPack artifacts from the explicit npm-pack `.tgz` resolver path instead of the legacy ZIP-shaped placeholder route. Thanks @vincentkoc.
|
||||
- Control UI: allow deployments to configure grouped chat message max-width with a validated `gateway.controlUi.chatMessageMaxWidth` setting instead of patching bundled CSS after upgrades. Fixes #67935. Thanks @xiew4589-lang.
|
||||
@@ -25,7 +26,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/update: treat inherited Gateway service markers as origin hints and only block package replacement when the managed Gateway is still live, so self-updates can stop the service and continue safely. (#75729) Thanks @hxy91819.
|
||||
- Agents/failover: exempt run-level timeouts that fire during tool execution from model fallback, timeout-triggered compaction, and generic timeout payload synthesis. Long `process(poll)`, browser, or `exec` tool calls that exceed `agents.defaults.timeoutSeconds` previously rotated auth profiles, switched to a fallback model, and surfaced a misleading "LLM request timed out" error even though the primary model had already responded. Mirrors the existing `timedOutDuringCompaction` precedent (#46889). Fixes #52147. (#75873) Thanks @simonusa.
|
||||
- Docker: copy Bun 1.3.13 from a digest-pinned image and keep CI on the same version. Fixes #74356. Thanks @fede-kamel and @sallyom.
|
||||
- Agents/compaction: keep prior context on consecutive turns against z.ai-style providers (z.ai direct, openrouter z-ai/*, in-house GLM gateways); Pi's internal auto-compaction was misfiring after successful turns and clearing state.messages before the next provider request. (#76056) Thanks @openperf.
|
||||
- Agents/compaction: keep prior context on consecutive turns against z.ai-style providers (z.ai direct, openrouter z-ai/\*, in-house GLM gateways); Pi's internal auto-compaction was misfiring after successful turns and clearing state.messages before the next provider request. (#76056) Thanks @openperf.
|
||||
|
||||
## 2026.5.2
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { mkdir, mkdtemp, realpath, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
@@ -83,6 +85,39 @@ describe("managed Codex app-server binary", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the resolved Codex package bin when no command shim exists", async () => {
|
||||
const installRoot = await mkdtemp(path.join(os.tmpdir(), "openclaw-codex-package-"));
|
||||
const pluginRoot = path.join(installRoot, "dist", "extensions", "codex");
|
||||
const packageRoot = path.join(installRoot, "node_modules", "@openai", "codex");
|
||||
const packageBin = path.join(packageRoot, "bin", "codex.js");
|
||||
await mkdir(path.dirname(packageBin), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(packageRoot, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@openai/codex",
|
||||
bin: {
|
||||
codex: "bin/codex.js",
|
||||
},
|
||||
}),
|
||||
);
|
||||
await writeFile(packageBin, "#!/usr/bin/env node\n");
|
||||
const resolvedPackageBin = await realpath(packageBin);
|
||||
|
||||
const pathExists = vi.fn(async (filePath: string) => filePath === resolvedPackageBin);
|
||||
|
||||
await expect(
|
||||
resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
|
||||
platform: "linux",
|
||||
pluginRoot,
|
||||
pathExists,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions("managed"),
|
||||
command: resolvedPackageBin,
|
||||
commandSource: "resolved-managed",
|
||||
});
|
||||
});
|
||||
|
||||
it("fails clearly when the managed Codex binary is missing", async () => {
|
||||
await expect(
|
||||
resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { constants as fsConstants } from "node:fs";
|
||||
import { constants as fsConstants, readFileSync } from "node:fs";
|
||||
import { access } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
@@ -66,7 +67,21 @@ function resolveManagedCodexAppServerCommandCandidates(
|
||||
): string[] {
|
||||
const pathApi = pathForPlatform(platform);
|
||||
const commandName = platform === "win32" ? "codex.cmd" : "codex";
|
||||
const roots = [
|
||||
const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
|
||||
return [
|
||||
...new Set([
|
||||
...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)),
|
||||
...resolveManagedCodexPackageBinCandidates(roots, platform),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
function resolveManagedCodexAppServerCandidateRoots(
|
||||
pluginRoot: string,
|
||||
platform: NodeJS.Platform,
|
||||
): string[] {
|
||||
const pathApi = pathForPlatform(platform);
|
||||
return [
|
||||
pluginRoot,
|
||||
pathApi.dirname(pluginRoot),
|
||||
pathApi.dirname(pathApi.dirname(pluginRoot)),
|
||||
@@ -74,7 +89,50 @@ function resolveManagedCodexAppServerCommandCandidates(
|
||||
? pathApi.dirname(pathApi.dirname(pathApi.dirname(pluginRoot)))
|
||||
: null,
|
||||
].filter((root): root is string => Boolean(root));
|
||||
return [...new Set(roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)))];
|
||||
}
|
||||
|
||||
function resolveManagedCodexPackageBinCandidates(
|
||||
roots: readonly string[],
|
||||
platform: NodeJS.Platform,
|
||||
): string[] {
|
||||
if (platform === "win32") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const candidates: string[] = [];
|
||||
for (const root of roots) {
|
||||
const candidate = resolveManagedCodexPackageBinCandidate(root);
|
||||
if (candidate) {
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function resolveManagedCodexPackageBinCandidate(root: string): string | null {
|
||||
try {
|
||||
const requireFromRoot = createRequire(path.join(root, "package.json"));
|
||||
const packageJsonPath = requireFromRoot.resolve(
|
||||
`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`,
|
||||
);
|
||||
const packageRoot = path.dirname(packageJsonPath);
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
||||
bin?: unknown;
|
||||
};
|
||||
const binPath =
|
||||
typeof packageJson.bin === "string"
|
||||
? packageJson.bin
|
||||
: isRecord(packageJson.bin) && typeof packageJson.bin.codex === "string"
|
||||
? packageJson.bin.codex
|
||||
: null;
|
||||
return binPath ? path.resolve(packageRoot, binPath) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function isDistExtensionRoot(pluginRoot: string, platform: NodeJS.Platform): boolean {
|
||||
|
||||
Reference in New Issue
Block a user