@gh-symphony/cli 0.4.6 → 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.
@@ -0,0 +1,1991 @@
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/observability/error-format.ts
923
+ function hasVerboseFlag(argv) {
924
+ return argv.some((arg) => arg === "--verbose" || arg === "-v");
925
+ }
926
+ function formatErrorForTerminal(error, options = {}) {
927
+ if (!options.verbose) {
928
+ return `${error instanceof Error ? error.message : "Unknown error"}
929
+ `;
930
+ }
931
+ const lines = [formatSingleError(error)];
932
+ const seenCauses = /* @__PURE__ */ new Set();
933
+ if (typeof error === "object" && error !== null) {
934
+ seenCauses.add(error);
935
+ }
936
+ let cause = resolveCause(error);
937
+ while (cause !== void 0) {
938
+ if (typeof cause === "object" && cause !== null) {
939
+ if (seenCauses.has(cause)) {
940
+ lines.push("Caused by: [Circular cause]");
941
+ break;
942
+ }
943
+ seenCauses.add(cause);
944
+ }
945
+ lines.push(`Caused by: ${formatSingleError(cause)}`);
946
+ cause = resolveCause(cause);
947
+ }
948
+ return `${lines.join("\n")}
949
+ `;
950
+ }
951
+ function formatSingleError(error) {
952
+ if (error instanceof Error) {
953
+ return error.stack ?? error.message;
954
+ }
955
+ return String(error);
956
+ }
957
+ function resolveCause(error) {
958
+ return error instanceof Error ? error.cause : void 0;
959
+ }
960
+
961
+ // ../core/src/contracts/status-surface.ts
962
+ var WORKFLOW_EXECUTION_PHASES = [
963
+ "planning",
964
+ "human-review",
965
+ "implementation",
966
+ "awaiting-merge",
967
+ "completed"
968
+ ];
969
+ function isWorkflowExecutionPhase(value) {
970
+ return typeof value === "string" && WORKFLOW_EXECUTION_PHASES.includes(value);
971
+ }
972
+ var SESSION_EXIT_CLASSIFICATIONS = [
973
+ "completed",
974
+ "budget-exceeded",
975
+ "convergence-detected",
976
+ "max-turns-reached",
977
+ "user-input-required",
978
+ "timeout",
979
+ "error",
980
+ "incomplete-turn-dirty-workspace"
981
+ ];
982
+ function isSessionExitClassification(value) {
983
+ return typeof value === "string" && SESSION_EXIT_CLASSIFICATIONS.includes(value);
984
+ }
985
+
986
+ // ../core/src/contracts/run-attempt-phase.ts
987
+ var RUN_ATTEMPT_PHASES = [
988
+ "preparing_workspace",
989
+ "building_prompt",
990
+ "launching_agent",
991
+ "initializing_session",
992
+ "streaming_turn",
993
+ "finishing",
994
+ "succeeded",
995
+ "failed",
996
+ "timed_out",
997
+ "stalled",
998
+ "canceled_by_reconciliation"
999
+ ];
1000
+ function isRunAttemptPhase(value) {
1001
+ return typeof value === "string" && RUN_ATTEMPT_PHASES.includes(value);
1002
+ }
1003
+
1004
+ // ../core/src/contracts/orchestrator-channel.ts
1005
+ function isRecord(value) {
1006
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1007
+ }
1008
+ function isTokenUsage(value) {
1009
+ if (!isRecord(value)) {
1010
+ return false;
1011
+ }
1012
+ return typeof value.inputTokens === "number" && typeof value.outputTokens === "number" && typeof value.totalTokens === "number";
1013
+ }
1014
+ function isSessionInfo(value) {
1015
+ if (!isRecord(value)) {
1016
+ return false;
1017
+ }
1018
+ 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));
1019
+ }
1020
+ function isNullableString(value) {
1021
+ return typeof value === "string" || value === null;
1022
+ }
1023
+ function isTurnEventBase(value) {
1024
+ return typeof value.startedAt === "string" && isNullableString(value.threadId) && isNullableString(value.turnId) && typeof value.turnCount === "number" && isNullableString(value.sessionId);
1025
+ }
1026
+ function isOrchestratorChannelEvent(value) {
1027
+ if (!isRecord(value)) {
1028
+ return false;
1029
+ }
1030
+ if (typeof value.issueId !== "string") {
1031
+ return false;
1032
+ }
1033
+ if (value.type === "codex_update") {
1034
+ if (typeof value.lastEventAt !== "string") {
1035
+ return false;
1036
+ }
1037
+ if ("event" in value && value.event !== void 0 && typeof value.event !== "string") {
1038
+ return false;
1039
+ }
1040
+ if ("tokenUsage" in value && value.tokenUsage !== void 0 && !isTokenUsage(value.tokenUsage)) {
1041
+ return false;
1042
+ }
1043
+ if ("rateLimits" in value && value.rateLimits !== void 0 && !isRecord(value.rateLimits)) {
1044
+ return false;
1045
+ }
1046
+ if ("sessionInfo" in value && value.sessionInfo !== void 0 && !isSessionInfo(value.sessionInfo)) {
1047
+ return false;
1048
+ }
1049
+ if ("executionPhase" in value && value.executionPhase !== void 0 && value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
1050
+ return false;
1051
+ }
1052
+ if ("runPhase" in value && value.runPhase !== void 0 && value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
1053
+ return false;
1054
+ }
1055
+ if ("lastError" in value && value.lastError !== void 0 && value.lastError !== null && typeof value.lastError !== "string") {
1056
+ return false;
1057
+ }
1058
+ return true;
1059
+ }
1060
+ if (value.type === "heartbeat") {
1061
+ if (value.lastEventAt !== null && typeof value.lastEventAt !== "string") {
1062
+ return false;
1063
+ }
1064
+ if (!isTokenUsage(value.tokenUsage)) {
1065
+ return false;
1066
+ }
1067
+ if (value.rateLimits !== null && !isRecord(value.rateLimits)) {
1068
+ return false;
1069
+ }
1070
+ if (value.sessionInfo !== null && !isSessionInfo(value.sessionInfo)) {
1071
+ return false;
1072
+ }
1073
+ if (value.executionPhase !== null && !isWorkflowExecutionPhase(value.executionPhase)) {
1074
+ return false;
1075
+ }
1076
+ if (value.runPhase !== null && !isRunAttemptPhase(value.runPhase)) {
1077
+ return false;
1078
+ }
1079
+ if (value.lastError !== null && typeof value.lastError !== "string") {
1080
+ return false;
1081
+ }
1082
+ return true;
1083
+ }
1084
+ if (value.type === "turn_started") {
1085
+ return isTurnEventBase(value);
1086
+ }
1087
+ if (value.type === "turn_completed") {
1088
+ return isTurnEventBase(value) && typeof value.completedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage);
1089
+ }
1090
+ if (value.type === "turn_failed") {
1091
+ return isTurnEventBase(value) && typeof value.failedAt === "string" && typeof value.durationMs === "number" && isTokenUsage(value.tokenUsage) && isNullableString(value.error);
1092
+ }
1093
+ return false;
1094
+ }
1095
+
1096
+ // ../core/src/workflow/loader.ts
1097
+ import { createHash } from "crypto";
1098
+ import { access, readFile, stat } from "fs/promises";
1099
+ import { constants } from "fs";
1100
+ var WorkflowConfigStore = class {
1101
+ cache = /* @__PURE__ */ new Map();
1102
+ async load(workflowPath, env = process.env) {
1103
+ await access(workflowPath, constants.R_OK);
1104
+ const fileStat = await stat(workflowPath);
1105
+ const cached = this.cache.get(workflowPath);
1106
+ const markdown = await readFile(workflowPath, "utf8");
1107
+ const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
1108
+ if (cached && cached.fingerprint === fingerprint) {
1109
+ return toWorkflowResolution(workflowPath, cached.workflow, {
1110
+ isValid: true,
1111
+ usedLastKnownGood: false,
1112
+ validationError: null
1113
+ });
1114
+ }
1115
+ try {
1116
+ const workflow = parseWorkflowMarkdown(markdown, env);
1117
+ this.cache.set(workflowPath, {
1118
+ fingerprint,
1119
+ workflow,
1120
+ loadedAt: (/* @__PURE__ */ new Date()).toISOString()
1121
+ });
1122
+ return toWorkflowResolution(workflowPath, workflow, {
1123
+ isValid: true,
1124
+ usedLastKnownGood: false,
1125
+ validationError: null
1126
+ });
1127
+ } catch (error) {
1128
+ if (cached) {
1129
+ return toWorkflowResolution(workflowPath, cached.workflow, {
1130
+ isValid: false,
1131
+ usedLastKnownGood: true,
1132
+ validationError: error instanceof Error ? error.message : "Invalid workflow definition."
1133
+ });
1134
+ }
1135
+ throw error;
1136
+ }
1137
+ }
1138
+ };
1139
+ function createDefaultWorkflowResolution() {
1140
+ return createInvalidWorkflowResolution(null, "missing_workflow_file");
1141
+ }
1142
+ function createInvalidWorkflowResolution(workflowPath, validationError) {
1143
+ return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
1144
+ isValid: false,
1145
+ usedLastKnownGood: false,
1146
+ validationError
1147
+ });
1148
+ }
1149
+ function toWorkflowResolution(workflowPath, workflow, metadata) {
1150
+ return {
1151
+ workflowPath,
1152
+ workflow,
1153
+ lifecycle: workflow.lifecycle,
1154
+ promptTemplate: workflow.promptTemplate,
1155
+ agentCommand: workflow.agentCommand,
1156
+ hookPath: workflow.hookPath ?? "",
1157
+ isValid: metadata.isValid,
1158
+ usedLastKnownGood: metadata.usedLastKnownGood,
1159
+ validationError: metadata.validationError
1160
+ };
1161
+ }
1162
+
1163
+ // ../core/src/workflow/exit-classification.ts
1164
+ function classifySessionExit(params) {
1165
+ if (params.userInputRequired) {
1166
+ return "user-input-required";
1167
+ }
1168
+ if (params.budgetExceeded) {
1169
+ return "budget-exceeded";
1170
+ }
1171
+ if (params.convergenceDetected) {
1172
+ return "convergence-detected";
1173
+ }
1174
+ if (params.runPhase === "timed_out" || params.runPhase === "stalled") {
1175
+ return "timeout";
1176
+ }
1177
+ if (params.maxTurnsReached) {
1178
+ return "max-turns-reached";
1179
+ }
1180
+ if (params.runPhase === "succeeded") {
1181
+ return "completed";
1182
+ }
1183
+ return "error";
1184
+ }
1185
+
1186
+ // ../core/src/orchestration/retry-policy.ts
1187
+ function calculateRetryDelay(attempt, options = {}) {
1188
+ const baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
1189
+ const maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1190
+ const normalizedAttempt = Math.max(1, attempt);
1191
+ const delay = baseDelayMs * 2 ** (normalizedAttempt - 1);
1192
+ return Math.min(delay, maxDelayMs);
1193
+ }
1194
+ function scheduleRetryAt(now, attempt, options = {}) {
1195
+ return new Date(now.getTime() + calculateRetryDelay(attempt, options));
1196
+ }
1197
+
1198
+ // ../core/src/runtime/credentials.ts
1199
+ import { readFile as readFile2, writeFile } from "fs/promises";
1200
+ var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
1201
+ var CODEX_ENV_KEYS = [
1202
+ "OPENAI_API_KEY",
1203
+ "OPENAI_BASE_URL",
1204
+ "OPENAI_ORG_ID",
1205
+ "OPENAI_PROJECT"
1206
+ ];
1207
+ var AgentRuntimeCredentialError = class extends Error {
1208
+ };
1209
+ function extractEnvForCodex(env) {
1210
+ return pickRuntimeEnv(env, CODEX_ENV_KEYS);
1211
+ }
1212
+ function extractEnvForClaude(env, envKey = "ANTHROPIC_API_KEY") {
1213
+ const apiKey = env[envKey];
1214
+ if (!apiKey) {
1215
+ throw new AgentRuntimeCredentialError(
1216
+ `${envKey} is required in the credential broker response.`
1217
+ );
1218
+ }
1219
+ return {
1220
+ [envKey]: apiKey
1221
+ };
1222
+ }
1223
+ function toAgentCredentialCacheEntry(brokerResponse, now = /* @__PURE__ */ new Date()) {
1224
+ return {
1225
+ env: brokerResponse.env,
1226
+ expires_at: brokerResponse.expires_at,
1227
+ cachedAt: now.toISOString()
1228
+ };
1229
+ }
1230
+ function shouldReuseAgentCredentialCache(entry, now = /* @__PURE__ */ new Date()) {
1231
+ if (Object.keys(entry.env).length === 0) {
1232
+ return false;
1233
+ }
1234
+ if (!entry.expires_at) {
1235
+ return true;
1236
+ }
1237
+ const expiresAt = Date.parse(entry.expires_at);
1238
+ if (Number.isNaN(expiresAt)) {
1239
+ return false;
1240
+ }
1241
+ return expiresAt - now.getTime() > TOKEN_REUSE_WINDOW_MS;
1242
+ }
1243
+ async function readAgentCredentialCache(path, readFileImpl = readFile2) {
1244
+ try {
1245
+ return normalizeAgentCredentialCacheEntry(
1246
+ JSON.parse(await readFileImpl(path, "utf8"))
1247
+ );
1248
+ } catch {
1249
+ return null;
1250
+ }
1251
+ }
1252
+ async function writeAgentCredentialCache(path, brokerResponse, writeFileImpl = writeFile, now = /* @__PURE__ */ new Date()) {
1253
+ const entry = toAgentCredentialCacheEntry(brokerResponse, now);
1254
+ await writeFileImpl(path, JSON.stringify(entry), "utf8");
1255
+ return entry;
1256
+ }
1257
+ function pickRuntimeEnv(env, keys) {
1258
+ const resolved = {};
1259
+ for (const key of keys) {
1260
+ const value = env[key];
1261
+ if (value) {
1262
+ resolved[key] = value;
1263
+ }
1264
+ }
1265
+ return resolved;
1266
+ }
1267
+ function normalizeAgentCredentialCacheEntry(payload) {
1268
+ if (!isRecord2(payload)) {
1269
+ return null;
1270
+ }
1271
+ if (!isRecord2(payload.env)) {
1272
+ return null;
1273
+ }
1274
+ const env = Object.fromEntries(
1275
+ Object.entries(payload.env).filter(
1276
+ (entry) => typeof entry[1] === "string"
1277
+ )
1278
+ );
1279
+ if (Object.keys(env).length === 0) {
1280
+ return null;
1281
+ }
1282
+ return {
1283
+ env,
1284
+ expires_at: typeof payload.expires_at === "string" ? payload.expires_at : void 0,
1285
+ cachedAt: typeof payload.cachedAt === "string" ? payload.cachedAt : (/* @__PURE__ */ new Date(0)).toISOString()
1286
+ };
1287
+ }
1288
+ function isRecord2(value) {
1289
+ return value !== null && typeof value === "object";
1290
+ }
1291
+
1292
+ // ../core/src/runtime/events.ts
1293
+ var DEFAULT_AGENT_INPUT_REQUIRED_REASON = "turn_input_required: agent requires user input";
1294
+ function buildAgentInputRequiredReason(prompt) {
1295
+ if (typeof prompt === "string") {
1296
+ const trimmedPrompt = prompt.trim();
1297
+ if (trimmedPrompt) {
1298
+ return `turn_input_required: ${trimmedPrompt}`;
1299
+ }
1300
+ }
1301
+ return DEFAULT_AGENT_INPUT_REQUIRED_REASON;
1302
+ }
1303
+
1304
+ // ../core/src/workspace/env-file.ts
1305
+ import { existsSync, readFileSync } from "fs";
1306
+ function readEnvFile(path) {
1307
+ if (!existsSync(path)) {
1308
+ return {};
1309
+ }
1310
+ return readFileSync(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).reduce((result, line) => {
1311
+ const separatorIndex = line.indexOf("=");
1312
+ const key = line.slice(0, separatorIndex).trim();
1313
+ const value = line.slice(separatorIndex + 1).trim();
1314
+ if (key) {
1315
+ result[key] = value;
1316
+ }
1317
+ return result;
1318
+ }, {});
1319
+ }
1320
+
1321
+ // ../core/src/workspace/safety.ts
1322
+ import { resolve } from "path";
1323
+
1324
+ // ../core/src/workspace/identity.ts
1325
+ import { resolve as resolve2, join } from "path";
1326
+ import { createHash as createHash2 } from "crypto";
1327
+ var RESERVED_WORKSPACE_KEYS = /* @__PURE__ */ new Set([
1328
+ "cache",
1329
+ "issues.json",
1330
+ "project.json",
1331
+ "runs",
1332
+ "status.json"
1333
+ ]);
1334
+ function deriveWorkspaceKey(identifier) {
1335
+ const sanitized = identifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1336
+ if (!sanitized || /^[.]+$/.test(sanitized)) {
1337
+ return "issue";
1338
+ }
1339
+ return sanitized;
1340
+ }
1341
+ var deriveIssueWorkspaceKeyFromIdentifier = deriveWorkspaceKey;
1342
+ function deriveIssueWorkspaceKey(identityOrIdentifier, issueIdentifier) {
1343
+ if (typeof identityOrIdentifier === "string") {
1344
+ return deriveWorkspaceKey(identityOrIdentifier);
1345
+ }
1346
+ return deriveWorkspaceKey(
1347
+ issueIdentifier ?? identityOrIdentifier.issueSubjectId
1348
+ );
1349
+ }
1350
+ function deriveLegacyIssueWorkspaceKey(identity, projectId) {
1351
+ const input = [projectId, identity.adapter, identity.issueSubjectId].filter((part) => typeof part === "string").join(":");
1352
+ return createHash2("sha256").update(input).digest("hex").slice(0, 16);
1353
+ }
1354
+ function resolveIssueWorkspaceDirectory(runtimeRoot, workspaceKey) {
1355
+ const normalizedRuntimeRoot = resolve2(runtimeRoot);
1356
+ const candidate = resolve2(normalizedRuntimeRoot, workspaceKey);
1357
+ if (!candidate.startsWith(`${normalizedRuntimeRoot}/`)) {
1358
+ throw new Error(
1359
+ "Issue workspace path escapes the configured runtime root."
1360
+ );
1361
+ }
1362
+ if (isReservedWorkspaceKey(workspaceKey)) {
1363
+ throw new Error("Issue workspace key is reserved by the runtime layout.");
1364
+ }
1365
+ return candidate;
1366
+ }
1367
+ function isReservedWorkspaceKey(workspaceKey) {
1368
+ return workspaceKey.startsWith(".") || RESERVED_WORKSPACE_KEYS.has(workspaceKey);
1369
+ }
1370
+
1371
+ // ../core/src/workspace/hooks.ts
1372
+ import { spawn } from "child_process";
1373
+ var DEFAULT_HOOK_TIMEOUT_MS2 = 6e4;
1374
+ async function executeHook(options) {
1375
+ const { kind, command, cwd, env, timeoutMs } = options;
1376
+ const start = Date.now();
1377
+ const normalizedCommand = normalizeHookCommand(command);
1378
+ return new Promise((resolveResult) => {
1379
+ let timedOut = false;
1380
+ let timer = null;
1381
+ const child = spawn("bash", ["-lc", normalizedCommand], {
1382
+ cwd,
1383
+ env: { ...process.env, ...env },
1384
+ stdio: "pipe"
1385
+ });
1386
+ const stderrChunks = [];
1387
+ child.stderr?.on("data", (chunk) => {
1388
+ stderrChunks.push(chunk);
1389
+ });
1390
+ if (timeoutMs > 0) {
1391
+ timer = setTimeout(() => {
1392
+ timedOut = true;
1393
+ child.kill("SIGTERM");
1394
+ setTimeout(() => {
1395
+ try {
1396
+ child.kill("SIGKILL");
1397
+ } catch {
1398
+ }
1399
+ }, 5e3);
1400
+ }, timeoutMs);
1401
+ }
1402
+ child.on("close", (code) => {
1403
+ if (timer) {
1404
+ clearTimeout(timer);
1405
+ }
1406
+ const durationMs = Date.now() - start;
1407
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
1408
+ if (timedOut) {
1409
+ resolveResult({
1410
+ kind,
1411
+ outcome: "timeout",
1412
+ exitCode: code,
1413
+ durationMs,
1414
+ error: `Hook "${kind}" timed out after ${timeoutMs}ms`
1415
+ });
1416
+ return;
1417
+ }
1418
+ if (code !== 0) {
1419
+ resolveResult({
1420
+ kind,
1421
+ outcome: "failure",
1422
+ exitCode: code,
1423
+ durationMs,
1424
+ error: stderr || `Hook "${kind}" exited with code ${code}`
1425
+ });
1426
+ return;
1427
+ }
1428
+ resolveResult({
1429
+ kind,
1430
+ outcome: "success",
1431
+ exitCode: 0,
1432
+ durationMs,
1433
+ error: null
1434
+ });
1435
+ });
1436
+ child.on("error", (err) => {
1437
+ if (timer) {
1438
+ clearTimeout(timer);
1439
+ }
1440
+ resolveResult({
1441
+ kind,
1442
+ outcome: "failure",
1443
+ exitCode: null,
1444
+ durationMs: Date.now() - start,
1445
+ error: err.message
1446
+ });
1447
+ });
1448
+ });
1449
+ }
1450
+ function buildHookEnv(context) {
1451
+ const env = {
1452
+ SYMPHONY_PROJECT_ID: context.projectId,
1453
+ SYMPHONY_ISSUE_WORKSPACE_KEY: context.workspaceKey,
1454
+ SYMPHONY_ISSUE_SUBJECT_ID: context.issueSubjectId,
1455
+ SYMPHONY_ISSUE_IDENTIFIER: context.issueIdentifier,
1456
+ SYMPHONY_WORKSPACE_PATH: context.workspacePath,
1457
+ SYMPHONY_REPOSITORY_PATH: context.repositoryPath
1458
+ };
1459
+ if (context.runId) {
1460
+ env.SYMPHONY_RUN_ID = context.runId;
1461
+ }
1462
+ if (context.state) {
1463
+ env.SYMPHONY_ISSUE_STATE = context.state;
1464
+ }
1465
+ return env;
1466
+ }
1467
+ function resolveHookCommand(hooks, kind) {
1468
+ switch (kind) {
1469
+ case "after_create":
1470
+ return hooks.afterCreate;
1471
+ case "before_run":
1472
+ return hooks.beforeRun;
1473
+ case "after_run":
1474
+ return hooks.afterRun;
1475
+ case "before_remove":
1476
+ return hooks.beforeRemove;
1477
+ }
1478
+ }
1479
+ async function executeWorkspaceHook(options) {
1480
+ const hookCommand = resolveHookCommand(options.hooks, options.kind);
1481
+ if (!hookCommand) {
1482
+ return {
1483
+ kind: options.kind,
1484
+ outcome: "skipped",
1485
+ exitCode: null,
1486
+ durationMs: 0,
1487
+ error: null
1488
+ };
1489
+ }
1490
+ return executeHook({
1491
+ kind: options.kind,
1492
+ command: hookCommand,
1493
+ cwd: options.repositoryPath,
1494
+ env: options.env,
1495
+ timeoutMs: options.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS2
1496
+ });
1497
+ }
1498
+ function normalizeHookCommand(command) {
1499
+ const trimmed = command.trim();
1500
+ if (trimmed.includes("/") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../") && !/\s/.test(trimmed)) {
1501
+ return `bash ./${trimmed}`;
1502
+ }
1503
+ return command;
1504
+ }
1505
+
1506
+ // ../core/src/observability/snapshot-builder.ts
1507
+ function buildProjectSnapshot(input) {
1508
+ const {
1509
+ project,
1510
+ activeRuns,
1511
+ allRuns,
1512
+ summary,
1513
+ lastTickAt,
1514
+ lastError,
1515
+ rateLimits
1516
+ } = input;
1517
+ const cumulativeTokenUsageByIssue = aggregateTokenUsageByIssue(
1518
+ allRuns ?? activeRuns
1519
+ );
1520
+ return {
1521
+ repository: project.repository,
1522
+ tracker: {
1523
+ adapter: project.tracker.adapter,
1524
+ bindingId: project.tracker.bindingId,
1525
+ settings: project.tracker.settings
1526
+ },
1527
+ lastTickAt,
1528
+ health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
1529
+ summary: {
1530
+ dispatched: summary.dispatched,
1531
+ suppressed: summary.suppressed,
1532
+ recovered: summary.recovered,
1533
+ activeRuns: activeRuns.length
1534
+ },
1535
+ activeRuns: activeRuns.map((run) => ({
1536
+ runId: run.runId,
1537
+ issueIdentifier: run.issueIdentifier,
1538
+ issueState: run.issueState,
1539
+ status: run.status,
1540
+ retryKind: run.retryKind,
1541
+ port: run.port,
1542
+ runtimeSession: run.runtimeSession ?? null,
1543
+ // New fields from live worker data
1544
+ processId: run.processId ?? null,
1545
+ turnCount: run.turnCount,
1546
+ startedAt: run.startedAt ?? null,
1547
+ lastEvent: run.lastEvent ?? null,
1548
+ lastEventAt: run.lastEventAt ?? null,
1549
+ executionPhase: run.executionPhase ?? null,
1550
+ runPhase: run.runPhase ?? null,
1551
+ tokenUsage: attachCumulativeTokenUsage(
1552
+ run.tokenUsage,
1553
+ cumulativeTokenUsageByIssue.get(run.issueId)
1554
+ )
1555
+ })),
1556
+ retryQueue: activeRuns.filter((run) => run.status === "retrying" && run.retryKind).map((run) => ({
1557
+ runId: run.runId,
1558
+ issueIdentifier: run.issueIdentifier,
1559
+ retryKind: run.retryKind ?? "failure",
1560
+ nextRetryAt: run.nextRetryAt
1561
+ })),
1562
+ recovery: findLatestRecovery([...allRuns ?? [], ...activeRuns]),
1563
+ lastError,
1564
+ codexTotals: aggregateTokenUsage(allRuns ?? activeRuns, lastTickAt),
1565
+ rateLimits: rateLimits ?? null
1566
+ };
1567
+ }
1568
+ function findLatestRecovery(runs) {
1569
+ return [...runs].filter((run) => isUnresolvedRecoveryRun(run, runs)).sort((left, right) => {
1570
+ const leftTime = new Date(left.updatedAt).getTime();
1571
+ const rightTime = new Date(right.updatedAt).getTime();
1572
+ return rightTime - leftTime;
1573
+ }).find((run) => run.recovery)?.recovery ?? null;
1574
+ }
1575
+ function isUnresolvedRecoveryRun(run, runs) {
1576
+ if (!run.recovery) {
1577
+ return false;
1578
+ }
1579
+ if (run.status === "suppressed" && runs.some(
1580
+ (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"
1581
+ )) {
1582
+ return false;
1583
+ }
1584
+ return run.status === "suppressed" || run.retryKind === "recovery" && (run.status === "running" || run.status === "retrying");
1585
+ }
1586
+ function aggregateTokenUsageByIssue(runs) {
1587
+ const totals = /* @__PURE__ */ new Map();
1588
+ for (const run of runs) {
1589
+ if (!run.tokenUsage) {
1590
+ continue;
1591
+ }
1592
+ const current = totals.get(run.issueId) ?? {
1593
+ inputTokens: 0,
1594
+ outputTokens: 0,
1595
+ totalTokens: 0
1596
+ };
1597
+ current.inputTokens += run.tokenUsage.inputTokens;
1598
+ current.outputTokens += run.tokenUsage.outputTokens;
1599
+ current.totalTokens += run.tokenUsage.totalTokens;
1600
+ totals.set(run.issueId, current);
1601
+ }
1602
+ return totals;
1603
+ }
1604
+ function attachCumulativeTokenUsage(tokenUsage, cumulative) {
1605
+ if (!tokenUsage) {
1606
+ return void 0;
1607
+ }
1608
+ return {
1609
+ ...tokenUsage,
1610
+ cumulativeInputTokens: cumulative?.inputTokens ?? tokenUsage.inputTokens,
1611
+ cumulativeOutputTokens: cumulative?.outputTokens ?? tokenUsage.outputTokens,
1612
+ cumulativeTotalTokens: cumulative?.totalTokens ?? tokenUsage.totalTokens
1613
+ };
1614
+ }
1615
+ function aggregateTokenUsage(runs, lastTickAt) {
1616
+ let inputTokens = 0;
1617
+ let outputTokens = 0;
1618
+ let totalTokens = 0;
1619
+ let earliestStart = null;
1620
+ let latestEnd = null;
1621
+ for (const run of runs) {
1622
+ if (run.tokenUsage) {
1623
+ inputTokens += run.tokenUsage.inputTokens;
1624
+ outputTokens += run.tokenUsage.outputTokens;
1625
+ totalTokens += run.tokenUsage.totalTokens;
1626
+ }
1627
+ if (run.startedAt) {
1628
+ const start = new Date(run.startedAt).getTime();
1629
+ if (earliestStart === null || start < earliestStart) {
1630
+ earliestStart = start;
1631
+ }
1632
+ }
1633
+ const end = run.completedAt ? new Date(run.completedAt).getTime() : new Date(lastTickAt).getTime();
1634
+ if (latestEnd === null || end > latestEnd) {
1635
+ latestEnd = end;
1636
+ }
1637
+ }
1638
+ const secondsRunning = earliestStart !== null && latestEnd !== null ? Math.max(0, Math.round((latestEnd - earliestStart) / 1e3)) : 0;
1639
+ return { inputTokens, outputTokens, totalTokens, secondsRunning };
1640
+ }
1641
+
1642
+ // ../core/src/observability/fs-reader.ts
1643
+ import { readFile as readFile3, readdir } from "fs/promises";
1644
+ async function readJsonFile(path) {
1645
+ try {
1646
+ const raw = await readFile3(path, "utf8");
1647
+ return JSON.parse(raw);
1648
+ } catch (error) {
1649
+ if (isFileMissing(error)) {
1650
+ return null;
1651
+ }
1652
+ throw error;
1653
+ }
1654
+ }
1655
+ async function safeReadDir(path) {
1656
+ try {
1657
+ return await readdir(path);
1658
+ } catch (error) {
1659
+ if (isFileMissing(error)) {
1660
+ return [];
1661
+ }
1662
+ throw error;
1663
+ }
1664
+ }
1665
+ function isFileMissing(error) {
1666
+ return Boolean(
1667
+ error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
1668
+ );
1669
+ }
1670
+
1671
+ // ../core/src/observability/event-formatter.ts
1672
+ function formatEventMessage(event) {
1673
+ switch (event.event) {
1674
+ case "tracker.list":
1675
+ return `Tracker list saw ${event.issue.identifier}`;
1676
+ case "tracker.fetchByIds":
1677
+ return `Tracker fetch refreshed ${event.issue.identifier}`;
1678
+ case "run-dispatched":
1679
+ return event.issueState ? `Dispatched from ${event.issueState}` : "Dispatched";
1680
+ case "run-recovered":
1681
+ return "Recovered existing run";
1682
+ case "run-retried":
1683
+ return `Retry ${event.attempt} scheduled (${event.retryKind})`;
1684
+ case "run-failed":
1685
+ return event.lastError;
1686
+ case "run-suppressed":
1687
+ return event.reason;
1688
+ case "hook-executed":
1689
+ return `${event.hook}: ${event.outcome}`;
1690
+ case "hook-failed":
1691
+ return event.error;
1692
+ case "workspace-cleanup":
1693
+ return event.error ? `${event.outcome}: ${event.error}` : event.outcome;
1694
+ case "worker-error":
1695
+ return event.error;
1696
+ case "turn_started":
1697
+ return `Turn ${event.turnCount} started`;
1698
+ case "turn_completed":
1699
+ return `Turn ${event.turnCount} completed in ${event.durationMs}ms`;
1700
+ case "turn_failed":
1701
+ return event.error ?? `Turn ${event.turnCount} failed`;
1702
+ case "session_invalidated":
1703
+ return event.reason;
1704
+ default:
1705
+ return null;
1706
+ }
1707
+ }
1708
+ function parseRecentEvents(raw, limit, options) {
1709
+ const lines = raw.split("\n");
1710
+ if (options.allowPartialFirstLine) {
1711
+ lines.shift();
1712
+ }
1713
+ const events = [];
1714
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1715
+ const line = lines[index]?.trim();
1716
+ if (!line) {
1717
+ continue;
1718
+ }
1719
+ const event = parseRunEventLine(line);
1720
+ if (!event) {
1721
+ continue;
1722
+ }
1723
+ events.push({
1724
+ at: event.at,
1725
+ event: event.event,
1726
+ message: formatEventMessage(event)
1727
+ });
1728
+ if (events.length === limit) {
1729
+ break;
1730
+ }
1731
+ }
1732
+ return events.reverse();
1733
+ }
1734
+ function parseRunEventLine(line) {
1735
+ try {
1736
+ return JSON.parse(line);
1737
+ } catch {
1738
+ return null;
1739
+ }
1740
+ }
1741
+
1742
+ // ../core/src/observability/redaction.ts
1743
+ var REDACTED = "[REDACTED]";
1744
+ var SENSITIVE_KEY_SUBSTRINGS = [
1745
+ "authorization",
1746
+ "secret",
1747
+ "apiKey",
1748
+ "api-key",
1749
+ "api_key"
1750
+ ];
1751
+ function redactObservabilitySecrets(value) {
1752
+ return redactObservabilitySecretsWithStats(value).value;
1753
+ }
1754
+ function redactObservabilitySecretsWithStats(value) {
1755
+ const counts = createRedactionCounts();
1756
+ const redacted = redactValue(value, counts, {
1757
+ redactStringValues: false
1758
+ });
1759
+ return { value: redacted, redactions: summarizeRedactionCounts(counts) };
1760
+ }
1761
+ function redactObservabilityDiagnosticsWithStats(value) {
1762
+ const counts = createRedactionCounts();
1763
+ const redacted = redactValue(value, counts, {
1764
+ redactStringValues: true
1765
+ });
1766
+ return { value: redacted, redactions: summarizeRedactionCounts(counts) };
1767
+ }
1768
+ function redactObservabilityTextWithStats(text) {
1769
+ const counts = createRedactionCounts();
1770
+ return {
1771
+ value: redactTextValue(text, counts),
1772
+ redactions: summarizeRedactionCounts(counts)
1773
+ };
1774
+ }
1775
+ function redactValue(value, counts, options) {
1776
+ if (Array.isArray(value)) {
1777
+ return value.map((item) => redactValue(item, counts, options));
1778
+ }
1779
+ if (typeof value === "string" && options.redactStringValues) {
1780
+ return redactTextValue(value, counts);
1781
+ }
1782
+ if (!isRecord3(value)) {
1783
+ return value;
1784
+ }
1785
+ return Object.fromEntries(
1786
+ Object.entries(value).map(([key, nested]) => {
1787
+ const redactionClass = redactionClassForKey(key);
1788
+ if (redactionClass) {
1789
+ incrementRedaction(counts, redactionClass);
1790
+ return [key, REDACTED];
1791
+ }
1792
+ return [key, redactValue(nested, counts, options)];
1793
+ })
1794
+ );
1795
+ }
1796
+ function redactionClassForKey(key) {
1797
+ const normalizedKey = key.toLowerCase();
1798
+ if (normalizedKey.includes("authorization")) {
1799
+ return "authorization_header";
1800
+ }
1801
+ if (normalizedKey.includes("apikey") || normalizedKey.includes("api-key") || normalizedKey.includes("api_key")) {
1802
+ return "api_key";
1803
+ }
1804
+ if (normalizedKey.includes("secret")) {
1805
+ return "secret_key";
1806
+ }
1807
+ if (normalizedKey === "token" || normalizedKey.endsWith("token")) {
1808
+ return "env_token";
1809
+ }
1810
+ if (SENSITIVE_KEY_SUBSTRINGS.some(
1811
+ (pattern) => normalizedKey.includes(pattern.toLowerCase())
1812
+ )) {
1813
+ return "secret_key";
1814
+ }
1815
+ return null;
1816
+ }
1817
+ function isRecord3(value) {
1818
+ return value != null && typeof value === "object";
1819
+ }
1820
+ function redactTextValue(text, counts) {
1821
+ let redacted = replaceAndCount(
1822
+ text,
1823
+ /\b(Authorization\s*:\s*Bearer\s+)([^\s]+)/gi,
1824
+ "authorization_header",
1825
+ counts,
1826
+ "$1[REDACTED]"
1827
+ );
1828
+ redacted = replaceAndCount(
1829
+ redacted,
1830
+ /\b(X-API-Key\s*:\s*)([^\s]+)/gi,
1831
+ "api_key",
1832
+ counts,
1833
+ "$1[REDACTED]"
1834
+ );
1835
+ redacted = replaceAndCount(
1836
+ redacted,
1837
+ /^([A-Z0-9_]*(?:TOKEN)\w*\s*=\s*)([^\s]+)/gim,
1838
+ "env_token",
1839
+ counts,
1840
+ "$1[REDACTED]"
1841
+ );
1842
+ redacted = replaceAndCount(
1843
+ redacted,
1844
+ /^([A-Z0-9_]*(?:API_KEY)\w*\s*=\s*)([^\s]+)/gim,
1845
+ "api_key",
1846
+ counts,
1847
+ "$1[REDACTED]"
1848
+ );
1849
+ redacted = replaceAndCount(
1850
+ redacted,
1851
+ /^([A-Z0-9_]*(?:SECRET)\w*\s*=\s*)([^\s]+)/gim,
1852
+ "secret_key",
1853
+ counts,
1854
+ "$1[REDACTED]"
1855
+ );
1856
+ redacted = replaceAndCount(
1857
+ redacted,
1858
+ /((?:"token"|'token'|token)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/gi,
1859
+ "env_token",
1860
+ counts,
1861
+ '$1"[REDACTED]"'
1862
+ );
1863
+ redacted = replaceAndCount(
1864
+ redacted,
1865
+ /((?:"secret"|'secret'|secret)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/gi,
1866
+ "secret_key",
1867
+ counts,
1868
+ '$1"[REDACTED]"'
1869
+ );
1870
+ redacted = replaceAndCount(
1871
+ redacted,
1872
+ /((?:"apiKey"|'apiKey'|apiKey)\s*:\s*)(?:"([^"]*)"|'([^']*)'|([^\s,}\]]+))/g,
1873
+ "api_key",
1874
+ counts,
1875
+ '$1"[REDACTED]"'
1876
+ );
1877
+ redacted = replaceAndCount(
1878
+ redacted,
1879
+ /\bghp_[A-Za-z0-9_]+/g,
1880
+ "env_token",
1881
+ counts,
1882
+ "[REDACTED]"
1883
+ );
1884
+ redacted = replaceAndCount(
1885
+ redacted,
1886
+ /\blin_[A-Za-z0-9_]+/g,
1887
+ "api_key",
1888
+ counts,
1889
+ "[REDACTED]"
1890
+ );
1891
+ redacted = replaceAndCount(
1892
+ redacted,
1893
+ /\bsk-[A-Za-z0-9_-]+/g,
1894
+ "api_key",
1895
+ counts,
1896
+ "[REDACTED]"
1897
+ );
1898
+ return redacted;
1899
+ }
1900
+ function replaceAndCount(text, pattern, redactionClass, counts, replacement) {
1901
+ return text.replace(pattern, (...args) => {
1902
+ const matched = typeof args[0] === "string" ? args[0] : "";
1903
+ if (matched.includes(REDACTED)) {
1904
+ return matched;
1905
+ }
1906
+ incrementRedaction(counts, redactionClass);
1907
+ return replacement.replace(/\$(\d+)/g, (_placeholder, index) => {
1908
+ const group = args[Number.parseInt(index, 10)];
1909
+ return typeof group === "string" ? group : "";
1910
+ });
1911
+ });
1912
+ }
1913
+ function createRedactionCounts() {
1914
+ return /* @__PURE__ */ new Map();
1915
+ }
1916
+ function incrementRedaction(counts, redactionClass) {
1917
+ counts.set(redactionClass, (counts.get(redactionClass) ?? 0) + 1);
1918
+ }
1919
+ function summarizeRedactionCounts(counts) {
1920
+ return Array.from(counts.entries()).filter(([, count]) => count > 0).map(([redactionClass, count]) => ({ class: redactionClass, count })).sort((left, right) => left.class.localeCompare(right.class));
1921
+ }
1922
+
1923
+ // ../core/src/observability/status-assembler.ts
1924
+ function isMatchingIssueRun(run, issueId, issueIdentifier) {
1925
+ return Boolean(
1926
+ run && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1927
+ );
1928
+ }
1929
+ function mapIssueOrchestrationStateToStatus(state) {
1930
+ switch (state) {
1931
+ case "claimed":
1932
+ return "starting";
1933
+ case "running":
1934
+ return "running";
1935
+ case "retry_queued":
1936
+ return "retrying";
1937
+ case "released":
1938
+ return "released";
1939
+ case "unclaimed":
1940
+ return "pending";
1941
+ default:
1942
+ return state;
1943
+ }
1944
+ }
1945
+
1946
+ export {
1947
+ isOrchestratorChannelEvent,
1948
+ DEFAULT_WORKFLOW_LIFECYCLE,
1949
+ isStateActive,
1950
+ isStateTerminal,
1951
+ matchesWorkflowState,
1952
+ DEFAULT_LINEAR_GRAPHQL_URL,
1953
+ DEFAULT_MAX_FAILURE_RETRIES,
1954
+ resolveWorkflowRuntimeCommand,
1955
+ resolveWorkflowRuntimeTimeouts,
1956
+ parseWorkflowMarkdown,
1957
+ WorkflowConfigStore,
1958
+ createDefaultWorkflowResolution,
1959
+ createInvalidWorkflowResolution,
1960
+ buildPromptVariables,
1961
+ renderPrompt,
1962
+ classifySessionExit,
1963
+ scheduleRetryAt,
1964
+ extractEnvForCodex,
1965
+ extractEnvForClaude,
1966
+ shouldReuseAgentCredentialCache,
1967
+ readAgentCredentialCache,
1968
+ writeAgentCredentialCache,
1969
+ DEFAULT_AGENT_INPUT_REQUIRED_REASON,
1970
+ buildAgentInputRequiredReason,
1971
+ readEnvFile,
1972
+ deriveIssueWorkspaceKeyFromIdentifier,
1973
+ deriveIssueWorkspaceKey,
1974
+ deriveLegacyIssueWorkspaceKey,
1975
+ resolveIssueWorkspaceDirectory,
1976
+ buildHookEnv,
1977
+ executeWorkspaceHook,
1978
+ buildProjectSnapshot,
1979
+ readJsonFile,
1980
+ safeReadDir,
1981
+ isFileMissing,
1982
+ formatEventMessage,
1983
+ parseRecentEvents,
1984
+ redactObservabilitySecrets,
1985
+ redactObservabilityDiagnosticsWithStats,
1986
+ redactObservabilityTextWithStats,
1987
+ isMatchingIssueRun,
1988
+ mapIssueOrchestrationStateToStatus,
1989
+ hasVerboseFlag,
1990
+ formatErrorForTerminal
1991
+ };