@jentrix/runner 0.5.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/README.md +385 -0
- package/dist/chunk-A6NTXRVS.js +7 -0
- package/dist/chunk-A6NTXRVS.js.map +7 -0
- package/dist/chunk-N2UNVXHV.js +184 -0
- package/dist/chunk-N2UNVXHV.js.map +7 -0
- package/dist/chunk-PMRDMBQR.js +35 -0
- package/dist/chunk-PMRDMBQR.js.map +7 -0
- package/dist/local-probes-24XPDSVY.js +495 -0
- package/dist/local-probes-24XPDSVY.js.map +7 -0
- package/dist/runner-cli.js +288 -0
- package/dist/runner-cli.js.map +7 -0
- package/dist/runner-config-THT646ZG.js +15 -0
- package/dist/runner-config-THT646ZG.js.map +7 -0
- package/dist/runner-setup-JHWM6BYD.js +153 -0
- package/dist/runner-setup-JHWM6BYD.js.map +7 -0
- package/dist/session-hook-log-KNO6C4BF.js +11 -0
- package/dist/session-hook-log-KNO6C4BF.js.map +7 -0
- package/dist/session-host-2Y7475EG.js +1894 -0
- package/dist/session-host-2Y7475EG.js.map +7 -0
- package/dist/workflow-runner.js +6414 -0
- package/dist/workflow-runner.js.map +7 -0
- package/package.json +49 -0
- package/rubrics/executor-evidence.md +51 -0
- package/rubrics/reviewer-disposition.md +81 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../lib/runner-config.ts"],
|
|
4
|
+
"sourcesContent": ["import { constants, promises as fs } from \"node:fs\";\nimport { dirname, isAbsolute, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport const RUNNER_CONFIG_SCHEMA_VERSION = 1;\n\nexport type RunnerRole = \"planner\" | \"executor\" | \"reviewer\";\n\nexport interface RunnerRoleBinding {\n agentId: string;\n runtimeAdapterKey: \"claude\" | \"codex\";\n provider: string;\n providerConnectionId: string;\n model: string;\n source: \"active_release\" | \"worker_draft\";\n requiresClaimCredential: boolean;\n}\n\nexport interface RunnerConfig {\n schemaVersion: 1;\n protocolVersion: number;\n runnerVersion: string;\n jentrixBaseUrl: string;\n mcpUrl: string;\n workspaceId: string;\n workerId: string;\n heartbeatToken: string;\n bootstrapToken: string;\n claimUrl: string;\n executables: Partial<Record<\"claude\" | \"codex\", string>>;\n roleBindings: Record<RunnerRole, RunnerRoleBinding>;\n}\n\nexport function runnerConfigPath(\n env: NodeJS.ProcessEnv = process.env,\n home: string = homedir(),\n): string {\n const override = env.STACKS_RUNNER_CONFIG?.trim();\n if (override) {\n if (!isAbsolute(override)) {\n throw new Error(\n \"RUNNER_CONFIG_INVALID: STACKS_RUNNER_CONFIG must be absolute.\",\n );\n }\n return override;\n }\n return join(home, \".config\", \"stacks\", \"runner.json\");\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction validateConfig(value: unknown): RunnerConfig {\n if (\n !isRecord(value) ||\n value.schemaVersion !== RUNNER_CONFIG_SCHEMA_VERSION\n ) {\n throw new Error(\n \"RUNNER_CONFIG_INVALID: unsupported or missing config schema version.\",\n );\n }\n const required = [\n \"protocolVersion\",\n \"runnerVersion\",\n \"jentrixBaseUrl\",\n \"mcpUrl\",\n \"workspaceId\",\n \"workerId\",\n \"heartbeatToken\",\n \"bootstrapToken\",\n \"claimUrl\",\n ] as const;\n for (const key of required) {\n if (\n (key === \"protocolVersion\" && typeof value[key] !== \"number\") ||\n (key !== \"protocolVersion\" &&\n (typeof value[key] !== \"string\" || String(value[key]).length === 0))\n ) {\n throw new Error(`RUNNER_CONFIG_INVALID: config field ${key} is missing.`);\n }\n }\n if (!isRecord(value.executables) || !isRecord(value.roleBindings)) {\n throw new Error(\n \"RUNNER_CONFIG_INVALID: executable paths or role bindings are missing.\",\n );\n }\n for (const [key, path] of Object.entries(value.executables)) {\n if (\n (key !== \"claude\" && key !== \"codex\") ||\n typeof path !== \"string\" ||\n !isAbsolute(path)\n ) {\n throw new Error(\n \"RUNNER_CONFIG_INVALID: executable paths must be absolute.\",\n );\n }\n }\n for (const role of [\"planner\", \"executor\", \"reviewer\"] as const) {\n const binding = value.roleBindings[role];\n if (!isRecord(binding)) {\n throw new Error(\n `RUNNER_CONFIG_INVALID: ${role} role binding is missing.`,\n );\n }\n for (const key of [\n \"agentId\",\n \"runtimeAdapterKey\",\n \"provider\",\n \"providerConnectionId\",\n \"model\",\n \"source\",\n ] as const) {\n if (typeof binding[key] !== \"string\" || binding[key].length === 0) {\n throw new Error(`RUNNER_CONFIG_INVALID: ${role}.${key} is missing.`);\n }\n }\n if (\n binding.runtimeAdapterKey !== \"claude\" &&\n binding.runtimeAdapterKey !== \"codex\"\n ) {\n throw new Error(\n `RUNNER_CONFIG_INVALID: ${role} names an unknown runtime adapter.`,\n );\n }\n if (\n binding.source !== \"active_release\" &&\n binding.source !== \"worker_draft\"\n ) {\n throw new Error(`RUNNER_CONFIG_INVALID: ${role}.source is invalid.`);\n }\n if (typeof binding.requiresClaimCredential !== \"boolean\") {\n throw new Error(\n `RUNNER_CONFIG_INVALID: ${role}.requiresClaimCredential is missing.`,\n );\n }\n }\n return value as unknown as RunnerConfig;\n}\n\nasync function assertPrivate(path: string): Promise<void> {\n if (process.platform === \"win32\") return;\n const mode = (await fs.stat(path)).mode & 0o777;\n if ((mode & 0o077) !== 0) {\n throw new Error(\n `RUNNER_CONFIG_INVALID: ${path} must be mode 0600; run: chmod 600 ${JSON.stringify(path)}`,\n );\n }\n}\n\nexport async function readRunnerConfig(\n path = runnerConfigPath(),\n): Promise<RunnerConfig> {\n await assertPrivate(path);\n let parsed: unknown;\n try {\n parsed = JSON.parse(await fs.readFile(path, \"utf8\"));\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(`RUNNER_CONFIG_INVALID: ${path} is not valid JSON.`);\n }\n throw error;\n }\n return validateConfig(parsed);\n}\n\nexport async function runnerConfigSummary(path = runnerConfigPath()): Promise<{\n path: string;\n configured: boolean;\n workspaceId: string | null;\n workerId: string | null;\n blocker: { code: string; message: string; nextCommand: string } | null;\n}> {\n try {\n const config = await readRunnerConfig(path);\n return {\n path,\n configured: true,\n workspaceId: config.workspaceId,\n workerId: config.workerId,\n blocker: null,\n };\n } catch (error) {\n const code = (error as NodeJS.ErrnoException)?.code;\n if (code === \"ENOENT\") {\n return {\n path,\n configured: false,\n workspaceId: null,\n workerId: null,\n blocker: {\n code: \"BOOTSTRAP_REQUIRED\",\n message: \"runner setup has not been applied.\",\n nextCommand: \"jentrix runner setup\",\n },\n };\n }\n return {\n path,\n configured: false,\n workspaceId: null,\n workerId: null,\n blocker: {\n code: \"RUNNER_CONFIG_INVALID\",\n message:\n error instanceof Error ? error.message : \"runner config is invalid.\",\n nextCommand: \"jentrix runner setup\",\n },\n };\n }\n}\n\nexport async function writeRunnerConfigAtomic(\n config: RunnerConfig,\n path = runnerConfigPath(),\n): Promise<void> {\n validateConfig(config);\n const dir = dirname(path);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n if (process.platform !== \"win32\") await fs.chmod(dir, 0o700);\n const temp = `${path}.tmp-${process.pid}-${Date.now()}`;\n try {\n const handle = await fs.open(\n temp,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600,\n );\n try {\n await handle.writeFile(`${JSON.stringify(config, null, 2)}\\n`, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n if (process.platform !== \"win32\") await fs.chmod(temp, 0o600);\n await fs.rename(temp, path);\n } catch (error) {\n await fs.unlink(temp).catch(() => undefined);\n throw error;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// lib/session-hook-log.ts
|
|
2
|
+
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function appendHookEvent(sessionDir, eventName, stdinJson) {
|
|
5
|
+
mkdirSync(sessionDir, { recursive: true, mode: 448 });
|
|
6
|
+
const line = `${JSON.stringify({ event: eventName, at: (/* @__PURE__ */ new Date()).toISOString(), payload: safeParse(stdinJson) })}
|
|
7
|
+
`;
|
|
8
|
+
appendFileSync(join(sessionDir, "hooks.ndjson"), line, { mode: 384 });
|
|
9
|
+
}
|
|
10
|
+
function safeParse(raw) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
} catch {
|
|
14
|
+
return { raw: raw.slice(0, 1e4) };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function readHookLines(sessionDir, fromOffset) {
|
|
18
|
+
const path = join(sessionDir, "hooks.ndjson");
|
|
19
|
+
let body;
|
|
20
|
+
try {
|
|
21
|
+
body = readFileSync(path, "utf8");
|
|
22
|
+
} catch {
|
|
23
|
+
return { lines: [], offset: fromOffset };
|
|
24
|
+
}
|
|
25
|
+
const slice = body.slice(fromOffset);
|
|
26
|
+
const lines = slice.split("\n").filter(Boolean).map((line) => safeParse(line)).filter((line) => typeof line?.event === "string");
|
|
27
|
+
return { lines, offset: fromOffset + slice.length };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
appendHookEvent,
|
|
32
|
+
safeParse,
|
|
33
|
+
readHookLines
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-PMRDMBQR.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../lib/session-hook-log.ts"],
|
|
4
|
+
"sourcesContent": ["import { appendFileSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// The session hook log (MVP separation P5) \u2014 split out of `session-host.ts`\n// for ONE reason: what the `session-hook` verb does is append a line to a file,\n// and it was paying to load the whole session host to do it.\n//\n// That verb is invoked by a Claude Code lifecycle hook, several times per\n// conversation, in front of the operator. Reaching it through `session-host.js`\n// meant loading the MCP client, the streamable-HTTP transport, the bridge, the\n// redactor, the spool and both transcript mappers first \u2014 ~5.8 MB of\n// `@modelcontextprotocol/sdk` alone \u2014 none of which an append touches.\n//\n// The WRITER lives here with the reader deliberately: they share the NDJSON\n// line shape and `safeParse`'s tolerance of a truncated final line, and a\n// writer that drifts from its reader is how a hook log stops being readable.\n// `session-host.ts` re-exports all four names, so existing importers are\n// unaffected \u2014 the split is a module boundary, not an API change.\n// ---------------------------------------------------------------------------\n\n/** `session-hook` verb: append hook stdin JSON to the session's hook file. */\nexport function appendHookEvent(\n sessionDir: string,\n eventName: string,\n stdinJson: string,\n): void {\n mkdirSync(sessionDir, { recursive: true, mode: 0o700 });\n const line = `${JSON.stringify({ event: eventName, at: new Date().toISOString(), payload: safeParse(stdinJson) })}\\n`;\n appendFileSync(join(sessionDir, \"hooks.ndjson\"), line, { mode: 0o600 });\n}\n\n/**\n * Never throws: a hook payload that is not JSON, or a final line the writer was\n * mid-append on, must not take down either side. The raw text is kept (capped)\n * so the failure is visible in the log rather than silently empty.\n */\nexport function safeParse(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n return { raw: raw.slice(0, 10_000) };\n }\n}\n\nexport interface HookLine {\n event: string;\n at: string;\n payload: {\n session_id?: string;\n transcript_path?: string;\n cwd?: string;\n [key: string]: unknown;\n };\n}\n\nexport function readHookLines(\n sessionDir: string,\n fromOffset: number,\n): {\n lines: HookLine[];\n offset: number;\n} {\n const path = join(sessionDir, \"hooks.ndjson\");\n let body: string;\n try {\n body = readFileSync(path, \"utf8\");\n } catch {\n return { lines: [], offset: fromOffset };\n }\n const slice = body.slice(fromOffset);\n const lines = slice\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => safeParse(line) as HookLine)\n .filter((line) => typeof line?.event === \"string\");\n return { lines, offset: fromOffset + slice.length };\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,gBAAgB,WAAW,oBAAoB;AACxD,SAAS,YAAY;AAqBd,SAAS,gBACd,YACA,WACA,WACM;AACN,YAAU,YAAY,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACtD,QAAM,OAAO,GAAG,KAAK,UAAU,EAAE,OAAO,WAAW,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,SAAS,UAAU,SAAS,EAAE,CAAC,CAAC;AAAA;AACjH,iBAAe,KAAK,YAAY,cAAc,GAAG,MAAM,EAAE,MAAM,IAAM,CAAC;AACxE;AAOO,SAAS,UAAU,KAAsB;AAC9C,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,EAAE,KAAK,IAAI,MAAM,GAAG,GAAM,EAAE;AAAA,EACrC;AACF;AAaO,SAAS,cACd,YACA,YAIA;AACA,QAAM,OAAO,KAAK,YAAY,cAAc;AAC5C,MAAI;AACJ,MAAI;AACF,WAAO,aAAa,MAAM,MAAM;AAAA,EAClC,QAAQ;AACN,WAAO,EAAE,OAAO,CAAC,GAAG,QAAQ,WAAW;AAAA,EACzC;AACA,QAAM,QAAQ,KAAK,MAAM,UAAU;AACnC,QAAM,QAAQ,MACX,MAAM,IAAI,EACV,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,UAAU,IAAI,CAAa,EACzC,OAAO,CAAC,SAAS,OAAO,MAAM,UAAU,QAAQ;AACnD,SAAO,EAAE,OAAO,QAAQ,aAAa,MAAM,OAAO;AACpD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
// lib/local-probes.ts
|
|
2
|
+
import { execFile, spawn } from "node:child_process";
|
|
3
|
+
import { constants, promises as fs } from "node:fs";
|
|
4
|
+
import { delimiter, extname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
+
var PROBE_TIMEOUT_MS = 25e3;
|
|
7
|
+
var OUTPUT_CAP_BYTES = 1024 * 1024;
|
|
8
|
+
var PROBE_SCRUBBED_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
9
|
+
"STACKS_TOKEN",
|
|
10
|
+
"STACKS_RUNNER_TOKEN",
|
|
11
|
+
"STACKS_PLANNER_TOKEN",
|
|
12
|
+
"STACKS_REVIEWER_TOKEN",
|
|
13
|
+
"STACKS_WEBHOOK_SECRET",
|
|
14
|
+
"STACKS_BOOTSTRAP_TOKEN",
|
|
15
|
+
"STACKS_CLAIM_URL",
|
|
16
|
+
"STACKS_RUNNER_AUTH_STATE",
|
|
17
|
+
"STACKS_WORKER_HEARTBEAT_TOKEN",
|
|
18
|
+
"STACKS_RUNNER_ROLE_BINDINGS"
|
|
19
|
+
]);
|
|
20
|
+
function providerProbeEnv(env, adapter) {
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.entries(env).filter(
|
|
23
|
+
([key, value]) => value !== void 0 && !PROBE_SCRUBBED_ENV_KEYS.has(key) && !(adapter === "codex" && key === "ANTHROPIC_API_KEY")
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function probeError(code, message) {
|
|
28
|
+
return Object.assign(new Error(message), { code });
|
|
29
|
+
}
|
|
30
|
+
function errorCode(error) {
|
|
31
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : null;
|
|
32
|
+
}
|
|
33
|
+
async function executableAt(path) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(path, constants.X_OK);
|
|
36
|
+
return await fs.realpath(path);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function resolveExecutable(name, override, env = process.env) {
|
|
42
|
+
if (override) {
|
|
43
|
+
if (!isAbsolute(override)) return null;
|
|
44
|
+
return executableAt(override);
|
|
45
|
+
}
|
|
46
|
+
const extensions = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
47
|
+
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
48
|
+
if (!dir) continue;
|
|
49
|
+
for (const extension of extensions) {
|
|
50
|
+
const candidate = resolve(
|
|
51
|
+
dir,
|
|
52
|
+
process.platform === "win32" && !extname(name) ? `${name}${extension.toLowerCase()}` : name
|
|
53
|
+
);
|
|
54
|
+
const found = await executableAt(candidate);
|
|
55
|
+
if (found) return found;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function runFileBounded(file, args, env = process.env) {
|
|
61
|
+
if (!isAbsolute(file)) {
|
|
62
|
+
return Promise.reject(new Error("probe executable path must be absolute"));
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolveResult, reject) => {
|
|
65
|
+
execFile(
|
|
66
|
+
file,
|
|
67
|
+
args,
|
|
68
|
+
{
|
|
69
|
+
env,
|
|
70
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
71
|
+
maxBuffer: OUTPUT_CAP_BYTES,
|
|
72
|
+
windowsHide: true
|
|
73
|
+
},
|
|
74
|
+
(error, stdout, stderr) => {
|
|
75
|
+
if (error && (error.killed || errorCode(error) === "ETIMEDOUT")) {
|
|
76
|
+
reject(probeError("PROBE_TIMEOUT", "local runtime probe timed out"));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (error && errorCode(error) === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
80
|
+
reject(new Error("local runtime probe exceeded its output limit"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolveResult({
|
|
84
|
+
code: typeof error?.code === "number" ? Number(error.code) : 0,
|
|
85
|
+
stdout: String(stdout).slice(0, OUTPUT_CAP_BYTES),
|
|
86
|
+
stderr: String(stderr).slice(0, OUTPUT_CAP_BYTES)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function cleanModels(value) {
|
|
93
|
+
if (!Array.isArray(value)) return [];
|
|
94
|
+
const out = [];
|
|
95
|
+
const seen = /* @__PURE__ */ new Set();
|
|
96
|
+
for (const item of value) {
|
|
97
|
+
if (!item || typeof item !== "object") continue;
|
|
98
|
+
const row = item;
|
|
99
|
+
const id = [row.id, row.model, row.slug, row.value].find((v) => typeof v === "string" && v.trim().length > 0)?.trim();
|
|
100
|
+
if (!id || seen.has(id)) continue;
|
|
101
|
+
seen.add(id);
|
|
102
|
+
const displayName = [row.displayName, row.display_name, row.name].find(
|
|
103
|
+
(v) => typeof v === "string" && v.trim().length > 0
|
|
104
|
+
);
|
|
105
|
+
out.push({
|
|
106
|
+
id,
|
|
107
|
+
...displayName ? { displayName: displayName.trim() } : {}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
function record(value) {
|
|
113
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
114
|
+
}
|
|
115
|
+
async function waitForAbort(signal) {
|
|
116
|
+
if (signal.aborted) return;
|
|
117
|
+
await new Promise(
|
|
118
|
+
(resolveAbort) => signal.addEventListener("abort", () => resolveAbort(), { once: true })
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
async function probeClaudeInit(executablePath, env = process.env) {
|
|
122
|
+
const abort = new AbortController();
|
|
123
|
+
const session = query({
|
|
124
|
+
// A never-yielding prompt lets the SDK finish local initialization without
|
|
125
|
+
// ever writing a user message or starting a provider request.
|
|
126
|
+
prompt: (async function* () {
|
|
127
|
+
await waitForAbort(abort.signal);
|
|
128
|
+
})(),
|
|
129
|
+
options: {
|
|
130
|
+
persistSession: false,
|
|
131
|
+
pathToClaudeCodeExecutable: executablePath,
|
|
132
|
+
abortController: abort,
|
|
133
|
+
settingSources: [],
|
|
134
|
+
allowedTools: [],
|
|
135
|
+
mcpServers: {},
|
|
136
|
+
strictMcpConfig: true,
|
|
137
|
+
env: { ...env, ENABLE_CLAUDEAI_MCP_SERVERS: "false" },
|
|
138
|
+
stderr: () => {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
let timer;
|
|
143
|
+
try {
|
|
144
|
+
const init = await Promise.race([
|
|
145
|
+
session.initializationResult(),
|
|
146
|
+
new Promise((_, reject) => {
|
|
147
|
+
timer = setTimeout(
|
|
148
|
+
() => reject(probeError("PROBE_TIMEOUT", "Claude probe timed out")),
|
|
149
|
+
PROBE_TIMEOUT_MS
|
|
150
|
+
);
|
|
151
|
+
})
|
|
152
|
+
]);
|
|
153
|
+
const account = init.account;
|
|
154
|
+
const authMethod = account?.apiKeySource ? "api_key" : account?.subscriptionType || account?.tokenSource || account?.apiProvider ? "subscription" : "unknown";
|
|
155
|
+
const models = cleanModels(init.models);
|
|
156
|
+
return {
|
|
157
|
+
authenticated: authMethod !== "unknown",
|
|
158
|
+
authMethod,
|
|
159
|
+
availableModels: models,
|
|
160
|
+
selectedModel: models[0]?.id ?? null
|
|
161
|
+
};
|
|
162
|
+
} finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
abort.abort();
|
|
165
|
+
session.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function probeCodexRpc(executablePath, env = process.env) {
|
|
169
|
+
if (!isAbsolute(executablePath)) {
|
|
170
|
+
return Promise.reject(new Error("probe executable path must be absolute"));
|
|
171
|
+
}
|
|
172
|
+
return new Promise((resolveProbe, rejectProbe) => {
|
|
173
|
+
const child = spawn(executablePath, ["app-server"], {
|
|
174
|
+
env,
|
|
175
|
+
shell: false,
|
|
176
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
177
|
+
windowsHide: true
|
|
178
|
+
});
|
|
179
|
+
let outputBytes = 0;
|
|
180
|
+
let buffer = "";
|
|
181
|
+
let nextId = 1;
|
|
182
|
+
let settled = false;
|
|
183
|
+
const pending = /* @__PURE__ */ new Map();
|
|
184
|
+
const finish = (error, value) => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
for (const waiter of pending.values()) {
|
|
189
|
+
waiter.reject(error ?? new Error("Codex app server stopped"));
|
|
190
|
+
}
|
|
191
|
+
pending.clear();
|
|
192
|
+
child.stdin.end();
|
|
193
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
194
|
+
if (error) rejectProbe(error);
|
|
195
|
+
else resolveProbe(value);
|
|
196
|
+
};
|
|
197
|
+
const timer = setTimeout(
|
|
198
|
+
() => finish(probeError("PROBE_TIMEOUT", "Codex probe timed out")),
|
|
199
|
+
PROBE_TIMEOUT_MS
|
|
200
|
+
);
|
|
201
|
+
const request = (method, params) => {
|
|
202
|
+
const id = nextId++;
|
|
203
|
+
return new Promise((resolveRequest, rejectRequest) => {
|
|
204
|
+
pending.set(id, { resolve: resolveRequest, reject: rejectRequest });
|
|
205
|
+
child.stdin.write(
|
|
206
|
+
`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
207
|
+
`
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
const onBytes = (chunk, protocol) => {
|
|
212
|
+
outputBytes += chunk.byteLength;
|
|
213
|
+
if (outputBytes > OUTPUT_CAP_BYTES) {
|
|
214
|
+
finish(new Error("Codex probe exceeded its output limit"));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (!protocol) return;
|
|
218
|
+
buffer += chunk.toString("utf8");
|
|
219
|
+
for (; ; ) {
|
|
220
|
+
const newline = buffer.indexOf("\n");
|
|
221
|
+
if (newline < 0) break;
|
|
222
|
+
const line = buffer.slice(0, newline).trim();
|
|
223
|
+
buffer = buffer.slice(newline + 1);
|
|
224
|
+
if (!line) continue;
|
|
225
|
+
try {
|
|
226
|
+
const message = JSON.parse(line);
|
|
227
|
+
if (typeof message.id !== "number") continue;
|
|
228
|
+
const waiter = pending.get(message.id);
|
|
229
|
+
if (!waiter) continue;
|
|
230
|
+
pending.delete(message.id);
|
|
231
|
+
if (message.error)
|
|
232
|
+
waiter.reject(new Error("Codex RPC method failed"));
|
|
233
|
+
else waiter.resolve(message.result);
|
|
234
|
+
} catch {
|
|
235
|
+
finish(new Error("Codex app server returned malformed JSON"));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
child.stdout.on("data", (chunk) => onBytes(chunk, true));
|
|
240
|
+
child.stderr.on("data", (chunk) => onBytes(chunk, false));
|
|
241
|
+
child.once("error", (error) => finish(error));
|
|
242
|
+
child.once("exit", () => {
|
|
243
|
+
if (!settled) finish(new Error("Codex app server exited during probe"));
|
|
244
|
+
});
|
|
245
|
+
void (async () => {
|
|
246
|
+
await request("initialize", {
|
|
247
|
+
clientInfo: {
|
|
248
|
+
name: "stacks_runner_probe",
|
|
249
|
+
title: "Jentrix Runner",
|
|
250
|
+
version: "1"
|
|
251
|
+
},
|
|
252
|
+
capabilities: { experimentalApi: true }
|
|
253
|
+
});
|
|
254
|
+
child.stdin.write(
|
|
255
|
+
`${JSON.stringify({ jsonrpc: "2.0", method: "initialized" })}
|
|
256
|
+
`
|
|
257
|
+
);
|
|
258
|
+
const accountResult = record(await request("account/read", {}));
|
|
259
|
+
const account = record(accountResult?.account);
|
|
260
|
+
if (!account && accountResult?.requiresOpenaiAuth === true) {
|
|
261
|
+
finish(void 0, {
|
|
262
|
+
authenticated: false,
|
|
263
|
+
authMethod: "unknown",
|
|
264
|
+
availableModels: [],
|
|
265
|
+
selectedModel: null
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const modelResult = record(await request("model/list", {}));
|
|
270
|
+
const models = cleanModels(modelResult?.data ?? modelResult?.models);
|
|
271
|
+
const authType = String(account?.type ?? "").toLowerCase();
|
|
272
|
+
finish(void 0, {
|
|
273
|
+
authenticated: Boolean(account),
|
|
274
|
+
authMethod: /api/.test(authType) ? "api_key" : account ? "subscription" : "unknown",
|
|
275
|
+
availableModels: models,
|
|
276
|
+
selectedModel: models.find((model) => {
|
|
277
|
+
const source = Array.isArray(modelResult?.data) ? modelResult.data : [];
|
|
278
|
+
return source.some(
|
|
279
|
+
(row) => (row.id === model.id || row.model === model.id) && (row.isDefault === true || row.is_default === true)
|
|
280
|
+
);
|
|
281
|
+
})?.id ?? null
|
|
282
|
+
});
|
|
283
|
+
})().catch(
|
|
284
|
+
(error) => finish(error instanceof Error ? error : new Error(String(error)))
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function versionOf(output) {
|
|
289
|
+
return output.match(/\b\d+\.\d+(?:\.\d+)?(?:[-+][\w.-]+)?\b/)?.[0] ?? null;
|
|
290
|
+
}
|
|
291
|
+
function blocker(code, message, nextCommand) {
|
|
292
|
+
return { code, message, ...nextCommand ? { nextCommand } : {} };
|
|
293
|
+
}
|
|
294
|
+
async function cliAuthFallback(adapter, file, runFile) {
|
|
295
|
+
const args = adapter === "claude" ? ["auth", "status", "--json"] : ["login", "status"];
|
|
296
|
+
try {
|
|
297
|
+
const result = await runFile(file, args);
|
|
298
|
+
const text = `${result.stdout}
|
|
299
|
+
${result.stderr}`.toLowerCase();
|
|
300
|
+
if (/not logged|login required|authentication required|"loggedin"\s*:\s*false/.test(
|
|
301
|
+
text
|
|
302
|
+
)) {
|
|
303
|
+
return "required";
|
|
304
|
+
}
|
|
305
|
+
if (result.code === 0 && /logged in|authenticated|"loggedin"\s*:\s*true/.test(text)) {
|
|
306
|
+
return "authenticated";
|
|
307
|
+
}
|
|
308
|
+
return "unknown";
|
|
309
|
+
} catch {
|
|
310
|
+
return "unknown";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function probeRuntime(adapterKey, override, deps) {
|
|
314
|
+
const checkedAt = deps.now().toISOString();
|
|
315
|
+
const nextCommand = adapterKey === "claude" ? "claude auth login" : "codex login";
|
|
316
|
+
const binaryPath = await deps.resolveExecutable(adapterKey, override);
|
|
317
|
+
const base = {
|
|
318
|
+
adapterKey,
|
|
319
|
+
binaryPath,
|
|
320
|
+
version: null,
|
|
321
|
+
authMethod: "unknown",
|
|
322
|
+
availableModels: [],
|
|
323
|
+
selectedModel: null,
|
|
324
|
+
capabilitySource: "none",
|
|
325
|
+
checkedAt
|
|
326
|
+
};
|
|
327
|
+
if (!binaryPath) {
|
|
328
|
+
return {
|
|
329
|
+
...base,
|
|
330
|
+
status: "missing",
|
|
331
|
+
blocker: blocker(
|
|
332
|
+
"BINARY_NOT_FOUND",
|
|
333
|
+
`${adapterKey === "claude" ? "Claude Code" : "Codex"} is not installed or the configured path is not executable.`,
|
|
334
|
+
`Install ${adapterKey === "claude" ? "Claude Code" : "Codex"} manually, then run jentrix runner doctor`
|
|
335
|
+
)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
let version = null;
|
|
339
|
+
try {
|
|
340
|
+
const result = await deps.runFile(binaryPath, ["--version"]);
|
|
341
|
+
version = versionOf(`${result.stdout}
|
|
342
|
+
${result.stderr}`);
|
|
343
|
+
if (result.code !== 0 || !version) {
|
|
344
|
+
return {
|
|
345
|
+
...base,
|
|
346
|
+
binaryPath,
|
|
347
|
+
status: "failed",
|
|
348
|
+
blocker: blocker(
|
|
349
|
+
"VERSION_PROBE_FAILED",
|
|
350
|
+
`The ${adapterKey} executable ran but did not report a usable version.`,
|
|
351
|
+
`Update ${adapterKey} manually, then run jentrix runner doctor`
|
|
352
|
+
)
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
return {
|
|
357
|
+
...base,
|
|
358
|
+
binaryPath,
|
|
359
|
+
status: errorCode(error) === "PROBE_TIMEOUT" ? "timeout" : "failed",
|
|
360
|
+
blocker: blocker(
|
|
361
|
+
errorCode(error) === "PROBE_TIMEOUT" ? "VERSION_PROBE_FAILED" : "VERSION_PROBE_FAILED",
|
|
362
|
+
`The ${adapterKey} version probe ${errorCode(error) === "PROBE_TIMEOUT" ? "timed out" : "failed"}.`,
|
|
363
|
+
"jentrix runner doctor"
|
|
364
|
+
)
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
const evidence = await (adapterKey === "claude" ? deps.probeClaudeInit(binaryPath) : deps.probeCodexRpc(binaryPath));
|
|
369
|
+
if (evidence.compatible === false) {
|
|
370
|
+
return {
|
|
371
|
+
...base,
|
|
372
|
+
binaryPath,
|
|
373
|
+
version,
|
|
374
|
+
status: "incompatible",
|
|
375
|
+
blocker: blocker(
|
|
376
|
+
"RUNTIME_INCOMPATIBLE",
|
|
377
|
+
`The installed ${adapterKey} runtime lacks the required read-only capability protocol.`,
|
|
378
|
+
`Update ${adapterKey} manually, then run jentrix runner doctor`
|
|
379
|
+
)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (!evidence.authenticated) {
|
|
383
|
+
return {
|
|
384
|
+
...base,
|
|
385
|
+
binaryPath,
|
|
386
|
+
version,
|
|
387
|
+
status: "auth_required",
|
|
388
|
+
capabilitySource: adapterKey === "claude" ? "sdk_init" : "local_rpc",
|
|
389
|
+
blocker: blocker(
|
|
390
|
+
"AUTH_REQUIRED",
|
|
391
|
+
`The local ${adapterKey} login is absent or expired.`,
|
|
392
|
+
nextCommand
|
|
393
|
+
)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (evidence.availableModels.length === 0) {
|
|
397
|
+
return {
|
|
398
|
+
...base,
|
|
399
|
+
binaryPath,
|
|
400
|
+
version,
|
|
401
|
+
authMethod: evidence.authMethod,
|
|
402
|
+
status: "installed_unverified",
|
|
403
|
+
capabilitySource: adapterKey === "claude" ? "sdk_init" : "local_rpc",
|
|
404
|
+
blocker: blocker(
|
|
405
|
+
"AUTH_UNVERIFIED",
|
|
406
|
+
`The local ${adapterKey} probe could not prove a usable model without making a model request.`,
|
|
407
|
+
nextCommand
|
|
408
|
+
)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
...base,
|
|
413
|
+
binaryPath,
|
|
414
|
+
version,
|
|
415
|
+
authMethod: evidence.authMethod,
|
|
416
|
+
availableModels: evidence.availableModels,
|
|
417
|
+
selectedModel: evidence.selectedModel,
|
|
418
|
+
status: "ready",
|
|
419
|
+
capabilitySource: adapterKey === "claude" ? "sdk_init" : "local_rpc",
|
|
420
|
+
blocker: null
|
|
421
|
+
};
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (errorCode(error) === "PROBE_TIMEOUT") {
|
|
424
|
+
return {
|
|
425
|
+
...base,
|
|
426
|
+
binaryPath,
|
|
427
|
+
version,
|
|
428
|
+
status: "timeout",
|
|
429
|
+
blocker: blocker(
|
|
430
|
+
"RUNTIME_INCOMPATIBLE",
|
|
431
|
+
`The ${adapterKey} read-only capability probe timed out.`,
|
|
432
|
+
"jentrix runner doctor"
|
|
433
|
+
)
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const fallback = await cliAuthFallback(
|
|
437
|
+
adapterKey,
|
|
438
|
+
binaryPath,
|
|
439
|
+
deps.runFile
|
|
440
|
+
);
|
|
441
|
+
if (fallback === "required") {
|
|
442
|
+
return {
|
|
443
|
+
...base,
|
|
444
|
+
binaryPath,
|
|
445
|
+
version,
|
|
446
|
+
status: "auth_required",
|
|
447
|
+
capabilitySource: "cli_status",
|
|
448
|
+
blocker: blocker(
|
|
449
|
+
"AUTH_REQUIRED",
|
|
450
|
+
`The local ${adapterKey} login is absent or expired.`,
|
|
451
|
+
nextCommand
|
|
452
|
+
)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
...base,
|
|
457
|
+
binaryPath,
|
|
458
|
+
version,
|
|
459
|
+
status: "installed_unverified",
|
|
460
|
+
capabilitySource: fallback === "authenticated" ? "cli_status" : "none",
|
|
461
|
+
blocker: blocker(
|
|
462
|
+
"AUTH_UNVERIFIED",
|
|
463
|
+
`The installed ${adapterKey} version cannot prove readiness without a model request.`,
|
|
464
|
+
nextCommand
|
|
465
|
+
)
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function defaultLocalProbeDeps(env = process.env) {
|
|
470
|
+
const safeEnv = providerProbeEnv(env);
|
|
471
|
+
return {
|
|
472
|
+
env,
|
|
473
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
474
|
+
resolveExecutable: (name, override) => resolveExecutable(name, override, env),
|
|
475
|
+
runFile: (file, args) => runFileBounded(file, args, safeEnv),
|
|
476
|
+
probeClaudeInit: (file) => probeClaudeInit(file, providerProbeEnv(env, "claude")),
|
|
477
|
+
probeCodexRpc: (file) => probeCodexRpc(file, providerProbeEnv(env, "codex"))
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function probeLocalRuntimes(options = {}, deps = defaultLocalProbeDeps()) {
|
|
481
|
+
return Promise.all([
|
|
482
|
+
probeRuntime("claude", options.claudePath, deps),
|
|
483
|
+
probeRuntime("codex", options.codexPath, deps)
|
|
484
|
+
]);
|
|
485
|
+
}
|
|
486
|
+
export {
|
|
487
|
+
defaultLocalProbeDeps,
|
|
488
|
+
probeClaudeInit,
|
|
489
|
+
probeCodexRpc,
|
|
490
|
+
probeLocalRuntimes,
|
|
491
|
+
providerProbeEnv,
|
|
492
|
+
resolveExecutable,
|
|
493
|
+
runFileBounded
|
|
494
|
+
};
|
|
495
|
+
//# sourceMappingURL=local-probes-24XPDSVY.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../lib/local-probes.ts"],
|
|
4
|
+
"sourcesContent": ["import { execFile, spawn } from \"node:child_process\";\nimport { constants, promises as fs } from \"node:fs\";\nimport { delimiter, extname, isAbsolute, resolve } from \"node:path\";\n\nimport { query, type SDKUserMessage } from \"@anthropic-ai/claude-agent-sdk\";\n\nconst PROBE_TIMEOUT_MS = 25_000;\nconst OUTPUT_CAP_BYTES = 1024 * 1024;\nconst PROBE_SCRUBBED_ENV_KEYS = new Set([\n \"STACKS_TOKEN\",\n \"STACKS_RUNNER_TOKEN\",\n \"STACKS_PLANNER_TOKEN\",\n \"STACKS_REVIEWER_TOKEN\",\n \"STACKS_WEBHOOK_SECRET\",\n \"STACKS_BOOTSTRAP_TOKEN\",\n \"STACKS_CLAIM_URL\",\n \"STACKS_RUNNER_AUTH_STATE\",\n \"STACKS_WORKER_HEARTBEAT_TOKEN\",\n \"STACKS_RUNNER_ROLE_BINDINGS\",\n]);\n\nexport function providerProbeEnv(\n env: NodeJS.ProcessEnv,\n adapter?: \"claude\" | \"codex\",\n): NodeJS.ProcessEnv {\n return Object.fromEntries(\n Object.entries(env).filter(\n ([key, value]) =>\n value !== undefined &&\n !PROBE_SCRUBBED_ENV_KEYS.has(key) &&\n !(adapter === \"codex\" && key === \"ANTHROPIC_API_KEY\"),\n ),\n );\n}\n\nexport type LocalRuntimeStatus =\n | \"ready\"\n | \"missing\"\n | \"auth_required\"\n | \"installed_unverified\"\n | \"incompatible\"\n | \"timeout\"\n | \"failed\";\n\nexport interface LocalRuntimeProbe {\n adapterKey: \"claude\" | \"codex\";\n status: LocalRuntimeStatus;\n binaryPath: string | null;\n version: string | null;\n authMethod: \"subscription\" | \"api_key\" | \"unknown\";\n availableModels: Array<{ id: string; displayName?: string }>;\n selectedModel: string | null;\n capabilitySource: \"local_rpc\" | \"sdk_init\" | \"cli_status\" | \"none\";\n checkedAt: string;\n blocker: { code: string; message: string; nextCommand?: string } | null;\n}\n\nexport interface ReadOnlyCapabilityResult {\n authenticated: boolean;\n authMethod: LocalRuntimeProbe[\"authMethod\"];\n availableModels: LocalRuntimeProbe[\"availableModels\"];\n selectedModel: string | null;\n compatible?: boolean;\n}\n\nexport interface RunFileResult {\n code: number;\n stdout: string;\n stderr: string;\n}\n\nexport interface LocalProbeDeps {\n env: NodeJS.ProcessEnv;\n now(): Date;\n resolveExecutable(\n name: \"claude\" | \"codex\",\n override?: string,\n ): Promise<string | null>;\n runFile(file: string, args: string[]): Promise<RunFileResult>;\n probeClaudeInit(file: string): Promise<ReadOnlyCapabilityResult>;\n probeCodexRpc(file: string): Promise<ReadOnlyCapabilityResult>;\n}\n\nexport interface LocalProbeOptions {\n claudePath?: string;\n codexPath?: string;\n}\n\nfunction probeError(code: string, message: string): Error {\n return Object.assign(new Error(message), { code });\n}\n\nfunction errorCode(error: unknown): string | null {\n return typeof error === \"object\" && error !== null && \"code\" in error\n ? String((error as { code: unknown }).code)\n : null;\n}\n\nasync function executableAt(path: string): Promise<string | null> {\n try {\n await fs.access(path, constants.X_OK);\n return await fs.realpath(path);\n } catch {\n return null;\n }\n}\n\nexport async function resolveExecutable(\n name: \"claude\" | \"codex\",\n override: string | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<string | null> {\n if (override) {\n if (!isAbsolute(override)) return null;\n return executableAt(override);\n }\n const extensions =\n process.platform === \"win32\"\n ? (env.PATHEXT ?? \".EXE;.CMD;.BAT\").split(\";\")\n : [\"\"];\n for (const dir of (env.PATH ?? \"\").split(delimiter)) {\n if (!dir) continue;\n for (const extension of extensions) {\n const candidate = resolve(\n dir,\n process.platform === \"win32\" && !extname(name)\n ? `${name}${extension.toLowerCase()}`\n : name,\n );\n const found = await executableAt(candidate);\n if (found) return found;\n }\n }\n return null;\n}\n\nexport function runFileBounded(\n file: string,\n args: string[],\n env: NodeJS.ProcessEnv = process.env,\n): Promise<RunFileResult> {\n if (!isAbsolute(file)) {\n return Promise.reject(new Error(\"probe executable path must be absolute\"));\n }\n return new Promise((resolveResult, reject) => {\n execFile(\n file,\n args,\n {\n env,\n timeout: PROBE_TIMEOUT_MS,\n maxBuffer: OUTPUT_CAP_BYTES,\n windowsHide: true,\n },\n (error, stdout, stderr) => {\n if (error && (error.killed || errorCode(error) === \"ETIMEDOUT\")) {\n reject(probeError(\"PROBE_TIMEOUT\", \"local runtime probe timed out\"));\n return;\n }\n if (error && errorCode(error) === \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\") {\n reject(new Error(\"local runtime probe exceeded its output limit\"));\n return;\n }\n resolveResult({\n code:\n typeof (error as { code?: unknown } | null)?.code === \"number\"\n ? Number((error as { code: number }).code)\n : 0,\n stdout: String(stdout).slice(0, OUTPUT_CAP_BYTES),\n stderr: String(stderr).slice(0, OUTPUT_CAP_BYTES),\n });\n },\n );\n });\n}\n\nfunction cleanModels(value: unknown): LocalRuntimeProbe[\"availableModels\"] {\n if (!Array.isArray(value)) return [];\n const out: LocalRuntimeProbe[\"availableModels\"] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (!item || typeof item !== \"object\") continue;\n const row = item as Record<string, unknown>;\n const id = [row.id, row.model, row.slug, row.value]\n .find((v): v is string => typeof v === \"string\" && v.trim().length > 0)\n ?.trim();\n if (!id || seen.has(id)) continue;\n seen.add(id);\n const displayName = [row.displayName, row.display_name, row.name].find(\n (v): v is string => typeof v === \"string\" && v.trim().length > 0,\n );\n out.push({\n id,\n ...(displayName ? { displayName: displayName.trim() } : {}),\n });\n }\n return out;\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nasync function waitForAbort(signal: AbortSignal): Promise<void> {\n if (signal.aborted) return;\n await new Promise<void>((resolveAbort) =>\n signal.addEventListener(\"abort\", () => resolveAbort(), { once: true }),\n );\n}\n\nexport async function probeClaudeInit(\n executablePath: string,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<ReadOnlyCapabilityResult> {\n const abort = new AbortController();\n const session = query({\n // A never-yielding prompt lets the SDK finish local initialization without\n // ever writing a user message or starting a provider request.\n prompt: (async function* (): AsyncGenerator<SDKUserMessage> {\n await waitForAbort(abort.signal);\n })(),\n options: {\n persistSession: false,\n pathToClaudeCodeExecutable: executablePath,\n abortController: abort,\n settingSources: [],\n allowedTools: [],\n mcpServers: {},\n strictMcpConfig: true,\n env: { ...env, ENABLE_CLAUDEAI_MCP_SERVERS: \"false\" },\n stderr: () => {},\n },\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const init = await Promise.race([\n session.initializationResult(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(probeError(\"PROBE_TIMEOUT\", \"Claude probe timed out\")),\n PROBE_TIMEOUT_MS,\n );\n }),\n ]);\n const account = init.account;\n const authMethod = account?.apiKeySource\n ? \"api_key\"\n : account?.subscriptionType ||\n account?.tokenSource ||\n account?.apiProvider\n ? \"subscription\"\n : \"unknown\";\n const models = cleanModels(init.models);\n return {\n authenticated: authMethod !== \"unknown\",\n authMethod,\n availableModels: models,\n selectedModel: models[0]?.id ?? null,\n };\n } finally {\n clearTimeout(timer);\n abort.abort();\n session.close();\n }\n}\n\nexport function probeCodexRpc(\n executablePath: string,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<ReadOnlyCapabilityResult> {\n if (!isAbsolute(executablePath)) {\n return Promise.reject(new Error(\"probe executable path must be absolute\"));\n }\n return new Promise((resolveProbe, rejectProbe) => {\n const child = spawn(executablePath, [\"app-server\"], {\n env,\n shell: false,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n windowsHide: true,\n });\n let outputBytes = 0;\n let buffer = \"\";\n let nextId = 1;\n let settled = false;\n const pending = new Map<\n number,\n { resolve(value: unknown): void; reject(error: Error): void }\n >();\n const finish = (error?: Error, value?: ReadOnlyCapabilityResult) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n for (const waiter of pending.values()) {\n waiter.reject(error ?? new Error(\"Codex app server stopped\"));\n }\n pending.clear();\n child.stdin.end();\n if (!child.killed) child.kill(\"SIGTERM\");\n if (error) rejectProbe(error);\n else resolveProbe(value!);\n };\n const timer = setTimeout(\n () => finish(probeError(\"PROBE_TIMEOUT\", \"Codex probe timed out\")),\n PROBE_TIMEOUT_MS,\n );\n const request = (method: string, params: unknown): Promise<unknown> => {\n const id = nextId++;\n return new Promise((resolveRequest, rejectRequest) => {\n pending.set(id, { resolve: resolveRequest, reject: rejectRequest });\n child.stdin.write(\n `${JSON.stringify({ jsonrpc: \"2.0\", id, method, params })}\\n`,\n );\n });\n };\n const onBytes = (chunk: Buffer, protocol: boolean) => {\n outputBytes += chunk.byteLength;\n if (outputBytes > OUTPUT_CAP_BYTES) {\n finish(new Error(\"Codex probe exceeded its output limit\"));\n return;\n }\n if (!protocol) return;\n buffer += chunk.toString(\"utf8\");\n for (;;) {\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) break;\n const line = buffer.slice(0, newline).trim();\n buffer = buffer.slice(newline + 1);\n if (!line) continue;\n try {\n const message = JSON.parse(line) as Record<string, unknown>;\n if (typeof message.id !== \"number\") continue;\n const waiter = pending.get(message.id);\n if (!waiter) continue;\n pending.delete(message.id);\n if (message.error)\n waiter.reject(new Error(\"Codex RPC method failed\"));\n else waiter.resolve(message.result);\n } catch {\n finish(new Error(\"Codex app server returned malformed JSON\"));\n }\n }\n };\n child.stdout.on(\"data\", (chunk: Buffer) => onBytes(chunk, true));\n child.stderr.on(\"data\", (chunk: Buffer) => onBytes(chunk, false));\n child.once(\"error\", (error) => finish(error));\n child.once(\"exit\", () => {\n if (!settled) finish(new Error(\"Codex app server exited during probe\"));\n });\n\n void (async () => {\n await request(\"initialize\", {\n clientInfo: {\n name: \"stacks_runner_probe\",\n title: \"Jentrix Runner\",\n version: \"1\",\n },\n capabilities: { experimentalApi: true },\n });\n child.stdin.write(\n `${JSON.stringify({ jsonrpc: \"2.0\", method: \"initialized\" })}\\n`,\n );\n const accountResult = record(await request(\"account/read\", {}));\n const account = record(accountResult?.account);\n if (!account && accountResult?.requiresOpenaiAuth === true) {\n finish(undefined, {\n authenticated: false,\n authMethod: \"unknown\",\n availableModels: [],\n selectedModel: null,\n });\n return;\n }\n const modelResult = record(await request(\"model/list\", {}));\n const models = cleanModels(modelResult?.data ?? modelResult?.models);\n const authType = String(account?.type ?? \"\").toLowerCase();\n finish(undefined, {\n authenticated: Boolean(account),\n authMethod: /api/.test(authType)\n ? \"api_key\"\n : account\n ? \"subscription\"\n : \"unknown\",\n availableModels: models,\n selectedModel:\n models.find((model) => {\n const source = Array.isArray(modelResult?.data)\n ? (modelResult!.data as Array<Record<string, unknown>>)\n : [];\n return source.some(\n (row) =>\n (row.id === model.id || row.model === model.id) &&\n (row.isDefault === true || row.is_default === true),\n );\n })?.id ?? null,\n });\n })().catch((error) =>\n finish(error instanceof Error ? error : new Error(String(error))),\n );\n });\n}\n\nfunction versionOf(output: string): string | null {\n return output.match(/\\b\\d+\\.\\d+(?:\\.\\d+)?(?:[-+][\\w.-]+)?\\b/)?.[0] ?? null;\n}\n\nfunction blocker(\n code: string,\n message: string,\n nextCommand?: string,\n): LocalRuntimeProbe[\"blocker\"] {\n return { code, message, ...(nextCommand ? { nextCommand } : {}) };\n}\n\nasync function cliAuthFallback(\n adapter: \"claude\" | \"codex\",\n file: string,\n runFile: LocalProbeDeps[\"runFile\"],\n): Promise<\"authenticated\" | \"required\" | \"unknown\"> {\n const args =\n adapter === \"claude\" ? [\"auth\", \"status\", \"--json\"] : [\"login\", \"status\"];\n try {\n const result = await runFile(file, args);\n const text = `${result.stdout}\\n${result.stderr}`.toLowerCase();\n if (\n /not logged|login required|authentication required|\"loggedin\"\\s*:\\s*false/.test(\n text,\n )\n ) {\n return \"required\";\n }\n if (\n result.code === 0 &&\n /logged in|authenticated|\"loggedin\"\\s*:\\s*true/.test(text)\n ) {\n return \"authenticated\";\n }\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nasync function probeRuntime(\n adapterKey: \"claude\" | \"codex\",\n override: string | undefined,\n deps: LocalProbeDeps,\n): Promise<LocalRuntimeProbe> {\n const checkedAt = deps.now().toISOString();\n const nextCommand =\n adapterKey === \"claude\" ? \"claude auth login\" : \"codex login\";\n const binaryPath = await deps.resolveExecutable(adapterKey, override);\n const base = {\n adapterKey,\n binaryPath,\n version: null,\n authMethod: \"unknown\" as const,\n availableModels: [],\n selectedModel: null,\n capabilitySource: \"none\" as const,\n checkedAt,\n };\n if (!binaryPath) {\n return {\n ...base,\n status: \"missing\",\n blocker: blocker(\n \"BINARY_NOT_FOUND\",\n `${adapterKey === \"claude\" ? \"Claude Code\" : \"Codex\"} is not installed or the configured path is not executable.`,\n `Install ${adapterKey === \"claude\" ? \"Claude Code\" : \"Codex\"} manually, then run jentrix runner doctor`,\n ),\n };\n }\n let version: string | null = null;\n try {\n const result = await deps.runFile(binaryPath, [\"--version\"]);\n version = versionOf(`${result.stdout}\\n${result.stderr}`);\n if (result.code !== 0 || !version) {\n return {\n ...base,\n binaryPath,\n status: \"failed\",\n blocker: blocker(\n \"VERSION_PROBE_FAILED\",\n `The ${adapterKey} executable ran but did not report a usable version.`,\n `Update ${adapterKey} manually, then run jentrix runner doctor`,\n ),\n };\n }\n } catch (error) {\n return {\n ...base,\n binaryPath,\n status: errorCode(error) === \"PROBE_TIMEOUT\" ? \"timeout\" : \"failed\",\n blocker: blocker(\n errorCode(error) === \"PROBE_TIMEOUT\"\n ? \"VERSION_PROBE_FAILED\"\n : \"VERSION_PROBE_FAILED\",\n `The ${adapterKey} version probe ${errorCode(error) === \"PROBE_TIMEOUT\" ? \"timed out\" : \"failed\"}.`,\n \"jentrix runner doctor\",\n ),\n };\n }\n\n try {\n const evidence = await (adapterKey === \"claude\"\n ? deps.probeClaudeInit(binaryPath)\n : deps.probeCodexRpc(binaryPath));\n if (evidence.compatible === false) {\n return {\n ...base,\n binaryPath,\n version,\n status: \"incompatible\",\n blocker: blocker(\n \"RUNTIME_INCOMPATIBLE\",\n `The installed ${adapterKey} runtime lacks the required read-only capability protocol.`,\n `Update ${adapterKey} manually, then run jentrix runner doctor`,\n ),\n };\n }\n if (!evidence.authenticated) {\n return {\n ...base,\n binaryPath,\n version,\n status: \"auth_required\",\n capabilitySource: adapterKey === \"claude\" ? \"sdk_init\" : \"local_rpc\",\n blocker: blocker(\n \"AUTH_REQUIRED\",\n `The local ${adapterKey} login is absent or expired.`,\n nextCommand,\n ),\n };\n }\n if (evidence.availableModels.length === 0) {\n return {\n ...base,\n binaryPath,\n version,\n authMethod: evidence.authMethod,\n status: \"installed_unverified\",\n capabilitySource: adapterKey === \"claude\" ? \"sdk_init\" : \"local_rpc\",\n blocker: blocker(\n \"AUTH_UNVERIFIED\",\n `The local ${adapterKey} probe could not prove a usable model without making a model request.`,\n nextCommand,\n ),\n };\n }\n return {\n ...base,\n binaryPath,\n version,\n authMethod: evidence.authMethod,\n availableModels: evidence.availableModels,\n selectedModel: evidence.selectedModel,\n status: \"ready\",\n capabilitySource: adapterKey === \"claude\" ? \"sdk_init\" : \"local_rpc\",\n blocker: null,\n };\n } catch (error) {\n if (errorCode(error) === \"PROBE_TIMEOUT\") {\n return {\n ...base,\n binaryPath,\n version,\n status: \"timeout\",\n blocker: blocker(\n \"RUNTIME_INCOMPATIBLE\",\n `The ${adapterKey} read-only capability probe timed out.`,\n \"jentrix runner doctor\",\n ),\n };\n }\n const fallback = await cliAuthFallback(\n adapterKey,\n binaryPath,\n deps.runFile,\n );\n if (fallback === \"required\") {\n return {\n ...base,\n binaryPath,\n version,\n status: \"auth_required\",\n capabilitySource: \"cli_status\",\n blocker: blocker(\n \"AUTH_REQUIRED\",\n `The local ${adapterKey} login is absent or expired.`,\n nextCommand,\n ),\n };\n }\n return {\n ...base,\n binaryPath,\n version,\n status: \"installed_unverified\",\n capabilitySource: fallback === \"authenticated\" ? \"cli_status\" : \"none\",\n blocker: blocker(\n \"AUTH_UNVERIFIED\",\n `The installed ${adapterKey} version cannot prove readiness without a model request.`,\n nextCommand,\n ),\n };\n }\n}\n\nexport function defaultLocalProbeDeps(\n env: NodeJS.ProcessEnv = process.env,\n): LocalProbeDeps {\n const safeEnv = providerProbeEnv(env);\n return {\n env,\n now: () => new Date(),\n resolveExecutable: (name, override) =>\n resolveExecutable(name, override, env),\n runFile: (file, args) => runFileBounded(file, args, safeEnv),\n probeClaudeInit: (file) =>\n probeClaudeInit(file, providerProbeEnv(env, \"claude\")),\n probeCodexRpc: (file) =>\n probeCodexRpc(file, providerProbeEnv(env, \"codex\")),\n };\n}\n\nexport async function probeLocalRuntimes(\n options: LocalProbeOptions = {},\n deps: LocalProbeDeps = defaultLocalProbeDeps(),\n): Promise<LocalRuntimeProbe[]> {\n return Promise.all([\n probeRuntime(\"claude\", options.claudePath, deps),\n probeRuntime(\"codex\", options.codexPath, deps),\n ]);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|