@gh-symphony/cli 0.4.7 → 0.4.9

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.
@@ -1,1918 +1,17 @@
1
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: [],
9
- planningStates: []
10
- };
11
- function isStateActive(state, lifecycle) {
12
- return matchesWorkflowState(state, lifecycle.activeStates);
13
- }
14
- function isStateTerminal(state, lifecycle) {
15
- return matchesWorkflowState(state, lifecycle.terminalStates);
16
- }
17
- function matchesWorkflowState(state, candidates) {
18
- const normalizedState = normalizeWorkflowState(state);
19
- return candidates.some(
20
- (candidate) => normalizeWorkflowState(candidate) === normalizedState
21
- );
22
- }
23
- function normalizeWorkflowState(state) {
24
- return state.trim().toLowerCase();
25
- }
26
-
27
- // ../core/src/workflow/config.ts
28
- var DEFAULT_CODEX_COMMAND = "codex app-server";
29
- var DEFAULT_CLAUDE_COMMAND = "claude";
30
- var DEFAULT_AGENT_COMMAND = DEFAULT_CODEX_COMMAND;
31
- var DEFAULT_LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
32
- var DEFAULT_HOOK_TIMEOUT_MS = 6e4;
33
- var DEFAULT_POLL_INTERVAL_MS = 3e4;
34
- var DEFAULT_MAX_RETRY_BACKOFF_MS = 3e5;
35
- var DEFAULT_MAX_DELAY_MS = DEFAULT_MAX_RETRY_BACKOFF_MS;
36
- var DEFAULT_BASE_DELAY_MS = 1e4;
37
- var DEFAULT_MAX_TURNS = 20;
38
- var DEFAULT_MAX_FAILURE_RETRIES = 10;
39
- var DEFAULT_READ_TIMEOUT_MS = 5e3;
40
- var DEFAULT_TURN_TIMEOUT_MS = 36e5;
41
- var DEFAULT_STALL_TIMEOUT_MS = 3e5;
42
- var DEFAULT_MAX_CONCURRENT_AGENTS = 10;
43
- var DEFAULT_WORKFLOW_HOOKS = {
44
- afterCreate: null,
45
- beforeRun: null,
46
- afterRun: null,
47
- beforeRemove: null,
48
- timeoutMs: DEFAULT_HOOK_TIMEOUT_MS
49
- };
50
- var DEFAULT_WORKFLOW_TRACKER = {
51
- kind: null,
52
- endpoint: null,
53
- apiKey: null,
54
- projectSlug: null,
55
- pickupLabels: {
56
- include: [],
57
- exclude: []
58
- },
59
- activeStates: DEFAULT_WORKFLOW_LIFECYCLE.activeStates,
60
- terminalStates: DEFAULT_WORKFLOW_LIFECYCLE.terminalStates,
61
- projectId: null,
62
- stateFieldName: DEFAULT_WORKFLOW_LIFECYCLE.stateFieldName,
63
- priority: null,
64
- priorityFieldName: null,
65
- blockerCheckStates: DEFAULT_WORKFLOW_LIFECYCLE.blockerCheckStates,
66
- planningStates: DEFAULT_WORKFLOW_LIFECYCLE.planningStates
67
- };
68
- var DEFAULT_WORKFLOW_WORKSPACE = {
69
- root: null
70
- };
71
- var DEFAULT_WORKFLOW_AGENT = {
72
- maxConcurrentAgents: DEFAULT_MAX_CONCURRENT_AGENTS,
73
- maxRetryBackoffMs: DEFAULT_MAX_RETRY_BACKOFF_MS,
74
- maxConcurrentAgentsByState: {},
75
- maxFailureRetries: DEFAULT_MAX_FAILURE_RETRIES,
76
- maxTurns: DEFAULT_MAX_TURNS,
77
- retryBaseDelayMs: DEFAULT_BASE_DELAY_MS
78
- };
79
- var DEFAULT_WORKFLOW_CODEX = {
80
- command: DEFAULT_CODEX_COMMAND,
81
- approvalPolicy: null,
82
- threadSandbox: null,
83
- turnSandboxPolicy: null,
84
- turnTimeoutMs: DEFAULT_TURN_TIMEOUT_MS,
85
- readTimeoutMs: DEFAULT_READ_TIMEOUT_MS,
86
- stallTimeoutMs: DEFAULT_STALL_TIMEOUT_MS
87
- };
88
- var DEFAULT_WORKFLOW_DEFINITION = {
89
- promptTemplate: "",
90
- continuationGuidance: null,
91
- tracker: DEFAULT_WORKFLOW_TRACKER,
92
- polling: {
93
- intervalMs: DEFAULT_POLL_INTERVAL_MS
94
- },
95
- workspace: DEFAULT_WORKFLOW_WORKSPACE,
96
- hooks: DEFAULT_WORKFLOW_HOOKS,
97
- agent: DEFAULT_WORKFLOW_AGENT,
98
- runtime: null,
99
- codex: DEFAULT_WORKFLOW_CODEX,
100
- lifecycle: DEFAULT_WORKFLOW_LIFECYCLE,
101
- format: "default",
102
- githubProjectId: null,
103
- agentCommand: DEFAULT_CODEX_COMMAND,
104
- hookPath: null,
105
- maxConcurrentByState: {}
106
- };
107
- function resolveWorkflowRuntimeCommand(workflow) {
108
- if (!workflow.runtime) {
109
- return workflow.codex.command;
110
- }
111
- if (workflow.runtime.args.length === 0) {
112
- return workflow.runtime.command;
113
- }
114
- return [workflow.runtime.command, ...workflow.runtime.args].join(" ");
115
- }
116
- function resolveWorkflowRuntimeTimeouts(workflow) {
117
- return workflow.runtime?.timeouts ?? workflow.codex;
118
- }
119
-
120
- // ../core/src/workflow/parser.ts
121
- function parseWorkflowMarkdown(markdown, env = process.env, options = {}) {
122
- const compatibilityMode = options.compatibilityMode ?? "strict";
123
- const frontMatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
124
- if (!frontMatterMatch) {
125
- if (compatibilityMode === "legacy") {
126
- return parseLegacyWorkflowMarkdown(markdown);
127
- }
128
- throw new Error("WORKFLOW.md must use YAML front matter.");
129
- }
130
- const [, rawFrontMatter, rawPromptTemplate = ""] = frontMatterMatch;
131
- const frontMatter = parseFrontMatter(rawFrontMatter);
132
- const promptTemplate = rawPromptTemplate.trim();
133
- const tracker = readRequiredObject(frontMatter, "tracker");
134
- const polling = readObject(frontMatter, "polling");
135
- const workspace = readObject(frontMatter, "workspace");
136
- const hooks = readObject(frontMatter, "hooks");
137
- const agent = readObject(frontMatter, "agent");
138
- const runtimeNode = readOptionalRuntimeObject(frontMatter);
139
- const hasRuntime = runtimeNode !== null;
140
- const codex = hasRuntime ? readObject(frontMatter, "codex") : readRequiredObject(frontMatter, "codex");
141
- const trackerKind = readRequiredString(tracker, "kind", env);
142
- validateTrackerConfig(tracker, trackerKind, env);
143
- const activeStates = readStringList(tracker, "active_states") ?? DEFAULT_WORKFLOW_TRACKER.activeStates;
144
- const terminalStates = readStringList(tracker, "terminal_states") ?? DEFAULT_WORKFLOW_TRACKER.terminalStates;
145
- const blockerCheckStates = readStringList(tracker, "blocker_check_states") ?? DEFAULT_WORKFLOW_TRACKER.blockerCheckStates;
146
- const planningStates = readStringList(tracker, "planning_states") ?? blockerCheckStates;
147
- const maxConcurrentAgentsByState = readNumberMap(
148
- agent,
149
- "max_concurrent_agents_by_state"
150
- );
151
- const runtime = hasRuntime ? parseRuntimeConfig(runtimeNode, env) : null;
152
- const codexConfig = {
153
- command: readOptionalString(codex, "command", env) ?? DEFAULT_AGENT_COMMAND,
154
- approvalPolicy: readOptionalString(codex, "approval_policy", env),
155
- threadSandbox: readOptionalString(codex, "thread_sandbox", env),
156
- turnSandboxPolicy: readOptionalString(codex, "turn_sandbox_policy", env),
157
- turnTimeoutMs: readOptionalIntegerLike(codex, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
158
- readTimeoutMs: readOptionalIntegerLike(codex, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
159
- stallTimeoutMs: readOptionalIntegerLike(codex, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
160
- };
161
- const agentCommand = resolveWorkflowRuntimeCommand({
162
- runtime,
163
- codex: codexConfig
164
- });
165
- const parsed = {
166
- promptTemplate,
167
- continuationGuidance: readOptionalWorkflowString(
168
- frontMatter,
169
- "continuationGuidance",
170
- "continuation_guidance",
171
- env
172
- ),
173
- tracker: {
174
- kind: trackerKind,
175
- endpoint: readOptionalString(tracker, "endpoint", env) ?? (trackerKind === "linear" ? DEFAULT_LINEAR_GRAPHQL_URL : null),
176
- apiKey: readOptionalString(tracker, "api_key", env),
177
- projectSlug: readOptionalString(tracker, "project_slug", env),
178
- pickupLabels: readPickupLabelsConfig(tracker),
179
- activeStates,
180
- terminalStates,
181
- projectId: readOptionalString(tracker, "project_id", env),
182
- stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
183
- priority: readPriorityConfig(tracker, env),
184
- priorityFieldName: readOptionalString(tracker, "priority_field", env),
185
- blockerCheckStates,
186
- planningStates
187
- },
188
- polling: {
189
- intervalMs: readOptionalIntegerLike(polling, "interval_ms") ?? DEFAULT_POLL_INTERVAL_MS
190
- },
191
- workspace: {
192
- root: readOptionalString(workspace, "root", env)
193
- },
194
- hooks: {
195
- afterCreate: readOptionalString(hooks, "after_create", env),
196
- beforeRun: readOptionalString(hooks, "before_run", env),
197
- afterRun: readOptionalString(hooks, "after_run", env),
198
- beforeRemove: readOptionalString(hooks, "before_remove", env),
199
- timeoutMs: readOptionalIntegerLike(hooks, "timeout_ms") ?? DEFAULT_HOOK_TIMEOUT_MS
200
- },
201
- agent: {
202
- maxConcurrentAgents: readOptionalIntegerLike(agent, "max_concurrent_agents") ?? DEFAULT_MAX_CONCURRENT_AGENTS,
203
- maxRetryBackoffMs: readOptionalIntegerLike(agent, "max_retry_backoff_ms") ?? DEFAULT_MAX_RETRY_BACKOFF_MS,
204
- maxConcurrentAgentsByState,
205
- maxFailureRetries: readOptionalIntegerLike(agent, "max_failure_retries") ?? DEFAULT_MAX_FAILURE_RETRIES,
206
- maxTurns: readOptionalIntegerLike(agent, "max_turns") ?? DEFAULT_MAX_TURNS,
207
- retryBaseDelayMs: readOptionalIntegerLike(agent, "retry_base_delay_ms") ?? DEFAULT_BASE_DELAY_MS
208
- },
209
- runtime,
210
- codex: codexConfig,
211
- lifecycle: {
212
- stateFieldName: readOptionalString(tracker, "state_field", env) ?? DEFAULT_WORKFLOW_TRACKER.stateFieldName,
213
- activeStates,
214
- terminalStates,
215
- blockerCheckStates,
216
- planningStates
217
- },
218
- format: "front-matter",
219
- githubProjectId: readOptionalString(tracker, "project_id", env),
220
- agentCommand,
221
- hookPath: readOptionalString(hooks, "after_create", env),
222
- maxConcurrentByState: maxConcurrentAgentsByState
223
- };
224
- return parsed;
225
- }
226
- function validateTrackerConfig(tracker, trackerKind, env) {
227
- if (trackerKind !== "linear") {
228
- return;
229
- }
230
- for (const key of ["project_id", "projectId", "teamId", "team_id"]) {
231
- if (key in tracker) {
232
- throw new Error(
233
- `Workflow front matter field "tracker.${key}" is not supported for tracker.kind "linear"; use "tracker.project_slug".`
234
- );
235
- }
236
- }
237
- const projectSlug = readOptionalString(tracker, "project_slug", env);
238
- if (!projectSlug || projectSlug.trim().length === 0) {
239
- throw new Error(
240
- 'Workflow front matter field "tracker.project_slug" is required for tracker.kind "linear".'
241
- );
242
- }
243
- if ("endpoint" in tracker) {
244
- const endpoint = readOptionalString(tracker, "endpoint", env);
245
- if (!endpoint || endpoint.trim().length === 0) {
246
- throw new Error(
247
- 'Workflow front matter field "tracker.endpoint" must be a non-empty string when provided for tracker.kind "linear".'
248
- );
249
- }
250
- }
251
- }
252
- function readPickupLabelsConfig(tracker) {
253
- const value = tracker.pickup_labels ?? tracker.pickupLabels;
254
- if (value === void 0 || value === null) {
255
- return DEFAULT_WORKFLOW_TRACKER.pickupLabels;
256
- }
257
- if (Array.isArray(value) || typeof value !== "object") {
258
- throw new Error(
259
- 'Workflow front matter field "tracker.pickup_labels" must be an object when provided.'
260
- );
261
- }
262
- const input = value;
263
- return {
264
- include: readStringList(input, "include") ?? [],
265
- exclude: readStringList(input, "exclude") ?? []
266
- };
267
- }
268
- function readPriorityConfig(tracker, env) {
269
- if (tracker.priority === void 0 || tracker.priority === null) {
270
- return null;
271
- }
272
- const priority = readObject(tracker, "priority", "tracker.priority");
273
- const source = readRequiredString(priority, "source", env);
274
- const keys = new Set(Object.keys(priority));
275
- if (source === "project-field") {
276
- rejectPriorityKeys(keys, ["source", "field", "values"], source);
277
- const field = readRequiredString(priority, "field", env);
278
- const values = readNumberMap(priority, "values", "tracker.priority.values");
279
- if (Object.keys(values).length === 0) {
280
- throw new Error(
281
- 'Workflow front matter field "tracker.priority.values" must be a non-empty object for tracker.priority.source "project-field".'
282
- );
283
- }
284
- return { source, field, values };
285
- }
286
- if (source === "labels") {
287
- rejectPriorityKeys(keys, ["source", "labels"], source);
288
- const labels = readNumberMap(priority, "labels", "tracker.priority.labels");
289
- if (Object.keys(labels).length === 0) {
290
- throw new Error(
291
- 'Workflow front matter field "tracker.priority.labels" must be a non-empty object for tracker.priority.source "labels".'
292
- );
293
- }
294
- return { source, labels };
295
- }
296
- if (source === "disabled") {
297
- rejectPriorityKeys(keys, ["source"], source);
298
- return { source };
299
- }
300
- throw new Error(
301
- `Unsupported workflow tracker.priority.source "${source}". Supported values: project-field, labels, disabled.`
302
- );
303
- }
304
- function rejectPriorityKeys(keys, allowedKeys, source) {
305
- const allowed = new Set(allowedKeys);
306
- for (const key of keys) {
307
- if (!allowed.has(key)) {
308
- throw new Error(
309
- `Workflow front matter field "tracker.priority.${key}" is not supported for tracker.priority.source "${source}".`
310
- );
311
- }
312
- }
313
- }
314
- function parseLegacyWorkflowMarkdown(markdown) {
315
- const promptGuidelines = matchOptionalSection(markdown, "Prompt Guidelines") ?? "";
316
- return {
317
- ...DEFAULT_WORKFLOW_DEFINITION,
318
- promptTemplate: promptGuidelines,
319
- format: "legacy-sectioned"
320
- };
321
- }
322
- function parseFrontMatter(frontMatter) {
323
- const lines = frontMatter.replace(/\r\n/g, "\n").split("\n");
324
- const [value] = parseBlock(lines, 0, 0);
325
- if (!value || Array.isArray(value) || typeof value !== "object") {
326
- throw new Error("Workflow front matter must be a YAML object.");
327
- }
328
- return value;
329
- }
330
- function parseBlock(lines, startIndex, indent) {
331
- let index = startIndex;
332
- let collectionType = null;
333
- const arrayValues = [];
334
- const objectValues = {};
335
- while (index < lines.length) {
336
- const line = lines[index] ?? "";
337
- if (!line.trim()) {
338
- index += 1;
339
- continue;
340
- }
341
- if (line.trim().startsWith("#")) {
342
- index += 1;
343
- continue;
344
- }
345
- const lineIndent = countIndent(line);
346
- if (lineIndent < indent) {
347
- break;
348
- }
349
- if (lineIndent > indent) {
350
- throw new Error(
351
- `Invalid workflow front matter indentation near "${line.trim()}".`
352
- );
353
- }
354
- const trimmed = line.trim();
355
- if (trimmed.startsWith("- ")) {
356
- if (collectionType === "object") {
357
- throw new Error(
358
- "Cannot mix array and object values in workflow front matter."
359
- );
360
- }
361
- collectionType = "array";
362
- const itemText = stripYamlInlineComment(trimmed.slice(2)).trim();
363
- if (itemText === "|" || itemText === "|-") {
364
- const [multiline, nextIndex3] = parseMultilineScalar(
365
- lines,
366
- index + 1,
367
- indent + 2
368
- );
369
- arrayValues.push(multiline);
370
- index = nextIndex3;
371
- continue;
372
- }
373
- if (itemText) {
374
- arrayValues.push(parseScalar(itemText));
375
- index += 1;
376
- continue;
377
- }
378
- const [child2, nextIndex2] = parseBlock(lines, index + 1, indent + 2);
379
- arrayValues.push(child2);
380
- index = nextIndex2;
381
- continue;
382
- }
383
- if (collectionType === "array") {
384
- throw new Error(
385
- "Cannot mix object and array values in workflow front matter."
386
- );
387
- }
388
- collectionType = "object";
389
- const separatorIndex = findMappingSeparator(trimmed);
390
- if (separatorIndex < 0) {
391
- throw new Error(`Invalid workflow front matter line "${trimmed}".`);
392
- }
393
- const rawKey = trimmed.slice(0, separatorIndex).trim();
394
- const parsedKey = parseScalar(rawKey);
395
- if (typeof parsedKey !== "string") {
396
- throw new Error(`Invalid workflow front matter key "${rawKey}".`);
397
- }
398
- const key = parsedKey;
399
- const remainder = stripYamlInlineComment(
400
- trimmed.slice(separatorIndex + 1)
401
- ).trim();
402
- if (remainder === "|" || remainder === "|-") {
403
- const [multiline, nextIndex2] = parseMultilineScalar(
404
- lines,
405
- index + 1,
406
- indent + 2
407
- );
408
- objectValues[key] = multiline;
409
- index = nextIndex2;
410
- continue;
411
- }
412
- if (remainder) {
413
- objectValues[key] = parseScalar(remainder);
414
- index += 1;
415
- continue;
416
- }
417
- const [child, nextIndex] = parseBlock(lines, index + 1, indent + 2);
418
- objectValues[key] = child;
419
- index = nextIndex;
420
- }
421
- return [collectionType === "array" ? arrayValues : objectValues, index];
422
- }
423
- function parseMultilineScalar(lines, startIndex, indent) {
424
- let index = startIndex;
425
- const collected = [];
426
- while (index < lines.length) {
427
- const line = lines[index] ?? "";
428
- if (!line.trim()) {
429
- collected.push("");
430
- index += 1;
431
- continue;
432
- }
433
- const lineIndent = countIndent(line);
434
- if (lineIndent < indent) {
435
- break;
436
- }
437
- collected.push(line.slice(indent));
438
- index += 1;
439
- }
440
- return [collected.join("\n").trimEnd(), index];
441
- }
442
- function countIndent(line) {
443
- return line.match(/^ */)?.[0].length ?? 0;
444
- }
445
- function findMappingSeparator(value) {
446
- let quote = null;
447
- for (let index = 0; index < value.length; index += 1) {
448
- const char = value[index];
449
- if (quote) {
450
- if (char === "\\") {
451
- index += 1;
452
- continue;
453
- }
454
- if (char === quote) {
455
- quote = null;
456
- }
457
- continue;
458
- }
459
- if (char === '"' || char === "'") {
460
- quote = char;
461
- continue;
462
- }
463
- if (char === ":") {
464
- return index;
465
- }
466
- }
467
- return -1;
468
- }
469
- function parseScalar(value) {
470
- value = stripYamlInlineComment(value).trim();
471
- if (value === "null") return null;
472
- if (value === "true") return true;
473
- if (value === "false") return false;
474
- if (value.startsWith("[") && value.endsWith("]")) {
475
- return parseInlineArray(value);
476
- }
477
- if (/^-?\d+$/.test(value)) return Number.parseInt(value, 10);
478
- if (value.startsWith('"') && value.endsWith('"')) {
479
- try {
480
- const parsed = JSON.parse(value);
481
- if (typeof parsed === "string") {
482
- return parsed;
483
- }
484
- } catch {
485
- throw new Error(
486
- `Invalid quoted workflow front matter scalar "${value}".`
487
- );
488
- }
489
- }
490
- if (value.startsWith("'") && value.endsWith("'")) {
491
- return value.slice(1, -1).replace(/''/g, "'");
492
- }
493
- return value;
494
- }
495
- function stripYamlInlineComment(value) {
496
- let quote = null;
497
- for (let index = 0; index < value.length; index += 1) {
498
- const char = value[index];
499
- if (quote) {
500
- if (quote === '"' && char === "\\") {
501
- index += 1;
502
- continue;
503
- }
504
- if (char === quote) {
505
- quote = null;
506
- }
507
- continue;
508
- }
509
- if (char === '"' || char === "'") {
510
- quote = char;
511
- continue;
512
- }
513
- if (char === "#" && (index === 0 || /\s/.test(value[index - 1] ?? ""))) {
514
- return value.slice(0, index).trimEnd();
515
- }
516
- }
517
- return value;
518
- }
519
- function parseInlineArray(value) {
520
- const inner = value.slice(1, -1).trim();
521
- if (!inner) {
522
- return [];
523
- }
524
- return splitInlineArrayEntries(inner).map((entry) => parseScalar(entry));
525
- }
526
- function splitInlineArrayEntries(inner) {
527
- const entries = [];
528
- let current = "";
529
- let quote = null;
530
- for (const char of inner) {
531
- if (quote) {
532
- current += char;
533
- if (char === quote) {
534
- quote = null;
535
- }
536
- continue;
537
- }
538
- if (char === '"' || char === "'") {
539
- quote = char;
540
- current += char;
541
- continue;
542
- }
543
- if (char === ",") {
544
- pushInlineArrayEntry(entries, current, "middle");
545
- current = "";
546
- continue;
547
- }
548
- current += char;
549
- }
550
- if (quote) {
551
- throw new Error(
552
- "Workflow front matter inline array has an unterminated string."
553
- );
554
- }
555
- pushInlineArrayEntry(entries, current, "end");
556
- return entries;
557
- }
558
- function pushInlineArrayEntry(entries, entry, position) {
559
- const trimmed = entry.trim();
560
- if (!trimmed) {
561
- const reason = position === "end" ? "has a trailing comma" : "contains an empty item";
562
- throw new Error(`Workflow front matter inline array ${reason}.`);
563
- }
564
- entries.push(trimmed);
565
- }
566
- function parseRuntimeConfig(runtime, env) {
567
- const kind = readRuntimeKind(runtime, env);
568
- const isolation = readObject(runtime, "isolation", "runtime.isolation");
569
- const auth = readObject(runtime, "auth", "runtime.auth");
570
- const timeouts = readObject(runtime, "timeouts", "runtime.timeouts");
571
- const configuredCommand = readOptionalString(runtime, "command", env);
572
- const command = configuredCommand ?? defaultRuntimeCommand(kind);
573
- if (!command) {
574
- throw new Error(
575
- 'Workflow front matter field "runtime.command" is required for runtime.kind "custom".'
576
- );
577
- }
578
- return {
579
- kind,
580
- command,
581
- args: readRuntimeArgs(runtime),
582
- isolation: {
583
- bare: readOptionalBoolean(isolation, "bare", "runtime.isolation.bare") ?? false,
584
- strictMcpConfig: readOptionalBoolean(
585
- isolation,
586
- "strict_mcp_config",
587
- "runtime.isolation.strict_mcp_config"
588
- ) ?? false
589
- },
590
- auth: {
591
- env: readOptionalString(auth, "env", env)
592
- },
593
- timeouts: {
594
- turnTimeoutMs: readOptionalIntegerLike(timeouts, "turn_timeout_ms") ?? DEFAULT_TURN_TIMEOUT_MS,
595
- readTimeoutMs: readOptionalIntegerLike(timeouts, "read_timeout_ms") ?? DEFAULT_READ_TIMEOUT_MS,
596
- stallTimeoutMs: readOptionalIntegerLike(timeouts, "stall_timeout_ms") ?? DEFAULT_STALL_TIMEOUT_MS
597
- }
598
- };
599
- }
600
- function readRuntimeKind(runtime, env) {
601
- const kind = readRequiredString(runtime, "kind", env);
602
- if (kind === "codex-app-server" || kind === "claude-print" || kind === "custom") {
603
- return kind;
604
- }
605
- throw new Error(
606
- `Unsupported workflow runtime kind "${kind}". Supported values: codex-app-server, claude-print, custom.`
607
- );
608
- }
609
- function defaultRuntimeCommand(kind) {
610
- if (kind === "claude-print") {
611
- return DEFAULT_CLAUDE_COMMAND;
612
- }
613
- if (kind === "codex-app-server") {
614
- return DEFAULT_AGENT_COMMAND;
615
- }
616
- return null;
617
- }
618
- function readObject(input, key, path = key) {
619
- const value = input[key];
620
- if (value === void 0 || value === null) {
621
- return {};
622
- }
623
- if (typeof value !== "object" || Array.isArray(value)) {
624
- throw new Error(`Workflow front matter field "${path}" must be an object.`);
625
- }
626
- return value;
627
- }
628
- function readOptionalRuntimeObject(input) {
629
- if (input.runtime === void 0 || input.runtime === null) {
630
- return null;
631
- }
632
- return readObject(input, "runtime");
633
- }
634
- function readRequiredObject(input, key) {
635
- if (!(key in input)) {
636
- throw new Error(`Workflow front matter field "${key}" is required.`);
637
- }
638
- return readObject(input, key);
639
- }
640
- function readOptionalString(input, key, env) {
641
- const value = input[key];
642
- if (value === void 0 || value === null) {
643
- return null;
644
- }
645
- if (typeof value !== "string") {
646
- throw new Error(`Workflow front matter field "${key}" must be a string.`);
647
- }
648
- return resolveEnvironmentValue(value, env);
649
- }
650
- function readOptionalWorkflowString(input, primaryKey, fallbackKey, env) {
651
- return readOptionalString(input, primaryKey, env) ?? readOptionalString(input, fallbackKey, env);
652
- }
653
- function readRequiredString(input, key, env) {
654
- const value = readOptionalString(input, key, env);
655
- if (!value) {
656
- throw new Error(`Workflow front matter field "${key}" is required.`);
657
- }
658
- return value;
659
- }
660
- function readStringList(input, key) {
661
- const value = input[key];
662
- if (value === void 0 || value === null) {
663
- return void 0;
664
- }
665
- if (typeof value === "string") {
666
- return value.split(",").map((entry) => entry.trim()).filter(Boolean);
667
- }
668
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
669
- throw new Error(
670
- `Workflow front matter field "${key}" must be an array of strings or comma-separated string.`
671
- );
672
- }
673
- return value;
674
- }
675
- function readRuntimeArgs(input) {
676
- const value = input.args;
677
- if (value === void 0 || value === null) {
678
- return [];
679
- }
680
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
681
- throw new Error(
682
- 'Workflow front matter field "runtime.args" must be an array of strings.'
683
- );
684
- }
685
- return value;
686
- }
687
- function readOptionalBoolean(input, key, path = key) {
688
- const value = input[key];
689
- if (value === void 0 || value === null) {
690
- return null;
691
- }
692
- if (typeof value !== "boolean") {
693
- throw new Error(`Workflow front matter field "${path}" must be a boolean.`);
694
- }
695
- return value;
696
- }
697
- function readOptionalIntegerLike(input, key) {
698
- const value = input[key];
699
- if (value === void 0 || value === null) {
700
- return null;
701
- }
702
- if (typeof value === "number") {
703
- return value;
704
- }
705
- if (typeof value === "string" && /^-?\d+$/.test(value)) {
706
- return Number.parseInt(value, 10);
707
- }
708
- throw new Error(`Workflow front matter field "${key}" must be an integer.`);
709
- }
710
- function readNumberMap(input, key, path = key) {
711
- const value = input[key];
712
- if (value === void 0 || value === null) {
713
- return {};
714
- }
715
- if (typeof value !== "object" || Array.isArray(value)) {
716
- throw new Error(`Workflow front matter field "${path}" must be an object.`);
717
- }
718
- const result = {};
719
- for (const [entryKey, entryValue] of Object.entries(value)) {
720
- if (typeof entryValue === "number") {
721
- result[entryKey] = entryValue;
722
- continue;
723
- }
724
- if (typeof entryValue === "string" && /^-?\d+$/.test(entryValue)) {
725
- result[entryKey] = Number.parseInt(entryValue, 10);
726
- continue;
727
- }
728
- throw new Error(
729
- `Workflow front matter field "${path}.${entryKey}" must be an integer.`
730
- );
731
- }
732
- return result;
733
- }
734
- function resolveEnvironmentValue(value, env) {
735
- const envTokenMatch = value.match(/^(?:env:)?([A-Z0-9_]+)$/);
736
- if (value.startsWith("env:") && envTokenMatch) {
737
- const resolved = env[envTokenMatch[1]];
738
- if (!resolved) {
739
- throw new Error(
740
- `Workflow front matter requires environment variable ${envTokenMatch[1]}.`
741
- );
742
- }
743
- return resolved;
744
- }
745
- const dollarEnvTokenMatch = value.match(/^\$([A-Z0-9_]+)$/);
746
- if (dollarEnvTokenMatch) {
747
- const resolved = env[dollarEnvTokenMatch[1]];
748
- if (!resolved) {
749
- throw new Error(
750
- `Workflow front matter requires environment variable ${dollarEnvTokenMatch[1]}.`
751
- );
752
- }
753
- return resolved;
754
- }
755
- return value.replace(/\$\{([A-Z0-9_]+)\}/g, (_, name) => {
756
- const resolved = env[name];
757
- if (!resolved) {
758
- throw new Error(
759
- `Workflow front matter requires environment variable ${name}.`
760
- );
761
- }
762
- return resolved;
763
- });
764
- }
765
- function matchOptionalSection(markdown, heading) {
766
- const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
767
- const pattern = new RegExp(
768
- `## ${escapedHeading}\\n\\n([\\s\\S]*?)(?=\\n## |$)`
769
- );
770
- const match = markdown.match(pattern);
771
- return match?.[1]?.trim() ?? null;
772
- }
773
-
774
- // ../core/src/workflow/render.ts
775
- import {
776
- Liquid,
777
- ParseError,
778
- RenderError,
779
- TokenizationError,
780
- UndefinedVariableError
781
- } from "liquidjs";
782
- function buildPromptVariables(issue, options) {
783
- const contentType = issue.metadata.contentType ?? "Issue";
784
- const linkedPullRequests = Array.isArray(issue.metadata.linkedPullRequests) ? issue.metadata.linkedPullRequests.map(normalizePullRequestContext) : [];
785
- const primaryPullRequest = contentType === "PullRequest" ? normalizePullRequestContext(
786
- issue.metadata.pullRequest ?? linkedPullRequests[0] ?? buildPullRequestContextFromIssue(issue)
787
- ) : linkedPullRequests[0] ?? null;
788
- const pullRequestContext = buildPullRequestVariables({
789
- contentType,
790
- linkedPullRequests,
791
- primaryPullRequest,
792
- issueBranchName: issue.branchName
793
- });
794
- return {
795
- issue: {
796
- id: issue.id,
797
- identifier: issue.identifier,
798
- number: issue.number,
799
- title: issue.title,
800
- description: issue.description,
801
- priority: issue.priority,
802
- url: issue.url,
803
- state: issue.state,
804
- labels: issue.labels,
805
- blocked_by: issue.blockedBy,
806
- branch_name: issue.branchName,
807
- content_type: contentType,
808
- linked_pull_requests: linkedPullRequests,
809
- primary_pull_request: primaryPullRequest,
810
- has_linked_pr: linkedPullRequests.length > 0,
811
- created_at: issue.createdAt,
812
- updated_at: issue.updatedAt,
813
- repository: `${issue.repository.owner}/${issue.repository.name}`
814
- },
815
- pull_request_context: pullRequestContext,
816
- attempt: options.attempt
817
- };
818
- }
819
- function normalizePullRequestContext(pullRequest) {
820
- return {
821
- ...pullRequest,
822
- state: pullRequest.state ?? null,
823
- projectState: pullRequest.projectState ?? null,
824
- isDraft: pullRequest.isDraft ?? null,
825
- merged: pullRequest.merged ?? null,
826
- headRefName: pullRequest.headRefName ?? null,
827
- baseRefName: pullRequest.baseRefName ?? null
828
- };
829
- }
830
- function buildPullRequestVariables(options) {
831
- const checkoutBranch = options.primaryPullRequest?.headRefName ?? (options.contentType === "PullRequest" ? options.issueBranchName : null);
832
- const hasPrimaryPr = options.primaryPullRequest !== null;
833
- return {
834
- subject_type: options.contentType,
835
- linked_pull_requests: options.linkedPullRequests,
836
- primary_pull_request: options.primaryPullRequest,
837
- has_linked_pr: options.linkedPullRequests.length > 0,
838
- has_primary_pr: hasPrimaryPr,
839
- checkout_branch: checkoutBranch,
840
- // Policy flag for prompt templates: any primary PR means the worker must
841
- // inspect review threads and checks before editing code.
842
- review_first: hasPrimaryPr
843
- };
844
- }
845
- function buildPullRequestContextFromIssue(issue) {
846
- return {
847
- id: issue.id,
848
- number: issue.number,
849
- identifier: issue.identifier,
850
- url: issue.url,
851
- state: null,
852
- projectState: issue.state,
853
- headRefName: issue.branchName,
854
- repository: {
855
- owner: issue.repository.owner,
856
- name: issue.repository.name,
857
- url: issue.repository.url ?? "",
858
- cloneUrl: issue.repository.cloneUrl
859
- }
860
- };
861
- }
862
- var STRICT_LIQUID_ENGINE = new Liquid({
863
- strictVariables: true,
864
- strictFilters: true,
865
- ownPropertyOnly: true
866
- });
867
- function renderPrompt(template, variables, options = {}) {
868
- const strict = options.strict ?? true;
869
- if (!strict) {
870
- return renderLegacyPrompt(template, variables);
871
- }
872
- try {
873
- return STRICT_LIQUID_ENGINE.parseAndRenderSync(template, variables);
874
- } catch (error) {
875
- throw normalizeTemplateError(error);
876
- }
877
- }
878
- function normalizeTemplateError(error) {
879
- const message = error instanceof Error ? error.message : String(error);
880
- if (error instanceof UndefinedVariableError || error instanceof RenderError || error instanceof ParseError && message.startsWith("undefined filter:")) {
881
- return new Error(`template_render_error: ${message}`, { cause: error });
882
- }
883
- if (error instanceof ParseError || error instanceof TokenizationError) {
884
- return new Error(`template_parse_error: ${message}`, { cause: error });
885
- }
886
- return new Error(`template_render_error: ${message}`, { cause: error });
887
- }
888
- function flattenVariables(obj, prefix = "") {
889
- const result = /* @__PURE__ */ new Map();
890
- for (const [key, value] of Object.entries(obj)) {
891
- const fullKey = prefix ? `${prefix}.${key}` : key;
892
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
893
- for (const [nestedKey, nestedValue] of flattenVariables(
894
- value,
895
- fullKey
896
- )) {
897
- result.set(nestedKey, nestedValue);
898
- }
899
- } else {
900
- result.set(fullKey, value);
901
- }
902
- }
903
- return result;
904
- }
905
- function renderLegacyPrompt(template, variables) {
906
- const flatVars = flattenVariables(variables);
907
- return template.replace(
908
- /\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g,
909
- (match, key) => {
910
- const value = flatVars.get(key);
911
- if (value === void 0) {
912
- return match;
913
- }
914
- if (value === null) {
915
- return "";
916
- }
917
- return String(value);
918
- }
919
- );
920
- }
921
-
922
- // ../core/src/contracts/status-surface.ts
923
- var WORKFLOW_EXECUTION_PHASES = [
924
- "planning",
925
- "human-review",
926
- "implementation",
927
- "awaiting-merge",
928
- "completed"
929
- ];
930
- function isWorkflowExecutionPhase(value) {
931
- return typeof value === "string" && WORKFLOW_EXECUTION_PHASES.includes(value);
932
- }
933
- var SESSION_EXIT_CLASSIFICATIONS = [
934
- "completed",
935
- "budget-exceeded",
936
- "convergence-detected",
937
- "max-turns-reached",
938
- "user-input-required",
939
- "timeout",
940
- "error",
941
- "incomplete-turn-dirty-workspace"
942
- ];
943
- function isSessionExitClassification(value) {
944
- return typeof value === "string" && SESSION_EXIT_CLASSIFICATIONS.includes(value);
945
- }
946
-
947
- // ../core/src/contracts/run-attempt-phase.ts
948
- var RUN_ATTEMPT_PHASES = [
949
- "preparing_workspace",
950
- "building_prompt",
951
- "launching_agent",
952
- "initializing_session",
953
- "streaming_turn",
954
- "finishing",
955
- "succeeded",
956
- "failed",
957
- "timed_out",
958
- "stalled",
959
- "canceled_by_reconciliation"
960
- ];
961
- function isRunAttemptPhase(value) {
962
- return typeof value === "string" && RUN_ATTEMPT_PHASES.includes(value);
963
- }
964
-
965
- // ../core/src/contracts/orchestrator-channel.ts
966
- function isRecord(value) {
967
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
968
- }
969
- function isTokenUsage(value) {
970
- if (!isRecord(value)) {
971
- return false;
972
- }
973
- return typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && typeof value.totalTokens === "number";
974
- }
975
- function isSessionInfo(value) {
976
- if (!isRecord(value)) {
977
- return false;
978
- }
979
- 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));
980
- }
981
- function isNullableString(value) {
982
- return typeof value === "string" || value === null;
983
- }
984
- function isTurnEventBase(value) {
985
- return typeof value.startedAt === "string" && isNullableString(value.threadId) && isNullableString(value.turnId) && typeof value.turnCount === "number" && isNullableString(value.sessionId);
986
- }
987
- function isOrchestratorChannelEvent(value) {
988
- if (!isRecord(value)) {
989
- return false;
990
- }
991
- if (typeof value.issueId !== "string") {
992
- return false;
993
- }
994
- if (value.type === "codex_update") {
995
- if (typeof value.lastEventAt !== "string") {
996
- return false;
997
- }
998
- if ("event" in value && value.event !== void 0 && typeof value.event !== "string") {
999
- return false;
1000
- }
1001
- if ("tokenUsage" in value && value.tokenUsage !== void 0 && !isTokenUsage(value.tokenUsage)) {
1002
- return false;
1003
- }
1004
- if ("rateLimits" in value && value.rateLimits !== void 0 && !isRecord(value.rateLimits)) {
1005
- return false;
1006
- }
1007
- if ("sessionInfo" in value && value.sessionInfo !== void 0 && !isSessionInfo(value.sessionInfo)) {
1008
- return false;
1009
- }
1010
- if ("executionPhase" in value && value.executionPhase !== void 0 && value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
1011
- return false;
1012
- }
1013
- if ("runPhase" in value && value.runPhase !== void 0 && value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
1014
- return false;
1015
- }
1016
- if ("lastError" in value && value.lastError !== void 0 && value.lastError !== null && typeof value.lastError !== "string") {
1017
- return false;
1018
- }
1019
- return true;
1020
- }
1021
- if (value.type === "heartbeat") {
1022
- if (value.lastEventAt !== null && typeof value.lastEventAt !== "string") {
1023
- return false;
1024
- }
1025
- if (!isTokenUsage(value.tokenUsage)) {
1026
- return false;
1027
- }
1028
- if (value.rateLimits !== null && !isRecord(value.rateLimits)) {
1029
- return false;
1030
- }
1031
- if (value.sessionInfo !== null && !isSessionInfo(value.sessionInfo)) {
1032
- return false;
1033
- }
1034
- if (value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
1035
- return false;
1036
- }
1037
- if (value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
1038
- return false;
1039
- }
1040
- if (value.lastError !== null && typeof value.lastError !== "string") {
1041
- return false;
1042
- }
1043
- return true;
1044
- }
1045
- if (value.type === "turn_started") {
1046
- return isTurnEventBase(value);
1047
- }
1048
- if (value.type === "turn_completed") {
1049
- return isTurnEventBase(value) && typeof value.completedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage);
1050
- }
1051
- if (value.type === "turn_failed") {
1052
- return isTurnEventBase(value) && typeof value.failedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage) && isNullableString(value.error);
1053
- }
1054
- return false;
1055
- }
1056
-
1057
- // ../core/src/workflow/loader.ts
1058
- import { createHash } from "crypto";
1059
- import { access, readFile, stat } from "fs/promises";
1060
- import { constants } from "fs";
1061
- var WorkflowConfigStore = class {
1062
- cache = /* @__PURE__ */ new Map();
1063
- async load(workflowPath, env = process.env) {
1064
- await access(workflowPath, constants.R_OK);
1065
- const fileStat = await stat(workflowPath);
1066
- const cached = this.cache.get(workflowPath);
1067
- const markdown = await readFile(workflowPath, "utf8");
1068
- const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
1069
- if (cached && cached.fingerprint === fingerprint) {
1070
- return toWorkflowResolution(workflowPath, cached.workflow, {
1071
- isValid: true,
1072
- usedLastKnownGood: false,
1073
- validationError: null
1074
- });
1075
- }
1076
- try {
1077
- const workflow = parseWorkflowMarkdown(markdown, env);
1078
- this.cache.set(workflowPath, {
1079
- fingerprint,
1080
- workflow,
1081
- loadedAt: (/* @__PURE__ */ new Date()).toISOString()
1082
- });
1083
- return toWorkflowResolution(workflowPath, workflow, {
1084
- isValid: true,
1085
- usedLastKnownGood: false,
1086
- validationError: null
1087
- });
1088
- } catch (error) {
1089
- if (cached) {
1090
- return toWorkflowResolution(workflowPath, cached.workflow, {
1091
- isValid: false,
1092
- usedLastKnownGood: true,
1093
- validationError: error instanceof Error ? error.message : "Invalid workflow definition."
1094
- });
1095
- }
1096
- throw error;
1097
- }
1098
- }
1099
- };
1100
- function createDefaultWorkflowResolution() {
1101
- return createInvalidWorkflowResolution(null, "missing_workflow_file");
1102
- }
1103
- function createInvalidWorkflowResolution(workflowPath, validationError) {
1104
- return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
1105
- isValid: false,
1106
- usedLastKnownGood: false,
1107
- validationError
1108
- });
1109
- }
1110
- function toWorkflowResolution(workflowPath, workflow, metadata) {
1111
- return {
1112
- workflowPath,
1113
- workflow,
1114
- lifecycle: workflow.lifecycle,
1115
- promptTemplate: workflow.promptTemplate,
1116
- agentCommand: workflow.agentCommand,
1117
- hookPath: workflow.hookPath ?? "",
1118
- isValid: metadata.isValid,
1119
- usedLastKnownGood: metadata.usedLastKnownGood,
1120
- validationError: metadata.validationError
1121
- };
1122
- }
1123
-
1124
- // ../core/src/workflow/exit-classification.ts
1125
- function classifySessionExit(params) {
1126
- if (params.userInputRequired) {
1127
- return "user-input-required";
1128
- }
1129
- if (params.budgetExceeded) {
1130
- return "budget-exceeded";
1131
- }
1132
- if (params.convergenceDetected) {
1133
- return "convergence-detected";
1134
- }
1135
- if (params.runPhase === "timed_out" || params.runPhase === "stalled") {
1136
- return "timeout";
1137
- }
1138
- if (params.maxTurnsReached) {
1139
- return "max-turns-reached";
1140
- }
1141
- if (params.runPhase === "succeeded") {
1142
- return "completed";
1143
- }
1144
- return "error";
1145
- }
1146
-
1147
- // ../core/src/orchestration/retry-policy.ts
1148
- function calculateRetryDelay(attempt, options = {}) {
1149
- const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
1150
- const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1151
- const normalizedAttempt = Math.max(1, attempt);
1152
- const delay = baseDelayMs * 2 ** (normalizedAttempt - 1);
1153
- return Math.min(delay, maxDelayMs);
1154
- }
1155
- function scheduleRetryAt(now, attempt, options = {}) {
1156
- return new Date(now.getTime() + calculateRetryDelay(attempt, options));
1157
- }
1158
-
1159
- // ../core/src/runtime/credentials.ts
1160
- import { readFile as readFile2, writeFile } from "fs/promises";
1161
- var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
1162
- var CODEX_ENV_KEYS = [
1163
- "OPENAI_API_KEY",
1164
- "OPENAI_BASE_URL",
1165
- "OPENAI_ORG_ID",
1166
- "OPENAI_PROJECT"
1167
- ];
1168
- var AgentRuntimeCredentialError = class extends Error {
1169
- };
1170
- function extractEnvForCodex(env) {
1171
- return pickRuntimeEnv(env, CODEX_ENV_KEYS);
1172
- }
1173
- function extractEnvForClaude(env, envKey = "ANTHROPIC_API_KEY") {
1174
- const apiKey = env[envKey];
1175
- if (!apiKey) {
1176
- throw new AgentRuntimeCredentialError(
1177
- `${envKey} is required in the credential broker response.`
1178
- );
1179
- }
1180
- return {
1181
- [envKey]: apiKey
1182
- };
1183
- }
1184
- function toAgentCredentialCacheEntry(brokerResponse, now = /* @__PURE__ */ new Date()) {
1185
- return {
1186
- env: brokerResponse.env,
1187
- expires_at: brokerResponse.expires_at,
1188
- cachedAt: now.toISOString()
1189
- };
1190
- }
1191
- function shouldReuseAgentCredentialCache(entry, now = /* @__PURE__ */ new Date()) {
1192
- if (Object.keys(entry.env).length === 0) {
1193
- return false;
1194
- }
1195
- if (!entry.expires_at) {
1196
- return true;
1197
- }
1198
- const expiresAt = Date.parse(entry.expires_at);
1199
- if (Number.isNaN(expiresAt)) {
1200
- return false;
1201
- }
1202
- return expiresAt - now.getTime() > TOKEN_REUSE_WINDOW_MS;
1203
- }
1204
- async function readAgentCredentialCache(path, readFileImpl = readFile2) {
1205
- try {
1206
- return normalizeAgentCredentialCacheEntry(
1207
- JSON.parse(await readFileImpl(path, "utf8"))
1208
- );
1209
- } catch {
1210
- return null;
1211
- }
1212
- }
1213
- async function writeAgentCredentialCache(path, brokerResponse, writeFileImpl = writeFile, now = /* @__PURE__ */ new Date()) {
1214
- const entry = toAgentCredentialCacheEntry(brokerResponse, now);
1215
- await writeFileImpl(path, JSON.stringify(entry), "utf8");
1216
- return entry;
1217
- }
1218
- function pickRuntimeEnv(env, keys) {
1219
- const resolved = {};
1220
- for (const key of keys) {
1221
- const value = env[key];
1222
- if (value) {
1223
- resolved[key] = value;
1224
- }
1225
- }
1226
- return resolved;
1227
- }
1228
- function normalizeAgentCredentialCacheEntry(payload) {
1229
- if (!isRecord2(payload)) {
1230
- return null;
1231
- }
1232
- if (!isRecord2(payload.env)) {
1233
- return null;
1234
- }
1235
- const env = Object.fromEntries(
1236
- Object.entries(payload.env).filter(
1237
- (entry) => typeof entry[1] === "string"
1238
- )
1239
- );
1240
- if (Object.keys(env).length === 0) {
1241
- return null;
1242
- }
1243
- return {
1244
- env,
1245
- expires_at: typeof payload.expires_at === "string" ? payload.expires_at : void 0,
1246
- cachedAt: typeof payload.cachedAt === "string" ? payload.cachedAt : (/* @__PURE__ */ new Date(0)).toISOString()
1247
- };
1248
- }
1249
- function isRecord2(value) {
1250
- return value !== null && typeof value === "object";
1251
- }
1252
-
1253
- // ../core/src/runtime/events.ts
1254
- var DEFAULT_AGENT_INPUT_REQUIRED_REASON = "turn_input_required: agent requires user input";
1255
- function buildAgentInputRequiredReason(prompt) {
1256
- if (typeof prompt === "string") {
1257
- const trimmedPrompt = prompt.trim();
1258
- if (trimmedPrompt) {
1259
- return `turn_input_required: ${trimmedPrompt}`;
1260
- }
1261
- }
1262
- return DEFAULT_AGENT_INPUT_REQUIRED_REASON;
1263
- }
1264
-
1265
- // ../core/src/workspace/env-file.ts
1266
- import { existsSync, readFileSync } from "fs";
1267
- function readEnvFile(path) {
1268
- if (!existsSync(path)) {
1269
- return {};
1270
- }
1271
- return readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).reduce((result, line) => {
1272
- const separatorIndex = line.indexOf("=");
1273
- const key = line.slice(0, separatorIndex).trim();
1274
- const value = line.slice(separatorIndex + 1).trim();
1275
- if (key) {
1276
- result[key] = value;
1277
- }
1278
- return result;
1279
- }, {});
1280
- }
1281
-
1282
- // ../core/src/workspace/safety.ts
1283
- import { resolve } from "path";
1284
-
1285
- // ../core/src/workspace/identity.ts
1286
- import { resolve as resolve2, join } from "path";
1287
- import { createHash as createHash2 } from "crypto";
1288
- var RESERVED_WORKSPACE_KEYS = /* @__PURE__ */ new Set([
1289
- "cache",
1290
- "issues.json",
1291
- "project.json",
1292
- "runs",
1293
- "status.json"
1294
- ]);
1295
- function deriveWorkspaceKey(identifier) {
1296
- const sanitized = identifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1297
- if (!sanitized || /^[.]+$/.test(sanitized)) {
1298
- return "issue";
1299
- }
1300
- return sanitized;
1301
- }
1302
- var deriveIssueWorkspaceKeyFromIdentifier = deriveWorkspaceKey;
1303
- function deriveIssueWorkspaceKey(identityOrIdentifier, issueIdentifier) {
1304
- if (typeof identityOrIdentifier === "string") {
1305
- return deriveWorkspaceKey(identityOrIdentifier);
1306
- }
1307
- return deriveWorkspaceKey(
1308
- issueIdentifier ?? identityOrIdentifier.issueSubjectId
1309
- );
1310
- }
1311
- function deriveLegacyIssueWorkspaceKey(identity, projectId) {
1312
- const input = [projectId, identity.adapter, identity.issueSubjectId].filter((part) => typeof part === "string").join(":");
1313
- return createHash2("sha256").update(input).digest("hex").slice(0, 16);
1314
- }
1315
- function resolveIssueWorkspaceDirectory(runtimeRoot, workspaceKey) {
1316
- const normalizedRuntimeRoot = resolve2(runtimeRoot);
1317
- const candidate = resolve2(normalizedRuntimeRoot, workspaceKey);
1318
- if (!candidate.startsWith(`${normalizedRuntimeRoot}/`)) {
1319
- throw new Error(
1320
- "Issue workspace path escapes the configured runtime root."
1321
- );
1322
- }
1323
- if (isReservedWorkspaceKey(workspaceKey)) {
1324
- throw new Error("Issue workspace key is reserved by the runtime layout.");
1325
- }
1326
- return candidate;
1327
- }
1328
- function isReservedWorkspaceKey(workspaceKey) {
1329
- return workspaceKey.startsWith(".") || RESERVED_WORKSPACE_KEYS.has(workspaceKey);
1330
- }
1331
-
1332
- // ../core/src/workspace/hooks.ts
1333
- import { spawn } from "child_process";
1334
- var DEFAULT_HOOK_TIMEOUT_MS2 = 6e4;
1335
- async function executeHook(options) {
1336
- const { kind, command, cwd, env, timeoutMs } = options;
1337
- const start = Date.now();
1338
- const normalizedCommand = normalizeHookCommand(command);
1339
- return new Promise((resolveResult) => {
1340
- let timedOut = false;
1341
- let timer = null;
1342
- const child = spawn("bash", ["-lc", normalizedCommand], {
1343
- cwd,
1344
- env: { ...process.env, ...env },
1345
- stdio: "pipe"
1346
- });
1347
- const stderrChunks = [];
1348
- child.stderr?.on("data", (chunk) => {
1349
- stderrChunks.push(chunk);
1350
- });
1351
- if (timeoutMs > 0) {
1352
- timer = setTimeout(() => {
1353
- timedOut = true;
1354
- child.kill("SIGTERM");
1355
- setTimeout(() => {
1356
- try {
1357
- child.kill("SIGKILL");
1358
- } catch {
1359
- }
1360
- }, 5e3);
1361
- }, timeoutMs);
1362
- }
1363
- child.on("close", (code) => {
1364
- if (timer) {
1365
- clearTimeout(timer);
1366
- }
1367
- const durationMs = Date.now() - start;
1368
- const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
1369
- if (timedOut) {
1370
- resolveResult({
1371
- kind,
1372
- outcome: "timeout",
1373
- exitCode: code,
1374
- durationMs,
1375
- error: `Hook "${kind}" timed out after ${timeoutMs}ms`
1376
- });
1377
- return;
1378
- }
1379
- if (code !== 0) {
1380
- resolveResult({
1381
- kind,
1382
- outcome: "failure",
1383
- exitCode: code,
1384
- durationMs,
1385
- error: stderr || `Hook "${kind}" exited with code ${code}`
1386
- });
1387
- return;
1388
- }
1389
- resolveResult({
1390
- kind,
1391
- outcome: "success",
1392
- exitCode: 0,
1393
- durationMs,
1394
- error: null
1395
- });
1396
- });
1397
- child.on("error", (err) => {
1398
- if (timer) {
1399
- clearTimeout(timer);
1400
- }
1401
- resolveResult({
1402
- kind,
1403
- outcome: "failure",
1404
- exitCode: null,
1405
- durationMs: Date.now() - start,
1406
- error: err.message
1407
- });
1408
- });
1409
- });
1410
- }
1411
- function buildHookEnv(context) {
1412
- const env = {
1413
- SYMPHONY_PROJECT_ID: context.projectId,
1414
- SYMPHONY_ISSUE_WORKSPACE_KEY: context.workspaceKey,
1415
- SYMPHONY_ISSUE_SUBJECT_ID: context.issueSubjectId,
1416
- SYMPHONY_ISSUE_IDENTIFIER: context.issueIdentifier,
1417
- SYMPHONY_WORKSPACE_PATH: context.workspacePath,
1418
- SYMPHONY_REPOSITORY_PATH: context.repositoryPath
1419
- };
1420
- if (context.runId) {
1421
- env.SYMPHONY_RUN_ID = context.runId;
1422
- }
1423
- if (context.state) {
1424
- env.SYMPHONY_ISSUE_STATE = context.state;
1425
- }
1426
- return env;
1427
- }
1428
- function resolveHookCommand(hooks, kind) {
1429
- switch (kind) {
1430
- case "after_create":
1431
- return hooks.afterCreate;
1432
- case "before_run":
1433
- return hooks.beforeRun;
1434
- case "after_run":
1435
- return hooks.afterRun;
1436
- case "before_remove":
1437
- return hooks.beforeRemove;
1438
- }
1439
- }
1440
- async function executeWorkspaceHook(options) {
1441
- const hookCommand = resolveHookCommand(options.hooks, options.kind);
1442
- if (!hookCommand) {
1443
- return {
1444
- kind: options.kind,
1445
- outcome: "skipped",
1446
- exitCode: null,
1447
- durationMs: 0,
1448
- error: null
1449
- };
1450
- }
1451
- return executeHook({
1452
- kind: options.kind,
1453
- command: hookCommand,
1454
- cwd: options.repositoryPath,
1455
- env: options.env,
1456
- timeoutMs: options.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS2
1457
- });
1458
- }
1459
- function normalizeHookCommand(command) {
1460
- const trimmed = command.trim();
1461
- if (trimmed.includes("/") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../") && !/\s/.test(trimmed)) {
1462
- return `bash ./${trimmed}`;
1463
- }
1464
- return command;
1465
- }
1466
-
1467
- // ../core/src/observability/snapshot-builder.ts
1468
- function buildProjectSnapshot(input) {
1469
- const {
1470
- project,
1471
- activeRuns,
1472
- allRuns,
1473
- summary,
1474
- lastTickAt,
1475
- lastError,
1476
- rateLimits
1477
- } = input;
1478
- const cumulativeTokenUsageByIssue = aggregateTokenUsageByIssue(
1479
- allRuns ?? activeRuns
1480
- );
1481
- return {
1482
- repository: project.repository,
1483
- tracker: {
1484
- adapter: project.tracker.adapter,
1485
- bindingId: project.tracker.bindingId,
1486
- settings: project.tracker.settings
1487
- },
1488
- lastTickAt,
1489
- health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
1490
- summary: {
1491
- dispatched: summary.dispatched,
1492
- suppressed: summary.suppressed,
1493
- recovered: summary.recovered,
1494
- activeRuns: activeRuns.length
1495
- },
1496
- activeRuns: activeRuns.map((run) => ({
1497
- runId: run.runId,
1498
- issueIdentifier: run.issueIdentifier,
1499
- issueState: run.issueState,
1500
- status: run.status,
1501
- retryKind: run.retryKind,
1502
- port: run.port,
1503
- runtimeSession: run.runtimeSession ?? null,
1504
- // New fields from live worker data
1505
- processId: run.processId ?? null,
1506
- turnCount: run.turnCount,
1507
- startedAt: run.startedAt ?? null,
1508
- lastEvent: run.lastEvent ?? null,
1509
- lastEventAt: run.lastEventAt ?? null,
1510
- executionPhase: run.executionPhase ?? null,
1511
- runPhase: run.runPhase ?? null,
1512
- tokenUsage: attachCumulativeTokenUsage(
1513
- run.tokenUsage,
1514
- cumulativeTokenUsageByIssue.get(run.issueId)
1515
- )
1516
- })),
1517
- retryQueue: activeRuns.filter((run) => run.status === "retrying" && run.retryKind).map((run) => ({
1518
- runId: run.runId,
1519
- issueIdentifier: run.issueIdentifier,
1520
- retryKind: run.retryKind ?? "failure",
1521
- nextRetryAt: run.nextRetryAt
1522
- })),
1523
- recovery: findLatestRecovery([...allRuns ?? [], ...activeRuns]),
1524
- lastError,
1525
- codexTotals: aggregateTokenUsage(allRuns ?? activeRuns, lastTickAt),
1526
- rateLimits: rateLimits ?? null
1527
- };
1528
- }
1529
- function findLatestRecovery(runs) {
1530
- return [...runs].filter((run) => isUnresolvedRecoveryRun(run, runs)).sort((left, right) => {
1531
- const leftTime = new Date(left.updatedAt).getTime();
1532
- const rightTime = new Date(right.updatedAt).getTime();
1533
- return rightTime - leftTime;
1534
- }).find((run) => run.recovery)?.recovery ?? null;
1535
- }
1536
- function isUnresolvedRecoveryRun(run, runs) {
1537
- if (!run.recovery) {
1538
- return false;
1539
- }
1540
- if (run.status === "suppressed" && runs.some(
1541
- (candidate) => candidate.runId !== run.runId && candidate.retryKind === "recovery" && candidate.recovery?.runId === run.recovery?.runId && new Date(candidate.updatedAt).getTime() > new Date(run.updatedAt).getTime() && candidate.status !== "running" && candidate.status !== "retrying"
1542
- )) {
1543
- return false;
1544
- }
1545
- return run.status === "suppressed" || run.retryKind === "recovery" && (run.status === "running" || run.status === "retrying");
1546
- }
1547
- function aggregateTokenUsageByIssue(runs) {
1548
- const totals = /* @__PURE__ */ new Map();
1549
- for (const run of runs) {
1550
- if (!run.tokenUsage) {
1551
- continue;
1552
- }
1553
- const current = totals.get(run.issueId) ?? {
1554
- inputTokens: 0,
1555
- outputTokens: 0,
1556
- totalTokens: 0
1557
- };
1558
- current.inputTokens += run.tokenUsage.inputTokens;
1559
- current.outputTokens += run.tokenUsage.outputTokens;
1560
- current.totalTokens += run.tokenUsage.totalTokens;
1561
- totals.set(run.issueId, current);
1562
- }
1563
- return totals;
1564
- }
1565
- function attachCumulativeTokenUsage(tokenUsage, cumulative) {
1566
- if (!tokenUsage) {
1567
- return void 0;
1568
- }
1569
- return {
1570
- ...tokenUsage,
1571
- cumulativeInputTokens: cumulative?.inputTokens ?? tokenUsage.inputTokens,
1572
- cumulativeOutputTokens: cumulative?.outputTokens ?? tokenUsage.outputTokens,
1573
- cumulativeTotalTokens: cumulative?.totalTokens ?? tokenUsage.totalTokens
1574
- };
1575
- }
1576
- function aggregateTokenUsage(runs, lastTickAt) {
1577
- let inputTokens = 0;
1578
- let outputTokens = 0;
1579
- let totalTokens = 0;
1580
- let earliestStart = null;
1581
- let latestEnd = null;
1582
- for (const run of runs) {
1583
- if (run.tokenUsage) {
1584
- inputTokens += run.tokenUsage.inputTokens;
1585
- outputTokens += run.tokenUsage.outputTokens;
1586
- totalTokens += run.tokenUsage.totalTokens;
1587
- }
1588
- if (run.startedAt) {
1589
- const start = new Date(run.startedAt).getTime();
1590
- if (earliestStart === null || start < earliestStart) {
1591
- earliestStart = start;
1592
- }
1593
- }
1594
- const end = run.completedAt ? new Date(run.completedAt).getTime() : new Date(lastTickAt).getTime();
1595
- if (latestEnd === null || end > latestEnd) {
1596
- latestEnd = end;
1597
- }
1598
- }
1599
- const secondsRunning = earliestStart !== null && latestEnd !== null ? Math.max(0, Math.round((latestEnd - earliestStart) / 1e3)) : 0;
1600
- return { inputTokens, outputTokens, totalTokens, secondsRunning };
1601
- }
1602
-
1603
- // ../core/src/observability/fs-reader.ts
1604
- import { readFile as readFile3, readdir } from "fs/promises";
1605
- async function readJsonFile(path) {
1606
- try {
1607
- const raw = await readFile3(path, "utf8");
1608
- return JSON.parse(raw);
1609
- } catch (error) {
1610
- if (isFileMissing(error)) {
1611
- return null;
1612
- }
1613
- throw error;
1614
- }
1615
- }
1616
- async function safeReadDir(path) {
1617
- try {
1618
- return await readdir(path);
1619
- } catch (error) {
1620
- if (isFileMissing(error)) {
1621
- return [];
1622
- }
1623
- throw error;
1624
- }
1625
- }
1626
- function isFileMissing(error) {
1627
- return Boolean(
1628
- error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
1629
- );
1630
- }
1631
-
1632
- // ../core/src/observability/event-formatter.ts
1633
- function formatEventMessage(event) {
1634
- switch (event.event) {
1635
- case "tracker.list":
1636
- return `Tracker list saw ${event.issue.identifier}`;
1637
- case "tracker.fetchByIds":
1638
- return `Tracker fetch refreshed ${event.issue.identifier}`;
1639
- case "run-dispatched":
1640
- return event.issueState ? `Dispatched from ${event.issueState}` : "Dispatched";
1641
- case "run-recovered":
1642
- return "Recovered existing run";
1643
- case "run-retried":
1644
- return `Retry ${event.attempt} scheduled (${event.retryKind})`;
1645
- case "run-failed":
1646
- return event.lastError;
1647
- case "run-suppressed":
1648
- return event.reason;
1649
- case "hook-executed":
1650
- return `${event.hook}: ${event.outcome}`;
1651
- case "hook-failed":
1652
- return event.error;
1653
- case "workspace-cleanup":
1654
- return event.error ? `${event.outcome}: ${event.error}` : event.outcome;
1655
- case "worker-error":
1656
- return event.error;
1657
- case "turn_started":
1658
- return `Turn ${event.turnCount} started`;
1659
- case "turn_completed":
1660
- return `Turn ${event.turnCount} completed in ${event.durationMs}ms`;
1661
- case "turn_failed":
1662
- return event.error ?? `Turn ${event.turnCount} failed`;
1663
- case "session_invalidated":
1664
- return event.reason;
1665
- default:
1666
- return null;
1667
- }
1668
- }
1669
- function parseRecentEvents(raw, limit, options) {
1670
- const lines = raw.split("\n");
1671
- if (options.allowPartialFirstLine) {
1672
- lines.shift();
1673
- }
1674
- const events = [];
1675
- for (let index = lines.length - 1; index >= 0; index -= 1) {
1676
- const line = lines[index]?.trim();
1677
- if (!line) {
1678
- continue;
1679
- }
1680
- const event = parseRunEventLine(line);
1681
- if (!event) {
1682
- continue;
1683
- }
1684
- events.push({
1685
- at: event.at,
1686
- event: event.event,
1687
- message: formatEventMessage(event)
1688
- });
1689
- if (events.length === limit) {
1690
- break;
1691
- }
1692
- }
1693
- return events.reverse();
1694
- }
1695
- function parseRunEventLine(line) {
1696
- try {
1697
- return JSON.parse(line);
1698
- } catch {
1699
- return null;
1700
- }
1701
- }
1702
-
1703
- // ../core/src/observability/redaction.ts
1704
- var REDACTED = "[REDACTED]";
1705
- var SENSITIVE_KEY_SUBSTRINGS = [
1706
- "authorization",
1707
- "secret",
1708
- "apiKey",
1709
- "api-key",
1710
- "api_key"
1711
- ];
1712
- function redactObservabilitySecrets(value) {
1713
- return redactObservabilitySecretsWithStats(value).value;
1714
- }
1715
- function redactObservabilitySecretsWithStats(value) {
1716
- const counts = createRedactionCounts();
1717
- const redacted = redactValue(value, counts, {
1718
- redactStringValues: false
1719
- });
1720
- return { value: redacted, redactions: summarizeRedactionCounts(counts) };
1721
- }
1722
- function redactObservabilityDiagnosticsWithStats(value) {
1723
- const counts = createRedactionCounts();
1724
- const redacted = redactValue(value, counts, {
1725
- redactStringValues: true
1726
- });
1727
- return { value: redacted, redactions: summarizeRedactionCounts(counts) };
1728
- }
1729
- function redactObservabilityTextWithStats(text) {
1730
- const counts = createRedactionCounts();
1731
- return {
1732
- value: redactTextValue(text, counts),
1733
- redactions: summarizeRedactionCounts(counts)
1734
- };
1735
- }
1736
- function redactValue(value, counts, options) {
1737
- if (Array.isArray(value)) {
1738
- return value.map((item) => redactValue(item, counts, options));
1739
- }
1740
- if (typeof value === "string" && options.redactStringValues) {
1741
- return redactTextValue(value, counts);
1742
- }
1743
- if (!isRecord3(value)) {
1744
- return value;
1745
- }
1746
- return Object.fromEntries(
1747
- Object.entries(value).map(([key, nested]) => {
1748
- const redactionClass = redactionClassForKey(key);
1749
- if (redactionClass) {
1750
- incrementRedaction(counts, redactionClass);
1751
- return [key, REDACTED];
1752
- }
1753
- return [key, redactValue(nested, counts, options)];
1754
- })
1755
- );
1756
- }
1757
- function redactionClassForKey(key) {
1758
- const normalizedKey = key.toLowerCase();
1759
- if (normalizedKey.includes("authorization")) {
1760
- return "authorization_header";
1761
- }
1762
- if (normalizedKey.includes("apikey") || normalizedKey.includes("api-key") || normalizedKey.includes("api_key")) {
1763
- return "api_key";
1764
- }
1765
- if (normalizedKey.includes("secret")) {
1766
- return "secret_key";
1767
- }
1768
- if (normalizedKey === "token" || normalizedKey.endsWith("token")) {
1769
- return "env_token";
1770
- }
1771
- if (SENSITIVE_KEY_SUBSTRINGS.some(
1772
- (pattern) => normalizedKey.includes(pattern.toLowerCase())
1773
- )) {
1774
- return "secret_key";
1775
- }
1776
- return null;
1777
- }
1778
- function isRecord3(value) {
1779
- return value != null && typeof value === "object";
1780
- }
1781
- function redactTextValue(text, counts) {
1782
- let redacted = replaceAndCount(
1783
- text,
1784
- /\b(Authorization\s*:\s*Bearer\s+)([^\s]+)/gi,
1785
- "authorization_header",
1786
- counts,
1787
- "$1[REDACTED]"
1788
- );
1789
- redacted = replaceAndCount(
1790
- redacted,
1791
- /\b(X-API-Key\s*:\s*)([^\s]+)/gi,
1792
- "api_key",
1793
- counts,
1794
- "$1[REDACTED]"
1795
- );
1796
- redacted = replaceAndCount(
1797
- redacted,
1798
- /^([A-Z0-9_]*(?:TOKEN)\w*\s*=\s*)([^\s]+)/gim,
1799
- "env_token",
1800
- counts,
1801
- "$1[REDACTED]"
1802
- );
1803
- redacted = replaceAndCount(
1804
- redacted,
1805
- /^([A-Z0-9_]*(?:API_KEY)\w*\s*=\s*)([^\s]+)/gim,
1806
- "api_key",
1807
- counts,
1808
- "$1[REDACTED]"
1809
- );
1810
- redacted = replaceAndCount(
1811
- redacted,
1812
- /^([A-Z0-9_]*(?:SECRET)\w*\s*=\s*)([^\s]+)/gim,
1813
- "secret_key",
1814
- counts,
1815
- "$1[REDACTED]"
1816
- );
1817
- redacted = replaceAndCount(
1818
- redacted,
1819
- /((?:"token"|'token'|token)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/gi,
1820
- "env_token",
1821
- counts,
1822
- '$1"[REDACTED]"'
1823
- );
1824
- redacted = replaceAndCount(
1825
- redacted,
1826
- /((?:"secret"|'secret'|secret)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/gi,
1827
- "secret_key",
1828
- counts,
1829
- '$1"[REDACTED]"'
1830
- );
1831
- redacted = replaceAndCount(
1832
- redacted,
1833
- /((?:"apiKey"|'apiKey'|apiKey)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/g,
1834
- "api_key",
1835
- counts,
1836
- '$1"[REDACTED]"'
1837
- );
1838
- redacted = replaceAndCount(
1839
- redacted,
1840
- /\bghp_[A-Za-z0-9_]+/g,
1841
- "env_token",
1842
- counts,
1843
- "[REDACTED]"
1844
- );
1845
- redacted = replaceAndCount(
1846
- redacted,
1847
- /\blin_[A-Za-z0-9_]+/g,
1848
- "api_key",
1849
- counts,
1850
- "[REDACTED]"
1851
- );
1852
- redacted = replaceAndCount(
1853
- redacted,
1854
- /\bsk-[A-Za-z0-9_-]+/g,
1855
- "api_key",
1856
- counts,
1857
- "[REDACTED]"
1858
- );
1859
- return redacted;
1860
- }
1861
- function replaceAndCount(text, pattern, redactionClass, counts, replacement) {
1862
- return text.replace(pattern, (...args) => {
1863
- const matched = typeof args[0] === "string" ? args[0] : "";
1864
- if (matched.includes(REDACTED)) {
1865
- return matched;
1866
- }
1867
- incrementRedaction(counts, redactionClass);
1868
- return replacement.replace(/\$(\d+)/g, (_placeholder, index) => {
1869
- const group = args[Number.parseInt(index, 10)];
1870
- return typeof group === "string" ? group : "";
1871
- });
1872
- });
1873
- }
1874
- function createRedactionCounts() {
1875
- return /* @__PURE__ */ new Map();
1876
- }
1877
- function incrementRedaction(counts, redactionClass) {
1878
- counts.set(redactionClass, (counts.get(redactionClass) ?? 0) + 1);
1879
- }
1880
- function summarizeRedactionCounts(counts) {
1881
- return Array.from(counts.entries()).filter(([, count]) => count > 0).map(([redactionClass, count]) => ({ class: redactionClass, count })).sort((left, right) => left.class.localeCompare(right.class));
1882
- }
1883
-
1884
- // ../core/src/observability/status-assembler.ts
1885
- function isMatchingIssueRun(run, issueId, issueIdentifier) {
1886
- return Boolean(
1887
- run && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1888
- );
1889
- }
1890
- function mapIssueOrchestrationStateToStatus(state) {
1891
- switch (state) {
1892
- case "claimed":
1893
- return "starting";
1894
- case "running":
1895
- return "running";
1896
- case "retry_queued":
1897
- return "retrying";
1898
- case "released":
1899
- return "released";
1900
- case "unclaimed":
1901
- return "pending";
1902
- default:
1903
- return state;
1904
- }
1905
- }
2
+ import {
3
+ extractEnvForClaude
4
+ } from "./chunk-77H5ED5L.js";
1906
5
 
1907
6
  // ../runtime-claude/src/preflight.ts
1908
7
  import { execFileSync } from "child_process";
1909
- import { constants as constants2 } from "fs";
1910
- import { access as access2, readFile as readFile4 } from "fs/promises";
1911
- import { isAbsolute, join as join2, resolve as resolve3 } from "path";
8
+ import { constants } from "fs";
9
+ import { access, readFile } from "fs/promises";
10
+ import { isAbsolute, join, resolve } from "path";
1912
11
  var DEFAULT_DEPENDENCIES = {
1913
12
  execFileSync,
1914
- readFile: readFile4,
1915
- access: access2,
13
+ readFile,
14
+ access,
1916
15
  fetchImpl: fetch,
1917
16
  platform: process.platform
1918
17
  };
@@ -1984,7 +83,7 @@ function checkClaudeBinary(command, cwd, deps) {
1984
83
  }
1985
84
  function resolveExecutableCommand(command, cwd) {
1986
85
  if ((command.includes("/") || command.includes("\\")) && !isAbsolute(command)) {
1987
- return resolve3(cwd, command);
86
+ return resolve(cwd, command);
1988
87
  }
1989
88
  return command;
1990
89
  }
@@ -2117,9 +216,9 @@ async function checkClaudeAuthentication(env, options, deps) {
2117
216
  }
2118
217
  }
2119
218
  async function checkWorkspaceMcpConfig(cwd, deps) {
2120
- const path = join2(cwd, ".mcp.json");
219
+ const path = join(cwd, ".mcp.json");
2121
220
  try {
2122
- await deps.access(path, constants2.R_OK);
221
+ await deps.access(path, constants.R_OK);
2123
222
  } catch (error) {
2124
223
  const err = error;
2125
224
  if (err.code === "ENOENT") {
@@ -2216,7 +315,7 @@ function isClaudeBinaryName(command) {
2216
315
  // ../runtime-claude/src/adapter.ts
2217
316
  import { randomUUID } from "crypto";
2218
317
  import { rm } from "fs/promises";
2219
- import { join as join5 } from "path";
318
+ import { join as join4 } from "path";
2220
319
 
2221
320
  // ../runtime-claude/src/argv.ts
2222
321
  var DEFAULT_CLAUDE_PRINT_ARGS = [
@@ -2290,13 +389,13 @@ function ensureFlagValue(args, flag, value) {
2290
389
  }
2291
390
 
2292
391
  // ../runtime-claude/src/mcp-compose.ts
2293
- import { mkdir, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
2294
- import { basename, dirname, join as join3, resolve as resolve4 } from "path";
392
+ import { mkdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
393
+ import { basename, dirname, join as join2, resolve as resolve2 } from "path";
2295
394
 
2296
395
  // ../tool-github-graphql/src/tool.ts
2297
- import { readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
396
+ import { readFile as readFile2, writeFile } from "fs/promises";
2298
397
  var DEFAULT_GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
2299
- var TOKEN_REUSE_WINDOW_MS2 = 60 * 1e3;
398
+ var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
2300
399
  async function executeGitHubGraphQL(invocation, config, fetchImpl = fetch) {
2301
400
  const token = await resolveGitHubGraphQLToken(config, {
2302
401
  fetchImpl
@@ -2333,10 +432,10 @@ async function resolveGitHubGraphQLToken(config, dependencies = {}) {
2333
432
  );
2334
433
  }
2335
434
  const now = dependencies.now ?? /* @__PURE__ */ new Date();
2336
- const readFileImpl = dependencies.readFileImpl ?? readFile5;
2337
- const writeFileImpl = dependencies.writeFileImpl ?? writeFile2;
435
+ const readFileImpl = dependencies.readFileImpl ?? readFile2;
436
+ const writeFileImpl = dependencies.writeFileImpl ?? writeFile;
2338
437
  const cachedToken = config.tokenCachePath ? await readCachedToken(config.tokenCachePath, readFileImpl) : null;
2339
- if (cachedToken && cachedToken.expiresAt.getTime() - now.getTime() > TOKEN_REUSE_WINDOW_MS2) {
438
+ if (cachedToken && cachedToken.expiresAt.getTime() - now.getTime() > TOKEN_REUSE_WINDOW_MS) {
2340
439
  return cachedToken.token;
2341
440
  }
2342
441
  const fetchImpl = dependencies.fetchImpl ?? fetch;
@@ -5447,7 +3546,7 @@ function createLinearGraphQLMcpServerEntry(options = {}) {
5447
3546
 
5448
3547
  // ../runtime-claude/src/mcp-compose.ts
5449
3548
  async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEnv = {}) {
5450
- const workspaceMcpPath = join3(workspaceRoot, ".mcp.json");
3549
+ const workspaceMcpPath = join2(workspaceRoot, ".mcp.json");
5451
3550
  const finalPath = resolveRuntimeMcpConfigPath(
5452
3551
  workspaceRoot,
5453
3552
  symphonyTokenEnv
@@ -5455,7 +3554,7 @@ async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEn
5455
3554
  const baseConfig = await readBaseMcpConfig(workspaceMcpPath);
5456
3555
  const mergedConfig = mergeSymphonyMcpServers(baseConfig, symphonyTokenEnv);
5457
3556
  await mkdir(dirname(finalPath), { recursive: true });
5458
- await writeFile3(
3557
+ await writeFile2(
5459
3558
  finalPath,
5460
3559
  JSON.stringify(mergedConfig, null, 2) + "\n",
5461
3560
  "utf8"
@@ -5468,9 +3567,9 @@ async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEn
5468
3567
  }
5469
3568
  async function readBaseMcpConfig(workspaceMcpPath) {
5470
3569
  try {
5471
- const raw = await readFile6(workspaceMcpPath, "utf8");
3570
+ const raw = await readFile3(workspaceMcpPath, "utf8");
5472
3571
  const parsed = JSON.parse(raw);
5473
- return isRecord4(parsed) ? parsed : { mcpServers: {} };
3572
+ return isRecord(parsed) ? parsed : { mcpServers: {} };
5474
3573
  } catch (error) {
5475
3574
  if (isNodeError(error) && error.code === "ENOENT") {
5476
3575
  return { mcpServers: {} };
@@ -5479,7 +3578,7 @@ async function readBaseMcpConfig(workspaceMcpPath) {
5479
3578
  }
5480
3579
  }
5481
3580
  function mergeSymphonyMcpServers(baseConfig, env) {
5482
- const mcpServers = isRecord4(baseConfig.mcpServers) ? baseConfig.mcpServers : {};
3581
+ const mcpServers = isRecord(baseConfig.mcpServers) ? baseConfig.mcpServers : {};
5483
3582
  const mergedServers = {
5484
3583
  ...mcpServers,
5485
3584
  github_graphql: createGitHubGraphQLMcpServerEntry({
@@ -5504,15 +3603,15 @@ function mergeSymphonyMcpServers(baseConfig, env) {
5504
3603
  };
5505
3604
  }
5506
3605
  function resolveRuntimeMcpConfigPath(workspaceRoot, env) {
5507
- const normalizedWorkspaceRoot = resolve4(workspaceRoot);
5508
- const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(
3606
+ const normalizedWorkspaceRoot = resolve2(workspaceRoot);
3607
+ const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join2(
5509
3608
  dirname(normalizedWorkspaceRoot),
5510
3609
  ".runtime",
5511
3610
  basename(normalizedWorkspaceRoot)
5512
3611
  );
5513
- return join3(runtimeDir, "mcp.json");
3612
+ return join2(runtimeDir, "mcp.json");
5514
3613
  }
5515
- function isRecord4(value) {
3614
+ function isRecord(value) {
5516
3615
  return value != null && typeof value === "object" && !Array.isArray(value);
5517
3616
  }
5518
3617
  function isNodeError(error) {
@@ -5520,7 +3619,7 @@ function isNodeError(error) {
5520
3619
  }
5521
3620
 
5522
3621
  // ../runtime-claude/src/spawn.ts
5523
- import { spawn as spawn2 } from "child_process";
3622
+ import { spawn } from "child_process";
5524
3623
  import { finished } from "stream/promises";
5525
3624
 
5526
3625
  // ../runtime-claude/src/internal.ts
@@ -5823,7 +3922,7 @@ function extractFailureMessage(event) {
5823
3922
  // ../runtime-claude/src/spawn.ts
5824
3923
  async function spawnClaudeTurn(input, dependencies = {}) {
5825
3924
  const command = input.command ?? "claude";
5826
- const child = (dependencies.spawnImpl ?? spawn2)(command, input.args, {
3925
+ const child = (dependencies.spawnImpl ?? spawn)(command, input.args, {
5827
3926
  cwd: input.cwd,
5828
3927
  env: input.env,
5829
3928
  stdio: "pipe"
@@ -5986,7 +4085,7 @@ async function writeToStdin(stream, line) {
5986
4085
  return waitForDrainOrClosure(stream);
5987
4086
  }
5988
4087
  function waitForDrainOrClosure(stream) {
5989
- return new Promise((resolve5) => {
4088
+ return new Promise((resolve3) => {
5990
4089
  const cleanup = () => {
5991
4090
  stream.removeListener("drain", handleDrain);
5992
4091
  stream.removeListener("close", handleClose);
@@ -5995,19 +4094,19 @@ function waitForDrainOrClosure(stream) {
5995
4094
  };
5996
4095
  const handleDrain = () => {
5997
4096
  cleanup();
5998
- resolve5(true);
4097
+ resolve3(true);
5999
4098
  };
6000
4099
  const handleClose = () => {
6001
4100
  cleanup();
6002
- resolve5(false);
4101
+ resolve3(false);
6003
4102
  };
6004
4103
  const handleFinish = () => {
6005
4104
  cleanup();
6006
- resolve5(false);
4105
+ resolve3(false);
6007
4106
  };
6008
4107
  const handleError = () => {
6009
4108
  cleanup();
6010
- resolve5(false);
4109
+ resolve3(false);
6011
4110
  };
6012
4111
  stream.once("drain", handleDrain);
6013
4112
  stream.once("close", handleClose);
@@ -6016,10 +4115,10 @@ function waitForDrainOrClosure(stream) {
6016
4115
  });
6017
4116
  }
6018
4117
  function waitForChildExit(child, records) {
6019
- return new Promise((resolve5) => {
4118
+ return new Promise((resolve3) => {
6020
4119
  const handleClose = (exitCode, signal) => {
6021
4120
  cleanup();
6022
- resolve5({ exitCode, signal });
4121
+ resolve3({ exitCode, signal });
6023
4122
  };
6024
4123
  const handleError = (error) => {
6025
4124
  cleanup();
@@ -6028,7 +4127,7 @@ function waitForChildExit(child, records) {
6028
4127
  line: "",
6029
4128
  parseError: error.message
6030
4129
  });
6031
- resolve5({
4130
+ resolve3({
6032
4131
  exitCode: null,
6033
4132
  signal: null,
6034
4133
  errorMessage: error.message
@@ -6044,8 +4143,8 @@ function waitForChildExit(child, records) {
6044
4143
  }
6045
4144
 
6046
4145
  // ../runtime-claude/src/session-store.ts
6047
- import { mkdir as mkdir2, readFile as readFile7, rename, writeFile as writeFile4 } from "fs/promises";
6048
- import { dirname as dirname2, join as join4 } from "path";
4146
+ import { mkdir as mkdir2, readFile as readFile4, rename, writeFile as writeFile3 } from "fs/promises";
4147
+ import { dirname as dirname2, join as join3 } from "path";
6049
4148
  var CLAUDE_SESSION_PROTOCOL = "claude-print";
6050
4149
  var CLAUDE_SESSION_FILENAME = "claude-session.json";
6051
4150
  var ClaudeSessionStore = class {
@@ -6053,7 +4152,7 @@ var ClaudeSessionStore = class {
6053
4152
  this.options = options;
6054
4153
  }
6055
4154
  sessionFilePath(options) {
6056
- return join4(
4155
+ return join3(
6057
4156
  options.runDirectory ?? this.runDirectory(options.runId),
6058
4157
  CLAUDE_SESSION_FILENAME
6059
4158
  );
@@ -6061,7 +4160,7 @@ var ClaudeSessionStore = class {
6061
4160
  async load(options) {
6062
4161
  let raw;
6063
4162
  try {
6064
- raw = await readFile7(this.sessionFilePath(options), "utf8");
4163
+ raw = await readFile4(this.sessionFilePath(options), "utf8");
6065
4164
  } catch (error) {
6066
4165
  if (isFileNotFoundError(error)) {
6067
4166
  return null;
@@ -6082,17 +4181,17 @@ var ClaudeSessionStore = class {
6082
4181
  }
6083
4182
  const path = this.sessionFilePath(options);
6084
4183
  await mkdir2(dirname2(path), { recursive: true });
6085
- await writeFile4(`${path}.tmp`, `${JSON.stringify(session, null, 2)}
4184
+ await writeFile3(`${path}.tmp`, `${JSON.stringify(session, null, 2)}
6086
4185
  `, "utf8");
6087
4186
  await rename(`${path}.tmp`, path);
6088
4187
  return session;
6089
4188
  }
6090
4189
  runDirectory(runId) {
6091
- return join4(this.options.runtimeRoot, "runs", runId);
4190
+ return join3(this.options.runtimeRoot, "runs", runId);
6092
4191
  }
6093
4192
  };
6094
4193
  function parseClaudeSessionFile(value) {
6095
- if (!isRecord5(value)) {
4194
+ if (!isRecord2(value)) {
6096
4195
  throw new Error("Claude session file must be a JSON object.");
6097
4196
  }
6098
4197
  if (value.protocol !== CLAUDE_SESSION_PROTOCOL) {
@@ -6109,7 +4208,7 @@ function parseClaudeSessionFile(value) {
6109
4208
  if ("parentRunId" in value && value.parentRunId !== void 0 && typeof value.parentRunId !== "string") {
6110
4209
  throw new Error("Claude session file parentRunId must be a string.");
6111
4210
  }
6112
- if ("protocolState" in value && value.protocolState !== void 0 && !isRecord5(value.protocolState)) {
4211
+ if ("protocolState" in value && value.protocolState !== void 0 && !isRecord2(value.protocolState)) {
6113
4212
  throw new Error("Claude session file protocolState must be an object.");
6114
4213
  }
6115
4214
  return {
@@ -6117,10 +4216,10 @@ function parseClaudeSessionFile(value) {
6117
4216
  sessionId: value.sessionId,
6118
4217
  createdAt: value.createdAt,
6119
4218
  parentRunId: typeof value.parentRunId === "string" ? value.parentRunId : void 0,
6120
- protocolState: isRecord5(value.protocolState) ? value.protocolState : {}
4219
+ protocolState: isRecord2(value.protocolState) ? value.protocolState : {}
6121
4220
  };
6122
4221
  }
6123
- function isRecord5(value) {
4222
+ function isRecord2(value) {
6124
4223
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
6125
4224
  }
6126
4225
  function isFileNotFoundError(error) {
@@ -6133,7 +4232,7 @@ var ClaudePrintRuntimeAdapter = class {
6133
4232
  this.config = config;
6134
4233
  this.dependencies = dependencies;
6135
4234
  this.sessionStore = new ClaudeSessionStore({
6136
- runtimeRoot: config.runtimeRoot ?? join5(config.workingDirectory, ".runtime", "orchestrator")
4235
+ runtimeRoot: config.runtimeRoot ?? join4(config.workingDirectory, ".runtime", "orchestrator")
6137
4236
  });
6138
4237
  }
6139
4238
  activeChild = null;
@@ -6584,48 +4683,6 @@ function buildClaudeMcpTokenEnvironment(options) {
6584
4683
  }
6585
4684
 
6586
4685
  export {
6587
- isOrchestratorChannelEvent,
6588
- DEFAULT_WORKFLOW_LIFECYCLE,
6589
- isStateActive,
6590
- isStateTerminal,
6591
- matchesWorkflowState,
6592
- DEFAULT_LINEAR_GRAPHQL_URL,
6593
- DEFAULT_MAX_FAILURE_RETRIES,
6594
- resolveWorkflowRuntimeCommand,
6595
- resolveWorkflowRuntimeTimeouts,
6596
- parseWorkflowMarkdown,
6597
- WorkflowConfigStore,
6598
- createDefaultWorkflowResolution,
6599
- createInvalidWorkflowResolution,
6600
- buildPromptVariables,
6601
- renderPrompt,
6602
- classifySessionExit,
6603
- scheduleRetryAt,
6604
- extractEnvForCodex,
6605
- extractEnvForClaude,
6606
- shouldReuseAgentCredentialCache,
6607
- readAgentCredentialCache,
6608
- writeAgentCredentialCache,
6609
- DEFAULT_AGENT_INPUT_REQUIRED_REASON,
6610
- buildAgentInputRequiredReason,
6611
- readEnvFile,
6612
- deriveIssueWorkspaceKeyFromIdentifier,
6613
- deriveIssueWorkspaceKey,
6614
- deriveLegacyIssueWorkspaceKey,
6615
- resolveIssueWorkspaceDirectory,
6616
- buildHookEnv,
6617
- executeWorkspaceHook,
6618
- buildProjectSnapshot,
6619
- readJsonFile,
6620
- safeReadDir,
6621
- isFileMissing,
6622
- formatEventMessage,
6623
- parseRecentEvents,
6624
- redactObservabilitySecrets,
6625
- redactObservabilityDiagnosticsWithStats,
6626
- redactObservabilityTextWithStats,
6627
- isMatchingIssueRun,
6628
- mapIssueOrchestrationStateToStatus,
6629
4686
  resolveGitHubGraphQLToken,
6630
4687
  createGitHubGraphQLMcpServerEntry,
6631
4688
  createLinearGraphQLMcpServerEntry,