@bman654/clodex 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-wrapper.js +9 -0
- package/dist/claude-wrapper.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +2 -0
- package/package.json +1 -1
package/dist/claude-wrapper.js
CHANGED
|
@@ -23,6 +23,14 @@ function isExecutableFile(path) {
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
function execIntoClaude(file, args, env) {
|
|
27
|
+
if (isWindows || typeof process.execve !== "function") return;
|
|
28
|
+
if (!isExecutableFile(file)) return;
|
|
29
|
+
try {
|
|
30
|
+
process.execve(file, [file, ...args], env);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
26
34
|
async function main() {
|
|
27
35
|
const argv = process.argv.slice(2);
|
|
28
36
|
const checkOnly = argv[0] === "--check";
|
|
@@ -53,6 +61,7 @@ async function main() {
|
|
|
53
61
|
process.exit(1);
|
|
54
62
|
}
|
|
55
63
|
const env = computeWrapperEnv(process.env, state);
|
|
64
|
+
execIntoClaude(claudePath, claudeArgs, env);
|
|
56
65
|
const child = spawn(claudePath, claudeArgs, {
|
|
57
66
|
stdio: "inherit",
|
|
58
67
|
env,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/claude-wrapper.ts"],"sourcesContent":["// src/claude-wrapper.ts — the `clodex-claude` bin.\n//\n// A tiny, fast exec-style wrapper around the Claude Code binary that injects\n// bridge env for a running standalone `clodex server` (discovered via\n// ~/.clodex/server-runtime.json). Two invocation shapes:\n//\n// 1. CLAUDE_CODE_PROCESS_WRAPPER contract: Claude Code invokes\n// `clodex-claude <claude-binary-path> <args...>` for every process it\n// spawns (agents view sessions, background agents). First arg is the\n// claude binary to exec.\n// 2. Direct terminal use: `clodex-claude [args...]` — the claude binary is\n// discovered the same way `clodex claude` discovers it\n// (CLODEX_CLAUDE_PATH override, config override, PATH, fallbacks).\n//\n// With a live proxy-mode server: HTTPS_PROXY/HTTP_PROXY + NODE_EXTRA_CA_CERTS\n// point at it and ANTHROPIC_BASE_URL is removed (claude keeps its own\n// Anthropic auth — this is the recommended mode). With a live endpoint-mode\n// server: ANTHROPIC_BASE_URL points at the gateway. With no live server the\n// env is passed through untouched, so claude always launches.\n//\n// This file must stay a thin shell over pure helpers (wrapper-env.ts,\n// server-runtime.ts) with minimal imports — it runs for every spawned agent.\n\nimport { spawn } from 'node:child_process';\nimport { accessSync, constants as fsConstants, statSync } from 'node:fs';\nimport { constants as osConstants } from 'node:os';\nimport { findClaudeBinary } from './launch.js';\nimport { waitForTcpListenerCandidate } from './listener-ready.js';\nimport {\n orderWrapperServerCandidates,\n readLiveServerRuntimeStates,\n type ServerRuntimeState,\n} from './server-runtime.js';\nimport { computeWrapperEnv, wrapperRequiresServer } from './wrapper-env.js';\n\nconst isWindows = process.platform === 'win32';\nconst WRAPPER_SERVER_READY_TIMEOUT_MS = 500;\n\nfunction isExecutableFile(path: string): boolean {\n try {\n if (!statSync(path).isFile()) return false;\n if (!isWindows) accessSync(path, fsConstants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const checkOnly = argv[0] === '--check';\n\n let claudePath: string | null = null;\n let claudeArgs: string[] = [];\n if (checkOnly) {\n // Readiness checks validate discovery and TCP state without launching Claude.\n } else if (argv[0] && isExecutableFile(argv[0])) {\n // CLAUDE_CODE_PROCESS_WRAPPER shape: first arg is the claude binary path.\n claudePath = argv[0];\n claudeArgs = argv.slice(1);\n } else {\n claudePath = findClaudeBinary();\n claudeArgs = argv;\n }\n\n if (!checkOnly && !claudePath) {\n process.stderr.write('clodex-claude: could not find the claude binary (set CLODEX_CLAUDE_PATH)\\n');\n process.exit(127);\n }\n\n // Selection policy (see orderWrapperServerCandidates): proxy-mode servers\n // are preferred over endpoint-mode ones — bridging keeps Claude Code's own\n // Anthropic auth — with newest startedAt breaking ties within a mode. A fast\n // probe round covers every candidate so an unreachable preferred record\n // cannot delay a reachable fallback. Timed-out probes retry under one shared\n // deadline; definitive connection errors fail immediately.\n const candidates = orderWrapperServerCandidates(readLiveServerRuntimeStates());\n const state: ServerRuntimeState | null = await waitForTcpListenerCandidate(\n '127.0.0.1',\n candidates,\n WRAPPER_SERVER_READY_TIMEOUT_MS,\n { retryFailure: result => result === 'timeout' },\n );\n if (checkOnly) process.exit(state ? 0 : 1);\n if (!state && wrapperRequiresServer(process.env)) {\n process.stderr.write('clodex-claude: no live clodex server is available\\n');\n process.exit(1);\n }\n const env = computeWrapperEnv(process.env, state);\n\n const child = spawn(claudePath!, claudeArgs, {\n stdio: 'inherit',\n env,\n shell: isWindows,\n });\n\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.once('SIGINT', () => forward('SIGINT'));\n process.once('SIGTERM', () => forward('SIGTERM'));\n\n child.on('error', err => {\n process.stderr.write(`clodex-claude: failed to launch ${claudePath}: ${err.message}\\n`);\n process.exit(127);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n const signum = osConstants.signals[signal as keyof typeof osConstants.signals];\n process.exit(signum ? 128 + signum : 1);\n }\n process.exit(code ?? 0);\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"sources":["../src/claude-wrapper.ts"],"sourcesContent":["// src/claude-wrapper.ts — the `clodex-claude` bin.\n//\n// A tiny, fast exec-style wrapper around the Claude Code binary that injects\n// bridge env for a running standalone `clodex server` (discovered via\n// ~/.clodex/server-runtime.json). Two invocation shapes:\n//\n// 1. CLAUDE_CODE_PROCESS_WRAPPER contract: Claude Code invokes\n// `clodex-claude <claude-binary-path> <args...>` for every process it\n// spawns (agents view sessions, background agents). First arg is the\n// claude binary to exec.\n// 2. Direct terminal use: `clodex-claude [args...]` — the claude binary is\n// discovered the same way `clodex claude` discovers it\n// (CLODEX_CLAUDE_PATH override, config override, PATH, fallbacks).\n//\n// With a live proxy-mode server: HTTPS_PROXY/HTTP_PROXY + NODE_EXTRA_CA_CERTS\n// point at it and ANTHROPIC_BASE_URL is removed (claude keeps its own\n// Anthropic auth — this is the recommended mode). With a live endpoint-mode\n// server: ANTHROPIC_BASE_URL points at the gateway. With no live server the\n// env is passed through untouched, so claude always launches.\n//\n// The wrapper REPLACES its own process image with claude (execve) rather than\n// parenting it — see execIntoClaude below for why that distinction matters.\n//\n// This file must stay a thin shell over pure helpers (wrapper-env.ts,\n// server-runtime.ts) with minimal imports — it runs for every spawned agent.\n\nimport { spawn } from 'node:child_process';\nimport { accessSync, constants as fsConstants, statSync } from 'node:fs';\nimport { constants as osConstants } from 'node:os';\nimport { findClaudeBinary } from './launch.js';\nimport { waitForTcpListenerCandidate } from './listener-ready.js';\nimport {\n orderWrapperServerCandidates,\n readLiveServerRuntimeStates,\n type ServerRuntimeState,\n} from './server-runtime.js';\nimport { computeWrapperEnv, wrapperRequiresServer } from './wrapper-env.js';\n\nconst isWindows = process.platform === 'win32';\nconst WRAPPER_SERVER_READY_TIMEOUT_MS = 500;\n\nfunction isExecutableFile(path: string): boolean {\n try {\n if (!statSync(path).isFile()) return false;\n if (!isWindows) accessSync(path, fsConstants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Replace this process with claude instead of parenting it. Returns when exec\n * is unavailable or declined, leaving the caller to spawn a child instead.\n *\n * Claude Code starts each background pty host with `detached: true` so the\n * process it spawns leads its own process group, then delivers terminal\n * resizes to that group with `process.kill(-process.pid, 'SIGWINCH')`. A\n * wrapper that spawns claude as a child takes the group-leader role for\n * itself: claude's pid no longer matches its group id, the signal fails with\n * ESRCH inside Claude Code's silent `catch {}`, and every background session\n * stays frozen at the startup size it was given on the command line (200x50)\n * however the terminal is later resized. Interactive sessions hid the bug\n * because there the kernel delivers SIGWINCH through the controlling terminal.\n *\n * Replacing the process image keeps the pid, process group, and inherited fds\n * exactly as Claude Code handed them out, so it cannot tell this wrapper apart\n * from launching claude directly. That also makes the signal forwarding and\n * exit-code mapping below unnecessary on this path.\n *\n * `process.execve` is POSIX-only and landed in Node 22.15. Windows and the\n * older 22.x releases still permitted by `engines.node` fall back to spawning,\n * which behaves correctly apart from background pty resizes.\n *\n * A failed exec cannot fall back to spawning: on syscall failure `execve`\n * aborts with a native crash dump (exit 134) rather than throwing. So re-check\n * the binary immediately before the call — `main` has awaited up to 500ms of\n * server probing since it first looked, and Claude Code replaces its own\n * binary when it self-updates — and let a vanished or unreadable file take the\n * spawn path, which still reports it as a one-line error and exit 127. Only\n * argument and platform validation, which run before the syscall, are\n * catchable; claude has not been launched when they throw.\n */\nfunction execIntoClaude(file: string, args: string[], env: NodeJS.ProcessEnv): void {\n if (isWindows || typeof process.execve !== 'function') return;\n if (!isExecutableFile(file)) return;\n\n try {\n process.execve(file, [file, ...args], env);\n } catch {\n // Rejected before the syscall — leave claude to the spawn path below.\n }\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const checkOnly = argv[0] === '--check';\n\n let claudePath: string | null = null;\n let claudeArgs: string[] = [];\n if (checkOnly) {\n // Readiness checks validate discovery and TCP state without launching Claude.\n } else if (argv[0] && isExecutableFile(argv[0])) {\n // CLAUDE_CODE_PROCESS_WRAPPER shape: first arg is the claude binary path.\n claudePath = argv[0];\n claudeArgs = argv.slice(1);\n } else {\n claudePath = findClaudeBinary();\n claudeArgs = argv;\n }\n\n if (!checkOnly && !claudePath) {\n process.stderr.write('clodex-claude: could not find the claude binary (set CLODEX_CLAUDE_PATH)\\n');\n process.exit(127);\n }\n\n // Selection policy (see orderWrapperServerCandidates): proxy-mode servers\n // are preferred over endpoint-mode ones — bridging keeps Claude Code's own\n // Anthropic auth — with newest startedAt breaking ties within a mode. A fast\n // probe round covers every candidate so an unreachable preferred record\n // cannot delay a reachable fallback. Timed-out probes retry under one shared\n // deadline; definitive connection errors fail immediately.\n const candidates = orderWrapperServerCandidates(readLiveServerRuntimeStates());\n const state: ServerRuntimeState | null = await waitForTcpListenerCandidate(\n '127.0.0.1',\n candidates,\n WRAPPER_SERVER_READY_TIMEOUT_MS,\n { retryFailure: result => result === 'timeout' },\n );\n if (checkOnly) process.exit(state ? 0 : 1);\n if (!state && wrapperRequiresServer(process.env)) {\n process.stderr.write('clodex-claude: no live clodex server is available\\n');\n process.exit(1);\n }\n const env = computeWrapperEnv(process.env, state);\n\n execIntoClaude(claudePath!, claudeArgs, env);\n\n // Only reached when exec is unavailable or failed.\n const child = spawn(claudePath!, claudeArgs, {\n stdio: 'inherit',\n env,\n shell: isWindows,\n });\n\n const forward = (signal: NodeJS.Signals) => child.kill(signal);\n process.once('SIGINT', () => forward('SIGINT'));\n process.once('SIGTERM', () => forward('SIGTERM'));\n\n child.on('error', err => {\n process.stderr.write(`clodex-claude: failed to launch ${claudePath}: ${err.message}\\n`);\n process.exit(127);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n const signum = osConstants.signals[signal as keyof typeof osConstants.signals];\n process.exit(signum ? 128 + signum : 1);\n }\n process.exit(code ?? 0);\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;;AA0BA,SAAS,aAAa;AACtB,SAAS,YAAY,aAAa,aAAa,gBAAgB;AAC/D,SAAS,aAAa,mBAAmB;AAUzC,IAAM,YAAY,QAAQ,aAAa;AACvC,IAAM,kCAAkC;AAExC,SAAS,iBAAiB,MAAuB;AAC/C,MAAI;AACF,QAAI,CAAC,SAAS,IAAI,EAAE,OAAO,EAAG,QAAO;AACrC,QAAI,CAAC,UAAW,YAAW,MAAM,YAAY,IAAI;AACjD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkCA,SAAS,eAAe,MAAc,MAAgB,KAA8B;AAClF,MAAI,aAAa,OAAO,QAAQ,WAAW,WAAY;AACvD,MAAI,CAAC,iBAAiB,IAAI,EAAG;AAE7B,MAAI;AACF,YAAQ,OAAO,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,GAAG;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,OAAsB;AACnC,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,YAAY,KAAK,CAAC,MAAM;AAE9B,MAAI,aAA4B;AAChC,MAAI,aAAuB,CAAC;AAC5B,MAAI,WAAW;AAAA,EAEf,WAAW,KAAK,CAAC,KAAK,iBAAiB,KAAK,CAAC,CAAC,GAAG;AAE/C,iBAAa,KAAK,CAAC;AACnB,iBAAa,KAAK,MAAM,CAAC;AAAA,EAC3B,OAAO;AACL,iBAAa,iBAAiB;AAC9B,iBAAa;AAAA,EACf;AAEA,MAAI,CAAC,aAAa,CAAC,YAAY;AAC7B,YAAQ,OAAO,MAAM,4EAA4E;AACjG,YAAQ,KAAK,GAAG;AAAA,EAClB;AAQA,QAAM,aAAa,6BAA6B,4BAA4B,CAAC;AAC7E,QAAM,QAAmC,MAAM;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,cAAc,YAAU,WAAW,UAAU;AAAA,EACjD;AACA,MAAI,UAAW,SAAQ,KAAK,QAAQ,IAAI,CAAC;AACzC,MAAI,CAAC,SAAS,sBAAsB,QAAQ,GAAG,GAAG;AAChD,YAAQ,OAAO,MAAM,qDAAqD;AAC1E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,MAAM,kBAAkB,QAAQ,KAAK,KAAK;AAEhD,iBAAe,YAAa,YAAY,GAAG;AAG3C,QAAM,QAAQ,MAAM,YAAa,YAAY;AAAA,IAC3C,OAAO;AAAA,IACP;AAAA,IACA,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,CAAC,WAA2B,MAAM,KAAK,MAAM;AAC7D,UAAQ,KAAK,UAAU,MAAM,QAAQ,QAAQ,CAAC;AAC9C,UAAQ,KAAK,WAAW,MAAM,QAAQ,SAAS,CAAC;AAEhD,QAAM,GAAG,SAAS,SAAO;AACvB,YAAQ,OAAO,MAAM,mCAAmC,UAAU,KAAK,IAAI,OAAO;AAAA,CAAI;AACtF,YAAQ,KAAK,GAAG;AAAA,EAClB,CAAC;AACD,QAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,QAAI,QAAQ;AACV,YAAM,SAAS,YAAY,QAAQ,MAA0C;AAC7E,cAAQ,KAAK,SAAS,MAAM,SAAS,CAAC;AAAA,IACxC;AACA,YAAQ,KAAK,QAAQ,CAAC;AAAA,EACxB,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}
|