fix: restore bundled agent CLI resolution
- Upgrade EJClaw bundled Codex CLI to 0.124.0 for gpt-5.5 access - Resolve Claude Agent SDK native binary packages via package.json - Prefer glibc Claude binary before musl on Linux and align platform binary names - Add regression coverage for optional package resolution
This commit is contained in:
@@ -45,7 +45,7 @@ function platformCandidates(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
pkg: `@anthropic-ai/claude-agent-sdk-darwin-${arch}`,
|
pkg: `@anthropic-ai/claude-agent-sdk-darwin-${arch}`,
|
||||||
file: arch === 'arm64' ? 'cli' : 'cli',
|
file: 'claude',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ function platformCandidates(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
pkg: `@anthropic-ai/claude-agent-sdk-win32-${arch}`,
|
pkg: `@anthropic-ai/claude-agent-sdk-win32-${arch}`,
|
||||||
file: 'cli.exe',
|
file: 'claude.exe',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -127,12 +127,22 @@ export function resolveBundledClaudeCodeExecutable(options?: {
|
|||||||
* Default package directory resolver. Uses `require.resolve` against this
|
* Default package directory resolver. Uses `require.resolve` against this
|
||||||
* module's location so it works regardless of whether agent-runner is invoked
|
* module's location so it works regardless of whether agent-runner is invoked
|
||||||
* from its own node_modules layout or via a parent workspace.
|
* from its own node_modules layout or via a parent workspace.
|
||||||
|
*
|
||||||
|
* Important: the SDK's optional native-binary packages may not expose a bare
|
||||||
|
* package entrypoint, so `require.resolve(pkg)` can fail even when the package
|
||||||
|
* and binary are installed. Resolve `package.json` first, then fall back to the
|
||||||
|
* bare package only for package layouts that do expose an entrypoint.
|
||||||
*/
|
*/
|
||||||
function defaultResolvePackageDir(pkg: string): string | null {
|
function defaultResolvePackageDir(pkg: string): string | null {
|
||||||
|
const req = createRequire(import.meta.url);
|
||||||
|
try {
|
||||||
|
const pkgJson = req.resolve(`${pkg}/package.json`);
|
||||||
|
return path.dirname(pkgJson);
|
||||||
|
} catch {
|
||||||
|
// Fall through to legacy/bare-entrypoint resolution below.
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Resolve the package entrypoint instead of package.json because the SDK's
|
|
||||||
// exports map does not expose `./package.json`.
|
|
||||||
const req = createRequire(import.meta.url);
|
|
||||||
const entrypoint = req.resolve(pkg);
|
const entrypoint = req.resolve(pkg);
|
||||||
return path.dirname(entrypoint);
|
return path.dirname(entrypoint);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -142,5 +152,6 @@ function defaultResolvePackageDir(pkg: string): string | null {
|
|||||||
|
|
||||||
export const __test__ = {
|
export const __test__ = {
|
||||||
platformCandidates,
|
platformCandidates,
|
||||||
|
defaultResolvePackageDir,
|
||||||
ENV_OVERRIDE,
|
ENV_OVERRIDE,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { createRequire } from 'module';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { afterEach, describe, expect, it } from 'vitest';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { resolveBundledClaudeCodeExecutable } from '../src/bundled-cli-path.js';
|
import {
|
||||||
|
__test__,
|
||||||
|
resolveBundledClaudeCodeExecutable,
|
||||||
|
} from '../src/bundled-cli-path.js';
|
||||||
|
|
||||||
describe('resolveBundledClaudeCodeExecutable', () => {
|
describe('resolveBundledClaudeCodeExecutable', () => {
|
||||||
const origEnv = process.env.EJCLAW_CLAUDE_CLI_PATH;
|
const origEnv = process.env.EJCLAW_CLAUDE_CLI_PATH;
|
||||||
@@ -83,10 +87,10 @@ describe('resolveBundledClaudeCodeExecutable', () => {
|
|||||||
expect(result).toBe(path.join(muslDir, 'claude'));
|
expect(result).toBe(path.join(muslDir, 'claude'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves Darwin arm64 binary under `cli`', () => {
|
it('resolves Darwin arm64 binary under `claude`', () => {
|
||||||
const dir =
|
const dir =
|
||||||
'/fake/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64';
|
'/fake/node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64';
|
||||||
const existsSync = (p: string): boolean => p === path.join(dir, 'cli');
|
const existsSync = (p: string): boolean => p === path.join(dir, 'claude');
|
||||||
const result = resolveBundledClaudeCodeExecutable({
|
const result = resolveBundledClaudeCodeExecutable({
|
||||||
env: {},
|
env: {},
|
||||||
existsSync,
|
existsSync,
|
||||||
@@ -95,13 +99,13 @@ describe('resolveBundledClaudeCodeExecutable', () => {
|
|||||||
resolvePackageDir: (pkg) =>
|
resolvePackageDir: (pkg) =>
|
||||||
pkg === '@anthropic-ai/claude-agent-sdk-darwin-arm64' ? dir : null,
|
pkg === '@anthropic-ai/claude-agent-sdk-darwin-arm64' ? dir : null,
|
||||||
});
|
});
|
||||||
expect(result).toBe(path.join(dir, 'cli'));
|
expect(result).toBe(path.join(dir, 'claude'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves Windows binary under `cli.exe`', () => {
|
it('resolves Windows binary under `claude.exe`', () => {
|
||||||
const dir =
|
const dir =
|
||||||
'C:\\\\fake\\\\node_modules\\\\@anthropic-ai\\\\claude-agent-sdk-win32-x64';
|
'C:\\\\fake\\\\node_modules\\\\@anthropic-ai\\\\claude-agent-sdk-win32-x64';
|
||||||
const binary = path.join(dir, 'cli.exe');
|
const binary = path.join(dir, 'claude.exe');
|
||||||
const existsSync = (p: string): boolean => p === binary;
|
const existsSync = (p: string): boolean => p === binary;
|
||||||
const result = resolveBundledClaudeCodeExecutable({
|
const result = resolveBundledClaudeCodeExecutable({
|
||||||
env: {},
|
env: {},
|
||||||
@@ -114,6 +118,17 @@ describe('resolveBundledClaudeCodeExecutable', () => {
|
|||||||
expect(result).toBe(binary);
|
expect(result).toBe(binary);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('default resolver handles SDK optional packages without a bare entrypoint', () => {
|
||||||
|
if (process.platform !== 'linux' || process.arch !== 'x64') return;
|
||||||
|
|
||||||
|
const dir = __test__.defaultResolvePackageDir(
|
||||||
|
'@anthropic-ai/claude-agent-sdk-linux-x64',
|
||||||
|
);
|
||||||
|
if (!dir) return;
|
||||||
|
|
||||||
|
expect(fs.existsSync(path.join(dir, 'claude'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('throws a descriptive error when no binary is found', () => {
|
it('throws a descriptive error when no binary is found', () => {
|
||||||
const existsSync = () => false;
|
const existsSync = () => false;
|
||||||
expect(() =>
|
expect(() =>
|
||||||
@@ -127,6 +142,34 @@ describe('resolveBundledClaudeCodeExecutable', () => {
|
|||||||
).toThrow(/Unable to locate a bundled Claude Code CLI binary/);
|
).toThrow(/Unable to locate a bundled Claude Code CLI binary/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resolves the linux x64 optional package even when it has no JS entrypoint', () => {
|
||||||
|
if (process.platform !== 'linux' || process.arch !== 'x64') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = createRequire(import.meta.url);
|
||||||
|
let packageJsonPath: string;
|
||||||
|
try {
|
||||||
|
packageJsonPath = req.resolve(
|
||||||
|
'@anthropic-ai/claude-agent-sdk-linux-x64/package.json',
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
req.resolve('@anthropic-ai/claude-agent-sdk-linux-x64');
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
// This is the regression case: package.json resolves, bare package does not.
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = resolveBundledClaudeCodeExecutable({ env: {} });
|
||||||
|
|
||||||
|
expect(result).toBe(path.join(path.dirname(packageJsonPath), 'claude'));
|
||||||
|
expect(fs.existsSync(result)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('end-to-end: resolves the actual bundled binary on this host when installed', () => {
|
it('end-to-end: resolves the actual bundled binary on this host when installed', () => {
|
||||||
// Real-world smoke test. Some CI/host sandboxes do not install the optional
|
// Real-world smoke test. Some CI/host sandboxes do not install the optional
|
||||||
// per-platform SDK package, so treat "package not resolvable" as a host
|
// per-platform SDK package, so treat "package not resolvable" as a host
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "ejclaw-codex-runner",
|
"name": "ejclaw-codex-runner",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openai/codex": "^0.120.0",
|
"@openai/codex": "^0.124.0",
|
||||||
"ejclaw-runners-shared": "file:../shared",
|
"ejclaw-runners-shared": "file:../shared",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -15,19 +15,19 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@openai/codex": ["@openai/codex@0.120.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.120.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.120.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.120.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.120.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.120.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.120.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-e2P1Gya3dwsRe9IPOiswVz5JfR700u+/sWCqDc3jkqv2QViPkNiBmZoGhFnZL5jBpKakSjehC4/Fpspg70nHTw=="],
|
"@openai/codex": ["@openai/codex@0.124.0", "", { "optionalDependencies": { "@openai/codex-darwin-arm64": "npm:@openai/codex@0.124.0-darwin-arm64", "@openai/codex-darwin-x64": "npm:@openai/codex@0.124.0-darwin-x64", "@openai/codex-linux-arm64": "npm:@openai/codex@0.124.0-linux-arm64", "@openai/codex-linux-x64": "npm:@openai/codex@0.124.0-linux-x64", "@openai/codex-win32-arm64": "npm:@openai/codex@0.124.0-win32-arm64", "@openai/codex-win32-x64": "npm:@openai/codex@0.124.0-win32-x64" }, "bin": { "codex": "bin/codex.js" } }, "sha512-1EVAuPyAQZ8zIVMw3bPJ6a4R8ifLAZ7LGsOyknj5c2he9AFXVRCmWx12WrdZJ25wcBvOEKt1n1Zx+QAj0EVGbQ=="],
|
||||||
|
|
||||||
"@openai/codex-darwin-arm64": ["@openai/codex@0.120.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7CU+I5kBaMuoqfG3xisq0mUWzxoEHvfu34cB8a0KpBiIhAgu12fKpmYgZ4/DvRP6Wm9Fu6LJYKVF5apUHFp8nQ=="],
|
"@openai/codex-darwin-arm64": ["@openai/codex@0.124.0-darwin-arm64", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lnuqeAdl+RjPWsqVZ8rrPskRIOZmzlyr8e5q/wFVEnMPsm9dWgjRW1PKa84UmDSQVOuz9GObF28DEHFyC4A8NA=="],
|
||||||
|
|
||||||
"@openai/codex-darwin-x64": ["@openai/codex@0.120.0-darwin-x64", "", { "os": "darwin", "cpu": "x64" }, "sha512-d7joNYuwrmd5iIdp/xAE5f8bZT1r82MnmU6Hzgxq3G+xClwEyhxU737ZWnstFSpnZNfxJ5zXCuFUJh4CAkHNtQ=="],
|
"@openai/codex-darwin-x64": ["@openai/codex@0.124.0-darwin-x64", "", { "os": "darwin", "cpu": "x64" }, "sha512-Br+VlL83IOu96Urw+mkZ1PQXLsQXGAYEH6kXNt4ULuVt7qAdQY2FKYwIgLLVLl0vpMk8qWxdfdVMqhiu2uCHlg=="],
|
||||||
|
|
||||||
"@openai/codex-linux-arm64": ["@openai/codex@0.120.0-linux-arm64", "", { "os": "linux", "cpu": "arm64" }, "sha512-sVYY25/URlpZPtb0Q0ryLh+lcq9UTEtHAkdZKa0a/R7mAdyPuhpU9V6jWmxwiUh7s53XZOEVFoKmLfH8YIDWCQ=="],
|
"@openai/codex-linux-arm64": ["@openai/codex@0.124.0-linux-arm64", "", { "os": "linux", "cpu": "arm64" }, "sha512-QXafFVQJxPfU1LSCI5afuHsF5evKAcbQpFuKou0Kl241/HG/zYHdwoWj546zH844gJgegIPAxPf36+RSkUsbbA=="],
|
||||||
|
|
||||||
"@openai/codex-linux-x64": ["@openai/codex@0.120.0-linux-x64", "", { "os": "linux", "cpu": "x64" }, "sha512-VcP9B/c/O+EFEgqoetCzvHrLfAdo8vrt09Gx1lJ8ikewctqAuJ/ozj/6wuvlz7XaaK64ib5cge01pOAeCyt2Sg=="],
|
"@openai/codex-linux-x64": ["@openai/codex@0.124.0-linux-x64", "", { "os": "linux", "cpu": "x64" }, "sha512-cJ4QfQIrz2gkXVWephiGo9JDyD3qLKhvkTTLk7gI8rKd7MZpVXn9/1e7pZCQnJVA7Dk6ocA5G+sxnR78SoIejw=="],
|
||||||
|
|
||||||
"@openai/codex-win32-arm64": ["@openai/codex@0.120.0-win32-arm64", "", { "os": "win32", "cpu": "arm64" }, "sha512-SAaTQU1XHa1qDnmQldmbyROIY5SiaspF+Cw3ziWeeTgyAET3rWusm4ELOElx6QiY1ugYW5ZD+7AFufS2z1xtpQ=="],
|
"@openai/codex-win32-arm64": ["@openai/codex@0.124.0-win32-arm64", "", { "os": "win32", "cpu": "arm64" }, "sha512-oDSI/CQh0kagBwWVPG9EiUBfHiY27ju3LPrjTVZg4VMpVJVNA31JTK8rHMZ6G/2gfNmOl6DMBA6+0ICxSkkshg=="],
|
||||||
|
|
||||||
"@openai/codex-win32-x64": ["@openai/codex@0.120.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-zja1GNrbHyOUTvOy5FVMa+rAYIs3m+FOS8rAXftxMEhodMmkMw2O8zcvso657SHhZR0hIEiZ6T70lcyH2YX0mQ=="],
|
"@openai/codex-win32-x64": ["@openai/codex@0.124.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-t+lAwmCWwIWQKk6dKaYetRhQsmA+uDmQy1NLka1xAAv3PlNOH8iNz9Lsisr3qYkBR8Tf7tbQlt+pl9tw/A5mfA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
|
"@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ejclaw-runners-shared": "file:../shared",
|
"ejclaw-runners-shared": "file:../shared",
|
||||||
"@openai/codex": "^0.120.0"
|
"@openai/codex": "^0.124.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
|||||||
Reference in New Issue
Block a user