@gh-symphony/cli 0.0.20 → 0.0.22

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.
Files changed (40) hide show
  1. package/README.md +66 -2
  2. package/dist/chunk-2TSM3INR.js +1085 -0
  3. package/dist/chunk-2UW7NQLX.js +684 -0
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
  6. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  7. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  8. package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
  9. package/dist/chunk-EEQQWTXS.js +3257 -0
  10. package/dist/chunk-GDE6FYN4.js +26 -0
  11. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  12. package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
  13. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  14. package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
  15. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  16. package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
  17. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  18. package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
  19. package/dist/index.js +112 -24
  20. package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
  21. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  22. package/dist/project-RMYMZSFV.js +25 -0
  23. package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
  24. package/dist/repo-WI7GF6XQ.js +749 -0
  25. package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
  26. package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
  27. package/dist/start-RTAHQMR2.js +19 -0
  28. package/dist/status-F4D52OVK.js +12 -0
  29. package/dist/stop-MDKMJPVR.js +10 -0
  30. package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
  31. package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
  32. package/dist/worker-entry.js +848 -693
  33. package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
  34. package/package.json +4 -2
  35. package/dist/chunk-M3IFVLQS.js +0 -1155
  36. package/dist/project-UUVHS3ZR.js +0 -22
  37. package/dist/repo-HDDE7OUI.js +0 -321
  38. package/dist/start-ENFLZUI6.js +0 -16
  39. package/dist/status-QSCFVGRQ.js +0 -11
  40. package/dist/stop-7MFCBQVW.js +0 -9
@@ -0,0 +1,3257 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../core/src/workflow/lifecycle.ts
4
+ var DEFAULT_WORKFLOW_LIFECYCLE = {
5
+ stateFieldName: "Status",
6
+ activeStates: ["Todo", "In Progress"],
7
+ terminalStates: ["Done"],
8
+ blockerCheckStates: ["Todo"]
9
+ };
10
+ function isStateActive(state, lifecycle) {
11
+ return matchesWorkflowState(state, lifecycle.activeStates);
12
+ }
13
+ function isStateTerminal(state, lifecycle) {
14
+ return matchesWorkflowState(state, lifecycle.terminalStates);
15
+ }
16
+ function matchesWorkflowState(state, candidates) {
17
+ const normalizedState = normalizeWorkflowState(state);
18
+ return candidates.some((candidate) => normalizeWorkflowState(candidate) === normalizedState);
19
+ }
20
+ function normalizeWorkflowState(state) {
21
+ return state.trim().toLowerCase();
22
+ }
23
+
24
+ // ../core/src/workflow/config.ts
25
+ var DEFAULT_CODEX_COMMAND = "codex app-server";
26
+ var DEFAULT_CLAUDE_COMMAND = "claude";
27
+ var DEFAULT_AGENT_COMMAND = DEFAULT_CODEX_COMMAND;
28
+ var DEFAULT_HOOK_TIMEOUT_MS = 6e4;
29
+ var DEFAULT_POLL_INTERVAL_MS = 3e4;
30
+ var DEFAULT_MAX_RETRY_BACKOFF_MS = 3e5;
31
+ var DEFAULT_MAX_DELAY_MS = DEFAULT_MAX_RETRY_BACKOFF_MS;
32
+ var DEFAULT_BASE_DELAY_MS = 1e4;
33
+ var DEFAULT_MAX_TURNS = 20;
34
+ var DEFAULT_MAX_FAILURE_RETRIES = 10;
35
+ var DEFAULT_READ_TIMEOUT_MS = 5e3;
36
+ var DEFAULT_TURN_TIMEOUT_MS = 36e5;
37
+ var DEFAULT_STALL_TIMEOUT_MS = 3e5;
38
+ var DEFAULT_MAX_CONCURRENT_AGENTS = 10;
39
+ var DEFAULT_WORKFLOW_HOOKS = {
40
+ afterCreate: null,
41
+ beforeRun: null,
42
+ afterRun: null,
43
+ beforeRemove: null,
44
+ timeoutMs: DEFAULT_HOOK_TIMEOUT_MS
45
+ };
46
+ var DEFAULT_WORKFLOW_TRACKER = {
47
+ kind: null,
48
+ endpoint: null,
49
+ apiKey: null,
50
+ projectSlug: null,
51
+ activeStates: DEFAULT_WORKFLOW_LIFECYCLE.activeStates,
52
+ terminalStates: DEFAULT_WORKFLOW_LIFECYCLE.terminalStates,
53
+ projectId: null,
54
+ stateFieldName: DEFAULT_WORKFLOW_LIFECYCLE.stateFieldName,
55
+ priorityFieldName: null,
56
+ blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates
57
+ };
58
+ var DEFAULT_WORKFLOW_WORKSPACE = {
59
+ root: null
60
+ };
61
+ var DEFAULT_WORKFLOW_AGENT = {
62
+ maxConcurrentAgents: DEFAULT_MAX_CONCURRENT_AGENTS,
63
+ maxRetryBackoffMs: DEFAULT_MAX_RETRY_BACKOFF_MS,
64
+ maxConcurrentAgentsByState: {},
65
+ maxFailureRetries: DEFAULT_MAX_FAILURE_RETRIES,
66
+ maxTurns: DEFAULT_MAX_TURNS,
67
+ retryBaseDelayMs: DEFAULT_BASE_DELAY_MS
68
+ };
69
+ var DEFAULT_WORKFLOW_CODEX = {
70
+ command: DEFAULT_CODEX_COMMAND,
71
+ approvalPolicy: null,
72
+ threadSandbox: null,
73
+ turnSandboxPolicy: null,
74
+ turnTimeoutMs: DEFAULT_TURN_TIMEOUT_MS,
75
+ readTimeoutMs: DEFAULT_READ_TIMEOUT_MS,
76
+ stallTimeoutMs: DEFAULT_STALL_TIMEOUT_MS
77
+ };
78
+ var DEFAULT_WORKFLOW_DEFINITION = {
79
+ promptTemplate: "",
80
+ continuationGuidance: null,
81
+ tracker: DEFAULT_WORKFLOW_TRACKER,
82
+ polling: {
83
+ intervalMs: DEFAULT_POLL_INTERVAL_MS
84
+ },
85
+ workspace: DEFAULT_WORKFLOW_WORKSPACE,
86
+ hooks: DEFAULT_WORKFLOW_HOOKS,
87
+ agent: DEFAULT_WORKFLOW_AGENT,
88
+ runtime: null,
89
+ codex: DEFAULT_WORKFLOW_CODEX,
90
+ lifecycle: DEFAULT_WORKFLOW_LIFECYCLE,
91
+ format: "default",
92
+ githubProjectId: null,
93
+ agentCommand: DEFAULT_CODEX_COMMAND,
94
+ hookPath: null,
95
+ maxConcurrentByState: {}
96
+ };
97
+ function resolveWorkflowRuntimeCommand(workflow) {
98
+ if (!workflow.runtime) {
99
+ return workflow.codex.command;
100
+ }
101
+ if (workflow.runtime.args.length === 0) {
102
+ return workflow.runtime.command;
103
+ }
104
+ return [workflow.runtime.command, ...workflow.runtime.args].join(" ");
105
+ }
106
+ function resolveWorkflowRuntimeTimeouts(workflow) {
107
+ return workflow.runtime?.timeouts ?? workflow.codex;
108
+ }
109
+
110
+ // ../core/src/workflow/parser.ts
111
+ function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
112
+ const compatibilityMode = options.compatibilityMode ?? "strict";
113
+ const frontMatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
114
+ if (!frontMatterMatch) {
115
+ if (compatibilityMode === "legacy") {
116
+ return parseLegacyWorkflowMarkdown(markdown);
117
+ }
118
+ throw new Error("WORKFLOW.md must use YAML front matter.");
119
+ }
120
+ const [, rawFrontMatter, rawPromptTemplate = ""] = frontMatterMatch;
121
+ const frontMatter = parseFrontMatter(rawFrontMatter);
122
+ const promptTemplate = rawPromptTemplate.trim();
123
+ const tracker = readRequiredObject(frontMatter, "tracker");
124
+ const polling = readObject(frontMatter, "polling");
125
+ const workspace = readObject(frontMatter, "workspace");
126
+ const hooks = readObject(frontMatter, "hooks");
127
+ const agent = readObject(frontMatter, "agent");
128
+ const runtimeNode = readOptionalRuntimeObject(frontMatter);
129
+ const hasRuntime = runtimeNode !== null;
130
+ const codex = hasRuntime ? readObject(frontMatter, "codex") : readRequiredObject(frontMatter, "codex");
131
+ const trackerKind = readRequiredString(tracker, "kind", env);
132
+ const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
133
+ const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
134
+ const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
135
+ const maxConcurrentAgentsByState = readNumberMap(
136
+ agent,
137
+ "max_concurrent_agents_by_state"
138
+ );
139
+ const runtime = hasRuntime ? parseRuntimeConfig(runtimeNode, env) : null;
140
+ const codexConfig = {
141
+ command: readOptionalString(codex, "command", env) ?? DEFAULT_AGENT_COMMAND,
142
+ approvalPolicy: readOptionalString(codex, "approval_policy", env),
143
+ threadSandbox: readOptionalString(codex, "thread_sandbox", env),
144
+ turnSandboxPolicy: readOptionalString(codex, "turn_sandbox_policy", env),
145
+ turnTimeoutMs: readOptionalIntegerLike(codex, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
146
+ readTimeoutMs: readOptionalIntegerLike(codex, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
147
+ stallTimeoutMs: readOptionalIntegerLike(codex, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
148
+ };
149
+ const agentCommand = resolveWorkflowRuntimeCommand({
150
+ runtime,
151
+ codex: codexConfig
152
+ });
153
+ const parsed = {
154
+ promptTemplate,
155
+ continuationGuidance: readOptionalWorkflowString(
156
+ frontMatter,
157
+ "continuationGuidance",
158
+ "continuation_guidance",
159
+ env
160
+ ),
161
+ tracker: {
162
+ kind: trackerKind,
163
+ endpoint: readOptionalString(tracker, "endpoint", env),
164
+ apiKey: readOptionalString(tracker, "api_key", env),
165
+ projectSlug: readOptionalString(tracker, "project_slug", env),
166
+ activeStates,
167
+ terminalStates,
168
+ projectId: readOptionalString(tracker, "project_id", env),
169
+ stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
170
+ priorityFieldName: readOptionalString(tracker, "priority_field", env),
171
+ blockerCheckStates
172
+ },
173
+ polling: {
174
+ intervalMs: readOptionalIntegerLike(polling, "interval_ms") ?? DEFAULT_POLL_INTERVAL_MS
175
+ },
176
+ workspace: {
177
+ root: readOptionalString(workspace, "root", env)
178
+ },
179
+ hooks: {
180
+ afterCreate: readOptionalString(hooks, "after_create", env),
181
+ beforeRun: readOptionalString(hooks, "before_run", env),
182
+ afterRun: readOptionalString(hooks, "after_run", env),
183
+ beforeRemove: readOptionalString(hooks, "before_remove", env),
184
+ timeoutMs: readOptionalIntegerLike(hooks, "timeout_ms") ?? DEFAULT_HOOK_TIMEOUT_MS
185
+ },
186
+ agent: {
187
+ maxConcurrentAgents: readOptionalIntegerLike(agent, "max_concurrent_agents") ?? DEFAULT_MAX_CONCURRENT_AGENTS,
188
+ maxRetryBackoffMs: readOptionalIntegerLike(agent, "max_retry_backoff_ms") ?? DEFAULT_MAX_RETRY_BACKOFF_MS,
189
+ maxConcurrentAgentsByState,
190
+ maxFailureRetries: readOptionalIntegerLike(agent, "max_failure_retries") ?? DEFAULT_MAX_FAILURE_RETRIES,
191
+ maxTurns: readOptionalIntegerLike(agent, "max_turns") ?? DEFAULT_MAX_TURNS,
192
+ retryBaseDelayMs: readOptionalIntegerLike(agent, "retry_base_delay_ms") ?? DEFAULT_BASE_DELAY_MS
193
+ },
194
+ runtime,
195
+ codex: codexConfig,
196
+ lifecycle: {
197
+ stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
198
+ activeStates,
199
+ terminalStates,
200
+ blockerCheckStates
201
+ },
202
+ format: "front-matter",
203
+ githubProjectId: readOptionalString(tracker, "project_id", env),
204
+ agentCommand,
205
+ hookPath: readOptionalString(hooks, "after_create", env),
206
+ maxConcurrentByState: maxConcurrentAgentsByState
207
+ };
208
+ return parsed;
209
+ }
210
+ function parseLegacyWorkflowMarkdown(markdown) {
211
+ const promptGuidelines = matchOptionalSection(markdown, "Prompt Guidelines") ?? "";
212
+ return {
213
+ ...DEFAULT_WORKFLOW_DEFINITION,
214
+ promptTemplate: promptGuidelines,
215
+ format: "legacy-sectioned"
216
+ };
217
+ }
218
+ function parseFrontMatter(frontMatter) {
219
+ const lines = frontMatter.replace(/\r\n/g, "\n").split("\n");
220
+ const [value] = parseBlock(lines, 0, 0);
221
+ if (!value || Array.isArray(value) || typeof value !== "object") {
222
+ throw new Error("Workflow front matter must be a YAML object.");
223
+ }
224
+ return value;
225
+ }
226
+ function parseBlock(lines, startIndex, indent) {
227
+ let index = startIndex;
228
+ let collectionType = null;
229
+ const arrayValues = [];
230
+ const objectValues = {};
231
+ while (index < lines.length) {
232
+ const line = lines[index] ?? "";
233
+ if (!line.trim()) {
234
+ index += 1;
235
+ continue;
236
+ }
237
+ const lineIndent = countIndent(line);
238
+ if (lineIndent < indent) {
239
+ break;
240
+ }
241
+ if (lineIndent > indent) {
242
+ throw new Error(
243
+ `Invalid workflow front matter indentation near "${line.trim()}".`
244
+ );
245
+ }
246
+ const trimmed = line.trim();
247
+ if (trimmed.startsWith("- ")) {
248
+ if (collectionType === "object") {
249
+ throw new Error(
250
+ "Cannot mix array and object values in workflow front matter."
251
+ );
252
+ }
253
+ collectionType = "array";
254
+ const itemText = trimmed.slice(2).trim();
255
+ if (itemText === "|" || itemText === "|-") {
256
+ const [multiline, nextIndex3] = parseMultilineScalar(
257
+ lines,
258
+ index + 1,
259
+ indent + 2
260
+ );
261
+ arrayValues.push(multiline);
262
+ index = nextIndex3;
263
+ continue;
264
+ }
265
+ if (itemText) {
266
+ arrayValues.push(parseScalar(itemText));
267
+ index += 1;
268
+ continue;
269
+ }
270
+ const [child2, nextIndex2] = parseBlock(lines, index + 1, indent + 2);
271
+ arrayValues.push(child2);
272
+ index = nextIndex2;
273
+ continue;
274
+ }
275
+ if (collectionType === "array") {
276
+ throw new Error(
277
+ "Cannot mix object and array values in workflow front matter."
278
+ );
279
+ }
280
+ collectionType = "object";
281
+ const separatorIndex = trimmed.indexOf(":");
282
+ if (separatorIndex < 0) {
283
+ throw new Error(`Invalid workflow front matter line "${trimmed}".`);
284
+ }
285
+ const key = trimmed.slice(0, separatorIndex).trim();
286
+ const remainder = trimmed.slice(separatorIndex + 1).trim();
287
+ if (remainder === "|" || remainder === "|-") {
288
+ const [multiline, nextIndex2] = parseMultilineScalar(
289
+ lines,
290
+ index + 1,
291
+ indent + 2
292
+ );
293
+ objectValues[key] = multiline;
294
+ index = nextIndex2;
295
+ continue;
296
+ }
297
+ if (remainder) {
298
+ objectValues[key] = parseScalar(remainder);
299
+ index += 1;
300
+ continue;
301
+ }
302
+ const [child, nextIndex] = parseBlock(lines, index + 1, indent + 2);
303
+ objectValues[key] = child;
304
+ index = nextIndex;
305
+ }
306
+ return [collectionType === "array" ? arrayValues : objectValues, index];
307
+ }
308
+ function parseMultilineScalar(lines, startIndex, indent) {
309
+ let index = startIndex;
310
+ const collected = [];
311
+ while (index < lines.length) {
312
+ const line = lines[index] ?? "";
313
+ if (!line.trim()) {
314
+ collected.push("");
315
+ index += 1;
316
+ continue;
317
+ }
318
+ const lineIndent = countIndent(line);
319
+ if (lineIndent < indent) {
320
+ break;
321
+ }
322
+ collected.push(line.slice(indent));
323
+ index += 1;
324
+ }
325
+ return [collected.join("\n").trimEnd(), index];
326
+ }
327
+ function countIndent(line) {
328
+ return line.match(/^ */)?.[0].length ?? 0;
329
+ }
330
+ function parseScalar(value) {
331
+ if (value === "null") return null;
332
+ if (value === "true") return true;
333
+ if (value === "false") return false;
334
+ if (value.startsWith("[") && value.endsWith("]")) {
335
+ return parseInlineArray(value);
336
+ }
337
+ if (/^-?\d+$/.test(value)) return Number.parseInt(value, 10);
338
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
339
+ return value.slice(1, -1);
340
+ }
341
+ return value;
342
+ }
343
+ function parseInlineArray(value) {
344
+ const inner = value.slice(1, -1).trim();
345
+ if (!inner) {
346
+ return [];
347
+ }
348
+ return splitInlineArrayEntries(inner).map((entry) => parseScalar(entry));
349
+ }
350
+ function splitInlineArrayEntries(inner) {
351
+ const entries = [];
352
+ let current = "";
353
+ let quote = null;
354
+ for (const char of inner) {
355
+ if (quote) {
356
+ current += char;
357
+ if (char === quote) {
358
+ quote = null;
359
+ }
360
+ continue;
361
+ }
362
+ if (char === '"' || char === "'") {
363
+ quote = char;
364
+ current += char;
365
+ continue;
366
+ }
367
+ if (char === ",") {
368
+ pushInlineArrayEntry(entries, current, "middle");
369
+ current = "";
370
+ continue;
371
+ }
372
+ current += char;
373
+ }
374
+ if (quote) {
375
+ throw new Error("Workflow front matter inline array has an unterminated string.");
376
+ }
377
+ pushInlineArrayEntry(entries, current, "end");
378
+ return entries;
379
+ }
380
+ function pushInlineArrayEntry(entries, entry, position) {
381
+ const trimmed = entry.trim();
382
+ if (!trimmed) {
383
+ const reason = position === "end" ? "has a trailing comma" : "contains an empty item";
384
+ throw new Error(`Workflow front matter inline array ${reason}.`);
385
+ }
386
+ entries.push(trimmed);
387
+ }
388
+ function parseRuntimeConfig(runtime, env) {
389
+ const kind = readRuntimeKind(runtime, env);
390
+ const isolation = readObject(runtime, "isolation", "runtime.isolation");
391
+ const auth = readObject(runtime, "auth", "runtime.auth");
392
+ const timeouts = readObject(runtime, "timeouts", "runtime.timeouts");
393
+ const configuredCommand = readOptionalString(runtime, "command", env);
394
+ const command = configuredCommand ?? defaultRuntimeCommand(kind);
395
+ if (!command) {
396
+ throw new Error(
397
+ 'Workflow front matter field "runtime.command" is required for runtime.kind "custom".'
398
+ );
399
+ }
400
+ return {
401
+ kind,
402
+ command,
403
+ args: readRuntimeArgs(runtime),
404
+ isolation: {
405
+ bare: readOptionalBoolean(isolation, "bare", "runtime.isolation.bare") ?? false,
406
+ strictMcpConfig: readOptionalBoolean(
407
+ isolation,
408
+ "strict_mcp_config",
409
+ "runtime.isolation.strict_mcp_config"
410
+ ) ?? false
411
+ },
412
+ auth: {
413
+ env: readOptionalString(auth, "env", env)
414
+ },
415
+ timeouts: {
416
+ turnTimeoutMs: readOptionalIntegerLike(timeouts, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
417
+ readTimeoutMs: readOptionalIntegerLike(timeouts, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
418
+ stallTimeoutMs: readOptionalIntegerLike(timeouts, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
419
+ }
420
+ };
421
+ }
422
+ function readRuntimeKind(runtime, env) {
423
+ const kind = readRequiredString(runtime, "kind", env);
424
+ if (kind === "codex-app-server" || kind === "claude-print" || kind === "custom") {
425
+ return kind;
426
+ }
427
+ throw new Error(
428
+ `Unsupported workflow runtime kind "${kind}". Supported values: codex-app-server, claude-print, custom.`
429
+ );
430
+ }
431
+ function defaultRuntimeCommand(kind) {
432
+ if (kind === "claude-print") {
433
+ return DEFAULT_CLAUDE_COMMAND;
434
+ }
435
+ if (kind === "codex-app-server") {
436
+ return DEFAULT_AGENT_COMMAND;
437
+ }
438
+ return null;
439
+ }
440
+ function readObject(input, key, path = key) {
441
+ const value = input[key];
442
+ if (value === void 0 || value === null) {
443
+ return {};
444
+ }
445
+ if (typeof value !== "object" || Array.isArray(value)) {
446
+ throw new Error(`Workflow front matter field "${path}" must be an object.`);
447
+ }
448
+ return value;
449
+ }
450
+ function readOptionalRuntimeObject(input) {
451
+ if (input.runtime === void 0 || input.runtime === null) {
452
+ return null;
453
+ }
454
+ return readObject(input, "runtime");
455
+ }
456
+ function readRequiredObject(input, key) {
457
+ if (!(key in input)) {
458
+ throw new Error(`Workflow front matter field "${key}" is required.`);
459
+ }
460
+ return readObject(input, key);
461
+ }
462
+ function readOptionalString(input, key, env) {
463
+ const value = input[key];
464
+ if (value === void 0 || value === null) {
465
+ return null;
466
+ }
467
+ if (typeof value !== "string") {
468
+ throw new Error(`Workflow front matter field "${key}" must be a string.`);
469
+ }
470
+ return resolveEnvironmentValue(value, env);
471
+ }
472
+ function readOptionalWorkflowString(input, primaryKey, fallbackKey, env) {
473
+ return readOptionalString(input, primaryKey, env) ?? readOptionalString(input, fallbackKey, env);
474
+ }
475
+ function readRequiredString(input, key, env) {
476
+ const value = readOptionalString(input, key, env);
477
+ if (!value) {
478
+ throw new Error(`Workflow front matter field "${key}" is required.`);
479
+ }
480
+ return value;
481
+ }
482
+ function readStringList(input, key) {
483
+ const value = input[key];
484
+ if (value === void 0 || value === null) {
485
+ return void 0;
486
+ }
487
+ if (typeof value === "string") {
488
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
489
+ }
490
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
491
+ throw new Error(
492
+ `Workflow front matter field "${key}" must be an array of strings or comma-separated string.`
493
+ );
494
+ }
495
+ return value;
496
+ }
497
+ function readRuntimeArgs(input) {
498
+ const value = input.args;
499
+ if (value === void 0 || value === null) {
500
+ return [];
501
+ }
502
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
503
+ throw new Error(
504
+ 'Workflow front matter field "runtime.args" must be an array of strings.'
505
+ );
506
+ }
507
+ return value;
508
+ }
509
+ function readOptionalBoolean(input, key, path = key) {
510
+ const value = input[key];
511
+ if (value === void 0 || value === null) {
512
+ return null;
513
+ }
514
+ if (typeof value !== "boolean") {
515
+ throw new Error(`Workflow front matter field "${path}" must be a boolean.`);
516
+ }
517
+ return value;
518
+ }
519
+ function readOptionalIntegerLike(input, key) {
520
+ const value = input[key];
521
+ if (value === void 0 || value === null) {
522
+ return null;
523
+ }
524
+ if (typeof value === "number") {
525
+ return value;
526
+ }
527
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
528
+ return Number.parseInt(value, 10);
529
+ }
530
+ throw new Error(`Workflow front matter field "${key}" must be an integer.`);
531
+ }
532
+ function readNumberMap(input, key) {
533
+ const value = input[key];
534
+ if (value === void 0 || value === null) {
535
+ return {};
536
+ }
537
+ if (typeof value !== "object" || Array.isArray(value)) {
538
+ throw new Error(`Workflow front matter field "${key}" must be an object.`);
539
+ }
540
+ const result = {};
541
+ for (const [entryKey, entryValue] of Object.entries(value)) {
542
+ if (typeof entryValue === "number") {
543
+ result[entryKey] = entryValue;
544
+ continue;
545
+ }
546
+ if (typeof entryValue === "string" && /^\d+$/.test(entryValue)) {
547
+ result[entryKey] = Number.parseInt(entryValue, 10);
548
+ continue;
549
+ }
550
+ throw new Error(
551
+ `Workflow front matter field "${key}.${entryKey}" must be an integer.`
552
+ );
553
+ }
554
+ return result;
555
+ }
556
+ function resolveEnvironmentValue(value, env) {
557
+ const envTokenMatch = value.match(/^(?:env:)?([A-Z0-9_]+)$/);
558
+ if (value.startsWith("env:") && envTokenMatch) {
559
+ const resolved = env[envTokenMatch[1]];
560
+ if (!resolved) {
561
+ throw new Error(
562
+ `Workflow front matter requires environment variable ${envTokenMatch[1]}.`
563
+ );
564
+ }
565
+ return resolved;
566
+ }
567
+ return value.replace(/\$\{([A-Z0-9_]+)\}/g, (_, name) => {
568
+ const resolved = env[name];
569
+ if (!resolved) {
570
+ throw new Error(
571
+ `Workflow front matter requires environment variable ${name}.`
572
+ );
573
+ }
574
+ return resolved;
575
+ });
576
+ }
577
+ function matchOptionalSection(markdown, heading) {
578
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
579
+ const pattern = new RegExp(
580
+ `## ${escapedHeading}\\n\\n([\\s\\S]*?)(?=\\n## |$)`
581
+ );
582
+ const match = markdown.match(pattern);
583
+ return match?.[1]?.trim() ?? null;
584
+ }
585
+
586
+ // ../core/src/workflow/loader.ts
587
+ import { createHash } from "crypto";
588
+ import { access, readFile, stat } from "fs/promises";
589
+ import { constants } from "fs";
590
+ var WorkflowConfigStore = class {
591
+ cache = /* @__PURE__ */ new Map();
592
+ async load(workflowPath, env = process.env) {
593
+ await access(workflowPath, constants.R_OK);
594
+ const fileStat = await stat(workflowPath);
595
+ const cached = this.cache.get(workflowPath);
596
+ const markdown = await readFile(workflowPath, "utf8");
597
+ const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
598
+ if (cached && cached.fingerprint === fingerprint) {
599
+ return toWorkflowResolution(workflowPath, cached.workflow, {
600
+ isValid: true,
601
+ usedLastKnownGood: false,
602
+ validationError: null
603
+ });
604
+ }
605
+ try {
606
+ const workflow = parseWorkflowMarkdown(markdown, env);
607
+ this.cache.set(workflowPath, {
608
+ fingerprint,
609
+ workflow,
610
+ loadedAt: (/* @__PURE__ */ new Date()).toISOString()
611
+ });
612
+ return toWorkflowResolution(workflowPath, workflow, {
613
+ isValid: true,
614
+ usedLastKnownGood: false,
615
+ validationError: null
616
+ });
617
+ } catch (error) {
618
+ if (cached) {
619
+ return toWorkflowResolution(workflowPath, cached.workflow, {
620
+ isValid: false,
621
+ usedLastKnownGood: true,
622
+ validationError: error instanceof Error ? error.message : "Invalid workflow definition."
623
+ });
624
+ }
625
+ throw error;
626
+ }
627
+ }
628
+ };
629
+ function createDefaultWorkflowResolution() {
630
+ return createInvalidWorkflowResolution(null, "missing_workflow_file");
631
+ }
632
+ function createInvalidWorkflowResolution(workflowPath, validationError) {
633
+ return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
634
+ isValid: false,
635
+ usedLastKnownGood: false,
636
+ validationError
637
+ });
638
+ }
639
+ function toWorkflowResolution(workflowPath, workflow, metadata) {
640
+ return {
641
+ workflowPath,
642
+ workflow,
643
+ lifecycle: workflow.lifecycle,
644
+ promptTemplate: workflow.promptTemplate,
645
+ agentCommand: workflow.agentCommand,
646
+ hookPath: workflow.hookPath ?? "",
647
+ isValid: metadata.isValid,
648
+ usedLastKnownGood: metadata.usedLastKnownGood,
649
+ validationError: metadata.validationError
650
+ };
651
+ }
652
+
653
+ // ../core/src/workflow/render.ts
654
+ import {
655
+ Liquid,
656
+ ParseError,
657
+ RenderError,
658
+ TokenizationError,
659
+ UndefinedVariableError
660
+ } from "liquidjs";
661
+ function buildPromptVariables(issue, options) {
662
+ return {
663
+ issue: {
664
+ id: issue.id,
665
+ identifier: issue.identifier,
666
+ number: issue.number,
667
+ title: issue.title,
668
+ description: issue.description,
669
+ priority: issue.priority,
670
+ url: issue.url,
671
+ state: issue.state,
672
+ labels: issue.labels,
673
+ blocked_by: issue.blockedBy,
674
+ branch_name: issue.branchName,
675
+ created_at: issue.createdAt,
676
+ updated_at: issue.updatedAt,
677
+ repository: `${issue.repository.owner}/${issue.repository.name}`
678
+ },
679
+ attempt: options.attempt
680
+ };
681
+ }
682
+ var STRICT_LIQUID_ENGINE = new Liquid({
683
+ strictVariables: true,
684
+ strictFilters: true,
685
+ ownPropertyOnly: true
686
+ });
687
+ function renderPrompt(template, variables, options = {}) {
688
+ const strict = options.strict ?? true;
689
+ if (!strict) {
690
+ return renderLegacyPrompt(template, variables);
691
+ }
692
+ try {
693
+ return STRICT_LIQUID_ENGINE.parseAndRenderSync(template, variables);
694
+ } catch (error) {
695
+ throw normalizeTemplateError(error);
696
+ }
697
+ }
698
+ function normalizeTemplateError(error) {
699
+ const message = error instanceof Error ? error.message : String(error);
700
+ if (error instanceof UndefinedVariableError || error instanceof RenderError || error instanceof ParseError && message.startsWith("undefined filter:")) {
701
+ return new Error(`template_render_error: ${message}`, { cause: error });
702
+ }
703
+ if (error instanceof ParseError || error instanceof TokenizationError) {
704
+ return new Error(`template_parse_error: ${message}`, { cause: error });
705
+ }
706
+ return new Error(`template_render_error: ${message}`, { cause: error });
707
+ }
708
+ function flattenVariables(obj, prefix = "") {
709
+ const result = /* @__PURE__ */ new Map();
710
+ for (const [key, value] of Object.entries(obj)) {
711
+ const fullKey = prefix ? `${prefix}.${key}` : key;
712
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
713
+ for (const [nestedKey, nestedValue] of flattenVariables(
714
+ value,
715
+ fullKey
716
+ )) {
717
+ result.set(nestedKey, nestedValue);
718
+ }
719
+ } else {
720
+ result.set(fullKey, value);
721
+ }
722
+ }
723
+ return result;
724
+ }
725
+ function renderLegacyPrompt(template, variables) {
726
+ const flatVars = flattenVariables(variables);
727
+ return template.replace(
728
+ /\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g,
729
+ (match, key) => {
730
+ const value = flatVars.get(key);
731
+ if (value === void 0) {
732
+ return match;
733
+ }
734
+ if (value === null) {
735
+ return "";
736
+ }
737
+ return String(value);
738
+ }
739
+ );
740
+ }
741
+
742
+ // ../core/src/contracts/status-surface.ts
743
+ var WORKFLOW_EXECUTION_PHASES = [
744
+ "planning",
745
+ "human-review",
746
+ "implementation",
747
+ "awaiting-merge",
748
+ "completed"
749
+ ];
750
+ function isWorkflowExecutionPhase(value) {
751
+ return typeof value === "string" && WORKFLOW_EXECUTION_PHASES.includes(value);
752
+ }
753
+ var SESSION_EXIT_CLASSIFICATIONS = [
754
+ "completed",
755
+ "budget-exceeded",
756
+ "convergence-detected",
757
+ "max-turns-reached",
758
+ "user-input-required",
759
+ "timeout",
760
+ "error"
761
+ ];
762
+ function isSessionExitClassification(value) {
763
+ return typeof value === "string" && SESSION_EXIT_CLASSIFICATIONS.includes(value);
764
+ }
765
+
766
+ // ../core/src/contracts/run-attempt-phase.ts
767
+ var RUN_ATTEMPT_PHASES = [
768
+ "preparing_workspace",
769
+ "building_prompt",
770
+ "launching_agent",
771
+ "initializing_session",
772
+ "streaming_turn",
773
+ "finishing",
774
+ "succeeded",
775
+ "failed",
776
+ "timed_out",
777
+ "stalled",
778
+ "canceled_by_reconciliation"
779
+ ];
780
+ function isRunAttemptPhase(value) {
781
+ return typeof value === "string" && RUN_ATTEMPT_PHASES.includes(value);
782
+ }
783
+
784
+ // ../core/src/contracts/orchestrator-channel.ts
785
+ function isRecord(value) {
786
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
787
+ }
788
+ function isTokenUsage(value) {
789
+ if (!isRecord(value)) {
790
+ return false;
791
+ }
792
+ return typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && typeof value.totalTokens === "number";
793
+ }
794
+ function isSessionInfo(value) {
795
+ if (!isRecord(value)) {
796
+ return false;
797
+ }
798
+ return (typeof value.threadId === "string" || value.threadId === null) && (typeof value.turnId === "string" || value.turnId === null) && typeof value.turnCount === "number" && (typeof value.sessionId === "string" || value.sessionId === null) && (!("exitClassification" in value) || value.exitClassification === void 0 || value.exitClassification === null || isSessionExitClassification(value.exitClassification));
799
+ }
800
+ function isNullableString(value) {
801
+ return typeof value === "string" || value === null;
802
+ }
803
+ function isTurnEventBase(value) {
804
+ return typeof value.startedAt === "string" && isNullableString(value.threadId) && isNullableString(value.turnId) && typeof value.turnCount === "number" && isNullableString(value.sessionId);
805
+ }
806
+ function isOrchestratorChannelEvent(value) {
807
+ if (!isRecord(value)) {
808
+ return false;
809
+ }
810
+ if (typeof value.issueId !== "string") {
811
+ return false;
812
+ }
813
+ if (value.type === "codex_update") {
814
+ if (typeof value.lastEventAt !== "string") {
815
+ return false;
816
+ }
817
+ if ("event" in value && value.event !== void 0 && typeof value.event !== "string") {
818
+ return false;
819
+ }
820
+ if ("tokenUsage" in value && value.tokenUsage !== void 0 && !isTokenUsage(value.tokenUsage)) {
821
+ return false;
822
+ }
823
+ if ("rateLimits" in value && value.rateLimits !== void 0 && !isRecord(value.rateLimits)) {
824
+ return false;
825
+ }
826
+ if ("sessionInfo" in value && value.sessionInfo !== void 0 && !isSessionInfo(value.sessionInfo)) {
827
+ return false;
828
+ }
829
+ if ("executionPhase" in value && value.executionPhase !== void 0 && value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
830
+ return false;
831
+ }
832
+ if ("runPhase" in value && value.runPhase !== void 0 && value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
833
+ return false;
834
+ }
835
+ if ("lastError" in value && value.lastError !== void 0 && value.lastError !== null && typeof value.lastError !== "string") {
836
+ return false;
837
+ }
838
+ return true;
839
+ }
840
+ if (value.type === "heartbeat") {
841
+ if (value.lastEventAt !== null && typeof value.lastEventAt !== "string") {
842
+ return false;
843
+ }
844
+ if (!isTokenUsage(value.tokenUsage)) {
845
+ return false;
846
+ }
847
+ if (value.rateLimits !== null && !isRecord(value.rateLimits)) {
848
+ return false;
849
+ }
850
+ if (value.sessionInfo !== null && !isSessionInfo(value.sessionInfo)) {
851
+ return false;
852
+ }
853
+ if (value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
854
+ return false;
855
+ }
856
+ if (value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
857
+ return false;
858
+ }
859
+ if (value.lastError !== null && typeof value.lastError !== "string") {
860
+ return false;
861
+ }
862
+ return true;
863
+ }
864
+ if (value.type === "turn_started") {
865
+ return isTurnEventBase(value);
866
+ }
867
+ if (value.type === "turn_completed") {
868
+ return isTurnEventBase(value) && typeof value.completedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage);
869
+ }
870
+ if (value.type === "turn_failed") {
871
+ return isTurnEventBase(value) && typeof value.failedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage) && isNullableString(value.error);
872
+ }
873
+ return false;
874
+ }
875
+
876
+ // ../core/src/workflow/exit-classification.ts
877
+ function classifySessionExit(params) {
878
+ if (params.userInputRequired) {
879
+ return "user-input-required";
880
+ }
881
+ if (params.budgetExceeded) {
882
+ return "budget-exceeded";
883
+ }
884
+ if (params.convergenceDetected) {
885
+ return "convergence-detected";
886
+ }
887
+ if (params.runPhase === "timed_out" || params.runPhase === "stalled") {
888
+ return "timeout";
889
+ }
890
+ if (params.maxTurnsReached) {
891
+ return "max-turns-reached";
892
+ }
893
+ if (params.runPhase === "succeeded") {
894
+ return "completed";
895
+ }
896
+ return "error";
897
+ }
898
+
899
+ // ../core/src/orchestration/retry-policy.ts
900
+ function calculateRetryDelay(attempt, options = {}) {
901
+ const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
902
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
903
+ const normalizedAttempt = Math.max(1, attempt);
904
+ const delay = baseDelayMs * 2 ** (normalizedAttempt - 1);
905
+ return Math.min(delay, maxDelayMs);
906
+ }
907
+ function scheduleRetryAt(now, attempt, options = {}) {
908
+ return new Date(now.getTime() + calculateRetryDelay(attempt, options));
909
+ }
910
+
911
+ // ../core/src/runtime/credentials.ts
912
+ import { readFile as readFile2, writeFile } from "fs/promises";
913
+ var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
914
+ var CODEX_ENV_KEYS = [
915
+ "OPENAI_API_KEY",
916
+ "OPENAI_BASE_URL",
917
+ "OPENAI_ORG_ID",
918
+ "OPENAI_PROJECT"
919
+ ];
920
+ var AgentRuntimeCredentialError = class extends Error {
921
+ };
922
+ function extractEnvForCodex(env) {
923
+ return pickRuntimeEnv(env, CODEX_ENV_KEYS);
924
+ }
925
+ function extractEnvForClaude(env, envKey = "ANTHROPIC_API_KEY") {
926
+ const apiKey = env[envKey];
927
+ if (!apiKey) {
928
+ throw new AgentRuntimeCredentialError(
929
+ `${envKey} is required in the credential broker response.`
930
+ );
931
+ }
932
+ return {
933
+ [envKey]: apiKey
934
+ };
935
+ }
936
+ function toAgentCredentialCacheEntry(brokerResponse, now = /* @__PURE__ */ new Date()) {
937
+ return {
938
+ env: brokerResponse.env,
939
+ expires_at: brokerResponse.expires_at,
940
+ cachedAt: now.toISOString()
941
+ };
942
+ }
943
+ function shouldReuseAgentCredentialCache(entry, now = /* @__PURE__ */ new Date()) {
944
+ if (Object.keys(entry.env).length === 0) {
945
+ return false;
946
+ }
947
+ if (!entry.expires_at) {
948
+ return true;
949
+ }
950
+ const expiresAt = Date.parse(entry.expires_at);
951
+ if (Number.isNaN(expiresAt)) {
952
+ return false;
953
+ }
954
+ return expiresAt - now.getTime() > TOKEN_REUSE_WINDOW_MS;
955
+ }
956
+ async function readAgentCredentialCache(path, readFileImpl = readFile2) {
957
+ try {
958
+ return normalizeAgentCredentialCacheEntry(
959
+ JSON.parse(await readFileImpl(path, "utf8"))
960
+ );
961
+ } catch {
962
+ return null;
963
+ }
964
+ }
965
+ async function writeAgentCredentialCache(path, brokerResponse, writeFileImpl = writeFile, now = /* @__PURE__ */ new Date()) {
966
+ const entry = toAgentCredentialCacheEntry(brokerResponse, now);
967
+ await writeFileImpl(path, JSON.stringify(entry), "utf8");
968
+ return entry;
969
+ }
970
+ function pickRuntimeEnv(env, keys) {
971
+ const resolved = {};
972
+ for (const key of keys) {
973
+ const value = env[key];
974
+ if (value) {
975
+ resolved[key] = value;
976
+ }
977
+ }
978
+ return resolved;
979
+ }
980
+ function normalizeAgentCredentialCacheEntry(payload) {
981
+ if (!isRecord2(payload)) {
982
+ return null;
983
+ }
984
+ if (!isRecord2(payload.env)) {
985
+ return null;
986
+ }
987
+ const env = Object.fromEntries(
988
+ Object.entries(payload.env).filter(
989
+ (entry) => typeof entry[1] === "string"
990
+ )
991
+ );
992
+ if (Object.keys(env).length === 0) {
993
+ return null;
994
+ }
995
+ return {
996
+ env,
997
+ expires_at: typeof payload.expires_at === "string" ? payload.expires_at : void 0,
998
+ cachedAt: typeof payload.cachedAt === "string" ? payload.cachedAt : (/* @__PURE__ */ new Date(0)).toISOString()
999
+ };
1000
+ }
1001
+ function isRecord2(value) {
1002
+ return value !== null && typeof value === "object";
1003
+ }
1004
+
1005
+ // ../core/src/runtime/events.ts
1006
+ var DEFAULT_AGENT_INPUT_REQUIRED_REASON = "turn_input_required: agent requires user input";
1007
+ function buildAgentInputRequiredReason(prompt) {
1008
+ if (typeof prompt === "string") {
1009
+ const trimmedPrompt = prompt.trim();
1010
+ if (trimmedPrompt) {
1011
+ return `turn_input_required: ${trimmedPrompt}`;
1012
+ }
1013
+ }
1014
+ return DEFAULT_AGENT_INPUT_REQUIRED_REASON;
1015
+ }
1016
+
1017
+ // ../core/src/workspace/env-file.ts
1018
+ import { existsSync, readFileSync } from "fs";
1019
+ function readEnvFile(path) {
1020
+ if (!existsSync(path)) {
1021
+ return {};
1022
+ }
1023
+ return readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).reduce((result, line) => {
1024
+ const separatorIndex = line.indexOf("=");
1025
+ const key = line.slice(0, separatorIndex).trim();
1026
+ const value = line.slice(separatorIndex + 1).trim();
1027
+ if (key) {
1028
+ result[key] = value;
1029
+ }
1030
+ return result;
1031
+ }, {});
1032
+ }
1033
+
1034
+ // ../core/src/workspace/safety.ts
1035
+ import { resolve } from "path";
1036
+
1037
+ // ../core/src/workspace/identity.ts
1038
+ import { resolve as resolve2, join } from "path";
1039
+ import { createHash as createHash2 } from "crypto";
1040
+ var RESERVED_WORKSPACE_KEYS = /* @__PURE__ */ new Set([
1041
+ "cache",
1042
+ "issues.json",
1043
+ "project.json",
1044
+ "runs",
1045
+ "status.json"
1046
+ ]);
1047
+ function deriveWorkspaceKey(identifier) {
1048
+ const sanitized = identifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1049
+ if (!sanitized || /^[.]+$/.test(sanitized)) {
1050
+ return "issue";
1051
+ }
1052
+ return sanitized;
1053
+ }
1054
+ var deriveIssueWorkspaceKeyFromIdentifier = deriveWorkspaceKey;
1055
+ function deriveIssueWorkspaceKey(identityOrIdentifier, issueIdentifier) {
1056
+ if (typeof identityOrIdentifier === "string") {
1057
+ return deriveWorkspaceKey(identityOrIdentifier);
1058
+ }
1059
+ return deriveWorkspaceKey(
1060
+ issueIdentifier ?? identityOrIdentifier.issueSubjectId
1061
+ );
1062
+ }
1063
+ function deriveLegacyIssueWorkspaceKey(identity, projectId) {
1064
+ const input = [projectId, identity.adapter, identity.issueSubjectId].filter((part) => typeof part === "string").join(":");
1065
+ return createHash2("sha256").update(input).digest("hex").slice(0, 16);
1066
+ }
1067
+ function resolveIssueWorkspaceDirectory(runtimeRoot, workspaceKey) {
1068
+ const normalizedRuntimeRoot = resolve2(runtimeRoot);
1069
+ const candidate = resolve2(normalizedRuntimeRoot, workspaceKey);
1070
+ if (!candidate.startsWith(`${normalizedRuntimeRoot}/`)) {
1071
+ throw new Error(
1072
+ "Issue workspace path escapes the configured runtime root."
1073
+ );
1074
+ }
1075
+ if (isReservedWorkspaceKey(workspaceKey)) {
1076
+ throw new Error("Issue workspace key is reserved by the runtime layout.");
1077
+ }
1078
+ return candidate;
1079
+ }
1080
+ function isReservedWorkspaceKey(workspaceKey) {
1081
+ return workspaceKey.startsWith(".") || RESERVED_WORKSPACE_KEYS.has(workspaceKey);
1082
+ }
1083
+
1084
+ // ../core/src/workspace/hooks.ts
1085
+ import { spawn } from "child_process";
1086
+ var DEFAULT_HOOK_TIMEOUT_MS2 = 6e4;
1087
+ async function executeHook(options) {
1088
+ const { kind, command, cwd, env, timeoutMs } = options;
1089
+ const start = Date.now();
1090
+ const normalizedCommand = normalizeHookCommand(command);
1091
+ return new Promise((resolveResult) => {
1092
+ let timedOut = false;
1093
+ let timer = null;
1094
+ const child = spawn("bash", ["-lc", normalizedCommand], {
1095
+ cwd,
1096
+ env: { ...process.env, ...env },
1097
+ stdio: "pipe"
1098
+ });
1099
+ const stderrChunks = [];
1100
+ child.stderr?.on("data", (chunk) => {
1101
+ stderrChunks.push(chunk);
1102
+ });
1103
+ if (timeoutMs > 0) {
1104
+ timer = setTimeout(() => {
1105
+ timedOut = true;
1106
+ child.kill("SIGTERM");
1107
+ setTimeout(() => {
1108
+ try {
1109
+ child.kill("SIGKILL");
1110
+ } catch {
1111
+ }
1112
+ }, 5e3);
1113
+ }, timeoutMs);
1114
+ }
1115
+ child.on("close", (code) => {
1116
+ if (timer) {
1117
+ clearTimeout(timer);
1118
+ }
1119
+ const durationMs = Date.now() - start;
1120
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
1121
+ if (timedOut) {
1122
+ resolveResult({
1123
+ kind,
1124
+ outcome: "timeout",
1125
+ exitCode: code,
1126
+ durationMs,
1127
+ error: `Hook "${kind}" timed out after ${timeoutMs}ms`
1128
+ });
1129
+ return;
1130
+ }
1131
+ if (code !== 0) {
1132
+ resolveResult({
1133
+ kind,
1134
+ outcome: "failure",
1135
+ exitCode: code,
1136
+ durationMs,
1137
+ error: stderr || `Hook "${kind}" exited with code ${code}`
1138
+ });
1139
+ return;
1140
+ }
1141
+ resolveResult({
1142
+ kind,
1143
+ outcome: "success",
1144
+ exitCode: 0,
1145
+ durationMs,
1146
+ error: null
1147
+ });
1148
+ });
1149
+ child.on("error", (err) => {
1150
+ if (timer) {
1151
+ clearTimeout(timer);
1152
+ }
1153
+ resolveResult({
1154
+ kind,
1155
+ outcome: "failure",
1156
+ exitCode: null,
1157
+ durationMs: Date.now() - start,
1158
+ error: err.message
1159
+ });
1160
+ });
1161
+ });
1162
+ }
1163
+ function buildHookEnv(context) {
1164
+ const env = {
1165
+ SYMPHONY_PROJECT_ID: context.projectId,
1166
+ SYMPHONY_ISSUE_WORKSPACE_KEY: context.workspaceKey,
1167
+ SYMPHONY_ISSUE_SUBJECT_ID: context.issueSubjectId,
1168
+ SYMPHONY_ISSUE_IDENTIFIER: context.issueIdentifier,
1169
+ SYMPHONY_WORKSPACE_PATH: context.workspacePath,
1170
+ SYMPHONY_REPOSITORY_PATH: context.repositoryPath
1171
+ };
1172
+ if (context.runId) {
1173
+ env.SYMPHONY_RUN_ID = context.runId;
1174
+ }
1175
+ if (context.state) {
1176
+ env.SYMPHONY_ISSUE_STATE = context.state;
1177
+ }
1178
+ return env;
1179
+ }
1180
+ function resolveHookCommand(hooks, kind) {
1181
+ switch (kind) {
1182
+ case "after_create":
1183
+ return hooks.afterCreate;
1184
+ case "before_run":
1185
+ return hooks.beforeRun;
1186
+ case "after_run":
1187
+ return hooks.afterRun;
1188
+ case "before_remove":
1189
+ return hooks.beforeRemove;
1190
+ }
1191
+ }
1192
+ async function executeWorkspaceHook(options) {
1193
+ const hookCommand = resolveHookCommand(options.hooks, options.kind);
1194
+ if (!hookCommand) {
1195
+ return {
1196
+ kind: options.kind,
1197
+ outcome: "skipped",
1198
+ exitCode: null,
1199
+ durationMs: 0,
1200
+ error: null
1201
+ };
1202
+ }
1203
+ return executeHook({
1204
+ kind: options.kind,
1205
+ command: hookCommand,
1206
+ cwd: options.repositoryPath,
1207
+ env: options.env,
1208
+ timeoutMs: options.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS2
1209
+ });
1210
+ }
1211
+ function normalizeHookCommand(command) {
1212
+ const trimmed = command.trim();
1213
+ if (trimmed.includes("/") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../") && !/\s/.test(trimmed)) {
1214
+ return `bash ./${trimmed}`;
1215
+ }
1216
+ return command;
1217
+ }
1218
+
1219
+ // ../core/src/observability/snapshot-builder.ts
1220
+ function buildProjectSnapshot(input) {
1221
+ const {
1222
+ project,
1223
+ activeRuns,
1224
+ allRuns,
1225
+ summary,
1226
+ lastTickAt,
1227
+ lastError,
1228
+ rateLimits
1229
+ } = input;
1230
+ const cumulativeTokenUsageByIssue = aggregateTokenUsageByIssue(
1231
+ allRuns ?? activeRuns
1232
+ );
1233
+ return {
1234
+ repository: project.repository,
1235
+ tracker: {
1236
+ adapter: project.tracker.adapter,
1237
+ bindingId: project.tracker.bindingId,
1238
+ settings: project.tracker.settings
1239
+ },
1240
+ lastTickAt,
1241
+ health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
1242
+ summary: {
1243
+ dispatched: summary.dispatched,
1244
+ suppressed: summary.suppressed,
1245
+ recovered: summary.recovered,
1246
+ activeRuns: activeRuns.length
1247
+ },
1248
+ activeRuns: activeRuns.map((run) => ({
1249
+ runId: run.runId,
1250
+ issueIdentifier: run.issueIdentifier,
1251
+ issueState: run.issueState,
1252
+ status: run.status,
1253
+ retryKind: run.retryKind,
1254
+ port: run.port,
1255
+ runtimeSession: run.runtimeSession ?? null,
1256
+ // New fields from live worker data
1257
+ processId: run.processId ?? null,
1258
+ turnCount: run.turnCount,
1259
+ startedAt: run.startedAt ?? null,
1260
+ lastEvent: run.lastEvent ?? null,
1261
+ lastEventAt: run.lastEventAt ?? null,
1262
+ executionPhase: run.executionPhase ?? null,
1263
+ runPhase: run.runPhase ?? null,
1264
+ tokenUsage: attachCumulativeTokenUsage(
1265
+ run.tokenUsage,
1266
+ cumulativeTokenUsageByIssue.get(run.issueId)
1267
+ )
1268
+ })),
1269
+ retryQueue: activeRuns.filter((run) => run.status === "retrying" && run.retryKind).map((run) => ({
1270
+ runId: run.runId,
1271
+ issueIdentifier: run.issueIdentifier,
1272
+ retryKind: run.retryKind ?? "failure",
1273
+ nextRetryAt: run.nextRetryAt
1274
+ })),
1275
+ lastError,
1276
+ codexTotals: aggregateTokenUsage(allRuns ?? activeRuns, lastTickAt),
1277
+ rateLimits: rateLimits ?? null
1278
+ };
1279
+ }
1280
+ function aggregateTokenUsageByIssue(runs) {
1281
+ const totals = /* @__PURE__ */ new Map();
1282
+ for (const run of runs) {
1283
+ if (!run.tokenUsage) {
1284
+ continue;
1285
+ }
1286
+ const current = totals.get(run.issueId) ?? {
1287
+ inputTokens: 0,
1288
+ outputTokens: 0,
1289
+ totalTokens: 0
1290
+ };
1291
+ current.inputTokens += run.tokenUsage.inputTokens;
1292
+ current.outputTokens += run.tokenUsage.outputTokens;
1293
+ current.totalTokens += run.tokenUsage.totalTokens;
1294
+ totals.set(run.issueId, current);
1295
+ }
1296
+ return totals;
1297
+ }
1298
+ function attachCumulativeTokenUsage(tokenUsage, cumulative) {
1299
+ if (!tokenUsage) {
1300
+ return void 0;
1301
+ }
1302
+ return {
1303
+ ...tokenUsage,
1304
+ cumulativeInputTokens: cumulative?.inputTokens ?? tokenUsage.inputTokens,
1305
+ cumulativeOutputTokens: cumulative?.outputTokens ?? tokenUsage.outputTokens,
1306
+ cumulativeTotalTokens: cumulative?.totalTokens ?? tokenUsage.totalTokens
1307
+ };
1308
+ }
1309
+ function aggregateTokenUsage(runs, lastTickAt) {
1310
+ let inputTokens = 0;
1311
+ let outputTokens = 0;
1312
+ let totalTokens = 0;
1313
+ let earliestStart = null;
1314
+ let latestEnd = null;
1315
+ for (const run of runs) {
1316
+ if (run.tokenUsage) {
1317
+ inputTokens += run.tokenUsage.inputTokens;
1318
+ outputTokens += run.tokenUsage.outputTokens;
1319
+ totalTokens += run.tokenUsage.totalTokens;
1320
+ }
1321
+ if (run.startedAt) {
1322
+ const start = new Date(run.startedAt).getTime();
1323
+ if (earliestStart === null || start < earliestStart) {
1324
+ earliestStart = start;
1325
+ }
1326
+ }
1327
+ const end = run.completedAt ? new Date(run.completedAt).getTime() : new Date(lastTickAt).getTime();
1328
+ if (latestEnd === null || end > latestEnd) {
1329
+ latestEnd = end;
1330
+ }
1331
+ }
1332
+ const secondsRunning = earliestStart !== null && latestEnd !== null ? Math.max(0, Math.round((latestEnd - earliestStart) / 1e3)) : 0;
1333
+ return { inputTokens, outputTokens, totalTokens, secondsRunning };
1334
+ }
1335
+
1336
+ // ../core/src/observability/fs-reader.ts
1337
+ import { readFile as readFile3, readdir } from "fs/promises";
1338
+ async function readJsonFile(path) {
1339
+ try {
1340
+ const raw = await readFile3(path, "utf8");
1341
+ return JSON.parse(raw);
1342
+ } catch (error) {
1343
+ if (isFileMissing(error)) {
1344
+ return null;
1345
+ }
1346
+ throw error;
1347
+ }
1348
+ }
1349
+ async function safeReadDir(path) {
1350
+ try {
1351
+ return await readdir(path);
1352
+ } catch (error) {
1353
+ if (isFileMissing(error)) {
1354
+ return [];
1355
+ }
1356
+ throw error;
1357
+ }
1358
+ }
1359
+ function isFileMissing(error) {
1360
+ return Boolean(
1361
+ error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
1362
+ );
1363
+ }
1364
+
1365
+ // ../core/src/observability/event-formatter.ts
1366
+ function formatEventMessage(event) {
1367
+ switch (event.event) {
1368
+ case "run-dispatched":
1369
+ return event.issueState ? `Dispatched from ${event.issueState}` : "Dispatched";
1370
+ case "run-recovered":
1371
+ return "Recovered existing run";
1372
+ case "run-retried":
1373
+ return `Retry ${event.attempt} scheduled (${event.retryKind})`;
1374
+ case "run-failed":
1375
+ return event.lastError;
1376
+ case "run-suppressed":
1377
+ return event.reason;
1378
+ case "hook-executed":
1379
+ return `${event.hook}: ${event.outcome}`;
1380
+ case "hook-failed":
1381
+ return event.error;
1382
+ case "workspace-cleanup":
1383
+ return event.error ? `${event.outcome}: ${event.error}` : event.outcome;
1384
+ case "worker-error":
1385
+ return event.error;
1386
+ case "turn_started":
1387
+ return `Turn ${event.turnCount} started`;
1388
+ case "turn_completed":
1389
+ return `Turn ${event.turnCount} completed in ${event.durationMs}ms`;
1390
+ case "turn_failed":
1391
+ return event.error ?? `Turn ${event.turnCount} failed`;
1392
+ case "session_invalidated":
1393
+ return event.reason;
1394
+ default:
1395
+ return null;
1396
+ }
1397
+ }
1398
+ function parseRecentEvents(raw, limit, options) {
1399
+ const lines = raw.split("\n");
1400
+ if (options.allowPartialFirstLine) {
1401
+ lines.shift();
1402
+ }
1403
+ const events = [];
1404
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1405
+ const line = lines[index]?.trim();
1406
+ if (!line) {
1407
+ continue;
1408
+ }
1409
+ const event = parseRunEventLine(line);
1410
+ if (!event) {
1411
+ continue;
1412
+ }
1413
+ events.push({
1414
+ at: event.at,
1415
+ event: event.event,
1416
+ message: formatEventMessage(event)
1417
+ });
1418
+ if (events.length === limit) {
1419
+ break;
1420
+ }
1421
+ }
1422
+ return events.reverse();
1423
+ }
1424
+ function parseRunEventLine(line) {
1425
+ try {
1426
+ return JSON.parse(line);
1427
+ } catch {
1428
+ return null;
1429
+ }
1430
+ }
1431
+
1432
+ // ../core/src/observability/status-assembler.ts
1433
+ function isMatchingIssueRun(run, issueId, issueIdentifier) {
1434
+ return Boolean(
1435
+ run && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1436
+ );
1437
+ }
1438
+ function mapIssueOrchestrationStateToStatus(state) {
1439
+ switch (state) {
1440
+ case "claimed":
1441
+ return "starting";
1442
+ case "running":
1443
+ return "running";
1444
+ case "retry_queued":
1445
+ return "retrying";
1446
+ case "released":
1447
+ return "released";
1448
+ case "unclaimed":
1449
+ return "pending";
1450
+ default:
1451
+ return state;
1452
+ }
1453
+ }
1454
+
1455
+ // ../runtime-claude/src/preflight.ts
1456
+ import { execFileSync } from "child_process";
1457
+ import { constants as constants2 } from "fs";
1458
+ import { access as access2, readFile as readFile4 } from "fs/promises";
1459
+ import { isAbsolute, join as join2, resolve as resolve3 } from "path";
1460
+ var DEFAULT_DEPENDENCIES = {
1461
+ execFileSync,
1462
+ readFile: readFile4,
1463
+ access: access2,
1464
+ fetchImpl: fetch,
1465
+ platform: process.platform
1466
+ };
1467
+ var CREDENTIAL_BROKER_TIMEOUT_MS = 5e3;
1468
+ async function runClaudePreflight(options, dependencies = {}) {
1469
+ const deps = { ...DEFAULT_DEPENDENCIES, ...dependencies };
1470
+ const env = options.env ?? process.env;
1471
+ const command = resolveRuntimeCommandBinary(options.command) ?? "claude";
1472
+ const checks = [];
1473
+ checks.push(checkClaudeBinary(command, options.cwd, deps));
1474
+ checks.push(await checkAnthropicApiKey(env, options, deps));
1475
+ checks.push(await checkWorkspaceMcpConfig(options.cwd, deps));
1476
+ if (options.includeGhAuth) {
1477
+ checks.push(checkGhAuthentication(options.cwd, deps));
1478
+ }
1479
+ return {
1480
+ ok: checks.every((check) => check.status !== "fail"),
1481
+ checks
1482
+ };
1483
+ }
1484
+ function formatClaudePreflightText(report) {
1485
+ const lines = ["Claude runtime preflight"];
1486
+ for (const check of report.checks) {
1487
+ const label = check.status === "pass" ? "PASS" : check.status === "warn" ? "WARN" : "FAIL";
1488
+ lines.push(`${label} ${check.title}`);
1489
+ lines.push(` ${check.summary}`);
1490
+ if (check.remediation) {
1491
+ lines.push(` Fix: ${check.remediation}`);
1492
+ }
1493
+ }
1494
+ return lines.join("\n");
1495
+ }
1496
+ function pass(id, title, summary, details) {
1497
+ return { id, title, status: "pass", summary, details };
1498
+ }
1499
+ function warn(id, title, summary, remediation, details) {
1500
+ return { id, title, status: "warn", summary, remediation, details };
1501
+ }
1502
+ function fail(id, title, summary, remediation, details) {
1503
+ return { id, title, status: "fail", summary, remediation, details };
1504
+ }
1505
+ function checkClaudeBinary(command, cwd, deps) {
1506
+ const executable = resolveExecutableCommand(command, cwd);
1507
+ try {
1508
+ const locatedPath = locateClaudeBinary(executable, cwd, deps);
1509
+ const version = deps.execFileSync(executable, ["--version"], {
1510
+ encoding: "utf8",
1511
+ cwd,
1512
+ stdio: ["pipe", "pipe", "pipe"]
1513
+ }).toString().trim();
1514
+ return pass(
1515
+ "claude_binary",
1516
+ "Claude CLI binary",
1517
+ version ? `${executable} is available: ${version}.` : `${executable} is available, but --version returned an empty response.`,
1518
+ { command: executable, path: locatedPath, version }
1519
+ );
1520
+ } catch (error) {
1521
+ return fail(
1522
+ "claude_binary",
1523
+ "Claude CLI binary",
1524
+ `${executable} could not be found or executed from PATH.`,
1525
+ `Install Claude Code and ensure '${executable}' is on PATH, then re-run the command.`,
1526
+ {
1527
+ command: executable,
1528
+ error: error instanceof Error ? error.message : String(error)
1529
+ }
1530
+ );
1531
+ }
1532
+ }
1533
+ function resolveExecutableCommand(command, cwd) {
1534
+ if ((command.includes("/") || command.includes("\\")) && !isAbsolute(command)) {
1535
+ return resolve3(cwd, command);
1536
+ }
1537
+ return command;
1538
+ }
1539
+ function locateClaudeBinary(command, cwd, deps) {
1540
+ if (command.includes("/") || command.includes("\\")) {
1541
+ return command;
1542
+ }
1543
+ try {
1544
+ const locator = deps.platform === "win32" ? "where" : "which";
1545
+ return deps.execFileSync(locator, [command], {
1546
+ encoding: "utf8",
1547
+ cwd,
1548
+ stdio: ["pipe", "pipe", "pipe"]
1549
+ }).toString().split(/\r?\n/).find((line) => line.trim())?.trim() ?? null;
1550
+ } catch {
1551
+ return null;
1552
+ }
1553
+ }
1554
+ function checkGhAuthentication(cwd, deps) {
1555
+ try {
1556
+ deps.execFileSync("gh", ["auth", "status"], {
1557
+ encoding: "utf8",
1558
+ cwd,
1559
+ stdio: ["pipe", "pipe", "pipe"]
1560
+ });
1561
+ return pass(
1562
+ "gh_authentication",
1563
+ "GitHub CLI authentication",
1564
+ "gh auth status succeeded."
1565
+ );
1566
+ } catch (error) {
1567
+ return fail(
1568
+ "gh_authentication",
1569
+ "GitHub CLI authentication",
1570
+ "gh auth status failed or no GitHub login is configured.",
1571
+ "Run 'gh auth login --scopes repo,read:org,project' and re-run the command.",
1572
+ { error: error instanceof Error ? error.message : String(error) }
1573
+ );
1574
+ }
1575
+ }
1576
+ async function checkAnthropicApiKey(env, options, deps) {
1577
+ if (env.ANTHROPIC_API_KEY?.trim()) {
1578
+ return pass(
1579
+ "anthropic_api_key",
1580
+ "Anthropic API key",
1581
+ "ANTHROPIC_API_KEY is configured in the environment.",
1582
+ { source: "env" }
1583
+ );
1584
+ }
1585
+ const brokerUrl = env.AGENT_CREDENTIAL_BROKER_URL?.trim();
1586
+ const brokerSecret = env.AGENT_CREDENTIAL_BROKER_SECRET?.trim();
1587
+ if (!brokerUrl || !brokerSecret) {
1588
+ return fail(
1589
+ "anthropic_api_key",
1590
+ "Anthropic API key",
1591
+ "Neither ANTHROPIC_API_KEY nor an agent credential broker is configured.",
1592
+ "Set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1593
+ { source: "missing" }
1594
+ );
1595
+ }
1596
+ if (options.probeCredentialBroker === false) {
1597
+ return pass(
1598
+ "anthropic_api_key",
1599
+ "Anthropic API key",
1600
+ "Agent credential broker configuration is present.",
1601
+ { source: "broker", brokerUrl }
1602
+ );
1603
+ }
1604
+ try {
1605
+ const response = await deps.fetchImpl(brokerUrl, {
1606
+ method: "POST",
1607
+ headers: {
1608
+ accept: "application/json",
1609
+ authorization: `Bearer ${brokerSecret}`
1610
+ },
1611
+ signal: AbortSignal.timeout(CREDENTIAL_BROKER_TIMEOUT_MS)
1612
+ });
1613
+ const payload = await response.json();
1614
+ if (response.ok && payload.env?.ANTHROPIC_API_KEY?.trim()) {
1615
+ return pass(
1616
+ "anthropic_api_key",
1617
+ "Anthropic API key",
1618
+ "Agent credential broker is reachable and returned ANTHROPIC_API_KEY.",
1619
+ { source: "broker", brokerUrl }
1620
+ );
1621
+ }
1622
+ return fail(
1623
+ "anthropic_api_key",
1624
+ "Anthropic API key",
1625
+ payload.error ? `Agent credential broker did not return ANTHROPIC_API_KEY: ${payload.error}.` : "Agent credential broker did not return ANTHROPIC_API_KEY.",
1626
+ "Set ANTHROPIC_API_KEY or configure the credential broker to return ANTHROPIC_API_KEY.",
1627
+ { source: "broker", brokerUrl, status: response.status }
1628
+ );
1629
+ } catch (error) {
1630
+ return fail(
1631
+ "anthropic_api_key",
1632
+ "Anthropic API key",
1633
+ "Agent credential broker could not be reached.",
1634
+ "Set ANTHROPIC_API_KEY or fix AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1635
+ {
1636
+ source: "broker",
1637
+ brokerUrl,
1638
+ error: error instanceof Error ? error.message : String(error)
1639
+ }
1640
+ );
1641
+ }
1642
+ }
1643
+ async function checkWorkspaceMcpConfig(cwd, deps) {
1644
+ const path = join2(cwd, ".mcp.json");
1645
+ try {
1646
+ await deps.access(path, constants2.R_OK);
1647
+ } catch (error) {
1648
+ const err = error;
1649
+ if (err.code === "ENOENT") {
1650
+ return warn(
1651
+ "claude_mcp_config",
1652
+ "Workspace .mcp.json",
1653
+ ".mcp.json was not found in the workspace root. Claude can still run without a workspace MCP config.",
1654
+ void 0,
1655
+ { path, reason: "missing" }
1656
+ );
1657
+ }
1658
+ return warn(
1659
+ "claude_mcp_config",
1660
+ "Workspace .mcp.json",
1661
+ `.mcp.json exists but is not readable: ${err.message}.`,
1662
+ "Fix .mcp.json file permissions if this workspace needs Claude MCP servers.",
1663
+ { path, reason: "unreadable", error: err.message }
1664
+ );
1665
+ }
1666
+ try {
1667
+ const content = await deps.readFile(path, "utf8");
1668
+ JSON.parse(content);
1669
+ return pass(
1670
+ "claude_mcp_config",
1671
+ "Workspace .mcp.json",
1672
+ ".mcp.json is readable and contains valid JSON.",
1673
+ { path }
1674
+ );
1675
+ } catch (error) {
1676
+ return fail(
1677
+ "claude_mcp_config",
1678
+ "Workspace .mcp.json",
1679
+ `.mcp.json could not be parsed as JSON: ${error instanceof Error ? error.message : String(error)}.`,
1680
+ "Fix or remove the workspace root .mcp.json file, then re-run the command.",
1681
+ { path, reason: "invalid_json" }
1682
+ );
1683
+ }
1684
+ }
1685
+ function isClaudeRuntimeCommand(command) {
1686
+ return resolveClaudeCommandBinary(command) != null;
1687
+ }
1688
+ function resolveClaudeCommandBinary(command) {
1689
+ const binary = resolveRuntimeCommandBinary(command);
1690
+ return binary != null && isClaudeBinaryName(binary) ? binary : null;
1691
+ }
1692
+ function resolveRuntimeCommandBinary(command) {
1693
+ const normalized = (command ?? "").trim();
1694
+ if (!normalized) {
1695
+ return null;
1696
+ }
1697
+ const tokens = tokenizeRuntimeCommand(normalized);
1698
+ if (tokens.length === 0) {
1699
+ return null;
1700
+ }
1701
+ const first = stripClaudeCommandQuotes(tokens[0]);
1702
+ if ((first === "bash" || first === "sh" || first === "zsh" || first === "fish") && tokens.length >= 3) {
1703
+ const flagIndex = tokens.findIndex((token) => {
1704
+ const value = stripClaudeCommandQuotes(token);
1705
+ return value === "-c" || value === "-lc";
1706
+ });
1707
+ if (flagIndex >= 0 && flagIndex + 1 < tokens.length) {
1708
+ const shellCommand = stripClaudeCommandQuotes(tokens[flagIndex + 1]);
1709
+ return resolveShellCommandClaudeBinary(shellCommand) ?? resolveRuntimeCommandBinary(shellCommand);
1710
+ }
1711
+ }
1712
+ return first;
1713
+ }
1714
+ function stripClaudeCommandQuotes(value) {
1715
+ return value.replace(/^['"]|['"]$/g, "");
1716
+ }
1717
+ function tokenizeRuntimeCommand(command) {
1718
+ return command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
1719
+ }
1720
+ function resolveShellCommandClaudeBinary(command) {
1721
+ for (const segment of command.split(/&&|\|\||[;\n]/g)) {
1722
+ const tokens = tokenizeRuntimeCommand(segment);
1723
+ for (const token of tokens) {
1724
+ const value = stripClaudeCommandQuotes(token);
1725
+ if (value.includes("=") && !value.startsWith("/") && !value.startsWith("./")) {
1726
+ continue;
1727
+ }
1728
+ if (isClaudeBinaryName(value)) {
1729
+ return value;
1730
+ }
1731
+ }
1732
+ }
1733
+ return null;
1734
+ }
1735
+ function isClaudeBinaryName(command) {
1736
+ const normalized = (command.split(/[\\/]/).pop() ?? command).toLowerCase().replace(/\.(exe|cmd|bat)$/i, "");
1737
+ return normalized === "claude" || normalized === "claude-code";
1738
+ }
1739
+
1740
+ // ../runtime-claude/src/adapter.ts
1741
+ import { randomUUID } from "crypto";
1742
+ import { rm } from "fs/promises";
1743
+ import { join as join5 } from "path";
1744
+
1745
+ // ../runtime-claude/src/argv.ts
1746
+ var DEFAULT_CLAUDE_PRINT_ARGS = [
1747
+ "-p",
1748
+ "--output-format",
1749
+ "stream-json",
1750
+ "--input-format",
1751
+ "stream-json",
1752
+ "--include-partial-messages",
1753
+ // Claude stream-json output requires verbose mode when partial message
1754
+ // events are included; keep this even when callers provide custom args.
1755
+ "--verbose",
1756
+ "--permission-mode",
1757
+ "bypassPermissions"
1758
+ ];
1759
+ function buildClaudePrintArgv(options = {}) {
1760
+ const args = options.baseArgs ? withRequiredClaudePrintArgs(options.baseArgs) : [...DEFAULT_CLAUDE_PRINT_ARGS];
1761
+ const { session, isolation, extraArgs } = options;
1762
+ if (session?.mode === "start") {
1763
+ ensureFlagValue(args, "--session-id", session.sessionId);
1764
+ }
1765
+ if (session?.mode === "resume") {
1766
+ ensureFlagValue(args, "--resume", session.sessionId);
1767
+ if (session.forkSession) {
1768
+ ensureFlag(args, "--fork-session");
1769
+ }
1770
+ }
1771
+ if (isolation?.bare) {
1772
+ ensureFlag(args, "--bare");
1773
+ }
1774
+ if (isolation?.strictMcpConfig) {
1775
+ ensureFlag(args, "--strict-mcp-config");
1776
+ if (isolation.mcpConfigPath) {
1777
+ ensureFlagValue(args, "--mcp-config", isolation.mcpConfigPath);
1778
+ }
1779
+ }
1780
+ if (extraArgs?.length) {
1781
+ args.push(...extraArgs);
1782
+ }
1783
+ return args;
1784
+ }
1785
+ function withRequiredClaudePrintArgs(baseArgs) {
1786
+ const args = [...baseArgs];
1787
+ ensureFlag(args, "-p");
1788
+ ensureFlagValue(args, "--output-format", "stream-json");
1789
+ ensureFlagValue(args, "--input-format", "stream-json");
1790
+ ensureFlag(args, "--include-partial-messages");
1791
+ ensureFlag(args, "--verbose");
1792
+ ensureFlagValue(args, "--permission-mode", "bypassPermissions");
1793
+ return args;
1794
+ }
1795
+ function ensureFlag(args, flag) {
1796
+ if (!args.includes(flag)) {
1797
+ args.push(flag);
1798
+ }
1799
+ }
1800
+ function ensureFlagValue(args, flag, value) {
1801
+ const index = args.indexOf(flag);
1802
+ if (index === -1) {
1803
+ args.push(flag, value);
1804
+ return;
1805
+ }
1806
+ const existingValue = args[index + 1];
1807
+ if (existingValue?.startsWith("-")) {
1808
+ args.splice(index + 1, 0, value);
1809
+ return;
1810
+ }
1811
+ if (existingValue !== value) {
1812
+ args.splice(index + 1, existingValue === void 0 ? 0 : 1, value);
1813
+ }
1814
+ }
1815
+
1816
+ // ../runtime-claude/src/mcp-compose.ts
1817
+ import { mkdir, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1818
+ import { basename, dirname, join as join3, resolve as resolve4 } from "path";
1819
+
1820
+ // ../tool-github-graphql/src/tool.ts
1821
+ import { readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
1822
+ var DEFAULT_GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
1823
+ var TOKEN_REUSE_WINDOW_MS2 = 60 * 1e3;
1824
+ async function executeGitHubGraphQL(invocation, config, fetchImpl = fetch) {
1825
+ const token = await resolveGitHubGraphQLToken(config, {
1826
+ fetchImpl
1827
+ });
1828
+ const response = await fetchImpl(
1829
+ config.apiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL,
1830
+ {
1831
+ method: "POST",
1832
+ headers: {
1833
+ "content-type": "application/json",
1834
+ authorization: `Bearer ${token}`
1835
+ },
1836
+ body: JSON.stringify(invocation)
1837
+ }
1838
+ );
1839
+ const payload = await response.json();
1840
+ if (!response.ok) {
1841
+ throw new Error(
1842
+ `GitHub GraphQL request failed with status ${response.status}: ${JSON.stringify(payload)}`
1843
+ );
1844
+ }
1845
+ if (payload.errors?.length) {
1846
+ throw new Error(payload.errors.map((error) => error.message).join("; "));
1847
+ }
1848
+ return payload;
1849
+ }
1850
+ async function resolveGitHubGraphQLToken(config, dependencies = {}) {
1851
+ if (config.token) {
1852
+ return config.token;
1853
+ }
1854
+ if (!config.tokenBrokerUrl || !config.tokenBrokerSecret) {
1855
+ throw new Error(
1856
+ "Either GITHUB_GRAPHQL_TOKEN or the runtime token broker configuration is required."
1857
+ );
1858
+ }
1859
+ const now = dependencies.now ?? /* @__PURE__ */ new Date();
1860
+ const readFileImpl = dependencies.readFileImpl ?? readFile5;
1861
+ const writeFileImpl = dependencies.writeFileImpl ?? writeFile2;
1862
+ const cachedToken = config.tokenCachePath ? await readCachedToken(config.tokenCachePath, readFileImpl) : null;
1863
+ if (cachedToken && cachedToken.expiresAt.getTime() - now.getTime() > TOKEN_REUSE_WINDOW_MS2) {
1864
+ return cachedToken.token;
1865
+ }
1866
+ const fetchImpl = dependencies.fetchImpl ?? fetch;
1867
+ const response = await fetchImpl(config.tokenBrokerUrl, {
1868
+ method: "POST",
1869
+ headers: {
1870
+ accept: "application/json",
1871
+ authorization: `Bearer ${config.tokenBrokerSecret}`
1872
+ }
1873
+ });
1874
+ const payload = await response.json();
1875
+ if (!response.ok || !payload.token || !payload.expiresAt) {
1876
+ throw new Error(
1877
+ payload.error ?? `Runtime token broker request failed with status ${response.status}.`
1878
+ );
1879
+ }
1880
+ if (config.tokenCachePath) {
1881
+ await writeFileImpl(config.tokenCachePath, JSON.stringify(payload), "utf8");
1882
+ }
1883
+ return payload.token;
1884
+ }
1885
+ async function readStdin() {
1886
+ const chunks = [];
1887
+ for await (const chunk of process.stdin) {
1888
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1889
+ }
1890
+ return Buffer.concat(chunks).toString("utf8");
1891
+ }
1892
+ async function main() {
1893
+ const rawInput = await readStdin();
1894
+ const invocation = JSON.parse(rawInput);
1895
+ const result = await executeGitHubGraphQL(invocation, {
1896
+ token: process.env.GITHUB_GRAPHQL_TOKEN,
1897
+ apiUrl: process.env.GITHUB_GRAPHQL_API_URL,
1898
+ tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
1899
+ tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
1900
+ tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH
1901
+ });
1902
+ process.stdout.write(`${JSON.stringify(result)}
1903
+ `);
1904
+ }
1905
+ if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
1906
+ main().catch((error) => {
1907
+ const message = error instanceof Error ? error.message : "Unknown error";
1908
+ process.stderr.write(`${message}
1909
+ `);
1910
+ process.exitCode = 1;
1911
+ });
1912
+ }
1913
+ async function readCachedToken(path, readFileImpl) {
1914
+ try {
1915
+ const payload = JSON.parse(await readFileImpl(path, "utf8"));
1916
+ if (!payload.token || !payload.expiresAt) {
1917
+ return null;
1918
+ }
1919
+ return {
1920
+ token: payload.token,
1921
+ expiresAt: new Date(payload.expiresAt)
1922
+ };
1923
+ } catch {
1924
+ return null;
1925
+ }
1926
+ }
1927
+
1928
+ // ../tool-github-graphql/src/mcp-server.ts
1929
+ import { fileURLToPath } from "url";
1930
+ var TOOL_SCHEMA = {
1931
+ name: "github_graphql",
1932
+ description: "Execute GitHub GraphQL queries for the active workspace so the agent can mutate project and issue state directly.",
1933
+ inputSchema: {
1934
+ type: "object",
1935
+ properties: {
1936
+ query: {
1937
+ type: "string",
1938
+ description: "GraphQL query or mutation document."
1939
+ },
1940
+ variables: {
1941
+ type: "object",
1942
+ description: "Variables for the GraphQL document."
1943
+ },
1944
+ operationName: {
1945
+ type: "string",
1946
+ description: "Optional GraphQL operation name."
1947
+ }
1948
+ },
1949
+ required: ["query"],
1950
+ additionalProperties: false
1951
+ }
1952
+ };
1953
+ var lineBuffer = "";
1954
+ function resolveGitHubGraphQLMcpServerEntryPoint() {
1955
+ return fileURLToPath(new URL("./mcp-server.js", import.meta.url));
1956
+ }
1957
+ function sendResponse(id, result) {
1958
+ const msg = JSON.stringify({ jsonrpc: "2.0", id, result });
1959
+ process.stdout.write(msg + "\n");
1960
+ }
1961
+ function sendError(id, code, message) {
1962
+ const msg = JSON.stringify({
1963
+ jsonrpc: "2.0",
1964
+ id,
1965
+ error: { code, message }
1966
+ });
1967
+ process.stdout.write(msg + "\n");
1968
+ }
1969
+ async function handleRequest(msg) {
1970
+ const id = msg.id ?? null;
1971
+ switch (msg.method) {
1972
+ case "initialize": {
1973
+ sendResponse(id, {
1974
+ protocolVersion: "2024-11-05",
1975
+ capabilities: { tools: {} },
1976
+ serverInfo: {
1977
+ name: "github-symphony-graphql",
1978
+ version: "0.1.0"
1979
+ }
1980
+ });
1981
+ process.stdout.write(
1982
+ JSON.stringify({
1983
+ jsonrpc: "2.0",
1984
+ method: "notifications/initialized"
1985
+ }) + "\n"
1986
+ );
1987
+ break;
1988
+ }
1989
+ case "tools/list": {
1990
+ sendResponse(id, { tools: [TOOL_SCHEMA] });
1991
+ break;
1992
+ }
1993
+ case "tools/call": {
1994
+ const params = msg.params;
1995
+ if (params.name !== "github_graphql") {
1996
+ sendError(id, -32602, `Unknown tool: ${params.name}`);
1997
+ return;
1998
+ }
1999
+ const args = params.arguments ?? {};
2000
+ const invocation = {
2001
+ query: args.query,
2002
+ variables: args.variables,
2003
+ operationName: args.operationName
2004
+ };
2005
+ try {
2006
+ const result = await executeGitHubGraphQL(invocation, {
2007
+ token: process.env.GITHUB_GRAPHQL_TOKEN,
2008
+ apiUrl: process.env.GITHUB_GRAPHQL_API_URL,
2009
+ tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
2010
+ tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
2011
+ tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH
2012
+ });
2013
+ sendResponse(id, {
2014
+ content: [
2015
+ {
2016
+ type: "text",
2017
+ text: JSON.stringify(result, null, 2)
2018
+ }
2019
+ ]
2020
+ });
2021
+ } catch (err) {
2022
+ const message = err instanceof Error ? err.message : String(err);
2023
+ sendResponse(id, {
2024
+ content: [{ type: "text", text: message }],
2025
+ isError: true
2026
+ });
2027
+ }
2028
+ break;
2029
+ }
2030
+ case "notifications/initialized":
2031
+ case "ping": {
2032
+ if (id !== null && id !== void 0) {
2033
+ sendResponse(id, {});
2034
+ }
2035
+ break;
2036
+ }
2037
+ default: {
2038
+ if (id !== null && id !== void 0) {
2039
+ sendError(id, -32601, `Method not found: ${msg.method}`);
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+ async function main2() {
2045
+ process.stdin.setEncoding("utf8");
2046
+ process.stdin.on("data", (chunk) => {
2047
+ lineBuffer += chunk;
2048
+ const lines = lineBuffer.split("\n");
2049
+ lineBuffer = lines.pop() ?? "";
2050
+ for (const line of lines) {
2051
+ const trimmed = line.trim();
2052
+ if (!trimmed) continue;
2053
+ try {
2054
+ const msg = JSON.parse(trimmed);
2055
+ void handleRequest(msg);
2056
+ } catch (err) {
2057
+ process.stderr.write(
2058
+ `[github-graphql-mcp] parse error: ${err instanceof Error ? err.message : String(err)}
2059
+ `
2060
+ );
2061
+ }
2062
+ }
2063
+ });
2064
+ process.stdin.on("end", () => {
2065
+ process.exit(0);
2066
+ });
2067
+ }
2068
+ if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
2069
+ main2().catch((err) => {
2070
+ process.stderr.write(
2071
+ `[github-graphql-mcp] fatal: ${err instanceof Error ? err.message : String(err)}
2072
+ `
2073
+ );
2074
+ process.exitCode = 1;
2075
+ });
2076
+ }
2077
+
2078
+ // ../tool-github-graphql/src/mcp-entry.ts
2079
+ var DEFAULT_GITHUB_GRAPHQL_API_URL2 = "https://api.github.com/graphql";
2080
+ function createGitHubGraphQLMcpServerEntry(options = {}) {
2081
+ return {
2082
+ command: "node",
2083
+ args: [resolveGitHubGraphQLMcpServerEntryPoint()],
2084
+ env: {
2085
+ GITHUB_GRAPHQL_API_URL: options.githubGraphqlApiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL2,
2086
+ ...options.githubToken ? {
2087
+ GITHUB_GRAPHQL_TOKEN: options.githubToken
2088
+ } : {},
2089
+ ...options.githubTokenBrokerUrl ? {
2090
+ GITHUB_TOKEN_BROKER_URL: options.githubTokenBrokerUrl
2091
+ } : {},
2092
+ ...options.githubTokenBrokerSecret ? {
2093
+ GITHUB_TOKEN_BROKER_SECRET: options.githubTokenBrokerSecret
2094
+ } : {},
2095
+ ...options.githubTokenCachePath ? {
2096
+ GITHUB_TOKEN_CACHE_PATH: options.githubTokenCachePath
2097
+ } : {},
2098
+ ...options.githubProjectId ? {
2099
+ GITHUB_PROJECT_ID: options.githubProjectId
2100
+ } : {}
2101
+ }
2102
+ };
2103
+ }
2104
+
2105
+ // ../runtime-claude/src/mcp-compose.ts
2106
+ async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEnv = {}) {
2107
+ const workspaceMcpPath = join3(workspaceRoot, ".mcp.json");
2108
+ const finalPath = strictMode ? resolveStrictMcpConfigPath(workspaceRoot, symphonyTokenEnv) : workspaceMcpPath;
2109
+ const baseConfig = await readBaseMcpConfig(workspaceMcpPath);
2110
+ const mergedConfig = mergeGitHubGraphQLMcpServer(baseConfig, symphonyTokenEnv);
2111
+ await mkdir(dirname(finalPath), { recursive: true });
2112
+ await writeFile3(finalPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf8");
2113
+ return {
2114
+ finalPath,
2115
+ extraArgv: strictMode ? ["--strict-mcp-config", "--mcp-config", finalPath] : [],
2116
+ ...strictMode ? { cleanupPath: finalPath } : {}
2117
+ };
2118
+ }
2119
+ async function readBaseMcpConfig(workspaceMcpPath) {
2120
+ try {
2121
+ const raw = await readFile6(workspaceMcpPath, "utf8");
2122
+ const parsed = JSON.parse(raw);
2123
+ return isRecord3(parsed) ? parsed : { mcpServers: {} };
2124
+ } catch (error) {
2125
+ if (isNodeError(error) && error.code === "ENOENT") {
2126
+ return { mcpServers: {} };
2127
+ }
2128
+ throw error;
2129
+ }
2130
+ }
2131
+ function mergeGitHubGraphQLMcpServer(baseConfig, env) {
2132
+ const mcpServers = isRecord3(baseConfig.mcpServers) ? baseConfig.mcpServers : {};
2133
+ return {
2134
+ ...baseConfig,
2135
+ mcpServers: {
2136
+ ...mcpServers,
2137
+ github_graphql: createGitHubGraphQLMcpServerEntry({
2138
+ githubToken: env.GITHUB_GRAPHQL_TOKEN,
2139
+ githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
2140
+ githubTokenBrokerUrl: env.GITHUB_TOKEN_BROKER_URL,
2141
+ githubTokenBrokerSecret: env.GITHUB_TOKEN_BROKER_SECRET,
2142
+ githubTokenCachePath: env.GITHUB_TOKEN_CACHE_PATH,
2143
+ githubProjectId: env.GITHUB_PROJECT_ID
2144
+ })
2145
+ }
2146
+ };
2147
+ }
2148
+ function resolveStrictMcpConfigPath(workspaceRoot, env) {
2149
+ const normalizedWorkspaceRoot = resolve4(workspaceRoot);
2150
+ const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(normalizedWorkspaceRoot, ".runtime", basename(normalizedWorkspaceRoot));
2151
+ return join3(runtimeDir, "mcp.json");
2152
+ }
2153
+ function isRecord3(value) {
2154
+ return value != null && typeof value === "object" && !Array.isArray(value);
2155
+ }
2156
+ function isNodeError(error) {
2157
+ return error instanceof Error && "code" in error;
2158
+ }
2159
+
2160
+ // ../runtime-claude/src/spawn.ts
2161
+ import { spawn as spawn2 } from "child_process";
2162
+ import { finished } from "stream/promises";
2163
+
2164
+ // ../runtime-claude/src/internal.ts
2165
+ function asRecord(value) {
2166
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2167
+ }
2168
+ function getString(value) {
2169
+ if (typeof value === "string") {
2170
+ return value;
2171
+ }
2172
+ if (typeof value === "number") {
2173
+ return String(value);
2174
+ }
2175
+ return void 0;
2176
+ }
2177
+
2178
+ // ../runtime-claude/src/events.ts
2179
+ var CLAUDE_OBSERVABILITY_PREFIX = "claude-print/";
2180
+ function parseClaudePrintNdjsonLine(line) {
2181
+ const trimmedLine = line.trim();
2182
+ if (!trimmedLine) {
2183
+ return null;
2184
+ }
2185
+ try {
2186
+ const parsed = JSON.parse(trimmedLine);
2187
+ if (!asRecord(parsed)) {
2188
+ return {
2189
+ line: trimmedLine,
2190
+ parseError: "Claude stream-json line is not a JSON object."
2191
+ };
2192
+ }
2193
+ return {
2194
+ line: trimmedLine,
2195
+ message: parsed
2196
+ };
2197
+ } catch (error) {
2198
+ return {
2199
+ line: trimmedLine,
2200
+ parseError: error instanceof Error ? error.message : "Unknown JSON parse error."
2201
+ };
2202
+ }
2203
+ }
2204
+ var ClaudePrintEventMapper = class {
2205
+ constructor(options = {}) {
2206
+ this.options = options;
2207
+ }
2208
+ hasStartedTurn = false;
2209
+ latestResultEvent = null;
2210
+ // Claude -p stream-json does not define an ordered collection of error
2211
+ // records for one turn; keep the terminal/latest error for exit classification.
2212
+ latestErrorEvent = null;
2213
+ sawRateLimit = false;
2214
+ mapLine(line) {
2215
+ const record = parseClaudePrintNdjsonLine(line);
2216
+ if (!record?.message) {
2217
+ return [];
2218
+ }
2219
+ return this.mapMessage(record.message);
2220
+ }
2221
+ mapMessage(message) {
2222
+ const type = getEventType(message);
2223
+ const events = [];
2224
+ if (type === "message_start") {
2225
+ events.push(this.buildTurnStartedEvent(message, type));
2226
+ this.hasStartedTurn = true;
2227
+ return events;
2228
+ }
2229
+ if (type === "content_block_start" || type === "tool_use") {
2230
+ if (!this.hasStartedTurn) {
2231
+ events.push(this.buildTurnStartedEvent(message, type));
2232
+ this.hasStartedTurn = true;
2233
+ }
2234
+ const toolUseEvent = mapToolUseEvent(message, this.options);
2235
+ if (toolUseEvent) {
2236
+ events.push(toolUseEvent);
2237
+ }
2238
+ }
2239
+ if (type === "content_block_delta") {
2240
+ if (!this.hasStartedTurn) {
2241
+ events.push(this.buildTurnStartedEvent(message, type));
2242
+ this.hasStartedTurn = true;
2243
+ }
2244
+ events.push({
2245
+ name: "agent.messageDelta",
2246
+ payload: {
2247
+ observabilityEvent: observabilityEventName(type),
2248
+ params: message,
2249
+ delta: extractDeltaText(message),
2250
+ itemId: extractItemId(message)
2251
+ }
2252
+ });
2253
+ }
2254
+ if (type === "result") {
2255
+ this.latestResultEvent = message;
2256
+ const rateLimit = extractRateLimit(message);
2257
+ if (rateLimit) {
2258
+ this.sawRateLimit = true;
2259
+ events.push({
2260
+ name: "agent.rateLimit",
2261
+ payload: {
2262
+ observabilityEvent: observabilityEventName(type),
2263
+ params: {
2264
+ source: "claude",
2265
+ rate_limit: rateLimit,
2266
+ usage: asRecord(message.usage),
2267
+ result: message
2268
+ }
2269
+ }
2270
+ });
2271
+ }
2272
+ if (isClaudeResultError(message)) {
2273
+ events.push(buildClaudeErrorEvent(message, type));
2274
+ } else {
2275
+ events.push({
2276
+ name: "agent.turnCompleted",
2277
+ payload: {
2278
+ observabilityEvent: observabilityEventName(type),
2279
+ params: message,
2280
+ inputRequired: false
2281
+ }
2282
+ });
2283
+ }
2284
+ }
2285
+ if (type === "error") {
2286
+ this.latestErrorEvent = message;
2287
+ events.push(buildClaudeErrorEvent(message, type));
2288
+ }
2289
+ return events;
2290
+ }
2291
+ snapshot() {
2292
+ return {
2293
+ hasStartedTurn: this.hasStartedTurn,
2294
+ latestResultEvent: this.latestResultEvent,
2295
+ latestErrorEvent: this.latestErrorEvent,
2296
+ sawRateLimit: this.sawRateLimit
2297
+ };
2298
+ }
2299
+ buildTurnStartedEvent(message, type) {
2300
+ return {
2301
+ name: "agent.turnStarted",
2302
+ payload: {
2303
+ observabilityEvent: observabilityEventName(type),
2304
+ params: message
2305
+ }
2306
+ };
2307
+ }
2308
+ };
2309
+ function isClaudeResultError(message) {
2310
+ const subtype = getString(message.subtype);
2311
+ const stopReason = getString(message.stop_reason);
2312
+ return message.is_error === true || subtype !== void 0 && subtype.startsWith("error") || stopReason !== void 0 && stopReason.startsWith("error");
2313
+ }
2314
+ function extractRateLimit(message) {
2315
+ const usage = asRecord(message.usage);
2316
+ const rateLimit = usage ? asRecord(usage.rate_limit) : null;
2317
+ if (rateLimit) {
2318
+ return rateLimit;
2319
+ }
2320
+ return asRecord(message.rate_limit);
2321
+ }
2322
+ function getClaudeResultStatus(message) {
2323
+ if (!message) {
2324
+ return void 0;
2325
+ }
2326
+ return getString(message.subtype) ?? getString(message.stop_reason);
2327
+ }
2328
+ function mapToolUseEvent(message, options) {
2329
+ const type = getEventType(message);
2330
+ const contentBlock = asRecord(message.content_block);
2331
+ const toolUse = type === "tool_use" ? message : contentBlock && getString(contentBlock.type) === "tool_use" ? contentBlock : null;
2332
+ if (!toolUse) {
2333
+ return null;
2334
+ }
2335
+ const input = toolUse.input !== void 0 ? toolUse.input : toolUse.arguments;
2336
+ return {
2337
+ name: "agent.toolCallRequested",
2338
+ payload: {
2339
+ observabilityEvent: observabilityEventName(type),
2340
+ params: message,
2341
+ callId: getString(toolUse.id) ?? "",
2342
+ toolName: getString(toolUse.name) ?? "",
2343
+ threadId: options.threadId ?? getString(message.thread_id),
2344
+ turnId: options.turnId ?? getString(message.turn_id),
2345
+ arguments: input
2346
+ }
2347
+ };
2348
+ }
2349
+ function buildClaudeErrorEvent(message, type) {
2350
+ return {
2351
+ name: "agent.error",
2352
+ payload: {
2353
+ observabilityEvent: observabilityEventName(type),
2354
+ params: message,
2355
+ error: describeClaudeError(message)
2356
+ }
2357
+ };
2358
+ }
2359
+ function describeClaudeError(message) {
2360
+ const error = asRecord(message.error);
2361
+ return getString(error?.message) ?? getString(error?.type) ?? getString(message.message) ?? getString(message.subtype) ?? getString(message.stop_reason) ?? JSON.stringify(message);
2362
+ }
2363
+ function extractDeltaText(message) {
2364
+ const delta = asRecord(message.delta);
2365
+ return getString(delta?.text) ?? getString(delta?.partial_json) ?? getString(message.text) ?? "";
2366
+ }
2367
+ function extractItemId(message) {
2368
+ return getString(message.item_id) ?? getString(message.content_block_id) ?? getString(message.index) ?? "";
2369
+ }
2370
+ function getEventType(message) {
2371
+ return getString(message.type) ?? "";
2372
+ }
2373
+ function observabilityEventName(type) {
2374
+ return `${CLAUDE_OBSERVABILITY_PREFIX}${type || "unknown"}`;
2375
+ }
2376
+
2377
+ // ../runtime-claude/src/exit-classifier.ts
2378
+ var TRANSIENT_ERROR_PATTERNS = [
2379
+ /rate.?limit/i,
2380
+ /\b429\b/,
2381
+ /timeout/i,
2382
+ /timed?.?out/i,
2383
+ /temporar/i,
2384
+ /overload/i,
2385
+ /unavailable/i,
2386
+ /ECONNRESET/,
2387
+ /ETIMEDOUT/,
2388
+ /EAI_AGAIN/
2389
+ ];
2390
+ function classifyClaudeTurnExit(input) {
2391
+ const resultStatus = getClaudeResultStatus(input.resultEvent);
2392
+ if (input.exitCode === 0 && input.resultEvent && !isClaudeResultError(input.resultEvent)) {
2393
+ return {
2394
+ kind: "success",
2395
+ transient: false,
2396
+ reason: "result_success",
2397
+ resultStatus
2398
+ };
2399
+ }
2400
+ if (input.exitCode === 0 && !input.resultEvent) {
2401
+ return {
2402
+ kind: "app-error",
2403
+ transient: false,
2404
+ reason: "missing_result",
2405
+ resultStatus
2406
+ };
2407
+ }
2408
+ if (input.exitCode === 0 && input.resultEvent && isClaudeResultError(input.resultEvent)) {
2409
+ return {
2410
+ kind: "app-error",
2411
+ transient: isTransientClaudeFailure(input),
2412
+ reason: resultStatus ?? "result_error",
2413
+ resultStatus
2414
+ };
2415
+ }
2416
+ return {
2417
+ kind: "process-error",
2418
+ transient: isTransientClaudeFailure(input),
2419
+ reason: describeProcessFailure(input),
2420
+ resultStatus
2421
+ };
2422
+ }
2423
+ function isTransientClaudeFailure(input) {
2424
+ if (input.sawRateLimit || input.resultEvent && (extractRateLimit(input.resultEvent) !== null || getClaudeResultStatus(input.resultEvent) === "error_rate_limit")) {
2425
+ return true;
2426
+ }
2427
+ if (input.signal === "SIGTERM") {
2428
+ return true;
2429
+ }
2430
+ const text = [
2431
+ input.spawnErrorMessage,
2432
+ extractFailureMessage(input.errorEvent),
2433
+ extractFailureMessage(input.resultEvent)
2434
+ ].join("\n");
2435
+ return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(text));
2436
+ }
2437
+ function describeProcessFailure(input) {
2438
+ if (input.signal) {
2439
+ return `signal_${input.signal}`;
2440
+ }
2441
+ if (input.spawnErrorMessage) {
2442
+ return input.spawnErrorMessage;
2443
+ }
2444
+ if (typeof input.exitCode === "number") {
2445
+ return `exit_${input.exitCode}`;
2446
+ }
2447
+ return "process_error";
2448
+ }
2449
+ function extractFailureMessage(event) {
2450
+ if (!event) {
2451
+ return "";
2452
+ }
2453
+ const error = asRecord(event.error);
2454
+ return [
2455
+ getString(error?.message),
2456
+ getString(error?.type),
2457
+ getString(event.message)
2458
+ ].filter((value) => value !== void 0).join("\n");
2459
+ }
2460
+
2461
+ // ../runtime-claude/src/spawn.ts
2462
+ async function spawnClaudeTurn(input, dependencies = {}) {
2463
+ const command = input.command ?? "claude";
2464
+ const child = (dependencies.spawnImpl ?? spawn2)(command, input.args, {
2465
+ cwd: input.cwd,
2466
+ env: input.env,
2467
+ stdio: "pipe"
2468
+ });
2469
+ dependencies.onSpawned?.(child);
2470
+ const records = [];
2471
+ const eventMapper = new ClaudePrintEventMapper();
2472
+ let emittedErrorEvent = false;
2473
+ const emitEvent = (event) => {
2474
+ if (event.name === "agent.error") {
2475
+ emittedErrorEvent = true;
2476
+ }
2477
+ dependencies.onEvent?.(event);
2478
+ };
2479
+ const stdoutDone = collectNdjsonStream(
2480
+ child.stdout,
2481
+ "stdout",
2482
+ records,
2483
+ eventMapper,
2484
+ emitEvent
2485
+ );
2486
+ const stderrDone = collectNdjsonStream(
2487
+ child.stderr,
2488
+ "stderr",
2489
+ records,
2490
+ null,
2491
+ null
2492
+ );
2493
+ const exitDone = waitForChildExit(child, records);
2494
+ const stdinMessages = Array.isArray(input.stdinMessages) ? input.stdinMessages : [input.stdinMessages];
2495
+ for (const message of stdinMessages) {
2496
+ const didWrite = await writeToStdin(
2497
+ child.stdin,
2498
+ `${JSON.stringify(message)}
2499
+ `
2500
+ );
2501
+ if (!didWrite) {
2502
+ break;
2503
+ }
2504
+ }
2505
+ if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded && !child.stdin.writableFinished) {
2506
+ child.stdin.end();
2507
+ }
2508
+ const outcome = await exitDone;
2509
+ await Promise.all([stdoutDone, stderrDone]);
2510
+ const mapperState = eventMapper.snapshot();
2511
+ const classification = classifyClaudeTurnExit({
2512
+ exitCode: outcome.exitCode,
2513
+ signal: outcome.signal,
2514
+ resultEvent: mapperState.latestResultEvent,
2515
+ errorEvent: mapperState.latestErrorEvent,
2516
+ sawRateLimit: mapperState.sawRateLimit,
2517
+ spawnErrorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2518
+ });
2519
+ if ((classification.kind === "app-error" || classification.kind === "process-error") && !emittedErrorEvent) {
2520
+ emitEvent({
2521
+ name: "agent.error",
2522
+ payload: {
2523
+ observabilityEvent: classification.kind === "app-error" ? "claude-print/app-error" : "claude-print/process-exit",
2524
+ params: {
2525
+ exitCode: outcome.exitCode,
2526
+ signal: outcome.signal,
2527
+ classification,
2528
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2529
+ },
2530
+ error: classification.reason
2531
+ }
2532
+ });
2533
+ }
2534
+ return {
2535
+ command,
2536
+ args: [...input.args],
2537
+ cwd: input.cwd,
2538
+ records,
2539
+ exitCode: outcome.exitCode,
2540
+ signal: outcome.signal,
2541
+ result: classification.kind,
2542
+ classification,
2543
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2544
+ };
2545
+ }
2546
+ async function collectNdjsonStream(stream, channel, records, eventMapper, onEvent) {
2547
+ if (!stream) {
2548
+ return;
2549
+ }
2550
+ let buffer = "";
2551
+ stream.setEncoding("utf8");
2552
+ stream.on("data", (chunk) => {
2553
+ buffer += chunk;
2554
+ while (true) {
2555
+ const newlineIndex = buffer.indexOf("\n");
2556
+ if (newlineIndex === -1) {
2557
+ break;
2558
+ }
2559
+ const line = buffer.slice(0, newlineIndex).trim();
2560
+ buffer = buffer.slice(newlineIndex + 1);
2561
+ if (line.length === 0) {
2562
+ continue;
2563
+ }
2564
+ records.push(parseClaudeRecord(channel, line, eventMapper, onEvent));
2565
+ }
2566
+ });
2567
+ try {
2568
+ await finished(stream);
2569
+ } catch (error) {
2570
+ records.push({
2571
+ stream: channel,
2572
+ line: "",
2573
+ parseError: error instanceof Error ? error.message : "Unknown stream error."
2574
+ });
2575
+ }
2576
+ const trailingLine = buffer.trim();
2577
+ if (trailingLine.length > 0) {
2578
+ records.push(
2579
+ parseClaudeRecord(channel, trailingLine, eventMapper, onEvent)
2580
+ );
2581
+ }
2582
+ }
2583
+ function parseClaudeRecord(stream, line, eventMapper, onEvent) {
2584
+ const record = parseClaudePrintNdjsonLine(line);
2585
+ if (record.message) {
2586
+ if (!eventMapper) {
2587
+ return {
2588
+ stream,
2589
+ line: record.line,
2590
+ message: record.message
2591
+ };
2592
+ }
2593
+ for (const event of eventMapper.mapMessage(record.message)) {
2594
+ onEvent?.(event);
2595
+ }
2596
+ return {
2597
+ stream,
2598
+ line: record.line,
2599
+ message: record.message
2600
+ };
2601
+ }
2602
+ return {
2603
+ stream,
2604
+ line: record.line,
2605
+ parseError: record.parseError
2606
+ };
2607
+ }
2608
+ async function writeToStdin(stream, line) {
2609
+ if (!stream || stream.destroyed || stream.writableEnded) {
2610
+ return false;
2611
+ }
2612
+ if (stream.write(line)) {
2613
+ return true;
2614
+ }
2615
+ return waitForDrainOrClosure(stream);
2616
+ }
2617
+ function waitForDrainOrClosure(stream) {
2618
+ return new Promise((resolve5) => {
2619
+ const cleanup = () => {
2620
+ stream.removeListener("drain", handleDrain);
2621
+ stream.removeListener("close", handleClose);
2622
+ stream.removeListener("finish", handleFinish);
2623
+ stream.removeListener("error", handleError);
2624
+ };
2625
+ const handleDrain = () => {
2626
+ cleanup();
2627
+ resolve5(true);
2628
+ };
2629
+ const handleClose = () => {
2630
+ cleanup();
2631
+ resolve5(false);
2632
+ };
2633
+ const handleFinish = () => {
2634
+ cleanup();
2635
+ resolve5(false);
2636
+ };
2637
+ const handleError = () => {
2638
+ cleanup();
2639
+ resolve5(false);
2640
+ };
2641
+ stream.once("drain", handleDrain);
2642
+ stream.once("close", handleClose);
2643
+ stream.once("finish", handleFinish);
2644
+ stream.once("error", handleError);
2645
+ });
2646
+ }
2647
+ function waitForChildExit(child, records) {
2648
+ return new Promise((resolve5) => {
2649
+ const handleClose = (exitCode, signal) => {
2650
+ cleanup();
2651
+ resolve5({ exitCode, signal });
2652
+ };
2653
+ const handleError = (error) => {
2654
+ cleanup();
2655
+ records.push({
2656
+ stream: "stderr",
2657
+ line: "",
2658
+ parseError: error.message
2659
+ });
2660
+ resolve5({
2661
+ exitCode: null,
2662
+ signal: null,
2663
+ errorMessage: error.message
2664
+ });
2665
+ };
2666
+ const cleanup = () => {
2667
+ child.removeListener("close", handleClose);
2668
+ child.removeListener("error", handleError);
2669
+ };
2670
+ child.on("close", handleClose);
2671
+ child.on("error", handleError);
2672
+ });
2673
+ }
2674
+
2675
+ // ../runtime-claude/src/session-store.ts
2676
+ import { mkdir as mkdir2, readFile as readFile7, rename, writeFile as writeFile4 } from "fs/promises";
2677
+ import { dirname as dirname2, join as join4 } from "path";
2678
+ var CLAUDE_SESSION_PROTOCOL = "claude-print";
2679
+ var CLAUDE_SESSION_FILENAME = "claude-session.json";
2680
+ var ClaudeSessionStore = class {
2681
+ constructor(options) {
2682
+ this.options = options;
2683
+ }
2684
+ sessionFilePath(options) {
2685
+ return join4(
2686
+ options.runDirectory ?? this.runDirectory(options.runId),
2687
+ CLAUDE_SESSION_FILENAME
2688
+ );
2689
+ }
2690
+ async load(options) {
2691
+ let raw;
2692
+ try {
2693
+ raw = await readFile7(this.sessionFilePath(options), "utf8");
2694
+ } catch (error) {
2695
+ if (isFileNotFoundError(error)) {
2696
+ return null;
2697
+ }
2698
+ throw error;
2699
+ }
2700
+ return parseClaudeSessionFile(JSON.parse(raw));
2701
+ }
2702
+ async save(options) {
2703
+ const session = {
2704
+ protocol: CLAUDE_SESSION_PROTOCOL,
2705
+ sessionId: options.sessionId,
2706
+ createdAt: options.createdAt,
2707
+ protocolState: options.protocolState ?? {}
2708
+ };
2709
+ if (options.parentRunId) {
2710
+ session.parentRunId = options.parentRunId;
2711
+ }
2712
+ const path = this.sessionFilePath(options);
2713
+ await mkdir2(dirname2(path), { recursive: true });
2714
+ await writeFile4(`${path}.tmp`, `${JSON.stringify(session, null, 2)}
2715
+ `, "utf8");
2716
+ await rename(`${path}.tmp`, path);
2717
+ return session;
2718
+ }
2719
+ runDirectory(runId) {
2720
+ return join4(this.options.runtimeRoot, "runs", runId);
2721
+ }
2722
+ };
2723
+ function parseClaudeSessionFile(value) {
2724
+ if (!isRecord4(value)) {
2725
+ throw new Error("Claude session file must be a JSON object.");
2726
+ }
2727
+ if (value.protocol !== CLAUDE_SESSION_PROTOCOL) {
2728
+ throw new Error(
2729
+ `Claude session file protocol must be ${CLAUDE_SESSION_PROTOCOL}.`
2730
+ );
2731
+ }
2732
+ if (typeof value.sessionId !== "string" || value.sessionId.length === 0) {
2733
+ throw new Error("Claude session file sessionId must be a non-empty string.");
2734
+ }
2735
+ if (typeof value.createdAt !== "string" || value.createdAt.length === 0) {
2736
+ throw new Error("Claude session file createdAt must be a non-empty string.");
2737
+ }
2738
+ if ("parentRunId" in value && value.parentRunId !== void 0 && typeof value.parentRunId !== "string") {
2739
+ throw new Error("Claude session file parentRunId must be a string.");
2740
+ }
2741
+ if ("protocolState" in value && value.protocolState !== void 0 && !isRecord4(value.protocolState)) {
2742
+ throw new Error("Claude session file protocolState must be an object.");
2743
+ }
2744
+ return {
2745
+ protocol: CLAUDE_SESSION_PROTOCOL,
2746
+ sessionId: value.sessionId,
2747
+ createdAt: value.createdAt,
2748
+ parentRunId: typeof value.parentRunId === "string" ? value.parentRunId : void 0,
2749
+ protocolState: isRecord4(value.protocolState) ? value.protocolState : {}
2750
+ };
2751
+ }
2752
+ function isRecord4(value) {
2753
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2754
+ }
2755
+ function isFileNotFoundError(error) {
2756
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
2757
+ }
2758
+
2759
+ // ../runtime-claude/src/adapter.ts
2760
+ var ClaudePrintRuntimeAdapter = class {
2761
+ constructor(config, dependencies = {}) {
2762
+ this.config = config;
2763
+ this.dependencies = dependencies;
2764
+ this.sessionStore = new ClaudeSessionStore({
2765
+ runtimeRoot: config.runtimeRoot ?? join5(config.workingDirectory, ".runtime", "orchestrator")
2766
+ });
2767
+ }
2768
+ activeChild = null;
2769
+ preparedMcpConfig = null;
2770
+ preparedSession = null;
2771
+ eventHandlers = /* @__PURE__ */ new Set();
2772
+ pendingEvents = [];
2773
+ sessionStore;
2774
+ async prepare(context) {
2775
+ await this.cleanupPreparedMcpConfig();
2776
+ this.pendingEvents.length = 0;
2777
+ this.preparedSession = await this.prepareSession(context);
2778
+ this.preparedMcpConfig = await composeClaudeMcpConfig(
2779
+ this.config.workingDirectory,
2780
+ this.config.isolation?.strictMcpConfig === true,
2781
+ buildClaudeMcpTokenEnvironment({
2782
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
2783
+ configEnv: this.config.env,
2784
+ runtimeDirectory: this.config.runtimeDirectory
2785
+ })
2786
+ );
2787
+ }
2788
+ async spawnTurn(input) {
2789
+ if (this.activeChild) {
2790
+ throw new Error(
2791
+ "TODO(#8): Claude print runtime adapter supports only one in-flight turn."
2792
+ );
2793
+ }
2794
+ const session = input.session ?? this.preparedSession?.session;
2795
+ const argv = buildClaudePrintArgv(this.buildArgvOptions(input, session));
2796
+ try {
2797
+ const result = await this.spawnWithArgv(input, argv);
2798
+ if (this.shouldInvalidatePreparedResume(session, result)) {
2799
+ return await this.retryWithFreshSession(input, result);
2800
+ }
2801
+ await this.persistStartedSessionId(result);
2802
+ await this.persistForkedSessionId(result);
2803
+ return result;
2804
+ } finally {
2805
+ this.activeChild = null;
2806
+ }
2807
+ }
2808
+ onEvent(handler) {
2809
+ this.eventHandlers.add(handler);
2810
+ for (const event of this.pendingEvents) {
2811
+ handler(event);
2812
+ }
2813
+ return () => {
2814
+ this.eventHandlers.delete(handler);
2815
+ };
2816
+ }
2817
+ resolveCredentials(brokerResponse) {
2818
+ return extractEnvForClaude(brokerResponse.env, this.config.authEnvKey);
2819
+ }
2820
+ async shutdown() {
2821
+ this.stopActiveChild();
2822
+ await this.cleanupPreparedMcpConfig();
2823
+ }
2824
+ async cancel(_reason) {
2825
+ this.stopActiveChild();
2826
+ await this.cleanupPreparedMcpConfig();
2827
+ }
2828
+ buildArgvOptions(input, session) {
2829
+ const isolation = {
2830
+ ...this.config.isolation,
2831
+ ...input.isolation
2832
+ };
2833
+ const configuredExtraArgs = input.extraArgs ?? this.config.extraArgs ?? [];
2834
+ if (this.preparedMcpConfig) {
2835
+ return {
2836
+ baseArgs: this.config.args,
2837
+ session,
2838
+ // prepare() owns MCP argv injection through extraArgv; suppress the
2839
+ // isolation flag here so buildClaudePrintArgv does not add it twice.
2840
+ // Any input mcpConfigPath is intentionally ignored while a prepared
2841
+ // composition result is active.
2842
+ isolation: {
2843
+ ...isolation,
2844
+ strictMcpConfig: false,
2845
+ mcpConfigPath: void 0
2846
+ },
2847
+ extraArgs: [
2848
+ ...this.preparedMcpConfig.extraArgv,
2849
+ ...configuredExtraArgs
2850
+ ]
2851
+ };
2852
+ }
2853
+ if (isolation.strictMcpConfig && !isolation.mcpConfigPath) {
2854
+ throw new Error(
2855
+ "Claude strict MCP config requires prepare() or an explicit mcpConfigPath."
2856
+ );
2857
+ }
2858
+ return {
2859
+ baseArgs: this.config.args,
2860
+ session,
2861
+ isolation,
2862
+ extraArgs: configuredExtraArgs
2863
+ };
2864
+ }
2865
+ async prepareSession(context) {
2866
+ const currentOptions = {
2867
+ runId: context.runId,
2868
+ runDirectory: context.runDirectory
2869
+ };
2870
+ const parentRunId = context.previousRunId;
2871
+ try {
2872
+ const current = await this.sessionStore.load(currentOptions);
2873
+ if (current) {
2874
+ return {
2875
+ runId: context.runId,
2876
+ runDirectory: context.runDirectory,
2877
+ sessionFile: current,
2878
+ session: {
2879
+ mode: "resume",
2880
+ sessionId: current.sessionId
2881
+ }
2882
+ };
2883
+ }
2884
+ } catch (error) {
2885
+ return await this.createFreshSession(context, {
2886
+ reason: `session file could not be read or parsed: ${formatErrorMessage(error)}`,
2887
+ invalidatedSessionId: "unknown",
2888
+ parentRunId
2889
+ });
2890
+ }
2891
+ if (context.previousRunId) {
2892
+ try {
2893
+ const previous = await this.sessionStore.load({
2894
+ runId: context.previousRunId,
2895
+ runDirectory: context.previousRunDirectory
2896
+ });
2897
+ if (previous) {
2898
+ const sessionFile = await this.sessionStore.save({
2899
+ ...currentOptions,
2900
+ sessionId: previous.sessionId,
2901
+ createdAt: this.nowIso(),
2902
+ parentRunId: context.previousRunId
2903
+ });
2904
+ return {
2905
+ runId: context.runId,
2906
+ runDirectory: context.runDirectory,
2907
+ sessionFile,
2908
+ session: {
2909
+ mode: "resume",
2910
+ sessionId: previous.sessionId,
2911
+ forkSession: true
2912
+ }
2913
+ };
2914
+ }
2915
+ } catch (error) {
2916
+ return await this.createFreshSession(context, {
2917
+ reason: `parent session file could not be read or parsed: ${formatErrorMessage(error)}`,
2918
+ invalidatedSessionId: "unknown",
2919
+ parentRunId
2920
+ });
2921
+ }
2922
+ }
2923
+ return await this.createFreshSession(context, { parentRunId });
2924
+ }
2925
+ async createFreshSession(context, options = {}) {
2926
+ const replacementSessionId = this.createSessionId();
2927
+ const sessionFile = await this.sessionStore.save({
2928
+ runId: context.runId,
2929
+ runDirectory: context.runDirectory,
2930
+ sessionId: replacementSessionId,
2931
+ createdAt: this.nowIso(),
2932
+ parentRunId: options.parentRunId
2933
+ });
2934
+ if (options.reason) {
2935
+ this.emitSessionInvalidated({
2936
+ runId: context.runId,
2937
+ sessionId: options.invalidatedSessionId ?? "unknown",
2938
+ replacementSessionId,
2939
+ reason: options.reason
2940
+ });
2941
+ }
2942
+ return {
2943
+ runId: context.runId,
2944
+ runDirectory: context.runDirectory,
2945
+ sessionFile,
2946
+ session: {
2947
+ mode: "start",
2948
+ sessionId: replacementSessionId
2949
+ }
2950
+ };
2951
+ }
2952
+ async retryWithFreshSession(input, failedResult) {
2953
+ if (!this.preparedSession) {
2954
+ return failedResult;
2955
+ }
2956
+ const invalidatedSessionId = this.preparedSession.session.sessionId;
2957
+ const replacementSessionId = this.createSessionId();
2958
+ const parentRunId = this.preparedSession.sessionFile.parentRunId;
2959
+ const sessionFile = await this.sessionStore.save({
2960
+ runId: this.preparedSession.runId,
2961
+ runDirectory: this.preparedSession.runDirectory,
2962
+ sessionId: replacementSessionId,
2963
+ createdAt: this.nowIso(),
2964
+ parentRunId
2965
+ });
2966
+ this.preparedSession = {
2967
+ ...this.preparedSession,
2968
+ sessionFile,
2969
+ session: {
2970
+ mode: "start",
2971
+ sessionId: replacementSessionId
2972
+ }
2973
+ };
2974
+ this.emitSessionInvalidated({
2975
+ runId: this.preparedSession.runId,
2976
+ sessionId: invalidatedSessionId,
2977
+ replacementSessionId,
2978
+ reason: "claude resume session was rejected with a 4xx response"
2979
+ });
2980
+ const retryArgv = buildClaudePrintArgv(
2981
+ this.buildArgvOptions(input, this.preparedSession.session)
2982
+ );
2983
+ const retryResult = await this.spawnWithArgv(input, retryArgv);
2984
+ await this.persistStartedSessionId(retryResult);
2985
+ return retryResult;
2986
+ }
2987
+ async spawnWithArgv(input, argv) {
2988
+ return await spawnClaudeTurn(
2989
+ {
2990
+ command: input.command ?? this.config.command,
2991
+ args: argv,
2992
+ cwd: input.cwd ?? this.config.workingDirectory,
2993
+ env: buildClaudeSpawnEnv({
2994
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
2995
+ configEnv: this.config.env,
2996
+ inputEnv: input.env
2997
+ }),
2998
+ stdinMessages: input.messages
2999
+ },
3000
+ {
3001
+ ...this.dependencies,
3002
+ onSpawned: (child) => {
3003
+ this.activeChild = child;
3004
+ this.dependencies.onSpawned?.(child);
3005
+ },
3006
+ onEvent: (event) => {
3007
+ this.emitEvent(event);
3008
+ try {
3009
+ this.dependencies.onEvent?.(event);
3010
+ } catch {
3011
+ }
3012
+ }
3013
+ }
3014
+ );
3015
+ }
3016
+ async persistForkedSessionId(result) {
3017
+ if (this.preparedSession?.session.mode !== "resume" || !this.preparedSession.session.forkSession) {
3018
+ return;
3019
+ }
3020
+ const forkedSessionId = findSessionIdInResult(result);
3021
+ const sessionId = forkedSessionId ?? this.preparedSession.session.sessionId;
3022
+ this.preparedSession = {
3023
+ ...this.preparedSession,
3024
+ sessionFile: await this.sessionStore.save({
3025
+ runId: this.preparedSession.runId,
3026
+ runDirectory: this.preparedSession.runDirectory,
3027
+ sessionId,
3028
+ createdAt: this.preparedSession.sessionFile.createdAt,
3029
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3030
+ protocolState: this.preparedSession.sessionFile.protocolState
3031
+ }),
3032
+ session: {
3033
+ mode: "resume",
3034
+ sessionId
3035
+ }
3036
+ };
3037
+ }
3038
+ async persistStartedSessionId(result) {
3039
+ if (this.preparedSession?.session.mode !== "start") {
3040
+ return;
3041
+ }
3042
+ if (result.result !== "success") {
3043
+ return;
3044
+ }
3045
+ const sessionId = findSessionIdInResult(result) ?? this.preparedSession.session.sessionId;
3046
+ this.preparedSession = {
3047
+ ...this.preparedSession,
3048
+ sessionFile: await this.sessionStore.save({
3049
+ runId: this.preparedSession.runId,
3050
+ runDirectory: this.preparedSession.runDirectory,
3051
+ sessionId,
3052
+ createdAt: this.preparedSession.sessionFile.createdAt,
3053
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3054
+ protocolState: this.preparedSession.sessionFile.protocolState
3055
+ }),
3056
+ session: {
3057
+ mode: "resume",
3058
+ sessionId
3059
+ }
3060
+ };
3061
+ }
3062
+ shouldInvalidatePreparedResume(session, result) {
3063
+ return session === this.preparedSession?.session && session?.mode === "resume" && isResumeRejectedWith4xx(result);
3064
+ }
3065
+ emitSessionInvalidated(payload) {
3066
+ const event = {
3067
+ name: "agent.sessionInvalidated",
3068
+ payload: {
3069
+ params: {},
3070
+ ...payload,
3071
+ observabilityEvent: "session_invalidated"
3072
+ }
3073
+ };
3074
+ if (this.eventHandlers.size === 0) {
3075
+ this.pendingEvents.push(event);
3076
+ } else {
3077
+ for (const handler of this.eventHandlers) {
3078
+ handler(event);
3079
+ }
3080
+ }
3081
+ }
3082
+ createSessionId() {
3083
+ return this.dependencies.createSessionId?.() ?? randomUUID();
3084
+ }
3085
+ nowIso() {
3086
+ return (this.dependencies.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
3087
+ }
3088
+ stopActiveChild() {
3089
+ if (!this.activeChild || this.activeChild.killed) {
3090
+ this.activeChild = null;
3091
+ return;
3092
+ }
3093
+ this.activeChild.kill("SIGTERM");
3094
+ this.activeChild = null;
3095
+ }
3096
+ async cleanupPreparedMcpConfig() {
3097
+ const cleanupPath = this.preparedMcpConfig?.cleanupPath;
3098
+ this.preparedMcpConfig = null;
3099
+ if (!cleanupPath) {
3100
+ return;
3101
+ }
3102
+ await rm(cleanupPath, { force: true });
3103
+ }
3104
+ emitEvent(event) {
3105
+ for (const handler of this.eventHandlers) {
3106
+ try {
3107
+ handler(event);
3108
+ } catch {
3109
+ }
3110
+ }
3111
+ }
3112
+ };
3113
+ function createClaudePrintRuntimeAdapter(config, dependencies = {}) {
3114
+ return new ClaudePrintRuntimeAdapter(config, dependencies);
3115
+ }
3116
+ var DEFAULT_INHERITED_ENV_KEYS = [
3117
+ "HOME",
3118
+ "LANG",
3119
+ "PATH",
3120
+ "SHELL",
3121
+ "SYSTEMROOT",
3122
+ "TEMP",
3123
+ "TERM",
3124
+ "TMP",
3125
+ "TMPDIR",
3126
+ "USER",
3127
+ "USERPROFILE"
3128
+ ];
3129
+ function buildClaudeSpawnEnv(options) {
3130
+ if (options.inheritProcessEnv) {
3131
+ return {
3132
+ ...process.env,
3133
+ ...options.configEnv,
3134
+ ...options.inputEnv
3135
+ };
3136
+ }
3137
+ const env = {};
3138
+ for (const key of DEFAULT_INHERITED_ENV_KEYS) {
3139
+ const value = process.env[key];
3140
+ if (value !== void 0) {
3141
+ env[key] = value;
3142
+ }
3143
+ }
3144
+ Object.assign(env, options.configEnv, options.inputEnv);
3145
+ return env;
3146
+ }
3147
+ function findSessionIdInResult(result) {
3148
+ for (const record of result.records) {
3149
+ const sessionId = findSessionId(record.message);
3150
+ if (sessionId) {
3151
+ return sessionId;
3152
+ }
3153
+ }
3154
+ return null;
3155
+ }
3156
+ function findSessionId(value, depth = 0) {
3157
+ if (depth > 5) {
3158
+ return null;
3159
+ }
3160
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3161
+ return null;
3162
+ }
3163
+ const record = value;
3164
+ if (typeof record.sessionId === "string") {
3165
+ return record.sessionId;
3166
+ }
3167
+ if (typeof record.session_id === "string") {
3168
+ return record.session_id;
3169
+ }
3170
+ for (const nested of Object.values(record)) {
3171
+ const sessionId = findSessionId(nested, depth + 1);
3172
+ if (sessionId) {
3173
+ return sessionId;
3174
+ }
3175
+ }
3176
+ return null;
3177
+ }
3178
+ function isResumeRejectedWith4xx(result) {
3179
+ if (result.result !== "process-error") {
3180
+ return false;
3181
+ }
3182
+ return result.records.some((record) => {
3183
+ const text = record.line.toLowerCase();
3184
+ return text.includes("resume") && /\b4\d\d\b/.test(text);
3185
+ });
3186
+ }
3187
+ function formatErrorMessage(error) {
3188
+ if (error instanceof Error) {
3189
+ return error.message;
3190
+ }
3191
+ return String(error);
3192
+ }
3193
+ function buildClaudeMcpTokenEnvironment(options) {
3194
+ const source = options.inheritProcessEnv ? {
3195
+ ...process.env,
3196
+ ...options.configEnv
3197
+ } : {
3198
+ ...options.configEnv
3199
+ };
3200
+ return {
3201
+ GITHUB_GRAPHQL_TOKEN: source.GITHUB_GRAPHQL_TOKEN,
3202
+ GITHUB_GRAPHQL_API_URL: source.GITHUB_GRAPHQL_API_URL,
3203
+ GITHUB_TOKEN_BROKER_URL: source.GITHUB_TOKEN_BROKER_URL,
3204
+ GITHUB_TOKEN_BROKER_SECRET: source.GITHUB_TOKEN_BROKER_SECRET,
3205
+ GITHUB_TOKEN_CACHE_PATH: source.GITHUB_TOKEN_CACHE_PATH,
3206
+ GITHUB_PROJECT_ID: source.GITHUB_PROJECT_ID,
3207
+ WORKSPACE_RUNTIME_DIR: options.runtimeDirectory ?? source.WORKSPACE_RUNTIME_DIR
3208
+ };
3209
+ }
3210
+
3211
+ export {
3212
+ isOrchestratorChannelEvent,
3213
+ DEFAULT_WORKFLOW_LIFECYCLE,
3214
+ isStateActive,
3215
+ isStateTerminal,
3216
+ matchesWorkflowState,
3217
+ DEFAULT_MAX_FAILURE_RETRIES,
3218
+ resolveWorkflowRuntimeCommand,
3219
+ resolveWorkflowRuntimeTimeouts,
3220
+ parseWorkflowMarkdown,
3221
+ WorkflowConfigStore,
3222
+ createDefaultWorkflowResolution,
3223
+ createInvalidWorkflowResolution,
3224
+ buildPromptVariables,
3225
+ renderPrompt,
3226
+ classifySessionExit,
3227
+ scheduleRetryAt,
3228
+ extractEnvForCodex,
3229
+ extractEnvForClaude,
3230
+ shouldReuseAgentCredentialCache,
3231
+ readAgentCredentialCache,
3232
+ writeAgentCredentialCache,
3233
+ DEFAULT_AGENT_INPUT_REQUIRED_REASON,
3234
+ buildAgentInputRequiredReason,
3235
+ readEnvFile,
3236
+ deriveIssueWorkspaceKeyFromIdentifier,
3237
+ deriveIssueWorkspaceKey,
3238
+ deriveLegacyIssueWorkspaceKey,
3239
+ resolveIssueWorkspaceDirectory,
3240
+ buildHookEnv,
3241
+ executeWorkspaceHook,
3242
+ buildProjectSnapshot,
3243
+ readJsonFile,
3244
+ safeReadDir,
3245
+ isFileMissing,
3246
+ parseRecentEvents,
3247
+ isMatchingIssueRun,
3248
+ mapIssueOrchestrationStateToStatus,
3249
+ resolveGitHubGraphQLToken,
3250
+ createGitHubGraphQLMcpServerEntry,
3251
+ createClaudePrintRuntimeAdapter,
3252
+ runClaudePreflight,
3253
+ formatClaudePreflightText,
3254
+ isClaudeRuntimeCommand,
3255
+ resolveClaudeCommandBinary,
3256
+ resolveRuntimeCommandBinary
3257
+ };