@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.
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ RUNNER_VERSION
4
+ } from "./chunk-A6NTXRVS.js";
5
+
6
+ // runner-cli.ts
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawn } from "node:child_process";
9
+ import { realpathSync } from "node:fs";
10
+ var RUNNER_PROTOCOL_VERSION = 1;
11
+ function runnerVersionEnvelope() {
12
+ return {
13
+ protocolVersion: RUNNER_PROTOCOL_VERSION,
14
+ runnerVersion: RUNNER_VERSION
15
+ };
16
+ }
17
+ function runnerEnvironment(config, configPath, once, base = process.env) {
18
+ return {
19
+ ...base,
20
+ STACKS_BASE_URL: config.jentrixBaseUrl,
21
+ STACKS_MCP_URL: config.mcpUrl,
22
+ STACKS_RUNNER_MODE: "self-hosted",
23
+ STACKS_BOOTSTRAP_TOKEN: config.bootstrapToken,
24
+ STACKS_CLAIM_URL: config.claimUrl,
25
+ STACKS_RUNNER_AUTH_STATE: `${configPath}.auth.json`,
26
+ STACKS_WORKER_HEARTBEAT_TOKEN: config.heartbeatToken,
27
+ STACKS_RUNNER_ROLE_BINDINGS: JSON.stringify(config.roleBindings),
28
+ STACKS_RUNNER_HOST: "127.0.0.1",
29
+ STACKS_CLAUDE_EXECUTABLE: config.executables.claude,
30
+ STACKS_CODEX_EXECUTABLE: config.executables.codex,
31
+ STACKS_POLL_MS: base.STACKS_POLL_MS ?? "30000",
32
+ ...once ? { STACKS_RUNNER_ONCE: "1" } : {}
33
+ };
34
+ }
35
+ function terminationHandlers(kill) {
36
+ return {
37
+ interrupt: () => kill("SIGINT"),
38
+ terminate: () => kill("SIGTERM")
39
+ };
40
+ }
41
+ function assertRunnerConfigCompatibility(config) {
42
+ if (config.protocolVersion !== RUNNER_PROTOCOL_VERSION) {
43
+ throw new Error(
44
+ `RUNNER_VERSION_MISMATCH: run: npm install --global @jentrix/cli@${RUNNER_VERSION} @jentrix/runner@${RUNNER_VERSION}`
45
+ );
46
+ }
47
+ }
48
+ async function runConfiguredRunner(once) {
49
+ const { probeLocalRuntimes } = await import("./local-probes-24XPDSVY.js");
50
+ const { readRunnerConfig, runnerConfigPath } = await import("./runner-config-THT646ZG.js");
51
+ const { applyRunnerSetup } = await import("./runner-setup-JHWM6BYD.js");
52
+ const configPath = runnerConfigPath();
53
+ let config;
54
+ try {
55
+ config = await readRunnerConfig(configPath);
56
+ } catch (error) {
57
+ if (error?.code === "ENOENT") {
58
+ throw new Error("BOOTSTRAP_REQUIRED: run jentrix runner setup.");
59
+ }
60
+ throw error;
61
+ }
62
+ assertRunnerConfigCompatibility(config);
63
+ const runtimes = await probeLocalRuntimes({
64
+ claudePath: config.executables.claude,
65
+ codexPath: config.executables.codex
66
+ });
67
+ const probes = new Map(
68
+ runtimes.map((runtime) => [runtime.adapterKey, runtime])
69
+ );
70
+ for (const binding of Object.values(config.roleBindings)) {
71
+ const probe = probes.get(binding.runtimeAdapterKey);
72
+ if (!probe || probe.status !== "ready") {
73
+ throw new Error(
74
+ `ROLE_PLAN_UNSATISFIED: ${binding.runtimeAdapterKey} is not ready for a configured role.`
75
+ );
76
+ }
77
+ if (probe.availableModels.length > 0 && !probe.availableModels.some((model) => model.id === binding.model)) {
78
+ throw new Error(
79
+ `ROLE_PLAN_UNSATISFIED: ${binding.runtimeAdapterKey} cannot run configured model ${binding.model}.`
80
+ );
81
+ }
82
+ }
83
+ await applyRunnerSetup(
84
+ {
85
+ protocolVersion: config.protocolVersion,
86
+ runnerVersion: config.runnerVersion,
87
+ configPath,
88
+ jentrixBaseUrl: config.jentrixBaseUrl,
89
+ mcpUrl: config.mcpUrl,
90
+ workspaceId: config.workspaceId,
91
+ enrollment: null,
92
+ bootstrap: { token: config.bootstrapToken, claimUrl: config.claimUrl },
93
+ runtimes,
94
+ roleBindings: config.roleBindings,
95
+ attestationConnections: [
96
+ ...new Map(
97
+ Object.values(config.roleBindings).filter((binding) => binding.requiresClaimCredential).map((binding) => [
98
+ binding.providerConnectionId,
99
+ {
100
+ connectionId: binding.providerConnectionId,
101
+ runtimeAdapterKey: binding.runtimeAdapterKey
102
+ }
103
+ ])
104
+ ).values()
105
+ ]
106
+ },
107
+ {
108
+ postJson: (path, body) => postSetupJson(config.jentrixBaseUrl, path, body)
109
+ }
110
+ );
111
+ const workflowRunner = fileURLToPath(
112
+ new URL("./workflow-runner.js", import.meta.url)
113
+ );
114
+ const child = spawn(process.execPath, [workflowRunner], {
115
+ env: runnerEnvironment(config, configPath, once),
116
+ stdio: "inherit"
117
+ });
118
+ const { interrupt, terminate } = terminationHandlers(
119
+ (signal) => child.kill(signal)
120
+ );
121
+ process.once("SIGINT", interrupt);
122
+ process.once("SIGTERM", terminate);
123
+ try {
124
+ return await new Promise((resolve, reject) => {
125
+ child.once("error", reject);
126
+ child.once("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
127
+ });
128
+ } finally {
129
+ process.off("SIGINT", interrupt);
130
+ process.off("SIGTERM", terminate);
131
+ }
132
+ }
133
+ function valueAfter(args, flag) {
134
+ const at = args.indexOf(flag);
135
+ return at >= 0 ? args[at + 1] : void 0;
136
+ }
137
+ async function readStdinCapped(cap = 2 * 1024 * 1024) {
138
+ const chunks = [];
139
+ let bytes = 0;
140
+ for await (const chunk of process.stdin) {
141
+ const buffer = Buffer.from(chunk);
142
+ bytes += buffer.byteLength;
143
+ if (bytes > cap)
144
+ throw new Error("runner setup input exceeded its size limit");
145
+ chunks.push(buffer);
146
+ }
147
+ return Buffer.concat(chunks).toString("utf8");
148
+ }
149
+ async function postSetupJson(baseUrl, path, body) {
150
+ const response = await fetch(new URL(path, baseUrl), {
151
+ method: "POST",
152
+ headers: { "content-type": "application/json" },
153
+ body: JSON.stringify(body),
154
+ signal: AbortSignal.timeout(3e4)
155
+ });
156
+ if (!response.ok) {
157
+ throw new Error(
158
+ `${path} refused the runner request (HTTP ${response.status}).`
159
+ );
160
+ }
161
+ const value = await response.json();
162
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
163
+ throw new Error(`${path} returned malformed JSON.`);
164
+ }
165
+ return value;
166
+ }
167
+ async function main(args = process.argv.slice(2)) {
168
+ const command = args[0];
169
+ if (command === "--version" || command === "-v") {
170
+ process.stdout.write(`${RUNNER_VERSION}
171
+ `);
172
+ return 0;
173
+ }
174
+ if (command === "version") {
175
+ if (args.includes("--json")) {
176
+ process.stdout.write(`${JSON.stringify(runnerVersionEnvelope())}
177
+ `);
178
+ } else {
179
+ process.stdout.write(`${RUNNER_VERSION}
180
+ `);
181
+ }
182
+ return 0;
183
+ }
184
+ if (command === "probe" && args.includes("--json")) {
185
+ const { probeLocalRuntimes } = await import("./local-probes-24XPDSVY.js");
186
+ const { runnerConfigPath, runnerConfigSummary } = await import("./runner-config-THT646ZG.js");
187
+ const [runtimes, config] = await Promise.all([
188
+ probeLocalRuntimes({
189
+ claudePath: valueAfter(args, "--claude-path"),
190
+ codexPath: valueAfter(args, "--codex-path")
191
+ }),
192
+ runnerConfigSummary(runnerConfigPath())
193
+ ]);
194
+ process.stdout.write(
195
+ `${JSON.stringify({ ...runnerVersionEnvelope(), runtimes, config })}
196
+ `
197
+ );
198
+ return runtimes.some((runtime) => runtime.status === "ready") ? 0 : 1;
199
+ }
200
+ if (command === "setup-apply" && args.includes("--plan-stdin")) {
201
+ const { applyRunnerSetup } = await import("./runner-setup-JHWM6BYD.js");
202
+ const parsed = JSON.parse(await readStdinCapped());
203
+ if (parsed.protocolVersion !== RUNNER_PROTOCOL_VERSION) {
204
+ throw new Error("runner setup protocol version mismatch");
205
+ }
206
+ const result = await applyRunnerSetup(parsed, {
207
+ postJson: (path, body) => postSetupJson(parsed.jentrixBaseUrl, path, body)
208
+ });
209
+ process.stdout.write(`${JSON.stringify(result)}
210
+ `);
211
+ return 0;
212
+ }
213
+ if (command === "run") {
214
+ return runConfiguredRunner(args.includes("--once"));
215
+ }
216
+ if (command === "session-run" && (args.includes("--plan-stdin") || args.includes("--plan-file"))) {
217
+ const { runSessionHost } = await import("./session-host-2Y7475EG.js");
218
+ let raw;
219
+ const planFile = valueAfter(args, "--plan-file");
220
+ if (planFile) {
221
+ const { readFileSync, unlinkSync } = await import("node:fs");
222
+ raw = readFileSync(planFile, "utf8");
223
+ try {
224
+ unlinkSync(planFile);
225
+ } catch {
226
+ }
227
+ } else {
228
+ raw = await readStdinCapped();
229
+ }
230
+ const plan = JSON.parse(raw);
231
+ if (plan.protocolVersion !== RUNNER_PROTOCOL_VERSION) {
232
+ throw new Error(
233
+ "RUNNER_VERSION_MISMATCH: session plan protocol mismatch"
234
+ );
235
+ }
236
+ if (plan.provider !== "claude" && plan.provider !== "codex") {
237
+ throw new Error("RUNNER_CONFIG_INVALID: unknown session provider");
238
+ }
239
+ return runSessionHost(
240
+ plan
241
+ );
242
+ }
243
+ if (command === "session-hook") {
244
+ const { appendHookEvent } = await import("./session-hook-log-KNO6C4BF.js");
245
+ const dir = valueAfter(args, "--dir");
246
+ const event = valueAfter(args, "--event") ?? "unknown";
247
+ if (!dir)
248
+ throw new Error("RUNNER_CONFIG_INVALID: session-hook needs --dir");
249
+ appendHookEvent(dir, event, await readStdinCapped());
250
+ return 0;
251
+ }
252
+ process.stderr.write(
253
+ "usage: stacks-runner version --json | probe --json [--claude-path PATH] [--codex-path PATH] | setup-apply --plan-stdin | run [--once] | session-run --plan-stdin | session-hook --dir DIR --event NAME\n"
254
+ );
255
+ return 2;
256
+ }
257
+ function isEntrypoint() {
258
+ try {
259
+ return realpathSync(process.argv[1] ?? "") === realpathSync(fileURLToPath(import.meta.url));
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ if (isEntrypoint()) {
265
+ main().then(
266
+ (code) => {
267
+ process.exitCode = code;
268
+ },
269
+ (error) => {
270
+ const message = error instanceof Error ? error.message : "";
271
+ process.stderr.write(
272
+ /^[A-Z][A-Z_]+: /.test(message) ? `${message}
273
+ ` : "RUNNER_CONFIG_INVALID: runner protocol failure\n"
274
+ );
275
+ process.exitCode = 2;
276
+ }
277
+ );
278
+ }
279
+ export {
280
+ RUNNER_PROTOCOL_VERSION,
281
+ RUNNER_VERSION,
282
+ assertRunnerConfigCompatibility,
283
+ main,
284
+ runnerEnvironment,
285
+ runnerVersionEnvelope,
286
+ terminationHandlers
287
+ };
288
+ //# sourceMappingURL=runner-cli.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../runner-cli.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\nimport { fileURLToPath } from \"node:url\";\nimport { spawn } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\n\nimport type { RunnerConfig } from \"./lib/runner-config.js\";\nimport type { RunnerSetupApply } from \"./lib/runner-setup.js\";\nimport { RUNNER_VERSION } from \"./lib/version.js\";\n\n// ---------------------------------------------------------------------------\n// TYPE-ONLY above, and that is load-bearing (MVP separation P5).\n//\n// `probe`, `setup-apply` and `run` are the PLATFORM half of this binary: they\n// enroll a worker and poll for agent jobs, both of which are planes. The MVP\n// deployment uses only the session half (`session-run`, `session-hook`), and\n// `session-hook` in particular is invoked by a Claude Code lifecycle hook \u2014\n// several times per conversation, on the operator's critical path.\n//\n// Statically importing `local-probes` put `@anthropic-ai/claude-agent-sdk` at\n// the top of the built bundle, so EVERY invocation paid to load 4.5 MB of a\n// provider SDK the session half never touches: ~90ms per hook against a ~10ms\n// bare-node baseline. The three `await import(\u2026)` calls below move that cost\n// into the branches that actually need it.\n//\n// The dependency itself stays REQUIRED rather than optional. Making it\n// optional is the packaging half of this question and it breaks a platform\n// operator's `npm i -g` in a way this refactor should not decide silently \u2014\n// see `plans/mvp-separation-strategy.md` \u00A7 P5.\n// ---------------------------------------------------------------------------\n\nexport const RUNNER_PROTOCOL_VERSION = 1;\nexport { RUNNER_VERSION };\n\nexport function runnerVersionEnvelope() {\n return {\n protocolVersion: RUNNER_PROTOCOL_VERSION,\n runnerVersion: RUNNER_VERSION,\n };\n}\n\nexport function runnerEnvironment(\n config: RunnerConfig,\n configPath: string,\n once: boolean,\n base: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n return {\n ...base,\n STACKS_BASE_URL: config.jentrixBaseUrl,\n STACKS_MCP_URL: config.mcpUrl,\n STACKS_RUNNER_MODE: \"self-hosted\",\n STACKS_BOOTSTRAP_TOKEN: config.bootstrapToken,\n STACKS_CLAIM_URL: config.claimUrl,\n STACKS_RUNNER_AUTH_STATE: `${configPath}.auth.json`,\n STACKS_WORKER_HEARTBEAT_TOKEN: config.heartbeatToken,\n STACKS_RUNNER_ROLE_BINDINGS: JSON.stringify(config.roleBindings),\n STACKS_RUNNER_HOST: \"127.0.0.1\",\n STACKS_CLAUDE_EXECUTABLE: config.executables.claude,\n STACKS_CODEX_EXECUTABLE: config.executables.codex,\n STACKS_POLL_MS: base.STACKS_POLL_MS ?? \"30000\",\n ...(once ? { STACKS_RUNNER_ONCE: \"1\" } : {}),\n };\n}\n\nexport function terminationHandlers(kill: (signal: NodeJS.Signals) => unknown) {\n return {\n interrupt: () => kill(\"SIGINT\"),\n terminate: () => kill(\"SIGTERM\"),\n };\n}\n\nexport function assertRunnerConfigCompatibility(config: RunnerConfig): void {\n if (config.protocolVersion !== RUNNER_PROTOCOL_VERSION) {\n throw new Error(\n `RUNNER_VERSION_MISMATCH: run: npm install --global @jentrix/cli@${RUNNER_VERSION} @jentrix/runner@${RUNNER_VERSION}`,\n );\n }\n}\n\nasync function runConfiguredRunner(once: boolean): Promise<number> {\n const { probeLocalRuntimes } = await import(\"./lib/local-probes.js\");\n const { readRunnerConfig, runnerConfigPath } = await import(\n \"./lib/runner-config.js\"\n );\n const { applyRunnerSetup } = await import(\"./lib/runner-setup.js\");\n const configPath = runnerConfigPath();\n let config: RunnerConfig;\n try {\n config = await readRunnerConfig(configPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException)?.code === \"ENOENT\") {\n throw new Error(\"BOOTSTRAP_REQUIRED: run jentrix runner setup.\");\n }\n throw error;\n }\n assertRunnerConfigCompatibility(config);\n const runtimes = await probeLocalRuntimes({\n claudePath: config.executables.claude,\n codexPath: config.executables.codex,\n });\n const probes = new Map(\n runtimes.map((runtime) => [runtime.adapterKey, runtime]),\n );\n for (const binding of Object.values(config.roleBindings)) {\n const probe = probes.get(binding.runtimeAdapterKey);\n if (!probe || probe.status !== \"ready\") {\n throw new Error(\n `ROLE_PLAN_UNSATISFIED: ${binding.runtimeAdapterKey} is not ready for a configured role.`,\n );\n }\n if (\n probe.availableModels.length > 0 &&\n !probe.availableModels.some((model) => model.id === binding.model)\n ) {\n throw new Error(\n `ROLE_PLAN_UNSATISFIED: ${binding.runtimeAdapterKey} cannot run configured model ${binding.model}.`,\n );\n }\n }\n await applyRunnerSetup(\n {\n protocolVersion: config.protocolVersion,\n runnerVersion: config.runnerVersion,\n configPath,\n jentrixBaseUrl: config.jentrixBaseUrl,\n mcpUrl: config.mcpUrl,\n workspaceId: config.workspaceId,\n enrollment: null,\n bootstrap: { token: config.bootstrapToken, claimUrl: config.claimUrl },\n runtimes,\n roleBindings: config.roleBindings,\n attestationConnections: [\n ...new Map(\n Object.values(config.roleBindings)\n .filter((binding) => binding.requiresClaimCredential)\n .map((binding) => [\n binding.providerConnectionId,\n {\n connectionId: binding.providerConnectionId,\n runtimeAdapterKey: binding.runtimeAdapterKey,\n },\n ]),\n ).values(),\n ],\n },\n {\n postJson: (path, body) =>\n postSetupJson(config.jentrixBaseUrl, path, body),\n },\n );\n const workflowRunner = fileURLToPath(\n new URL(\"./workflow-runner.js\", import.meta.url),\n );\n const child = spawn(process.execPath, [workflowRunner], {\n env: runnerEnvironment(config, configPath, once),\n stdio: \"inherit\",\n });\n const { interrupt, terminate } = terminationHandlers((signal) =>\n child.kill(signal),\n );\n process.once(\"SIGINT\", interrupt);\n process.once(\"SIGTERM\", terminate);\n try {\n return await new Promise<number>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", (code, signal) => resolve(code ?? (signal ? 1 : 0)));\n });\n } finally {\n process.off(\"SIGINT\", interrupt);\n process.off(\"SIGTERM\", terminate);\n }\n}\n\nfunction valueAfter(args: string[], flag: string): string | undefined {\n const at = args.indexOf(flag);\n return at >= 0 ? args[at + 1] : undefined;\n}\n\nasync function readStdinCapped(cap = 2 * 1024 * 1024): Promise<string> {\n const chunks: Buffer[] = [];\n let bytes = 0;\n for await (const chunk of process.stdin) {\n const buffer = Buffer.from(chunk);\n bytes += buffer.byteLength;\n if (bytes > cap)\n throw new Error(\"runner setup input exceeded its size limit\");\n chunks.push(buffer);\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nasync function postSetupJson(\n baseUrl: string,\n path: string,\n body: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n const response = await fetch(new URL(path, baseUrl), {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(30_000),\n });\n if (!response.ok) {\n throw new Error(\n `${path} refused the runner request (HTTP ${response.status}).`,\n );\n }\n const value = await response.json();\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${path} returned malformed JSON.`);\n }\n return value as Record<string, unknown>;\n}\n\nexport async function main(\n args: string[] = process.argv.slice(2),\n): Promise<number> {\n const command = args[0];\n // F-2/AGE-933: the conventional spellings must work too \u2014 every other CLI\n // on the machine answers `--version`, so rejecting it reads as breakage.\n if (command === \"--version\" || command === \"-v\") {\n process.stdout.write(`${RUNNER_VERSION}\\n`);\n return 0;\n }\n if (command === \"version\") {\n if (args.includes(\"--json\")) {\n process.stdout.write(`${JSON.stringify(runnerVersionEnvelope())}\\n`);\n } else {\n process.stdout.write(`${RUNNER_VERSION}\\n`);\n }\n return 0;\n }\n if (command === \"probe\" && args.includes(\"--json\")) {\n const { probeLocalRuntimes } = await import(\"./lib/local-probes.js\");\n const { runnerConfigPath, runnerConfigSummary } = await import(\n \"./lib/runner-config.js\"\n );\n const [runtimes, config] = await Promise.all([\n probeLocalRuntimes({\n claudePath: valueAfter(args, \"--claude-path\"),\n codexPath: valueAfter(args, \"--codex-path\"),\n }),\n runnerConfigSummary(runnerConfigPath()),\n ]);\n process.stdout.write(\n `${JSON.stringify({ ...runnerVersionEnvelope(), runtimes, config })}\\n`,\n );\n return runtimes.some((runtime) => runtime.status === \"ready\") ? 0 : 1;\n }\n if (command === \"setup-apply\" && args.includes(\"--plan-stdin\")) {\n const { applyRunnerSetup } = await import(\"./lib/runner-setup.js\");\n const parsed = JSON.parse(await readStdinCapped()) as RunnerSetupApply;\n if (parsed.protocolVersion !== RUNNER_PROTOCOL_VERSION) {\n throw new Error(\"runner setup protocol version mismatch\");\n }\n const result = await applyRunnerSetup(parsed, {\n postJson: (path, body) =>\n postSetupJson(parsed.jentrixBaseUrl, path, body),\n });\n process.stdout.write(`${JSON.stringify(result)}\\n`);\n return 0;\n }\n if (command === \"run\") {\n return runConfiguredRunner(args.includes(\"--once\"));\n }\n // M20.1: connected-session host. The plan (including the TRANSIENT human\n // bearer) arrives over stdin or a 0600 plan file that is unlinked on read \u2014\n // never argv, never persisted (AC4). The file form exists because a LAUNCH\n // keeps the TTY's stdin for the interactive provider.\n if (\n command === \"session-run\" &&\n (args.includes(\"--plan-stdin\") || args.includes(\"--plan-file\"))\n ) {\n const { runSessionHost } = await import(\"./lib/session-host.js\");\n let raw: string;\n const planFile = valueAfter(args, \"--plan-file\");\n if (planFile) {\n const { readFileSync, unlinkSync } = await import(\"node:fs\");\n raw = readFileSync(planFile, \"utf8\");\n try {\n unlinkSync(planFile);\n } catch {\n // best-effort \u2014 the file is 0600 and holds a short-lived bearer\n }\n } else {\n raw = await readStdinCapped();\n }\n const plan = JSON.parse(raw) as {\n protocolVersion?: number;\n provider?: string;\n };\n if (plan.protocolVersion !== RUNNER_PROTOCOL_VERSION) {\n throw new Error(\n \"RUNNER_VERSION_MISMATCH: session plan protocol mismatch\",\n );\n }\n if (plan.provider !== \"claude\" && plan.provider !== \"codex\") {\n throw new Error(\"RUNNER_CONFIG_INVALID: unknown session provider\");\n }\n return runSessionHost(\n plan as import(\"./lib/session-host.js\").SessionRunPlan,\n );\n }\n // M20.1 \u00A715.1: the hook forwarder Claude lifecycle hooks invoke. Argv holds\n // only the session DIRECTORY and event name; the payload rides stdin.\n if (command === \"session-hook\") {\n // The hook-log module, NOT `session-host` \u2014 appending a line must not load\n // the MCP client and both transcript mappers (P5).\n const { appendHookEvent } = await import(\"./lib/session-hook-log.js\");\n const dir = valueAfter(args, \"--dir\");\n const event = valueAfter(args, \"--event\") ?? \"unknown\";\n if (!dir)\n throw new Error(\"RUNNER_CONFIG_INVALID: session-hook needs --dir\");\n appendHookEvent(dir, event, await readStdinCapped());\n return 0;\n }\n process.stderr.write(\n \"usage: stacks-runner version --json | probe --json [--claude-path PATH] [--codex-path PATH] | setup-apply --plan-stdin | run [--once] | session-run --plan-stdin | session-hook --dir DIR --event NAME\\n\",\n );\n return 2;\n}\n\nfunction isEntrypoint(): boolean {\n try {\n return (\n realpathSync(process.argv[1] ?? \"\") ===\n realpathSync(fileURLToPath(import.meta.url))\n );\n } catch {\n return false;\n }\n}\n\nif (isEntrypoint()) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error) => {\n const message = error instanceof Error ? error.message : \"\";\n process.stderr.write(\n /^[A-Z][A-Z_]+: /.test(message)\n ? `${message}\\n`\n : \"RUNNER_CONFIG_INVALID: runner protocol failure\\n\",\n );\n process.exitCode = 2;\n },\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ import {
2
+ RUNNER_CONFIG_SCHEMA_VERSION,
3
+ readRunnerConfig,
4
+ runnerConfigPath,
5
+ runnerConfigSummary,
6
+ writeRunnerConfigAtomic
7
+ } from "./chunk-N2UNVXHV.js";
8
+ export {
9
+ RUNNER_CONFIG_SCHEMA_VERSION,
10
+ readRunnerConfig,
11
+ runnerConfigPath,
12
+ runnerConfigSummary,
13
+ writeRunnerConfigAtomic
14
+ };
15
+ //# sourceMappingURL=runner-config-THT646ZG.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,153 @@
1
+ import {
2
+ RUNNER_CONFIG_SCHEMA_VERSION,
3
+ readRunnerConfig,
4
+ writeRunnerConfigAtomic
5
+ } from "./chunk-N2UNVXHV.js";
6
+
7
+ // lib/runner-setup.ts
8
+ import { createHmac } from "node:crypto";
9
+ async function postWithBlocker(deps, code, path, body) {
10
+ try {
11
+ return await deps.postJson(path, body);
12
+ } catch {
13
+ throw new Error(`${code}: ${path} request failed.`);
14
+ }
15
+ }
16
+ function canonicalAttestationPayload(connectionId, evidence) {
17
+ return JSON.stringify({
18
+ connectionId,
19
+ ok: evidence.ok === true,
20
+ runtimeAdapterKey: String(evidence.runtimeAdapterKey),
21
+ method: String(evidence.method),
22
+ outcome: String(evidence.outcome),
23
+ checkedAt: String(evidence.checkedAt),
24
+ modelCount: typeof evidence.modelCount === "number" ? evidence.modelCount : null,
25
+ runtimeVersion: typeof evidence.runtimeVersion === "string" ? evidence.runtimeVersion : null
26
+ });
27
+ }
28
+ function signAttestationEvidence(runnerToken, connectionId, evidence) {
29
+ return createHmac("sha256", runnerToken).update(canonicalAttestationPayload(connectionId, evidence)).digest("hex");
30
+ }
31
+ function requiredString(value, label) {
32
+ if (typeof value !== "string" || value.length === 0) {
33
+ throw new Error(`NODE_ENROLLMENT_FAILED: response omitted ${label}.`);
34
+ }
35
+ return value;
36
+ }
37
+ async function applyRunnerSetup(input, deps) {
38
+ let workerId;
39
+ let heartbeatToken;
40
+ if (input.enrollment) {
41
+ const enrolled = await postWithBlocker(
42
+ deps,
43
+ "NODE_ENROLLMENT_FAILED",
44
+ "/api/workers/enroll",
45
+ {
46
+ enrollmentToken: input.enrollment.token,
47
+ capabilities: [
48
+ "self-hosted",
49
+ ...new Set(
50
+ input.runtimes.filter((r) => r.status === "ready").map((r) => r.adapterKey)
51
+ )
52
+ ]
53
+ }
54
+ );
55
+ workerId = requiredString(enrolled.workerId, "workerId");
56
+ heartbeatToken = requiredString(enrolled.heartbeatToken, "heartbeatToken");
57
+ } else {
58
+ const existing = await readRunnerConfig(input.configPath);
59
+ if (existing.workspaceId !== input.workspaceId) {
60
+ throw new Error(
61
+ "RUNNER_CONFIG_INVALID: existing runner belongs to another workspace."
62
+ );
63
+ }
64
+ workerId = existing.workerId;
65
+ heartbeatToken = existing.heartbeatToken;
66
+ }
67
+ const executables = {};
68
+ for (const runtime of input.runtimes) {
69
+ if (runtime.status === "ready" && runtime.binaryPath) {
70
+ executables[runtime.adapterKey] = runtime.binaryPath;
71
+ }
72
+ }
73
+ const config = {
74
+ schemaVersion: RUNNER_CONFIG_SCHEMA_VERSION,
75
+ protocolVersion: input.protocolVersion,
76
+ runnerVersion: input.runnerVersion,
77
+ jentrixBaseUrl: input.jentrixBaseUrl,
78
+ mcpUrl: input.mcpUrl,
79
+ workspaceId: input.workspaceId,
80
+ workerId,
81
+ heartbeatToken,
82
+ bootstrapToken: input.bootstrap.token,
83
+ claimUrl: input.bootstrap.claimUrl,
84
+ executables,
85
+ roleBindings: input.roleBindings
86
+ };
87
+ await writeRunnerConfigAtomic(config, input.configPath);
88
+ const heartbeat = await postWithBlocker(
89
+ deps,
90
+ "NODE_ENROLLMENT_FAILED",
91
+ "/api/workers/heartbeat",
92
+ { heartbeatToken }
93
+ );
94
+ if (heartbeat.disabled === true || heartbeat.ok !== true) {
95
+ throw new Error(
96
+ "NODE_ENROLLMENT_FAILED: enrolled runner is disabled or did not heartbeat."
97
+ );
98
+ }
99
+ const probes = new Map(
100
+ input.runtimes.filter((runtime) => runtime.status === "ready").map((runtime) => [runtime.adapterKey, runtime])
101
+ );
102
+ const attestedConnectionIds = [];
103
+ for (const binding of input.attestationConnections) {
104
+ const connectionId = binding.connectionId;
105
+ const probe = probes.get(binding.runtimeAdapterKey);
106
+ if (!probe) {
107
+ throw new Error(
108
+ `ATTESTATION_FAILED: no ready ${binding.runtimeAdapterKey} probe.`
109
+ );
110
+ }
111
+ const evidence = {
112
+ ok: true,
113
+ runtimeAdapterKey: binding.runtimeAdapterKey,
114
+ method: probe.capabilitySource,
115
+ outcome: probe.availableModels.length > 0 ? "success" : "success_no_models",
116
+ checkedAt: probe.checkedAt,
117
+ modelCount: probe.availableModels.length,
118
+ ...probe.version ? { runtimeVersion: probe.version } : {}
119
+ };
120
+ const response = await postWithBlocker(
121
+ deps,
122
+ "ATTESTATION_FAILED",
123
+ "/api/providers/attest",
124
+ {
125
+ runnerToken: heartbeatToken,
126
+ connectionId,
127
+ evidence,
128
+ evidenceSignature: signAttestationEvidence(
129
+ heartbeatToken,
130
+ connectionId,
131
+ evidence
132
+ )
133
+ }
134
+ );
135
+ if (response.ok !== true) {
136
+ throw new Error(
137
+ `ATTESTATION_FAILED: ${binding.runtimeAdapterKey} subscription proof was refused.`
138
+ );
139
+ }
140
+ attestedConnectionIds.push(connectionId);
141
+ }
142
+ return {
143
+ ok: true,
144
+ workerId,
145
+ configPath: input.configPath,
146
+ attestedConnectionIds
147
+ };
148
+ }
149
+ export {
150
+ applyRunnerSetup,
151
+ signAttestationEvidence
152
+ };
153
+ //# sourceMappingURL=runner-setup-JHWM6BYD.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../lib/runner-setup.ts"],
4
+ "sourcesContent": ["import { createHmac } from \"node:crypto\";\n\nimport type { LocalRuntimeProbe } from \"./local-probes.js\";\nimport {\n RUNNER_CONFIG_SCHEMA_VERSION,\n readRunnerConfig,\n writeRunnerConfigAtomic,\n type RunnerConfig,\n type RunnerRoleBinding,\n} from \"./runner-config.js\";\n\nexport interface RunnerSetupApply {\n protocolVersion: number;\n runnerVersion: string;\n configPath: string;\n jentrixBaseUrl: string;\n mcpUrl: string;\n workspaceId: string;\n enrollment: { token: string } | null;\n bootstrap: { token: string; claimUrl: string };\n runtimes: LocalRuntimeProbe[];\n roleBindings: Record<\"planner\" | \"executor\" | \"reviewer\", RunnerRoleBinding>;\n attestationConnections: Array<{\n connectionId: string;\n runtimeAdapterKey: \"claude\" | \"codex\";\n }>;\n}\n\nexport interface RunnerSetupDeps {\n postJson(\n path: string,\n body: Record<string, unknown>,\n ): Promise<Record<string, unknown>>;\n}\n\nexport interface AttestationEvidence {\n ok: boolean;\n runtimeAdapterKey: string;\n method: string;\n outcome: string;\n checkedAt: string;\n modelCount?: number;\n runtimeVersion?: string;\n}\n\nasync function postWithBlocker(\n deps: RunnerSetupDeps,\n code: \"NODE_ENROLLMENT_FAILED\" | \"ATTESTATION_FAILED\",\n path: string,\n body: Record<string, unknown>,\n): Promise<Record<string, unknown>> {\n try {\n return await deps.postJson(path, body);\n } catch {\n throw new Error(`${code}: ${path} request failed.`);\n }\n}\n\nfunction canonicalAttestationPayload(\n connectionId: string,\n evidence: AttestationEvidence,\n): string {\n return JSON.stringify({\n connectionId,\n ok: evidence.ok === true,\n runtimeAdapterKey: String(evidence.runtimeAdapterKey),\n method: String(evidence.method),\n outcome: String(evidence.outcome),\n checkedAt: String(evidence.checkedAt),\n modelCount:\n typeof evidence.modelCount === \"number\" ? evidence.modelCount : null,\n runtimeVersion:\n typeof evidence.runtimeVersion === \"string\"\n ? evidence.runtimeVersion\n : null,\n });\n}\n\nexport function signAttestationEvidence(\n runnerToken: string,\n connectionId: string,\n evidence: AttestationEvidence,\n): string {\n return createHmac(\"sha256\", runnerToken)\n .update(canonicalAttestationPayload(connectionId, evidence))\n .digest(\"hex\");\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(`NODE_ENROLLMENT_FAILED: response omitted ${label}.`);\n }\n return value;\n}\n\nexport async function applyRunnerSetup(\n input: RunnerSetupApply,\n deps: RunnerSetupDeps,\n): Promise<{\n ok: true;\n workerId: string;\n configPath: string;\n attestedConnectionIds: string[];\n}> {\n let workerId: string;\n let heartbeatToken: string;\n if (input.enrollment) {\n const enrolled = await postWithBlocker(\n deps,\n \"NODE_ENROLLMENT_FAILED\",\n \"/api/workers/enroll\",\n {\n enrollmentToken: input.enrollment.token,\n capabilities: [\n \"self-hosted\",\n ...new Set(\n input.runtimes\n .filter((r) => r.status === \"ready\")\n .map((r) => r.adapterKey),\n ),\n ],\n },\n );\n workerId = requiredString(enrolled.workerId, \"workerId\");\n heartbeatToken = requiredString(enrolled.heartbeatToken, \"heartbeatToken\");\n } else {\n const existing = await readRunnerConfig(input.configPath);\n if (existing.workspaceId !== input.workspaceId) {\n throw new Error(\n \"RUNNER_CONFIG_INVALID: existing runner belongs to another workspace.\",\n );\n }\n workerId = existing.workerId;\n heartbeatToken = existing.heartbeatToken;\n }\n\n const executables: RunnerConfig[\"executables\"] = {};\n for (const runtime of input.runtimes) {\n if (runtime.status === \"ready\" && runtime.binaryPath) {\n executables[runtime.adapterKey] = runtime.binaryPath;\n }\n }\n const config: RunnerConfig = {\n schemaVersion: RUNNER_CONFIG_SCHEMA_VERSION,\n protocolVersion: input.protocolVersion,\n runnerVersion: input.runnerVersion,\n jentrixBaseUrl: input.jentrixBaseUrl,\n mcpUrl: input.mcpUrl,\n workspaceId: input.workspaceId,\n workerId,\n heartbeatToken,\n bootstrapToken: input.bootstrap.token,\n claimUrl: input.bootstrap.claimUrl,\n executables,\n roleBindings: input.roleBindings,\n };\n // The identity is durable before any repairable network step. A retry can\n // heartbeat and re-attest this exact node instead of creating another one.\n await writeRunnerConfigAtomic(config, input.configPath);\n\n const heartbeat = await postWithBlocker(\n deps,\n \"NODE_ENROLLMENT_FAILED\",\n \"/api/workers/heartbeat\",\n { heartbeatToken },\n );\n if (heartbeat.disabled === true || heartbeat.ok !== true) {\n throw new Error(\n \"NODE_ENROLLMENT_FAILED: enrolled runner is disabled or did not heartbeat.\",\n );\n }\n\n const probes = new Map(\n input.runtimes\n .filter((runtime) => runtime.status === \"ready\")\n .map((runtime) => [runtime.adapterKey, runtime]),\n );\n const attestedConnectionIds: string[] = [];\n for (const binding of input.attestationConnections) {\n const connectionId = binding.connectionId;\n const probe = probes.get(binding.runtimeAdapterKey);\n if (!probe) {\n throw new Error(\n `ATTESTATION_FAILED: no ready ${binding.runtimeAdapterKey} probe.`,\n );\n }\n const evidence: AttestationEvidence = {\n ok: true,\n runtimeAdapterKey: binding.runtimeAdapterKey,\n method: probe.capabilitySource,\n outcome:\n probe.availableModels.length > 0 ? \"success\" : \"success_no_models\",\n checkedAt: probe.checkedAt,\n modelCount: probe.availableModels.length,\n ...(probe.version ? { runtimeVersion: probe.version } : {}),\n };\n const response = await postWithBlocker(\n deps,\n \"ATTESTATION_FAILED\",\n \"/api/providers/attest\",\n {\n runnerToken: heartbeatToken,\n connectionId,\n evidence,\n evidenceSignature: signAttestationEvidence(\n heartbeatToken,\n connectionId,\n evidence,\n ),\n },\n );\n if (response.ok !== true) {\n throw new Error(\n `ATTESTATION_FAILED: ${binding.runtimeAdapterKey} subscription proof was refused.`,\n );\n }\n attestedConnectionIds.push(connectionId);\n }\n return {\n ok: true,\n workerId,\n configPath: input.configPath,\n attestedConnectionIds,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import {
2
+ appendHookEvent,
3
+ readHookLines,
4
+ safeParse
5
+ } from "./chunk-PMRDMBQR.js";
6
+ export {
7
+ appendHookEvent,
8
+ readHookLines,
9
+ safeParse
10
+ };
11
+ //# sourceMappingURL=session-hook-log-KNO6C4BF.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }