@gh-symphony/cli 0.0.19 → 0.0.21

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,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ resolveTrackerAdapter
4
+ } from "./chunk-SXGT7LOF.js";
2
5
  import {
3
6
  DEFAULT_MAX_FAILURE_RETRIES,
4
7
  DEFAULT_WORKFLOW_LIFECYCLE,
@@ -24,11 +27,13 @@ import {
24
27
  readJsonFile,
25
28
  renderPrompt,
26
29
  resolveIssueWorkspaceDirectory,
30
+ resolveWorkflowRuntimeCommand,
31
+ resolveWorkflowRuntimeTimeouts,
27
32
  safeReadDir,
28
33
  scheduleRetryAt
29
- } from "./chunk-M3IFVLQS.js";
34
+ } from "./chunk-QEONJ5DZ.js";
30
35
 
31
- // ../orchestrator/dist/service.js
36
+ // ../orchestrator/src/service.ts
32
37
  import { mkdir as mkdir3, readFile as readFile3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
33
38
  import { createWriteStream, mkdirSync } from "fs";
34
39
  import { spawn as spawn2 } from "child_process";
@@ -36,10 +41,18 @@ import { join as join3 } from "path";
36
41
  import { StringDecoder } from "string_decoder";
37
42
  import { fileURLToPath } from "url";
38
43
 
39
- // ../orchestrator/dist/git.js
44
+ // ../orchestrator/src/git.ts
40
45
  import { spawn } from "child_process";
41
46
  import { randomUUID } from "crypto";
42
- import { access, mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
47
+ import {
48
+ access,
49
+ mkdir,
50
+ readFile,
51
+ rename,
52
+ rm,
53
+ stat,
54
+ writeFile
55
+ } from "fs/promises";
43
56
  import { constants } from "fs";
44
57
  import { join } from "path";
45
58
  var workflowConfigStore = new WorkflowConfigStore();
@@ -81,7 +94,10 @@ async function syncRepositoryForRun(input) {
81
94
  } else {
82
95
  await rm(repositoryDirectory, { recursive: true, force: true });
83
96
  }
84
- const tempRepositoryDirectory = join(input.targetDirectory, `repository.tmp-${process.pid}-${Date.now()}`);
97
+ const tempRepositoryDirectory = join(
98
+ input.targetDirectory,
99
+ `repository.tmp-${process.pid}-${Date.now()}`
100
+ );
85
101
  await rm(tempRepositoryDirectory, { recursive: true, force: true });
86
102
  try {
87
103
  await runCommand("git", [
@@ -115,7 +131,10 @@ async function loadRepositoryWorkflow(repositoryDirectory, _repository) {
115
131
  if (isMissingFileError(error)) {
116
132
  return createDefaultWorkflowResolution();
117
133
  }
118
- return createInvalidWorkflowResolution(workflowPath, error instanceof Error ? error.message : "workflow_parse_error");
134
+ return createInvalidWorkflowResolution(
135
+ workflowPath,
136
+ error instanceof Error ? error.message : "workflow_parse_error"
137
+ );
119
138
  }
120
139
  }
121
140
  function runCommand(command, args) {
@@ -133,7 +152,11 @@ function runCommand(command, args) {
133
152
  resolve4();
134
153
  return;
135
154
  }
136
- reject(new Error(stderr.trim() || `${command} exited with code ${code ?? "unknown"}`));
155
+ reject(
156
+ new Error(
157
+ stderr.trim() || `${command} exited with code ${code ?? "unknown"}`
158
+ )
159
+ );
137
160
  });
138
161
  });
139
162
  }
@@ -168,7 +191,11 @@ function runCommandCapture(command, args) {
168
191
  resolve4(stdout.trim());
169
192
  return;
170
193
  }
171
- reject(new Error(stderr.trim() || `${command} exited with code ${code ?? "unknown"}`));
194
+ reject(
195
+ new Error(
196
+ stderr.trim() || `${command} exited with code ${code ?? "unknown"}`
197
+ )
198
+ );
172
199
  });
173
200
  });
174
201
  }
@@ -186,9 +213,13 @@ async function acquireRepositoryLock(lockDirectory) {
186
213
  for (; ; ) {
187
214
  try {
188
215
  await mkdir(lockDirectory);
189
- await writeFile(join(lockDirectory, "owner"), `${ownerToken}
216
+ await writeFile(
217
+ join(lockDirectory, "owner"),
218
+ `${ownerToken}
190
219
  ${(/* @__PURE__ */ new Date()).toISOString()}
191
- `, "utf8");
220
+ `,
221
+ "utf8"
222
+ );
192
223
  return ownerToken;
193
224
  } catch (error) {
194
225
  if (!isAlreadyExistsError(error)) {
@@ -201,7 +232,9 @@ ${(/* @__PURE__ */ new Date()).toISOString()}
201
232
  continue;
202
233
  }
203
234
  if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
204
- throw new Error(`Timed out waiting for repository cache lock: ${lockDirectory}`);
235
+ throw new Error(
236
+ `Timed out waiting for repository cache lock: ${lockDirectory}`
237
+ );
205
238
  }
206
239
  await wait(LOCK_RETRY_MS);
207
240
  }
@@ -232,7 +265,9 @@ async function isStaleLock(lockDirectory) {
232
265
  }
233
266
  }
234
267
  function isAlreadyExistsError(error) {
235
- return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
268
+ return Boolean(
269
+ error && typeof error === "object" && "code" in error && error.code === "EEXIST"
270
+ );
236
271
  }
237
272
  async function readLockOwner(lockDirectory) {
238
273
  await access(join(lockDirectory, "owner"), constants.R_OK);
@@ -245,21 +280,30 @@ function wait(ms) {
245
280
  });
246
281
  }
247
282
  function isMissingFileError(error) {
248
- return Boolean(error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"));
283
+ return Boolean(
284
+ error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
285
+ );
249
286
  }
250
287
 
251
- // ../orchestrator/dist/fs-store.js
252
- import { mkdir as mkdir2, open, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2, appendFile } from "fs/promises";
288
+ // ../orchestrator/src/fs-store.ts
289
+ import {
290
+ mkdir as mkdir2,
291
+ open,
292
+ rename as rename2,
293
+ rm as rm2,
294
+ stat as stat2,
295
+ writeFile as writeFile2,
296
+ appendFile
297
+ } from "fs/promises";
253
298
  import { dirname, join as join2, relative, resolve } from "path";
254
299
  var OrchestratorFsStore = class {
255
- runtimeRoot;
256
- resolvedRuntimeRoot;
257
- resolvedEventsMirrorRoot;
258
300
  constructor(runtimeRoot, options = {}) {
259
301
  this.runtimeRoot = runtimeRoot;
260
302
  this.resolvedRuntimeRoot = resolve(runtimeRoot);
261
303
  this.resolvedEventsMirrorRoot = options.eventsMirrorRoot ? resolve(options.eventsMirrorRoot) : null;
262
304
  }
305
+ resolvedRuntimeRoot;
306
+ resolvedEventsMirrorRoot;
263
307
  projectsRoot() {
264
308
  return join2(this.runtimeRoot, "projects");
265
309
  }
@@ -276,10 +320,15 @@ var OrchestratorFsStore = class {
276
320
  return join2(this.projectRunsDir(projectId), runId);
277
321
  }
278
322
  async loadProjectConfig(projectId) {
279
- return readJsonFile(join2(this.projectDir(projectId), "project.json"));
323
+ return readJsonFile(
324
+ join2(this.projectDir(projectId), "project.json")
325
+ );
280
326
  }
281
327
  async saveProjectConfig(config) {
282
- await writeJsonFile(join2(this.projectDir(config.projectId), "project.json"), config);
328
+ await writeJsonFile(
329
+ join2(this.projectDir(config.projectId), "project.json"),
330
+ config
331
+ );
283
332
  }
284
333
  async loadProjectIssueOrchestrations(projectId) {
285
334
  const issuesPath = join2(this.projectDir(projectId), "issues.json");
@@ -295,53 +344,78 @@ var OrchestratorFsStore = class {
295
344
  if (legacyLeases.length === 0) {
296
345
  return [];
297
346
  }
298
- const migratedIssues = legacyLeases.map((lease) => ({
299
- issueId: lease.issueId,
300
- identifier: lease.issueIdentifier,
301
- workspaceKey: deriveIssueWorkspaceKeyFromIdentifier(lease.issueIdentifier),
302
- completedOnce: false,
303
- failureRetryCount: 0,
304
- state: lease.status === "active" ? "claimed" : "released",
305
- currentRunId: lease.status === "active" ? lease.runId : null,
306
- retryEntry: null,
307
- updatedAt: lease.updatedAt
308
- }));
347
+ const migratedIssues = legacyLeases.map(
348
+ (lease) => ({
349
+ issueId: lease.issueId,
350
+ identifier: lease.issueIdentifier,
351
+ workspaceKey: deriveIssueWorkspaceKeyFromIdentifier(
352
+ lease.issueIdentifier
353
+ ),
354
+ completedOnce: false,
355
+ failureRetryCount: 0,
356
+ state: lease.status === "active" ? "claimed" : "released",
357
+ currentRunId: lease.status === "active" ? lease.runId : null,
358
+ retryEntry: null,
359
+ updatedAt: lease.updatedAt
360
+ })
361
+ );
309
362
  await this.saveProjectIssueOrchestrations(projectId, migratedIssues);
310
363
  return migratedIssues;
311
364
  }
312
365
  async saveProjectIssueOrchestrations(projectId, issues) {
313
- await writeJsonFile(join2(this.projectDir(projectId), "issues.json"), issues);
366
+ await writeJsonFile(
367
+ join2(this.projectDir(projectId), "issues.json"),
368
+ issues
369
+ );
314
370
  }
315
371
  async saveProjectStatus(status) {
316
- await writeJsonFile(join2(this.projectDir(status.projectId), "status.json"), status);
372
+ await writeJsonFile(
373
+ join2(this.projectDir(status.projectId), "status.json"),
374
+ status
375
+ );
317
376
  }
318
377
  async loadProjectStatus(projectId) {
319
- return await readJsonFile(join2(this.projectDir(projectId), "status.json")) ?? null;
378
+ return await readJsonFile(
379
+ join2(this.projectDir(projectId), "status.json")
380
+ ) ?? null;
320
381
  }
321
382
  async loadRun(runId, projectId) {
322
383
  const runDirectory = projectId !== void 0 ? this.runDir(runId, projectId) : await this.findRunDir(runId);
323
384
  if (!runDirectory) {
324
385
  return null;
325
386
  }
326
- return await readJsonFile(join2(runDirectory, "run.json")) ?? null;
387
+ return await readJsonFile(
388
+ join2(runDirectory, "run.json")
389
+ ) ?? null;
327
390
  }
328
391
  async loadAllRuns() {
329
392
  const projectIds = await safeReadDir(this.projectsRoot());
330
- const runDirectories = await Promise.all(projectIds.map(async (projectId) => {
331
- const entries = await safeReadDir(this.projectRunsDir(projectId));
332
- return entries.map((entry) => this.runDir(entry, projectId));
333
- }));
334
- const runs = await Promise.all(runDirectories.flat().map((directory) => readJsonFile(join2(directory, "run.json"))));
393
+ const runDirectories = await Promise.all(
394
+ projectIds.map(async (projectId) => {
395
+ const entries = await safeReadDir(this.projectRunsDir(projectId));
396
+ return entries.map((entry) => this.runDir(entry, projectId));
397
+ })
398
+ );
399
+ const runs = await Promise.all(
400
+ runDirectories.flat().map(
401
+ (directory) => readJsonFile(join2(directory, "run.json"))
402
+ )
403
+ );
335
404
  return runs.filter((run) => Boolean(run));
336
405
  }
337
406
  async saveRun(run) {
338
- await writeJsonFile(join2(this.runDir(run.runId, run.projectId), "run.json"), run);
407
+ await writeJsonFile(
408
+ join2(this.runDir(run.runId, run.projectId), "run.json"),
409
+ run
410
+ );
339
411
  }
340
412
  async appendRunEvent(runId, event) {
341
413
  const resolvedProjectId = "projectId" in event && typeof event.projectId === "string" ? event.projectId : void 0;
342
414
  const runDirectory = resolvedProjectId !== void 0 ? this.runDir(runId, resolvedProjectId) : await this.findRunDir(runId);
343
415
  if (!runDirectory) {
344
- throw new Error(`Unable to resolve run directory for event append: ${runId}`);
416
+ throw new Error(
417
+ `Unable to resolve run directory for event append: ${runId}`
418
+ );
345
419
  }
346
420
  const path = join2(runDirectory, "events.ndjson");
347
421
  const resolvedPath = resolve(path);
@@ -362,7 +436,9 @@ var OrchestratorFsStore = class {
362
436
  mode: 420
363
437
  });
364
438
  } catch (error) {
365
- console.warn(`Failed to mirror orchestrator event log to ${mirrorPath}: ${error instanceof Error ? error.message : String(error)}`);
439
+ console.warn(
440
+ `Failed to mirror orchestrator event log to ${mirrorPath}: ${error instanceof Error ? error.message : String(error)}`
441
+ );
366
442
  }
367
443
  }
368
444
  async loadRecentRunEvents(runId, limit = 20, projectId) {
@@ -410,16 +486,28 @@ var OrchestratorFsStore = class {
410
486
  return join2(this.projectDir(projectId), "issues", workspaceKey);
411
487
  }
412
488
  async loadIssueWorkspace(projectId, workspaceKey) {
413
- return await readJsonFile(join2(this.issueWorkspaceDir(projectId, workspaceKey), "workspace.json")) ?? null;
489
+ return await readJsonFile(
490
+ join2(this.issueWorkspaceDir(projectId, workspaceKey), "workspace.json")
491
+ ) ?? null;
414
492
  }
415
493
  async loadIssueWorkspaces(projectId) {
416
494
  const issuesDir = join2(this.projectDir(projectId), "issues");
417
495
  const entries = await safeReadDir(issuesDir);
418
- const records = await Promise.all(entries.map((entry) => this.loadIssueWorkspace(projectId, entry)));
419
- return records.filter((record) => Boolean(record));
496
+ const records = await Promise.all(
497
+ entries.map((entry) => this.loadIssueWorkspace(projectId, entry))
498
+ );
499
+ return records.filter(
500
+ (record) => Boolean(record)
501
+ );
420
502
  }
421
503
  async saveIssueWorkspace(record) {
422
- await writeJsonFile(join2(this.issueWorkspaceDir(record.projectId, record.workspaceKey), "workspace.json"), record);
504
+ await writeJsonFile(
505
+ join2(
506
+ this.issueWorkspaceDir(record.projectId, record.workspaceKey),
507
+ "workspace.json"
508
+ ),
509
+ record
510
+ );
423
511
  }
424
512
  async removeIssueWorkspace(projectId, workspaceKey) {
425
513
  const dir = this.issueWorkspaceDir(projectId, workspaceKey);
@@ -429,7 +517,9 @@ var OrchestratorFsStore = class {
429
517
  const projectIds = await safeReadDir(this.projectsRoot());
430
518
  for (const projectId of projectIds) {
431
519
  const candidate = this.runDir(runId, projectId);
432
- const run = await readJsonFile(join2(candidate, "run.json"));
520
+ const run = await readJsonFile(
521
+ join2(candidate, "run.json")
522
+ );
433
523
  if (run || await pathExists(join2(candidate, "events.ndjson"))) {
434
524
  return candidate;
435
525
  }
@@ -466,848 +556,46 @@ async function pathExists(path) {
466
556
  }
467
557
  }
468
558
 
469
- // ../tracker-github/dist/adapter.js
470
- import { createHash } from "crypto";
471
- var DEFAULT_API_URL = "https://api.github.com/graphql";
472
- var DEFAULT_PAGE_SIZE = 25;
473
- var DEFAULT_NETWORK_TIMEOUT_MS = 3e4;
474
- var RATE_LIMIT_THRESHOLD = 100;
475
- var MAX_RATE_LIMIT_WAIT_MS = 6e4;
476
- var GitHubTrackerError = class extends Error {
477
- };
478
- var GitHubTrackerHttpError = class extends GitHubTrackerError {
479
- status;
480
- details;
481
- constructor(message, status, details) {
482
- super(message);
483
- this.status = status;
484
- this.details = details;
485
- }
486
- };
487
- var GitHubTrackerQueryError = class extends GitHubTrackerError {
488
- };
489
- var cachedGitHubGraphQLRateLimits = /* @__PURE__ */ new Map();
490
- function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, priority = {}, rateLimits = null) {
491
- if (item.content?.__typename !== "Issue") {
492
- return null;
493
- }
494
- const fieldValues = extractFieldValues(item.fieldValues?.nodes ?? []);
495
- const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
496
- const repository = item.content.repository;
497
- const blockedBy = (item.content.blockedBy?.nodes ?? []).flatMap((node) => node ? [
498
- {
499
- id: node.id,
500
- identifier: `${node.repository.owner.login}/${node.repository.name}#${node.number}`,
501
- state: normalizeBlockerState(node.state, lifecycle)
502
- }
503
- ] : []);
504
- const issueUpdatedAtMs = parseTimestampMs(item.content.updatedAt);
505
- const itemUpdatedAtMs = parseTimestampMs(item.updatedAt);
506
- const trackedUpdatedAt = itemUpdatedAtMs !== null && (issueUpdatedAtMs === null || itemUpdatedAtMs > issueUpdatedAtMs) ? item.updatedAt : item.content.updatedAt ?? item.updatedAt;
507
- return {
508
- id: item.content.id,
509
- identifier: `${repository.owner.login}/${repository.name}#${item.content.number}`,
510
- number: item.content.number,
511
- title: item.content.title,
512
- description: item.content.body,
513
- priority: resolvePriority(item, priority),
514
- state,
515
- branchName: null,
516
- url: item.content.url,
517
- labels: (item.content.labels?.nodes ?? []).flatMap((label) => label?.name ? [label.name.toLowerCase()] : []).sort(),
518
- blockedBy,
519
- createdAt: item.content.createdAt,
520
- updatedAt: trackedUpdatedAt,
521
- repository: {
522
- owner: repository.owner.login,
523
- name: repository.name,
524
- url: repository.url,
525
- cloneUrl: deriveCloneUrl(repository.url)
526
- },
527
- tracker: {
528
- adapter: "github-project",
529
- bindingId: projectId,
530
- itemId: item.id
531
- },
532
- metadata: fieldValues,
533
- rateLimits
534
- };
535
- }
536
- async function fetchProjectIssues(config, fetchImpl = fetch) {
537
- const issues = [];
538
- let cursor = null;
539
- const priorityOptionIds = config.priorityFieldName ? await fetchPriorityOptionOrder(config, config.priorityFieldName, fetchImpl) : void 0;
540
- const currentUserLogin = config.assignedOnly ? await fetchCurrentUserLogin(config, fetchImpl) : null;
541
- let excludedCount = 0;
542
- let latestRateLimits = null;
543
- do {
544
- const pageResult = await fetchProjectItemsPage(config, cursor, fetchImpl);
545
- const page = pageResult.page;
546
- latestRateLimits = pageResult.rateLimits ?? latestRateLimits;
547
- const pageIssues = (page.nodes ?? []).flatMap((item) => {
548
- if (!item) {
549
- return [];
550
- }
551
- const normalized = normalizeProjectItem(config.projectId, item, config.lifecycle, {
552
- fieldName: config.priorityFieldName,
553
- optionIds: priorityOptionIds
554
- }, latestRateLimits);
555
- if (!normalized) {
556
- return [];
557
- }
558
- if (currentUserLogin && !isIssueAssignedToLogin(item, currentUserLogin)) {
559
- excludedCount += 1;
560
- return [];
561
- }
562
- return [normalized];
563
- });
564
- issues.push(...pageIssues);
565
- cursor = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
566
- } while (cursor);
567
- if (currentUserLogin) {
568
- emitAssignedOnlyFilterEvent({
569
- projectId: config.projectId,
570
- currentUserLogin,
571
- includedCount: issues.length,
572
- excludedCount
573
- });
574
- }
575
- if (latestRateLimits) {
576
- for (const issue of issues) {
577
- issue.rateLimits = latestRateLimits;
578
- }
579
- }
580
- return issues;
581
- }
582
- async function fetchIssueStatesByIds(config, issueIds, fetchImpl = fetch) {
583
- if (issueIds.length === 0) {
584
- return [];
585
- }
586
- const issues = [];
587
- for (const issueIdBatch of chunkValues([...new Set(issueIds)], 100)) {
588
- const result = await executeGraphQLQueryWithMetadata(config, ISSUE_STATES_BY_IDS_QUERY, {
589
- issueIds: issueIdBatch
590
- }, fetchImpl);
591
- const data = result.data;
592
- const rateLimits = result.rateLimits;
593
- for (const node of data.nodes ?? []) {
594
- const projectItem = await resolveIssueProjectItemForStateLookup(config, node, fetchImpl);
595
- const normalized = normalizeIssueStateLookupNode(config.projectId, node, projectItem, config.lifecycle, rateLimits);
596
- if (normalized) {
597
- issues.push(normalized);
598
- }
599
- }
600
- }
601
- return issues;
602
- }
603
- async function fetchProjectItemsPage(config, cursor, fetchImpl) {
604
- const result = await executeGraphQLQueryWithMetadata(config, PROJECT_ITEMS_QUERY, {
605
- projectId: config.projectId,
606
- cursor,
607
- pageSize: config.pageSize ?? DEFAULT_PAGE_SIZE
608
- }, fetchImpl);
609
- const data = result.data;
610
- const items = data.node?.items;
611
- if (!items) {
612
- throw new GitHubTrackerQueryError("GitHub GraphQL response did not include project items.");
613
- }
614
- return {
615
- page: items,
616
- rateLimits: result.rateLimits
617
- };
618
- }
619
- var fetchGithubProjectIssues = fetchProjectIssues;
620
- var fetchGithubIssueStatesByIds = fetchIssueStatesByIds;
621
- async function fetchCurrentUserLogin(config, fetchImpl) {
622
- const response = await fetchImpl(resolveRestUserApiUrl(config.apiUrl), {
623
- method: "GET",
624
- headers: {
625
- authorization: `Bearer ${config.token}`,
626
- "user-agent": "gh-symphony",
627
- accept: "application/vnd.github+json"
628
- },
629
- signal: buildRequestSignal(config.timeoutMs)
630
- });
631
- if (!response.ok) {
632
- const details = await response.text();
633
- throw new GitHubTrackerHttpError(`GitHub REST request failed with status ${response.status}`, response.status, details);
634
- }
635
- const payload = await response.json();
636
- if (!payload.login) {
637
- throw new GitHubTrackerQueryError("GitHub REST response did not include the authenticated user login.");
638
- }
639
- return payload.login;
640
- }
641
- function isIssueAssignedToLogin(item, login) {
642
- if (item.content?.__typename !== "Issue") {
643
- return false;
644
- }
645
- return (item.content.assignees?.nodes ?? []).some((assignee) => assignee?.login === login);
646
- }
647
- function emitAssignedOnlyFilterEvent(input) {
648
- console.info(JSON.stringify({
649
- event: "tracker-assigned-only-filtered",
650
- projectId: input.projectId,
651
- currentUserLogin: input.currentUserLogin,
652
- includedCount: input.includedCount,
653
- excludedCount: input.excludedCount
654
- }));
655
- }
656
- function extractFieldValues(nodes) {
657
- return nodes.reduce((values, node) => {
658
- const fieldName = node?.field?.name;
659
- if (!fieldName) {
660
- return values;
661
- }
662
- if (node.__typename === "ProjectV2ItemFieldSingleSelectValue" && node.name) {
663
- values[fieldName] = node.name;
664
- }
665
- if (node.__typename === "ProjectV2ItemFieldTextValue" && node.text) {
666
- values[fieldName] = node.text;
667
- }
668
- return values;
669
- }, {});
670
- }
671
- function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, rateLimits = null) {
672
- if (issue?.__typename !== "Issue") {
673
- return null;
674
- }
675
- if (!projectItem) {
676
- return null;
677
- }
678
- const fieldValues = extractFieldValues(projectItem.fieldValues?.nodes ?? []);
679
- const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
680
- const repository = issue.repository;
681
- const identifier = `${repository.owner.login}/${repository.name}#${issue.number}`;
682
- return {
683
- id: issue.id,
684
- identifier,
685
- number: issue.number,
686
- title: identifier,
687
- description: null,
688
- priority: null,
689
- state,
690
- branchName: null,
691
- url: `${repository.url}/issues/${issue.number}`,
692
- labels: [],
693
- blockedBy: [],
694
- createdAt: null,
695
- updatedAt: projectItem.updatedAt ?? issue.updatedAt,
696
- repository: {
697
- owner: repository.owner.login,
698
- name: repository.name,
699
- url: repository.url,
700
- cloneUrl: deriveCloneUrl(repository.url)
701
- },
702
- tracker: {
703
- adapter: "github-project",
704
- bindingId: projectId,
705
- itemId: projectItem.id
706
- },
707
- metadata: fieldValues,
708
- rateLimits
709
- };
710
- }
711
- async function resolveIssueProjectItemForStateLookup(config, issue, fetchImpl) {
712
- if (issue?.__typename !== "Issue") {
713
- return null;
714
- }
715
- let connection = issue.projectItems;
716
- let projectItem = findProjectItemByProjectId(connection?.nodes ?? [], config.projectId);
717
- let cursor = connection?.pageInfo.endCursor ?? null;
718
- while (!projectItem && connection?.pageInfo.hasNextPage) {
719
- const nextPage = await fetchIssueProjectItemsPage(config, issue.id, cursor, fetchImpl);
720
- projectItem = findProjectItemByProjectId(nextPage.nodes ?? [], config.projectId);
721
- connection = nextPage;
722
- cursor = nextPage.pageInfo.endCursor;
723
- }
724
- return projectItem;
725
- }
726
- async function fetchIssueProjectItemsPage(config, issueId, cursor, fetchImpl) {
727
- const result = await executeGraphQLQueryWithMetadata(config, ISSUE_PROJECT_ITEMS_PAGE_QUERY, {
728
- issueId,
729
- cursor
730
- }, fetchImpl);
731
- const data = result.data;
732
- const issue = data.node;
733
- if (issue?.__typename !== "Issue" || !issue.projectItems) {
734
- throw new GitHubTrackerQueryError("GitHub GraphQL response did not include issue project items.");
735
- }
736
- return issue.projectItems;
737
- }
738
- function findProjectItemByProjectId(nodes, projectId) {
739
- return nodes.find((item) => item?.project?.id === projectId) ?? null;
740
- }
741
- function resolvePriority(item, priority) {
742
- if (!priority.fieldName || !priority.optionIds) {
743
- return null;
744
- }
745
- for (const node of item.fieldValues?.nodes ?? []) {
746
- if (node?.__typename === "ProjectV2ItemFieldSingleSelectValue" && node.field?.name === priority.fieldName && node.optionId) {
747
- return priority.optionIds[node.optionId] ?? null;
748
- }
749
- }
750
- return null;
751
- }
752
- function extractPriorityOptionOrder(fields, priorityFieldName) {
753
- for (const field of fields) {
754
- if (isSingleSelectProjectField(field) && field.name === priorityFieldName) {
755
- let nextPriority = 0;
756
- const optionEntries = (field.options ?? []).flatMap((option) => {
757
- if (!option?.id) {
758
- return [];
759
- }
760
- const entry = [option.id, nextPriority];
761
- nextPriority += 1;
762
- return [entry];
763
- });
764
- return Object.fromEntries(optionEntries);
765
- }
766
- }
767
- return void 0;
768
- }
769
- async function fetchPriorityOptionOrder(config, priorityFieldName, fetchImpl) {
770
- const data = await executeGraphQLQuery(config, PROJECT_FIELDS_QUERY, { projectId: config.projectId }, fetchImpl);
771
- return extractPriorityOptionOrder(data.node?.fields?.nodes ?? [], priorityFieldName);
772
- }
773
- function isSingleSelectProjectField(field) {
774
- return field?.__typename === "ProjectV2SingleSelectField";
775
- }
776
- function deriveCloneUrl(repositoryUrl) {
777
- if (repositoryUrl.startsWith("file://") || repositoryUrl.endsWith(".git")) {
778
- return repositoryUrl;
779
- }
780
- return `${repositoryUrl}.git`;
781
- }
782
- function normalizeBlockerState(state, lifecycle) {
783
- if (!state) {
784
- return null;
785
- }
786
- const normalized = state.trim().toLowerCase();
787
- if (normalized === "closed") {
788
- return lifecycle.terminalStates[0] ?? state;
789
- }
790
- if (normalized === "open") {
791
- return null;
792
- }
793
- return state;
794
- }
795
- function resolveRestUserApiUrl(apiUrl) {
796
- const parsed = new URL(apiUrl ?? DEFAULT_API_URL);
797
- const pathSegments = parsed.pathname.split("/").filter(Boolean);
798
- if (pathSegments.at(-1) === "graphql") {
799
- pathSegments.pop();
800
- }
801
- parsed.pathname = `/${pathSegments.join("/")}/user`.replace(/\/{2,}/g, "/");
802
- parsed.search = "";
803
- parsed.hash = "";
804
- return parsed.toString();
805
- }
806
- function chunkValues(values, size) {
807
- const chunks = [];
808
- for (let index = 0; index < values.length; index += size) {
809
- chunks.push(values.slice(index, index + size));
810
- }
811
- return chunks;
812
- }
813
- function buildRequestSignal(timeoutMs) {
814
- return AbortSignal.timeout(resolveNetworkTimeoutMs(timeoutMs));
815
- }
816
- function resolveNetworkTimeoutMs(timeoutMs) {
817
- if (timeoutMs !== void 0 && Number.isInteger(timeoutMs) && timeoutMs > 0) {
818
- return timeoutMs;
819
- }
820
- return DEFAULT_NETWORK_TIMEOUT_MS;
821
- }
822
- async function executeGraphQLQuery(config, query, variables, fetchImpl) {
823
- const result = await executeGraphQLQueryWithMetadata(config, query, variables, fetchImpl);
824
- return result.data;
825
- }
826
- async function executeGraphQLQueryWithMetadata(config, query, variables, fetchImpl) {
827
- const tokenFingerprint = fingerprintToken(config.token);
828
- await guardGraphQLRateLimit(tokenFingerprint);
829
- const response = await fetchImpl(config.apiUrl ?? DEFAULT_API_URL, {
830
- method: "POST",
831
- headers: {
832
- "content-type": "application/json",
833
- authorization: `Bearer ${config.token}`
834
- },
835
- body: JSON.stringify({
836
- query,
837
- variables
838
- }),
839
- signal: buildRequestSignal(config.timeoutMs)
840
- });
841
- if (!response.ok) {
842
- const details = await response.text();
843
- throw new GitHubTrackerHttpError(`GitHub GraphQL request failed with status ${response.status}`, response.status, details);
844
- }
845
- const payload = await response.json();
846
- if (payload.errors?.length) {
847
- throw new GitHubTrackerQueryError(payload.errors.map((error) => error.message).join("; "));
848
- }
849
- if (!payload.data) {
850
- throw new GitHubTrackerQueryError("GitHub GraphQL response did not include data.");
851
- }
852
- const data = payload.data;
853
- const rateLimits = extractGitHubRateLimits(response.headers);
854
- cachedGitHubGraphQLRateLimits.set(tokenFingerprint, rateLimits);
855
- return {
856
- data,
857
- rateLimits
858
- };
859
- }
860
- async function guardGraphQLRateLimit(tokenFingerprint) {
861
- const rateLimit = cachedGitHubGraphQLRateLimits.get(tokenFingerprint) ?? null;
862
- if (!rateLimit) {
863
- return;
864
- }
865
- const remaining = rateLimit.remaining;
866
- if (remaining === null || remaining > RATE_LIMIT_THRESHOLD) {
867
- return;
868
- }
869
- const resetAtMs = parseTimestampMs(rateLimit.resetAt);
870
- if (resetAtMs === null) {
871
- throw new GitHubTrackerError("Rate limit near exhaustion");
872
- }
873
- const waitMs = Math.max(0, resetAtMs - Date.now());
874
- if (waitMs > MAX_RATE_LIMIT_WAIT_MS) {
875
- throw new GitHubTrackerError("Rate limit near exhaustion");
876
- }
877
- cachedGitHubGraphQLRateLimits.delete(tokenFingerprint);
878
- if (waitMs > 0) {
879
- await sleep(waitMs);
880
- }
881
- }
882
- function fingerprintToken(token) {
883
- return createHash("sha256").update(token).digest("hex");
884
- }
885
- function extractGitHubRateLimits(headers) {
886
- if (!headers || typeof headers.get !== "function") {
887
- return null;
888
- }
889
- const limit = parseIntegerHeader(headers.get("x-ratelimit-limit"));
890
- const remaining = parseIntegerHeader(headers.get("x-ratelimit-remaining"));
891
- const used = parseIntegerHeader(headers.get("x-ratelimit-used"));
892
- const reset = parseIntegerHeader(headers.get("x-ratelimit-reset"));
893
- const resource = headers.get("x-ratelimit-resource");
894
- if (limit === null && remaining === null && used === null && reset === null && resource === null) {
895
- return null;
896
- }
897
- return {
898
- source: "github",
899
- limit,
900
- remaining,
901
- used,
902
- reset,
903
- resetAt: reset === null ? null : new Date(reset * 1e3).toISOString(),
904
- resource
905
- };
906
- }
907
- function parseIntegerHeader(value) {
908
- if (value === null) {
909
- return null;
910
- }
911
- const parsed = Number.parseInt(value, 10);
912
- return Number.isFinite(parsed) ? parsed : null;
913
- }
914
- function parseTimestampMs(value) {
915
- if (!value) {
916
- return null;
917
- }
918
- const timestampMs = Date.parse(value);
919
- return Number.isFinite(timestampMs) ? timestampMs : null;
920
- }
921
- function sleep(ms) {
922
- return new Promise((resolve4) => {
923
- setTimeout(resolve4, ms);
924
- });
925
- }
926
- var PROJECT_ITEMS_QUERY = `
927
- query ProjectItems($projectId: ID!, $cursor: String, $pageSize: Int!) {
928
- node(id: $projectId) {
929
- __typename
930
- ... on ProjectV2 {
931
- items(first: $pageSize, after: $cursor) {
932
- nodes {
933
- id
934
- updatedAt
935
- fieldValues(first: 20) {
936
- nodes {
937
- __typename
938
- ... on ProjectV2ItemFieldSingleSelectValue {
939
- name
940
- optionId
941
- field {
942
- ... on ProjectV2SingleSelectField {
943
- name
944
- }
945
- }
946
- }
947
- ... on ProjectV2ItemFieldTextValue {
948
- text
949
- field {
950
- ... on ProjectV2FieldCommon {
951
- name
952
- }
953
- }
954
- }
955
- }
956
- }
957
- content {
958
- __typename
959
- ... on Issue {
960
- id
961
- number
962
- title
963
- body
964
- url
965
- createdAt
966
- updatedAt
967
- labels(first: 20) {
968
- nodes {
969
- name
970
- }
971
- }
972
- assignees(first: 20) {
973
- nodes {
974
- login
975
- }
976
- }
977
- repository {
978
- name
979
- url
980
- owner {
981
- login
982
- }
983
- }
984
- blockedBy(first: 100) {
985
- nodes {
986
- id
987
- number
988
- state
989
- repository {
990
- name
991
- owner {
992
- login
993
- }
994
- }
995
- }
996
- }
997
- }
998
- }
999
- }
1000
- pageInfo {
1001
- endCursor
1002
- hasNextPage
1003
- }
1004
- }
1005
- }
1006
- }
1007
- }
1008
- `;
1009
- var PROJECT_FIELDS_QUERY = `
1010
- query ProjectFields($projectId: ID!) {
1011
- node(id: $projectId) {
1012
- __typename
1013
- ... on ProjectV2 {
1014
- fields(first: 100) {
1015
- nodes {
1016
- __typename
1017
- ... on ProjectV2SingleSelectField {
1018
- name
1019
- options {
1020
- id
1021
- name
1022
- }
1023
- }
1024
- }
1025
- }
1026
- }
1027
- }
1028
- }
1029
- `;
1030
- var ISSUE_STATES_BY_IDS_QUERY = `
1031
- query IssueStatesByIds($issueIds: [ID!]!) {
1032
- nodes(ids: $issueIds) {
1033
- __typename
1034
- ... on Issue {
1035
- id
1036
- number
1037
- updatedAt
1038
- repository {
1039
- name
1040
- url
1041
- owner {
1042
- login
1043
- }
1044
- }
1045
- projectItems(first: 100, includeArchived: false) {
1046
- nodes {
1047
- id
1048
- updatedAt
1049
- project {
1050
- id
1051
- }
1052
- fieldValues(first: 20) {
1053
- nodes {
1054
- __typename
1055
- ... on ProjectV2ItemFieldSingleSelectValue {
1056
- name
1057
- optionId
1058
- field {
1059
- ... on ProjectV2SingleSelectField {
1060
- name
1061
- }
1062
- }
1063
- }
1064
- ... on ProjectV2ItemFieldTextValue {
1065
- text
1066
- field {
1067
- ... on ProjectV2FieldCommon {
1068
- name
1069
- }
1070
- }
1071
- }
1072
- }
1073
- }
1074
- }
1075
- pageInfo {
1076
- endCursor
1077
- hasNextPage
1078
- }
1079
- }
1080
- }
1081
- }
1082
- }
1083
- `;
1084
- var ISSUE_PROJECT_ITEMS_PAGE_QUERY = `
1085
- query IssueProjectItemsPage($issueId: ID!, $cursor: String) {
1086
- node(id: $issueId) {
1087
- __typename
1088
- ... on Issue {
1089
- id
1090
- number
1091
- updatedAt
1092
- repository {
1093
- name
1094
- url
1095
- owner {
1096
- login
1097
- }
1098
- }
1099
- projectItems(first: 100, after: $cursor, includeArchived: false) {
1100
- nodes {
1101
- id
1102
- updatedAt
1103
- project {
1104
- id
1105
- }
1106
- fieldValues(first: 20) {
1107
- nodes {
1108
- __typename
1109
- ... on ProjectV2ItemFieldSingleSelectValue {
1110
- name
1111
- optionId
1112
- field {
1113
- ... on ProjectV2SingleSelectField {
1114
- name
1115
- }
1116
- }
1117
- }
1118
- ... on ProjectV2ItemFieldTextValue {
1119
- text
1120
- field {
1121
- ... on ProjectV2FieldCommon {
1122
- name
1123
- }
1124
- }
1125
- }
1126
- }
1127
- }
1128
- }
1129
- pageInfo {
1130
- endCursor
1131
- hasNextPage
1132
- }
1133
- }
1134
- }
1135
- }
1136
- }
1137
- `;
1138
-
1139
- // ../tracker-github/dist/orchestrator-adapter.js
1140
- import { createHash as createHash2 } from "crypto";
1141
- var githubProjectTrackerAdapter = {
1142
- async listIssues(project, dependencies = {}) {
1143
- return listProjectIssues(project, dependencies);
1144
- },
1145
- async listIssuesByStates(project, states, dependencies = {}) {
1146
- if (states.length === 0) {
1147
- return [];
1148
- }
1149
- const issues = await listProjectIssues(project, dependencies);
1150
- const normalizedStates = new Set(states.map((state) => state.trim().toLowerCase()));
1151
- return issues.filter((issue) => normalizedStates.has(issue.state.trim().toLowerCase()));
1152
- },
1153
- async fetchIssueStatesByIds(project, issueIds, dependencies = {}) {
1154
- if (issueIds.length === 0) {
1155
- return [];
1156
- }
1157
- return fetchProjectIssueStatesByIds(project, issueIds, dependencies);
1158
- },
1159
- buildWorkerEnvironment(project) {
1160
- return {
1161
- GITHUB_PROJECT_ID: requireTrackerSetting(project.tracker, "projectId")
1162
- };
1163
- },
1164
- reviveIssue(project, run) {
1165
- return {
1166
- id: run.issueId,
1167
- identifier: run.issueIdentifier,
1168
- number: parseIssueNumber(run.issueIdentifier),
1169
- title: run.issueTitle ?? run.issueIdentifier,
1170
- description: null,
1171
- priority: null,
1172
- state: run.issueState,
1173
- branchName: null,
1174
- url: null,
1175
- labels: [],
1176
- blockedBy: [],
1177
- createdAt: null,
1178
- updatedAt: null,
1179
- repository: run.repository,
1180
- tracker: {
1181
- adapter: "github-project",
1182
- bindingId: project.tracker.bindingId,
1183
- itemId: run.issueId
1184
- },
1185
- metadata: {}
1186
- };
1187
- }
1188
- };
1189
- async function listProjectIssues(project, dependencies = {}) {
1190
- const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
1191
- const loadProjectIssues = () => fetchGithubProjectIssues(trackerConfig, dependencies.fetchImpl);
1192
- return dependencies.projectItemsCache?.getOrLoad(buildProjectItemsCacheKey(trackerConfig, dependencies), loadProjectIssues) ?? loadProjectIssues();
1193
- }
1194
- async function fetchProjectIssueStatesByIds(project, issueIds, dependencies = {}) {
1195
- const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
1196
- return fetchGithubIssueStatesByIds(trackerConfig, [...issueIds], dependencies.fetchImpl);
1197
- }
1198
- function resolveGitHubTrackerConfig(project, dependencies = {}) {
1199
- const token = dependencies.token ?? process.env.GITHUB_GRAPHQL_TOKEN;
1200
- if (!token) {
1201
- throw new Error("GITHUB_GRAPHQL_TOKEN environment variable is required. Run 'gh auth token' or set the variable.");
1202
- }
1203
- const githubProjectId = requireTrackerSetting(project.tracker, "projectId");
1204
- return {
1205
- projectId: githubProjectId,
1206
- token,
1207
- apiUrl: project.tracker.apiUrl,
1208
- assignedOnly: readBooleanTrackerSetting(project.tracker, "assignedOnly"),
1209
- priorityFieldName: readOptionalStringTrackerSetting(project.tracker, "priorityFieldName"),
1210
- timeoutMs: readNumberTrackerSetting(project.tracker, "timeoutMs")
1211
- };
1212
- }
1213
- function buildProjectItemsCacheKey(config, _dependencies) {
1214
- return JSON.stringify({
1215
- adapter: "github-project",
1216
- apiUrl: config.apiUrl,
1217
- assignedOnly: config.assignedOnly ?? false,
1218
- priorityFieldName: config.priorityFieldName ?? null,
1219
- projectId: config.projectId,
1220
- timeoutMs: config.timeoutMs,
1221
- tokenFingerprint: hashToken(config.token)
1222
- });
1223
- }
1224
- function hashToken(token) {
1225
- if (!token) {
1226
- return null;
1227
- }
1228
- return createHash2("sha256").update(token).digest("hex");
1229
- }
1230
- var trackerAdapters = {
1231
- "github-project": githubProjectTrackerAdapter
1232
- };
1233
- function resolveTrackerAdapter(tracker) {
1234
- const adapter = trackerAdapters[tracker.adapter];
1235
- if (!adapter) {
1236
- throw new Error(`Unsupported tracker adapter: ${tracker.adapter}`);
1237
- }
1238
- return adapter;
1239
- }
1240
- function requireTrackerSetting(tracker, key) {
1241
- const value = tracker.settings?.[key];
1242
- if (typeof value !== "string" || value.length === 0) {
1243
- throw new Error(`Tracker adapter "${tracker.adapter}" requires the "${key}" setting.`);
1244
- }
1245
- return value;
1246
- }
1247
- function readBooleanTrackerSetting(tracker, key) {
1248
- const value = tracker.settings?.[key];
1249
- return value === true || value === "true";
1250
- }
1251
- function readNumberTrackerSetting(tracker, key) {
1252
- const value = tracker.settings?.[key];
1253
- if (value === void 0) {
1254
- return void 0;
1255
- }
1256
- if (typeof value === "number" && Number.isInteger(value) && value > 0) {
1257
- return value;
1258
- }
1259
- if (typeof value === "string") {
1260
- const parsed = Number(value);
1261
- if (Number.isInteger(parsed) && parsed > 0) {
1262
- return parsed;
1263
- }
1264
- }
1265
- throw new Error(`Tracker adapter "${tracker.adapter}" requires the "${key}" setting to be a positive integer when provided.`);
1266
- }
1267
- function readOptionalStringTrackerSetting(tracker, key) {
1268
- const value = tracker.settings?.[key];
1269
- return typeof value === "string" && value.length > 0 ? value : void 0;
1270
- }
1271
- function parseIssueNumber(identifier) {
1272
- const match = identifier.match(/#(\d+)$/);
1273
- return match ? Number.parseInt(match[1] ?? "0", 10) : 0;
1274
- }
1275
-
1276
- // ../tracker-file/dist/file-tracker-adapter.js
559
+ // ../tracker-file/src/file-tracker-adapter.ts
1277
560
  import { readFile as readFile2 } from "fs/promises";
1278
- function requireTrackerSetting2(project, key) {
561
+ function requireTrackerSetting(project, key) {
1279
562
  const value = project.tracker.settings?.[key];
1280
563
  if (typeof value !== "string" || value.length === 0) {
1281
- throw new Error(`Tracker adapter "file" requires the "${key}" setting.`);
564
+ throw new Error(
565
+ `Tracker adapter "file" requires the "${key}" setting.`
566
+ );
1282
567
  }
1283
568
  return value;
1284
569
  }
1285
- function parseIssueNumber2(identifier) {
570
+ function parseIssueNumber(identifier) {
1286
571
  const match = identifier.match(/#(\d+)$/);
1287
572
  return match ? Number.parseInt(match[1] ?? "0", 10) : 0;
1288
573
  }
1289
574
  function isValidIssueShape(entry) {
1290
- if (!entry || typeof entry !== "object")
1291
- return false;
575
+ if (!entry || typeof entry !== "object") return false;
1292
576
  const e = entry;
1293
577
  return typeof e.id === "string" && typeof e.identifier === "string" && typeof e.state === "string" && e.repository !== null && typeof e.repository === "object" && e.tracker !== null && typeof e.tracker === "object";
1294
578
  }
1295
579
  var fileTrackerAdapter = {
1296
580
  async listIssues(project) {
1297
- const issuesPath = requireTrackerSetting2(project, "issuesPath");
581
+ const issuesPath = requireTrackerSetting(project, "issuesPath");
1298
582
  try {
1299
583
  const raw = await readFile2(issuesPath, "utf-8");
1300
584
  const parsed = JSON.parse(raw);
1301
585
  if (!Array.isArray(parsed)) {
1302
- throw new Error(`Expected an array of issues in ${issuesPath}, got ${typeof parsed}`);
586
+ throw new Error(
587
+ `Expected an array of issues in ${issuesPath}, got ${typeof parsed}`
588
+ );
1303
589
  }
1304
590
  const valid = [];
1305
591
  for (let i = 0; i < parsed.length; i++) {
1306
592
  if (isValidIssueShape(parsed[i])) {
1307
593
  valid.push(parsed[i]);
1308
594
  } else {
1309
- process.stderr.write(`[tracker-file] Skipping invalid issue at index ${i} in ${issuesPath}
1310
- `);
595
+ process.stderr.write(
596
+ `[tracker-file] Skipping invalid issue at index ${i} in ${issuesPath}
597
+ `
598
+ );
1311
599
  }
1312
600
  }
1313
601
  return valid;
@@ -1326,8 +614,12 @@ var fileTrackerAdapter = {
1326
614
  return [];
1327
615
  }
1328
616
  const issues = await this.listIssues(project);
1329
- const normalizedStates = new Set(states.map((state) => state.trim().toLowerCase()));
1330
- return issues.filter((issue) => normalizedStates.has(issue.state.trim().toLowerCase()));
617
+ const normalizedStates = new Set(
618
+ states.map((state) => state.trim().toLowerCase())
619
+ );
620
+ return issues.filter(
621
+ (issue) => normalizedStates.has(issue.state.trim().toLowerCase())
622
+ );
1331
623
  },
1332
624
  async fetchIssueStatesByIds(project, issueIds) {
1333
625
  if (issueIds.length === 0) {
@@ -1346,7 +638,7 @@ var fileTrackerAdapter = {
1346
638
  return {
1347
639
  id: run.issueId,
1348
640
  identifier: run.issueIdentifier,
1349
- number: parseIssueNumber2(run.issueIdentifier),
641
+ number: parseIssueNumber(run.issueIdentifier),
1350
642
  title: run.issueTitle ?? run.issueIdentifier,
1351
643
  description: null,
1352
644
  priority: null,
@@ -1368,24 +660,21 @@ var fileTrackerAdapter = {
1368
660
  }
1369
661
  };
1370
662
 
1371
- // ../orchestrator/dist/tracker-adapters.js
663
+ // ../orchestrator/src/tracker-adapters.ts
1372
664
  var localAdapters = /* @__PURE__ */ new Map([
1373
665
  ["file", fileTrackerAdapter]
1374
666
  ]);
1375
667
  function resolveTrackerAdapter2(tracker) {
1376
668
  const local = localAdapters.get(tracker.adapter);
1377
- if (local)
1378
- return local;
669
+ if (local) return local;
1379
670
  return resolveTrackerAdapter(tracker);
1380
671
  }
1381
672
 
1382
- // ../orchestrator/dist/service.js
673
+ // ../orchestrator/src/service.ts
1383
674
  var DEFAULT_POLL_INTERVAL_MS = 3e4;
1384
675
  var DEFAULT_CONCURRENCY = 3;
1385
676
  var DEFAULT_RETRY_BACKOFF_MS = 3e4;
1386
677
  var CONTINUATION_RETRY_DELAY_MS = 1e3;
1387
- var DEFAULT_GLOBAL_MAX_TURNS = 100;
1388
- var DEFAULT_MAX_TOKENS = 256e3;
1389
678
  var DEFAULT_WORKER_COMMAND = "node packages/worker/dist/index.js";
1390
679
  var DEFAULT_MAX_NONPRODUCTIVE_TURNS = 3;
1391
680
  var LOW_RATE_LIMIT_WARNING_THRESHOLD = 0.05;
@@ -1394,7 +683,7 @@ var STUCK_WORKER_TIMEOUT_MS = 30 * 60 * 1e3;
1394
683
  function isUsableWorkflowResolution(resolution) {
1395
684
  return resolution.isValid || resolution.usedLastKnownGood;
1396
685
  }
1397
- function parseTimestampMs2(value) {
686
+ function parseTimestampMs(value) {
1398
687
  if (!value) {
1399
688
  return null;
1400
689
  }
@@ -1412,9 +701,11 @@ function parseFiniteNumber(value) {
1412
701
  return null;
1413
702
  }
1414
703
  var OrchestratorService = class {
1415
- store;
1416
- projectConfig;
1417
- dependencies;
704
+ constructor(store, projectConfig, dependencies = {}) {
705
+ this.store = store;
706
+ this.projectConfig = projectConfig;
707
+ this.dependencies = dependencies;
708
+ }
1418
709
  projectPollIntervals = /* @__PURE__ */ new Map();
1419
710
  activeWorkerPids = /* @__PURE__ */ new Set();
1420
711
  workerStderrBuffers = /* @__PURE__ */ new Map();
@@ -1429,23 +720,25 @@ var OrchestratorService = class {
1429
720
  sleepResolver = null;
1430
721
  reconcilePromise = Promise.resolve();
1431
722
  reconcileRequested = false;
1432
- constructor(store, projectConfig, dependencies = {}) {
1433
- this.store = store;
1434
- this.projectConfig = projectConfig;
1435
- this.dependencies = dependencies;
1436
- }
1437
723
  async run(options = {}) {
1438
724
  this.running = true;
1439
- await this.runSerialized(() => this.performStartupCleanup(this.createTrackerDependencies()));
725
+ await this.runSerialized(
726
+ () => this.performStartupCleanup(this.createTrackerDependencies())
727
+ );
1440
728
  while (this.running) {
1441
729
  try {
1442
- const snapshot = await this.runOnceInternal(options.issueIdentifier, this.createTrackerDependencies());
730
+ const snapshot = await this.runOnceInternal(
731
+ options.issueIdentifier,
732
+ this.createTrackerDependencies()
733
+ );
1443
734
  await this.notifyTick(snapshot);
1444
735
  } catch (error) {
1445
736
  if (options.once) {
1446
737
  throw error;
1447
738
  }
1448
- this.writeStderr(`[orchestrator] run loop failed for ${this.projectConfig.projectId}: ${this.formatErrorMessage(error)}`);
739
+ this.writeStderr(
740
+ `[orchestrator] run loop failed for ${this.projectConfig.projectId}: ${this.formatErrorMessage(error)}`
741
+ );
1449
742
  }
1450
743
  if (options.once || !this.running) {
1451
744
  return;
@@ -1454,20 +747,39 @@ var OrchestratorService = class {
1454
747
  }
1455
748
  }
1456
749
  async runOnce(options = {}) {
1457
- return this.runOnceInternal(options.issueIdentifier, this.createTrackerDependencies());
750
+ return this.runOnceInternal(
751
+ options.issueIdentifier,
752
+ this.createTrackerDependencies()
753
+ );
1458
754
  }
1459
755
  async status() {
1460
756
  return this.store.loadProjectStatus(this.projectConfig.projectId);
1461
757
  }
1462
758
  async statusForIssue(issueIdentifier) {
1463
- const issueRecords = await this.store.loadProjectIssueOrchestrations(this.projectConfig.projectId);
1464
- const issueRecord = issueRecords.find((record) => record.identifier === issueIdentifier);
759
+ const issueRecords = await this.store.loadProjectIssueOrchestrations(
760
+ this.projectConfig.projectId
761
+ );
762
+ const issueRecord = issueRecords.find(
763
+ (record) => record.identifier === issueIdentifier
764
+ );
1465
765
  if (!issueRecord) {
1466
766
  return null;
1467
767
  }
1468
- const currentRunCandidate = issueRecord.currentRunId ? await this.store.loadRun(issueRecord.currentRunId, this.projectConfig.projectId) : null;
1469
- const currentRun = isMatchingIssueRun(currentRunCandidate, this.projectConfig.projectId, issueRecord.issueId, issueIdentifier) ? currentRunCandidate : await this.findLatestRunForIssue(issueRecord.issueId, issueIdentifier);
1470
- const recentEvents = currentRun === null ? [] : await this.store.loadRecentRunEvents(currentRun.runId, 20, currentRun.projectId);
768
+ const currentRunCandidate = issueRecord.currentRunId ? await this.store.loadRun(
769
+ issueRecord.currentRunId,
770
+ this.projectConfig.projectId
771
+ ) : null;
772
+ const currentRun = isMatchingIssueRun(
773
+ currentRunCandidate,
774
+ this.projectConfig.projectId,
775
+ issueRecord.issueId,
776
+ issueIdentifier
777
+ ) ? currentRunCandidate : await this.findLatestRunForIssue(issueRecord.issueId, issueIdentifier);
778
+ const recentEvents = currentRun === null ? [] : await this.store.loadRecentRunEvents(
779
+ currentRun.runId,
780
+ 20,
781
+ currentRun.projectId
782
+ );
1471
783
  const latestEventMessage = recentEvents[recentEvents.length - 1]?.message ?? null;
1472
784
  const currentAttempt = currentRun?.attempt ?? issueRecord.retryEntry?.attempt ?? 0;
1473
785
  return {
@@ -1504,7 +816,10 @@ var OrchestratorService = class {
1504
816
  codex_session_logs: currentRun === null ? [] : [
1505
817
  {
1506
818
  label: "worker",
1507
- path: join3(this.store.runDir(currentRun.runId, currentRun.projectId), "worker.log"),
819
+ path: join3(
820
+ this.store.runDir(currentRun.runId, currentRun.projectId),
821
+ "worker.log"
822
+ ),
1508
823
  url: null
1509
824
  }
1510
825
  ]
@@ -1566,7 +881,9 @@ var OrchestratorService = class {
1566
881
  if (this.dependencies.pollIntervalMs) {
1567
882
  return this.dependencies.pollIntervalMs;
1568
883
  }
1569
- const configuredIntervals = [...this.projectPollIntervals.values()].filter((value) => Number.isFinite(value) && value > 0);
884
+ const configuredIntervals = [...this.projectPollIntervals.values()].filter(
885
+ (value) => Number.isFinite(value) && value > 0
886
+ );
1570
887
  return configuredIntervals.length ? Math.min(...configuredIntervals) : DEFAULT_POLL_INTERVAL_MS;
1571
888
  }
1572
889
  async reconcileProject(tenant, issueIdentifier, trackerDependencies = {}) {
@@ -1579,27 +896,57 @@ var OrchestratorService = class {
1579
896
  let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
1580
897
  let rateLimits = null;
1581
898
  let trackerRateLimits = null;
1582
- let issueRecords = await this.store.loadProjectIssueOrchestrations(tenant.projectId);
1583
- const allRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
899
+ let issueRecords = await this.store.loadProjectIssueOrchestrations(
900
+ tenant.projectId
901
+ );
902
+ const allRuns = (await this.store.loadAllRuns()).filter(
903
+ (run) => run.projectId === tenant.projectId
904
+ );
1584
905
  const activeRuns = allRuns.filter((run) => isActiveRunStatus(run.status));
1585
906
  for (const run of activeRuns) {
1586
- const outcome = await this.reconcileRun(tenant, run, issueRecords, trackerDependencies);
907
+ const outcome = await this.reconcileRun(
908
+ tenant,
909
+ run,
910
+ issueRecords,
911
+ trackerDependencies
912
+ );
1587
913
  issueRecords = outcome.issueRecords;
1588
914
  if (outcome.recovered) {
1589
915
  recovered += 1;
1590
916
  }
1591
917
  }
1592
- const reconciledRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status));
1593
- const projectRunsAfterReconcile = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
918
+ const reconciledRuns = (await this.store.loadAllRuns()).filter(
919
+ (run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status)
920
+ );
921
+ const projectRunsAfterReconcile = (await this.store.loadAllRuns()).filter(
922
+ (run) => run.projectId === tenant.projectId
923
+ );
1594
924
  rateLimits = resolveProjectRateLimits(reconciledRuns, []);
1595
925
  try {
1596
926
  pollIntervalMs = await this.loadProjectPollInterval(tenant);
1597
- const currentActiveRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status));
1598
- const { runs: syncedActiveRuns, issuesByIdentifier: syncedIssuesByIdentifier } = await this.syncActiveRunIssueStates(tenant, trackerAdapter, currentActiveRuns, now);
1599
- const issues = await trackerAdapter.listIssues(tenant, trackerDependencies);
1600
- const filteredIssues = issueIdentifier ? issues.filter((issue) => issue.identifier === issueIdentifier) : issues;
927
+ const currentActiveRuns = (await this.store.loadAllRuns()).filter(
928
+ (run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status)
929
+ );
930
+ const {
931
+ runs: syncedActiveRuns,
932
+ issuesByIdentifier: syncedIssuesByIdentifier
933
+ } = await this.syncActiveRunIssueStates(
934
+ tenant,
935
+ trackerAdapter,
936
+ currentActiveRuns,
937
+ now
938
+ );
939
+ const issues = await trackerAdapter.listIssues(
940
+ tenant,
941
+ trackerDependencies
942
+ );
943
+ const filteredIssues = issueIdentifier ? issues.filter(
944
+ (issue) => issue.identifier === issueIdentifier
945
+ ) : issues;
1601
946
  const { candidates: actionableCandidates, lifecycle } = await this.resolveActionableCandidates(tenant, filteredIssues);
1602
- const trackedIssuesByIdentifier = new Map(syncedIssuesByIdentifier);
947
+ const trackedIssuesByIdentifier = new Map(
948
+ syncedIssuesByIdentifier
949
+ );
1603
950
  for (const issue of filteredIssues) {
1604
951
  const existing = trackedIssuesByIdentifier.get(issue.identifier);
1605
952
  trackedIssuesByIdentifier.set(issue.identifier, {
@@ -1620,17 +967,33 @@ var OrchestratorService = class {
1620
967
  rateLimits: existing.rateLimits ?? issue.rateLimits ?? null
1621
968
  });
1622
969
  }
1623
- rateLimits = resolveProjectRateLimits(syncedActiveRuns, trackedIssuesByIdentifier.values());
1624
- trackerRateLimits = resolveTrackerRateLimits(trackedIssuesByIdentifier.values());
970
+ rateLimits = resolveProjectRateLimits(
971
+ syncedActiveRuns,
972
+ trackedIssuesByIdentifier.values()
973
+ );
974
+ trackerRateLimits = resolveTrackerRateLimits(
975
+ trackedIssuesByIdentifier.values()
976
+ );
1625
977
  const concurrency = await this.getProjectConcurrency(tenant);
1626
- const currentlyActive = issueRecords.filter((record) => isIssueOrchestrationClaimed(record.state)).length;
978
+ const currentlyActive = issueRecords.filter(
979
+ (record) => isIssueOrchestrationClaimed(record.state)
980
+ ).length;
1627
981
  const availableSlots = Math.max(0, concurrency - currentlyActive);
1628
- const latestRunsByIssueId = buildLatestRunMapByIssueId(projectRunsAfterReconcile);
982
+ const latestRunsByIssueId = buildLatestRunMapByIssueId(
983
+ projectRunsAfterReconcile
984
+ );
1629
985
  const unscheduledCandidates = actionableCandidates.filter((issue) => {
1630
- if (hasConvergenceLockedRun(projectRunsAfterReconcile, issue.id, issue.state, issue.updatedAt)) {
986
+ if (hasConvergenceLockedRun(
987
+ projectRunsAfterReconcile,
988
+ issue.id,
989
+ issue.state,
990
+ issue.updatedAt
991
+ )) {
1631
992
  return false;
1632
993
  }
1633
- return !issueRecords.some((record) => record.issueId === issue.id && isIssueOrchestrationClaimed(record.state));
994
+ return !issueRecords.some(
995
+ (record) => record.issueId === issue.id && isIssueOrchestrationClaimed(record.state)
996
+ );
1634
997
  });
1635
998
  const sortedCandidates = sortCandidatesForDispatch(unscheduledCandidates);
1636
999
  const activeByState = /* @__PURE__ */ new Map();
@@ -1645,12 +1008,13 @@ var OrchestratorService = class {
1645
1008
  if (this.shuttingDown) {
1646
1009
  break;
1647
1010
  }
1648
- if (slotsRemaining <= 0)
1649
- break;
1650
- if (await this.isFailureRetrySuppressedIssue(tenant, issue, issueRecords, latestRunsByIssueId.get(issue.id) ?? null)) {
1651
- continue;
1652
- }
1653
- if (isIssueBudgetExceeded(resolveIssueBudgetSnapshot(projectRunsAfterReconcile, issue.id), now)) {
1011
+ if (slotsRemaining <= 0) break;
1012
+ if (await this.isFailureRetrySuppressedIssue(
1013
+ tenant,
1014
+ issue,
1015
+ issueRecords,
1016
+ latestRunsByIssueId.get(issue.id) ?? null
1017
+ )) {
1654
1018
  continue;
1655
1019
  }
1656
1020
  const stateLimit = maxConcurrentByState[issue.state];
@@ -1660,11 +1024,14 @@ var OrchestratorService = class {
1660
1024
  continue;
1661
1025
  }
1662
1026
  }
1663
- const preferredWorkspaceKey = deriveIssueWorkspaceKey({
1664
- projectId: tenant.projectId,
1665
- adapter: issue.tracker.adapter,
1666
- issueSubjectId: issue.id
1667
- }, issue.identifier);
1027
+ const preferredWorkspaceKey = deriveIssueWorkspaceKey(
1028
+ {
1029
+ projectId: tenant.projectId,
1030
+ adapter: issue.tracker.adapter,
1031
+ issueSubjectId: issue.id
1032
+ },
1033
+ issue.identifier
1034
+ );
1668
1035
  issueRecords = upsertIssueOrchestration(issueRecords, {
1669
1036
  issueId: issue.id,
1670
1037
  identifier: issue.identifier,
@@ -1700,10 +1067,15 @@ var OrchestratorService = class {
1700
1067
  issueId: run.issueId,
1701
1068
  issueState: issue.state
1702
1069
  });
1703
- this.logVerbose(`[dispatch] Issue ${issue.identifier} \u2192 run ${run.runId}`);
1070
+ this.logVerbose(
1071
+ `[dispatch] Issue ${issue.identifier} \u2192 run ${run.runId}`
1072
+ );
1704
1073
  dispatched += 1;
1705
1074
  slotsRemaining -= 1;
1706
- activeByState.set(issue.state, (activeByState.get(issue.state) ?? 0) + 1);
1075
+ activeByState.set(
1076
+ issue.state,
1077
+ (activeByState.get(issue.state) ?? 0) + 1
1078
+ );
1707
1079
  }
1708
1080
  for (const issueRecord of issueRecords) {
1709
1081
  if (!isIssueOrchestrationClaimed(issueRecord.state)) {
@@ -1714,8 +1086,17 @@ var OrchestratorService = class {
1714
1086
  continue;
1715
1087
  }
1716
1088
  const persistedRun = issueRecord.currentRunId ? await this.store.loadRun(issueRecord.currentRunId, tenant.projectId) : null;
1717
- const activeRun = syncedActiveRuns.find((run) => isMatchingIssueRun(run, tenant.projectId, issueRecord.issueId, issueRecord.identifier)) ?? persistedRun;
1718
- const resolvedIssue = actionableCandidates.find((candidate) => candidate.identifier === issue.identifier);
1089
+ const activeRun = syncedActiveRuns.find(
1090
+ (run) => isMatchingIssueRun(
1091
+ run,
1092
+ tenant.projectId,
1093
+ issueRecord.issueId,
1094
+ issueRecord.identifier
1095
+ )
1096
+ ) ?? persistedRun;
1097
+ const resolvedIssue = actionableCandidates.find(
1098
+ (candidate) => candidate.identifier === issue.identifier
1099
+ );
1719
1100
  if (resolvedIssue) {
1720
1101
  continue;
1721
1102
  }
@@ -1734,9 +1115,15 @@ var OrchestratorService = class {
1734
1115
  lastError: "Run suppressed because the tracker state is no longer actionable."
1735
1116
  };
1736
1117
  await this.store.saveRun(suppressedRun);
1737
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
1118
+ this.logVerbose(
1119
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
1120
+ );
1738
1121
  }
1739
- issueRecords = releaseIssueOrchestration(issueRecords, issueRecord.issueId, now);
1122
+ issueRecords = releaseIssueOrchestration(
1123
+ issueRecords,
1124
+ issueRecord.issueId,
1125
+ now
1126
+ );
1740
1127
  suppressed += 1;
1741
1128
  }
1742
1129
  const terminalIssuesByIdentifier = /* @__PURE__ */ new Map();
@@ -1752,14 +1139,28 @@ var OrchestratorService = class {
1752
1139
  } catch (error) {
1753
1140
  lastError = error instanceof Error ? error.message : "Unknown orchestration error";
1754
1141
  }
1755
- const effectivePollIntervalMs = resolveAdaptivePollIntervalMs(pollIntervalMs, trackerRateLimits);
1142
+ const effectivePollIntervalMs = resolveAdaptivePollIntervalMs(
1143
+ pollIntervalMs,
1144
+ trackerRateLimits
1145
+ );
1756
1146
  if (effectivePollIntervalMs > pollIntervalMs && isLowRateLimit(trackerRateLimits, LOW_RATE_LIMIT_WARNING_THRESHOLD)) {
1757
- this.writeStderr(`[orchestrator] low GitHub rate limit for ${tenant.projectId}: interval=${effectivePollIntervalMs}ms rateLimits=${JSON.stringify(trackerRateLimits)}`);
1147
+ this.writeStderr(
1148
+ `[orchestrator] low GitHub rate limit for ${tenant.projectId}: interval=${effectivePollIntervalMs}ms rateLimits=${JSON.stringify(
1149
+ trackerRateLimits
1150
+ )}`
1151
+ );
1758
1152
  }
1759
1153
  this.projectPollIntervals.set(tenant.projectId, effectivePollIntervalMs);
1760
- await this.store.saveProjectIssueOrchestrations(tenant.projectId, issueRecords);
1761
- const allTenantRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
1762
- const latestRuns = allTenantRuns.filter((run) => isActiveRunStatus(run.status));
1154
+ await this.store.saveProjectIssueOrchestrations(
1155
+ tenant.projectId,
1156
+ issueRecords
1157
+ );
1158
+ const allTenantRuns = (await this.store.loadAllRuns()).filter(
1159
+ (run) => run.projectId === tenant.projectId
1160
+ );
1161
+ const latestRuns = allTenantRuns.filter(
1162
+ (run) => isActiveRunStatus(run.status)
1163
+ );
1763
1164
  rateLimits = rateLimits ?? resolveProjectRateLimits(latestRuns, []);
1764
1165
  const status = buildProjectSnapshot({
1765
1166
  project: tenant,
@@ -1776,7 +1177,9 @@ var OrchestratorService = class {
1776
1177
  async performStartupCleanup(trackerDependencies = {}) {
1777
1178
  const tenant = this.projectConfig;
1778
1179
  const now = this.now();
1779
- const workspaceRecords = await this.store.loadIssueWorkspaces(tenant.projectId);
1180
+ const workspaceRecords = await this.store.loadIssueWorkspaces(
1181
+ tenant.projectId
1182
+ );
1780
1183
  if (workspaceRecords.length === 0) {
1781
1184
  return;
1782
1185
  }
@@ -1784,10 +1187,20 @@ var OrchestratorService = class {
1784
1187
  const workflowCache = /* @__PURE__ */ new Map();
1785
1188
  let issues;
1786
1189
  try {
1787
- issues = await trackerAdapter.listIssuesByStates(tenant, await this.resolveStartupCleanupTerminalStates(tenant, workspaceRecords, workflowCache), trackerDependencies);
1190
+ issues = await trackerAdapter.listIssuesByStates(
1191
+ tenant,
1192
+ await this.resolveStartupCleanupTerminalStates(
1193
+ tenant,
1194
+ workspaceRecords,
1195
+ workflowCache
1196
+ ),
1197
+ trackerDependencies
1198
+ );
1788
1199
  } catch (error) {
1789
1200
  const message = error instanceof Error ? error.message : "Unknown tracker error";
1790
- console.warn(`[orchestrator] Startup cleanup skipped for project ${tenant.projectId}: ${message}`);
1201
+ console.warn(
1202
+ `[orchestrator] Startup cleanup skipped for project ${tenant.projectId}: ${message}`
1203
+ );
1791
1204
  return;
1792
1205
  }
1793
1206
  const issuesById = new Map(issues.map((issue) => [issue.id, issue]));
@@ -1800,17 +1213,28 @@ var OrchestratorService = class {
1800
1213
  continue;
1801
1214
  }
1802
1215
  try {
1803
- const resolution = await this.loadStartupCleanupWorkflow(tenant, issue.repository, workflowCache);
1216
+ const resolution = await this.loadStartupCleanupWorkflow(
1217
+ tenant,
1218
+ issue.repository,
1219
+ workflowCache
1220
+ );
1804
1221
  if (!resolution.isValid) {
1805
1222
  continue;
1806
1223
  }
1807
1224
  if (!isStateTerminal(issue.state, resolution.lifecycle)) {
1808
1225
  continue;
1809
1226
  }
1810
- await this.cleanupTerminalIssueWorkspace(tenant, issue, now, resolution);
1227
+ await this.cleanupTerminalIssueWorkspace(
1228
+ tenant,
1229
+ issue,
1230
+ now,
1231
+ resolution
1232
+ );
1811
1233
  } catch (error) {
1812
1234
  const message = error instanceof Error ? error.message : "Unknown startup cleanup error";
1813
- console.warn(`[orchestrator] Startup cleanup skipped workspace for ${issue.identifier}: ${message}`);
1235
+ console.warn(
1236
+ `[orchestrator] Startup cleanup skipped workspace for ${issue.identifier}: ${message}`
1237
+ );
1814
1238
  }
1815
1239
  }
1816
1240
  }
@@ -1821,7 +1245,9 @@ var OrchestratorService = class {
1821
1245
  try {
1822
1246
  await this.dependencies.onTick(snapshot);
1823
1247
  } catch (error) {
1824
- this.writeStderr(`[orchestrator] onTick callback failed: ${this.formatErrorMessage(error)}`);
1248
+ this.writeStderr(
1249
+ `[orchestrator] onTick callback failed: ${this.formatErrorMessage(error)}`
1250
+ );
1825
1251
  }
1826
1252
  }
1827
1253
  formatErrorMessage(error) {
@@ -1832,11 +1258,18 @@ var OrchestratorService = class {
1832
1258
  }
1833
1259
  async resolveStartupCleanupTerminalStates(tenant, workspaceRecords, workflowCache) {
1834
1260
  const terminalStates = /* @__PURE__ */ new Map();
1835
- const repositories = this.resolveStartupCleanupRepositories(tenant, workspaceRecords);
1261
+ const repositories = this.resolveStartupCleanupRepositories(
1262
+ tenant,
1263
+ workspaceRecords
1264
+ );
1836
1265
  for (const repository of repositories) {
1837
1266
  let resolution;
1838
1267
  try {
1839
- resolution = await this.loadStartupCleanupWorkflow(tenant, repository, workflowCache);
1268
+ resolution = await this.loadStartupCleanupWorkflow(
1269
+ tenant,
1270
+ repository,
1271
+ workflowCache
1272
+ );
1840
1273
  } catch {
1841
1274
  continue;
1842
1275
  }
@@ -1860,14 +1293,20 @@ var OrchestratorService = class {
1860
1293
  resolveStartupCleanupRepositories(tenant, workspaceRecords) {
1861
1294
  const repositories = /* @__PURE__ */ new Map();
1862
1295
  for (const repository of tenant.repositories) {
1863
- repositories.set(this.startupCleanupRepositoryKey(repository.owner, repository.name), repository);
1296
+ repositories.set(
1297
+ this.startupCleanupRepositoryKey(repository.owner, repository.name),
1298
+ repository
1299
+ );
1864
1300
  }
1865
1301
  for (const workspaceRecord of workspaceRecords) {
1866
1302
  const repository = this.parseWorkspaceRepositoryRef(workspaceRecord);
1867
1303
  if (!repository) {
1868
1304
  continue;
1869
1305
  }
1870
- const key = this.startupCleanupRepositoryKey(repository.owner, repository.name);
1306
+ const key = this.startupCleanupRepositoryKey(
1307
+ repository.owner,
1308
+ repository.name
1309
+ );
1871
1310
  if (!repositories.has(key)) {
1872
1311
  repositories.set(key, repository);
1873
1312
  }
@@ -1875,7 +1314,9 @@ var OrchestratorService = class {
1875
1314
  return [...repositories.values()];
1876
1315
  }
1877
1316
  parseWorkspaceRepositoryRef(workspaceRecord) {
1878
- const match = workspaceRecord.issueIdentifier.match(/^([^/]+)\/([^#]+)#\d+$/);
1317
+ const match = workspaceRecord.issueIdentifier.match(
1318
+ /^([^/]+)\/([^#]+)#\d+$/
1319
+ );
1879
1320
  if (!match) {
1880
1321
  return null;
1881
1322
  }
@@ -1899,7 +1340,9 @@ var OrchestratorService = class {
1899
1340
  if (cachedResolution) {
1900
1341
  return cachedResolution;
1901
1342
  }
1902
- const resolutionPromise = tenant.repositories.some((candidate) => candidate.owner === repository.owner && candidate.name === repository.name) ? this.loadProjectWorkflow(tenant, repository) : loadRepositoryWorkflow(repository.cloneUrl, repository);
1343
+ const resolutionPromise = tenant.repositories.some(
1344
+ (candidate) => candidate.owner === repository.owner && candidate.name === repository.name
1345
+ ) ? this.loadProjectWorkflow(tenant, repository) : loadRepositoryWorkflow(repository.cloneUrl, repository);
1903
1346
  workflowCache.set(cacheKey, resolutionPromise);
1904
1347
  return resolutionPromise;
1905
1348
  }
@@ -1921,7 +1364,11 @@ var OrchestratorService = class {
1921
1364
  const workflowResolutionCache = /* @__PURE__ */ new Map();
1922
1365
  this.workflowResolutionCache = workflowResolutionCache;
1923
1366
  try {
1924
- return await this.reconcileProject(this.projectConfig, issueIdentifier, trackerDependencies);
1367
+ return await this.reconcileProject(
1368
+ this.projectConfig,
1369
+ issueIdentifier,
1370
+ trackerDependencies
1371
+ );
1925
1372
  } finally {
1926
1373
  if (this.workflowResolutionCache === workflowResolutionCache) {
1927
1374
  this.workflowResolutionCache = null;
@@ -1936,14 +1383,21 @@ var OrchestratorService = class {
1936
1383
  };
1937
1384
  }
1938
1385
  async findLatestRunForIssue(issueId, issueIdentifier) {
1939
- const matchingRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === this.projectConfig.projectId).filter((run) => run.issueId === issueId || run.issueIdentifier === issueIdentifier).sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime());
1386
+ const matchingRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === this.projectConfig.projectId).filter(
1387
+ (run) => run.issueId === issueId || run.issueIdentifier === issueIdentifier
1388
+ ).sort(
1389
+ (left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime()
1390
+ );
1940
1391
  return matchingRuns[0] ?? null;
1941
1392
  }
1942
1393
  async resolveActionableCandidates(tenant, issues) {
1943
1394
  const candidates = [];
1944
1395
  let lifecycle = null;
1945
1396
  for (const issue of issues) {
1946
- const resolution = await this.loadProjectWorkflow(tenant, issue.repository);
1397
+ const resolution = await this.loadProjectWorkflow(
1398
+ tenant,
1399
+ issue.repository
1400
+ );
1947
1401
  if (!isUsableWorkflowResolution(resolution)) {
1948
1402
  continue;
1949
1403
  }
@@ -1956,7 +1410,10 @@ var OrchestratorService = class {
1956
1410
  candidates.push(issue);
1957
1411
  }
1958
1412
  if (!lifecycle && tenant.repositories.length > 0) {
1959
- const resolution = await this.loadProjectWorkflow(tenant, tenant.repositories[0]);
1413
+ const resolution = await this.loadProjectWorkflow(
1414
+ tenant,
1415
+ tenant.repositories[0]
1416
+ );
1960
1417
  if (isUsableWorkflowResolution(resolution)) {
1961
1418
  lifecycle = resolution.lifecycle;
1962
1419
  }
@@ -1983,7 +1440,9 @@ var OrchestratorService = class {
1983
1440
  return false;
1984
1441
  }
1985
1442
  if (blockerRef.identifier) {
1986
- const blockerIssue = issues.find((candidate) => candidate.identifier === blockerRef.identifier);
1443
+ const blockerIssue = issues.find(
1444
+ (candidate) => candidate.identifier === blockerRef.identifier
1445
+ );
1987
1446
  if (blockerIssue?.state) {
1988
1447
  return !isStateTerminal(blockerIssue.state, lifecycle);
1989
1448
  }
@@ -1999,24 +1458,42 @@ var OrchestratorService = class {
1999
1458
  if (cachedResolution) {
2000
1459
  return cachedResolution;
2001
1460
  }
2002
- const resolutionPromise = this.loadProjectWorkflowUncached(tenant, repository);
1461
+ const resolutionPromise = this.loadProjectWorkflowUncached(
1462
+ tenant,
1463
+ repository
1464
+ );
2003
1465
  pendingCache.set(cacheKey, resolutionPromise);
2004
1466
  return resolutionPromise;
2005
1467
  }
2006
1468
  return this.loadProjectWorkflowUncached(tenant, repository);
2007
1469
  }
2008
1470
  async loadProjectWorkflowUncached(tenant, repository) {
2009
- const cacheRoot = join3(this.store.projectDir(tenant.projectId), "cache", repository.owner, repository.name);
1471
+ const cacheRoot = join3(
1472
+ this.store.projectDir(tenant.projectId),
1473
+ "cache",
1474
+ repository.owner,
1475
+ repository.name
1476
+ );
2010
1477
  const { repositoryDirectory, changed } = await syncRepositoryForRun({
2011
1478
  repository,
2012
1479
  targetDirectory: cacheRoot
2013
1480
  });
2014
- const resolution = await loadRepositoryWorkflow(repositoryDirectory, repository);
2015
- return this.resolveWorkflowResolution(repository, cacheRoot, resolution, changed);
1481
+ const resolution = await loadRepositoryWorkflow(
1482
+ repositoryDirectory,
1483
+ repository
1484
+ );
1485
+ return this.resolveWorkflowResolution(
1486
+ repository,
1487
+ cacheRoot,
1488
+ resolution,
1489
+ changed
1490
+ );
2016
1491
  }
2017
- async startRun(tenant, issue, resumeContext) {
1492
+ async startRun(tenant, issue) {
2018
1493
  if (this.shuttingDown || !this.running) {
2019
- throw new Error("Orchestrator is shutting down and cannot start new runs.");
1494
+ throw new Error(
1495
+ "Orchestrator is shutting down and cannot start new runs."
1496
+ );
2020
1497
  }
2021
1498
  const trackerAdapter = resolveTrackerAdapter2(tenant.tracker);
2022
1499
  const now = this.now();
@@ -2029,12 +1506,24 @@ var OrchestratorService = class {
2029
1506
  adapter: issue.tracker.adapter,
2030
1507
  issueSubjectId
2031
1508
  };
2032
- const preferredWorkspaceKey = deriveIssueWorkspaceKey(identity, issue.identifier);
1509
+ const preferredWorkspaceKey = deriveIssueWorkspaceKey(
1510
+ identity,
1511
+ issue.identifier
1512
+ );
2033
1513
  const legacyWorkspaceKey = deriveLegacyIssueWorkspaceKey(identity);
2034
- const existingWorkspaceRecord = await this.store.loadIssueWorkspace(tenant.projectId, preferredWorkspaceKey) ?? (legacyWorkspaceKey === preferredWorkspaceKey ? null : await this.store.loadIssueWorkspace(tenant.projectId, legacyWorkspaceKey));
1514
+ const existingWorkspaceRecord = await this.store.loadIssueWorkspace(
1515
+ tenant.projectId,
1516
+ preferredWorkspaceKey
1517
+ ) ?? (legacyWorkspaceKey === preferredWorkspaceKey ? null : await this.store.loadIssueWorkspace(
1518
+ tenant.projectId,
1519
+ legacyWorkspaceKey
1520
+ ));
2035
1521
  const workspaceKey = existingWorkspaceRecord?.workspaceKey ?? preferredWorkspaceKey;
2036
1522
  const projectDir = this.store.projectDir(tenant.projectId);
2037
- const issueWorkspacePath = resolveIssueWorkspaceDirectory(projectDir, workspaceKey);
1523
+ const issueWorkspacePath = resolveIssueWorkspaceDirectory(
1524
+ projectDir,
1525
+ workspaceKey
1526
+ );
2038
1527
  const repositoryDirectory = await ensureIssueWorkspaceRepository({
2039
1528
  repository: issue.repository,
2040
1529
  issueWorkspacePath
@@ -2054,14 +1543,20 @@ var OrchestratorService = class {
2054
1543
  lastError: null
2055
1544
  };
2056
1545
  await this.store.saveIssueWorkspace(workspaceRecord);
2057
- const afterCreateResult = await this.runHook("after_create", tenant, repositoryDirectory, issue.repository, {
2058
- projectId: tenant.projectId,
2059
- workspaceKey,
2060
- issueSubjectId,
2061
- issueIdentifier: issue.identifier,
2062
- workspacePath: issueWorkspacePath,
2063
- repositoryPath: repositoryDirectory
2064
- });
1546
+ const afterCreateResult = await this.runHook(
1547
+ "after_create",
1548
+ tenant,
1549
+ repositoryDirectory,
1550
+ issue.repository,
1551
+ {
1552
+ projectId: tenant.projectId,
1553
+ workspaceKey,
1554
+ issueSubjectId,
1555
+ issueIdentifier: issue.identifier,
1556
+ workspacePath: issueWorkspacePath,
1557
+ repositoryPath: repositoryDirectory
1558
+ }
1559
+ );
2065
1560
  if (afterCreateResult && afterCreateResult.outcome !== "success" && afterCreateResult.outcome !== "skipped") {
2066
1561
  await this.store.appendRunEvent(runId, {
2067
1562
  at: now.toISOString(),
@@ -2074,25 +1569,35 @@ var OrchestratorService = class {
2074
1569
  }
2075
1570
  const workflow = await this.loadProjectWorkflow(tenant, issue.repository);
2076
1571
  if (!isUsableWorkflowResolution(workflow)) {
2077
- throw new Error(workflow.validationError ?? "Invalid repository WORKFLOW.md");
1572
+ throw new Error(
1573
+ workflow.validationError ?? "Invalid repository WORKFLOW.md"
1574
+ );
2078
1575
  }
2079
- const allProjectRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
2080
- const issueBudgetSnapshot = resolveIssueBudgetSnapshot(allProjectRuns, issue.id);
2081
1576
  const promptVariables = buildPromptVariables(issue, {
2082
1577
  attempt: null
2083
1578
  // first execution
2084
1579
  });
2085
- const renderedPrompt = renderPrompt(workflow.promptTemplate, promptVariables);
2086
- await this.runHook("before_run", tenant, repositoryDirectory, issue.repository, {
2087
- projectId: tenant.projectId,
2088
- workspaceKey,
2089
- issueSubjectId,
2090
- issueIdentifier: issue.identifier,
2091
- workspacePath: issueWorkspacePath,
2092
- repositoryPath: repositoryDirectory,
2093
- runId,
2094
- state: issue.state
2095
- });
1580
+ const renderedPrompt = renderPrompt(
1581
+ workflow.promptTemplate,
1582
+ promptVariables
1583
+ );
1584
+ await this.runHook(
1585
+ "before_run",
1586
+ tenant,
1587
+ repositoryDirectory,
1588
+ issue.repository,
1589
+ {
1590
+ projectId: tenant.projectId,
1591
+ workspaceKey,
1592
+ issueSubjectId,
1593
+ issueIdentifier: issue.identifier,
1594
+ workspacePath: issueWorkspacePath,
1595
+ repositoryPath: repositoryDirectory,
1596
+ runId,
1597
+ state: issue.state
1598
+ }
1599
+ );
1600
+ const runtimeTimeouts = resolveWorkflowRuntimeTimeouts(workflow.workflow);
2096
1601
  mkdirSync(runDir, { recursive: true });
2097
1602
  const workerLogStream = (this.dependencies.createWriteStreamImpl ?? createWriteStream)(join3(runDir, "worker.log"), {
2098
1603
  flags: "a"
@@ -2115,55 +1620,69 @@ var OrchestratorService = class {
2115
1620
  }
2116
1621
  workerLogAvailable = false;
2117
1622
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2118
- this.writeStderr(`[orchestrator] failed to write worker log for ${runId}: ${message}`);
1623
+ this.writeStderr(
1624
+ `[orchestrator] failed to write worker log for ${runId}: ${message}`
1625
+ );
2119
1626
  };
2120
- const child = (this.dependencies.spawnImpl ?? spawn2)("bash", ["-lc", resolveWorkerCommand()], {
2121
- cwd: process.cwd(),
2122
- env: this.buildProjectExecutionEnv(tenant.projectId, {
2123
- GITHUB_GRAPHQL_TOKEN: process.env.GITHUB_GRAPHQL_TOKEN ?? "",
2124
- CODEX_PROJECT_ID: tenant.projectId,
2125
- PROJECT_ID: tenant.projectId,
2126
- WORKING_DIRECTORY: repositoryDirectory,
2127
- WORKSPACE_RUNTIME_DIR: workspaceRuntimeDir,
2128
- SYMPHONY_RUN_ID: runId,
2129
- SYMPHONY_ISSUE_STATE: issue.state,
2130
- SYMPHONY_ISSUE_ID: issue.id,
2131
- SYMPHONY_ISSUE_IDENTIFIER: issue.identifier,
2132
- SYMPHONY_ISSUE_TITLE: issue.title,
2133
- SYMPHONY_ISSUE_SUBJECT_ID: issueSubjectId,
2134
- SYMPHONY_ISSUE_WORKSPACE_KEY: workspaceKey,
2135
- SYMPHONY_TRACKER_ADAPTER: issue.tracker.adapter,
2136
- SYMPHONY_TRACKER_BINDING_ID: issue.tracker.bindingId,
2137
- SYMPHONY_TRACKER_ITEM_ID: issue.tracker.itemId,
2138
- TARGET_REPOSITORY_CLONE_URL: issue.repository.cloneUrl,
2139
- TARGET_REPOSITORY_OWNER: issue.repository.owner,
2140
- TARGET_REPOSITORY_NAME: issue.repository.name,
2141
- TARGET_REPOSITORY_URL: issue.repository.url,
2142
- ...trackerAdapter.buildWorkerEnvironment(tenant, issue),
2143
- SYMPHONY_RENDERED_PROMPT: renderedPrompt,
2144
- SYMPHONY_WORKFLOW_PATH: workflow.workflowPath ?? "",
2145
- SYMPHONY_AGENT_COMMAND: workflow.workflow.codex.command,
2146
- SYMPHONY_APPROVAL_POLICY: workflow.workflow.codex.approvalPolicy ?? "",
2147
- SYMPHONY_THREAD_SANDBOX: workflow.workflow.codex.threadSandbox ?? "",
2148
- SYMPHONY_TURN_SANDBOX_POLICY: workflow.workflow.codex.turnSandboxPolicy ?? "",
2149
- SYMPHONY_MAX_TURNS: String(workflow.workflow.agent.maxTurns),
2150
- SYMPHONY_GLOBAL_MAX_TURNS: process.env.SYMPHONY_GLOBAL_MAX_TURNS ?? "",
2151
- SYMPHONY_MAX_TOKENS: process.env.SYMPHONY_MAX_TOKENS ?? "",
2152
- SYMPHONY_MAX_NONPRODUCTIVE_TURNS: process.env.SYMPHONY_MAX_NONPRODUCTIVE_TURNS ?? String(DEFAULT_MAX_NONPRODUCTIVE_TURNS),
2153
- SYMPHONY_SESSION_TIMEOUT_MS: process.env.SYMPHONY_SESSION_TIMEOUT_MS ?? "",
2154
- SYMPHONY_RESUME_THREAD_ID: resumeContext?.threadId ?? "",
2155
- SYMPHONY_CUMULATIVE_TURN_COUNT: String(Math.max(0, resumeContext?.cumulativeTurnCount ?? issueBudgetSnapshot.cumulativeTurnCount)),
2156
- SYMPHONY_CUMULATIVE_INPUT_TOKENS: String(issueBudgetSnapshot.tokenUsage.inputTokens),
2157
- SYMPHONY_CUMULATIVE_OUTPUT_TOKENS: String(issueBudgetSnapshot.tokenUsage.outputTokens),
2158
- SYMPHONY_CUMULATIVE_TOTAL_TOKENS: String(issueBudgetSnapshot.tokenUsage.totalTokens),
2159
- SYMPHONY_LAST_TURN_SUMMARY: resumeContext?.lastTurnSummary ?? "",
2160
- SYMPHONY_SESSION_STARTED_AT: issueBudgetSnapshot.sessionStartedAt ?? "",
2161
- SYMPHONY_READ_TIMEOUT_MS: String(workflow.workflow.codex.readTimeoutMs),
2162
- SYMPHONY_TURN_TIMEOUT_MS: String(workflow.workflow.codex.turnTimeoutMs)
2163
- }),
2164
- detached: true,
2165
- stdio: ["ignore", "ignore", "pipe"]
2166
- });
1627
+ const child = (this.dependencies.spawnImpl ?? spawn2)(
1628
+ "bash",
1629
+ ["-lc", resolveWorkerCommand()],
1630
+ {
1631
+ cwd: process.cwd(),
1632
+ env: this.buildProjectExecutionEnv(tenant.projectId, {
1633
+ GITHUB_GRAPHQL_TOKEN: process.env.GITHUB_GRAPHQL_TOKEN ?? "",
1634
+ CODEX_PROJECT_ID: tenant.projectId,
1635
+ PROJECT_ID: tenant.projectId,
1636
+ WORKING_DIRECTORY: repositoryDirectory,
1637
+ WORKSPACE_RUNTIME_DIR: workspaceRuntimeDir,
1638
+ SYMPHONY_RUN_ID: runId,
1639
+ SYMPHONY_ISSUE_STATE: issue.state,
1640
+ SYMPHONY_ISSUE_ID: issue.id,
1641
+ SYMPHONY_ISSUE_IDENTIFIER: issue.identifier,
1642
+ SYMPHONY_ISSUE_TITLE: issue.title,
1643
+ SYMPHONY_ISSUE_SUBJECT_ID: issueSubjectId,
1644
+ SYMPHONY_ISSUE_WORKSPACE_KEY: workspaceKey,
1645
+ SYMPHONY_TRACKER_ADAPTER: issue.tracker.adapter,
1646
+ SYMPHONY_TRACKER_BINDING_ID: issue.tracker.bindingId,
1647
+ SYMPHONY_TRACKER_ITEM_ID: issue.tracker.itemId,
1648
+ TARGET_REPOSITORY_CLONE_URL: issue.repository.cloneUrl,
1649
+ TARGET_REPOSITORY_OWNER: issue.repository.owner,
1650
+ TARGET_REPOSITORY_NAME: issue.repository.name,
1651
+ TARGET_REPOSITORY_URL: issue.repository.url,
1652
+ ...trackerAdapter.buildWorkerEnvironment(tenant, issue),
1653
+ SYMPHONY_RENDERED_PROMPT: renderedPrompt,
1654
+ SYMPHONY_WORKFLOW_PATH: workflow.workflowPath ?? "",
1655
+ SYMPHONY_AGENT_COMMAND: resolveWorkflowRuntimeCommand(
1656
+ workflow.workflow
1657
+ ),
1658
+ SYMPHONY_APPROVAL_POLICY: workflow.workflow.codex.approvalPolicy ?? "",
1659
+ SYMPHONY_THREAD_SANDBOX: workflow.workflow.codex.threadSandbox ?? "",
1660
+ SYMPHONY_TURN_SANDBOX_POLICY: workflow.workflow.codex.turnSandboxPolicy ?? "",
1661
+ SYMPHONY_MAX_TURNS: String(workflow.workflow.agent.maxTurns),
1662
+ SYMPHONY_MAX_NONPRODUCTIVE_TURNS: process.env.SYMPHONY_MAX_NONPRODUCTIVE_TURNS ?? String(DEFAULT_MAX_NONPRODUCTIVE_TURNS),
1663
+ // Clear legacy resume/budget env so fresh worker sessions do not
1664
+ // inherit stale process-level values.
1665
+ SYMPHONY_GLOBAL_MAX_TURNS: "",
1666
+ SYMPHONY_MAX_TOKENS: "",
1667
+ SYMPHONY_SESSION_TIMEOUT_MS: "",
1668
+ SYMPHONY_RESUME_THREAD_ID: "",
1669
+ SYMPHONY_CUMULATIVE_TURN_COUNT: "0",
1670
+ SYMPHONY_CUMULATIVE_INPUT_TOKENS: "0",
1671
+ SYMPHONY_CUMULATIVE_OUTPUT_TOKENS: "0",
1672
+ SYMPHONY_CUMULATIVE_TOTAL_TOKENS: "0",
1673
+ SYMPHONY_LAST_TURN_SUMMARY: "",
1674
+ SYMPHONY_SESSION_STARTED_AT: "",
1675
+ SYMPHONY_READ_TIMEOUT_MS: String(
1676
+ runtimeTimeouts.readTimeoutMs
1677
+ ),
1678
+ SYMPHONY_TURN_TIMEOUT_MS: String(
1679
+ runtimeTimeouts.turnTimeoutMs
1680
+ )
1681
+ }),
1682
+ detached: true,
1683
+ stdio: ["ignore", "ignore", "pipe"]
1684
+ }
1685
+ );
2167
1686
  const handleWorkerStderrChunk = (chunk) => {
2168
1687
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8");
2169
1688
  if (workerLogAvailable) {
@@ -2200,7 +1719,9 @@ var OrchestratorService = class {
2200
1719
  if (child.pid) {
2201
1720
  this.retireWorkerPid(child.pid);
2202
1721
  }
2203
- this.logVerbose(`[worker-exited] ${runId} (code=${code ?? "null"}, signal=${signal ?? "null"})`);
1722
+ this.logVerbose(
1723
+ `[worker-exited] ${runId} (code=${code ?? "null"}, signal=${signal ?? "null"})`
1724
+ );
2204
1725
  };
2205
1726
  const finalizeWorkerStderr = (code, signal) => {
2206
1727
  if (workerExited || workerStderrFinalizing) {
@@ -2240,7 +1761,9 @@ var OrchestratorService = class {
2240
1761
  }
2241
1762
  child.on?.("error", (error) => {
2242
1763
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2243
- this.writeStderr(`[orchestrator] worker process error for ${runId}: ${message}`);
1764
+ this.writeStderr(
1765
+ `[orchestrator] worker process error for ${runId}: ${message}`
1766
+ );
2244
1767
  finalizeWorkerStderr(null, null);
2245
1768
  });
2246
1769
  child.on?.("close", (code, signal) => {
@@ -2287,14 +1810,24 @@ var OrchestratorService = class {
2287
1810
  issuesByIdentifier: /* @__PURE__ */ new Map()
2288
1811
  };
2289
1812
  }
2290
- const issues = await trackerAdapter.fetchIssueStatesByIds(tenant, activeIssueIds, {
2291
- fetchImpl: this.dependencies.fetchImpl
2292
- });
2293
- const issuesByIdentifier = new Map(issues.map((issue) => [issue.identifier, issue]));
2294
- const issueStateByIdentifier = new Map(issues.map((issue) => [issue.identifier, issue.state]));
1813
+ const issues = await trackerAdapter.fetchIssueStatesByIds(
1814
+ tenant,
1815
+ activeIssueIds,
1816
+ {
1817
+ fetchImpl: this.dependencies.fetchImpl
1818
+ }
1819
+ );
1820
+ const issuesByIdentifier = new Map(
1821
+ issues.map((issue) => [issue.identifier, issue])
1822
+ );
1823
+ const issueStateByIdentifier = new Map(
1824
+ issues.map((issue) => [issue.identifier, issue.state])
1825
+ );
2295
1826
  const syncedRuns = [];
2296
1827
  for (const run of activeRuns) {
2297
- const currentTrackerState = issueStateByIdentifier.get(run.issueIdentifier);
1828
+ const currentTrackerState = issueStateByIdentifier.get(
1829
+ run.issueIdentifier
1830
+ );
2298
1831
  if (!currentTrackerState || currentTrackerState === run.issueState) {
2299
1832
  syncedRuns.push(run);
2300
1833
  continue;
@@ -2317,8 +1850,10 @@ var OrchestratorService = class {
2317
1850
  if (run.processId && this.isProcessRunning(run.processId)) {
2318
1851
  const retryPolicy = await this.loadRetryPolicy(tenant, run.repository);
2319
1852
  const configuredStallTimeoutMs = retryPolicy?.stallTimeoutMs ?? null;
2320
- const lastActivityAtMs = parseTimestampMs2(run.lastEventAt ?? run.startedAt);
2321
- const startedAtMs = parseTimestampMs2(run.startedAt);
1853
+ const lastActivityAtMs = parseTimestampMs(
1854
+ run.lastEventAt ?? run.startedAt
1855
+ );
1856
+ const startedAtMs = parseTimestampMs(run.startedAt);
2322
1857
  const elapsedSinceLastActivityMs = lastActivityAtMs === null ? null : now.getTime() - lastActivityAtMs;
2323
1858
  const runningSinceMs = startedAtMs === null ? null : now.getTime() - startedAtMs;
2324
1859
  const isStalledByWorkflowTimeout = configuredStallTimeoutMs !== null && configuredStallTimeoutMs > 0 && elapsedSinceLastActivityMs !== null && elapsedSinceLastActivityMs > configuredStallTimeoutMs;
@@ -2329,9 +1864,13 @@ var OrchestratorService = class {
2329
1864
  const elapsedSeconds = Math.round((elapsedMs ?? 0) / 1e3);
2330
1865
  const timeoutSeconds = Math.round((timeoutMs ?? 0) / 1e3);
2331
1866
  if (this.isVerboseLoggingEnabled()) {
2332
- this.writeStderr(`[stall-detected] ${run.runId} (elapsed=${elapsedSeconds}s > ${timeoutSeconds}s)`);
1867
+ this.writeStderr(
1868
+ `[stall-detected] ${run.runId} (elapsed=${elapsedSeconds}s > ${timeoutSeconds}s)`
1869
+ );
2333
1870
  } else {
2334
- this.writeStderr(`[orchestrator] stuck worker detected for ${run.runId} (elapsed ${elapsedSeconds}s > ${timeoutSeconds}s) \u2014 sending SIGTERM`);
1871
+ this.writeStderr(
1872
+ `[orchestrator] stuck worker detected for ${run.runId} (elapsed ${elapsedSeconds}s > ${timeoutSeconds}s) \u2014 sending SIGTERM`
1873
+ );
2335
1874
  }
2336
1875
  this.sendSignal(run.processId, "SIGTERM");
2337
1876
  } else {
@@ -2344,11 +1883,14 @@ var OrchestratorService = class {
2344
1883
  issueRecords = upsertIssueOrchestration(issueRecords, {
2345
1884
  issueId: run.issueId,
2346
1885
  identifier: run.issueIdentifier,
2347
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2348
- projectId: tenant.projectId,
2349
- adapter: tenant.tracker.adapter,
2350
- issueSubjectId: run.issueSubjectId
2351
- }, run.issueIdentifier),
1886
+ workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey(
1887
+ {
1888
+ projectId: tenant.projectId,
1889
+ adapter: tenant.tracker.adapter,
1890
+ issueSubjectId: run.issueSubjectId
1891
+ },
1892
+ run.issueIdentifier
1893
+ ),
2352
1894
  state: "running",
2353
1895
  currentRunId: run.runId,
2354
1896
  retryEntry: null,
@@ -2366,12 +1908,29 @@ var OrchestratorService = class {
2366
1908
  const workerInfo = await this.fetchWorkerRunInfo(run);
2367
1909
  const runWithTokens = {
2368
1910
  ...run,
2369
- runtimeSession: buildRuntimeSession(run.runtimeSession, workerInfo.sessionId, workerInfo.threadId, run.status === "running" ? "failed" : run.runtimeSession?.status ?? null, run.runtimeSession?.startedAt ?? run.startedAt ?? now.toISOString(), now.toISOString(), workerInfo.exitClassification),
1911
+ runtimeSession: buildRuntimeSession(
1912
+ run.runtimeSession,
1913
+ workerInfo.sessionId,
1914
+ workerInfo.threadId,
1915
+ run.status === "running" ? "failed" : run.runtimeSession?.status ?? null,
1916
+ run.runtimeSession?.startedAt ?? run.startedAt ?? now.toISOString(),
1917
+ now.toISOString(),
1918
+ workerInfo.exitClassification
1919
+ ),
2370
1920
  threadId: workerInfo.threadId ?? run.threadId ?? null,
2371
- cumulativeTurnCount: resolveCumulativeTurnCount(run, workerInfo.turnCount ?? null),
1921
+ cumulativeTurnCount: resolveCumulativeTurnCount(
1922
+ run,
1923
+ workerInfo.turnCount ?? null
1924
+ ),
2372
1925
  tokenUsage: workerInfo.tokenUsage ?? run.tokenUsage,
2373
1926
  lastEvent: workerInfo.lastEvent ?? run.lastEvent,
2374
- lastTurnSummary: resolveLastTurnSummary(run.lastTurnSummary, resolveLastTurnSummaryCandidate(workerInfo.lastEvent, workerInfo.lastError)),
1927
+ lastTurnSummary: resolveLastTurnSummary(
1928
+ run.lastTurnSummary,
1929
+ resolveLastTurnSummaryCandidate(
1930
+ workerInfo.lastEvent,
1931
+ workerInfo.lastError
1932
+ )
1933
+ ),
2375
1934
  lastEventAt: workerInfo.lastEventAt ?? run.lastEventAt ?? void 0,
2376
1935
  lastEventAtSource: workerInfo.lastEventAtSource ?? run.lastEventAtSource ?? void 0,
2377
1936
  executionPhase: workerInfo.executionPhase ?? run.executionPhase ?? null,
@@ -2397,46 +1956,68 @@ var OrchestratorService = class {
2397
1956
  recovered: false
2398
1957
  };
2399
1958
  }
2400
- if (await this.resolveRetryRestartAction(tenant, run, trackerDependencies) === "release") {
1959
+ if (await this.resolveRetryRestartAction(
1960
+ tenant,
1961
+ run,
1962
+ trackerDependencies
1963
+ ) === "release") {
2401
1964
  return this.releaseRetryingRun(runWithTokens, issueRecords, now);
2402
1965
  }
2403
1966
  return this.restartRun(tenant, run, issueRecords, now, workerSessionId);
2404
1967
  }
2405
- if (workerInfo.exitClassification === "budget-exceeded" || workerInfo.exitClassification === "convergence-detected") {
1968
+ if (workerInfo.exitClassification === "convergence-detected") {
2406
1969
  const completedRun = {
2407
1970
  ...runWithTokens,
2408
- status: workerInfo.exitClassification === "budget-exceeded" ? "succeeded" : "failed",
1971
+ status: "failed",
2409
1972
  processId: null,
2410
1973
  updatedAt: now.toISOString(),
2411
1974
  completedAt: now.toISOString(),
2412
1975
  nextRetryAt: null,
2413
1976
  retryKind: null,
2414
- lastError: workerInfo.exitClassification === "budget-exceeded" ? null : runWithTokens.lastError,
2415
- runPhase: runWithTokens.runPhase ?? (workerInfo.exitClassification === "budget-exceeded" ? "succeeded" : "failed")
1977
+ lastError: runWithTokens.lastError,
1978
+ runPhase: runWithTokens.runPhase ?? "failed"
2416
1979
  };
2417
1980
  await this.store.saveRun(completedRun);
2418
- this.logVerbose(`[run-completed] ${completedRun.runId} status=${completedRun.status}`);
1981
+ this.logVerbose(
1982
+ `[run-completed] ${completedRun.runId} status=${completedRun.status}`
1983
+ );
2419
1984
  return {
2420
1985
  issueRecords: releaseIssueOrchestration(issueRecords, run.issueId, now),
2421
1986
  recovered: false
2422
1987
  };
2423
1988
  }
2424
1989
  if (run.issueWorkspaceKey) {
2425
- const issueWorkspacePath = resolveIssueWorkspaceDirectory(this.store.projectDir(tenant.projectId), run.issueWorkspaceKey);
2426
- await this.runHook("after_run", tenant, run.workingDirectory, run.repository, {
2427
- projectId: run.projectId,
2428
- workspaceKey: run.issueWorkspaceKey,
2429
- issueSubjectId: run.issueSubjectId,
2430
- issueIdentifier: run.issueIdentifier,
2431
- workspacePath: issueWorkspacePath,
2432
- repositoryPath: run.workingDirectory,
2433
- runId: run.runId,
2434
- state: run.issueState
2435
- });
1990
+ const issueWorkspacePath = resolveIssueWorkspaceDirectory(
1991
+ this.store.projectDir(tenant.projectId),
1992
+ run.issueWorkspaceKey
1993
+ );
1994
+ await this.runHook(
1995
+ "after_run",
1996
+ tenant,
1997
+ run.workingDirectory,
1998
+ run.repository,
1999
+ {
2000
+ projectId: run.projectId,
2001
+ workspaceKey: run.issueWorkspaceKey,
2002
+ issueSubjectId: run.issueSubjectId,
2003
+ issueIdentifier: run.issueIdentifier,
2004
+ workspacePath: issueWorkspacePath,
2005
+ repositoryPath: run.workingDirectory,
2006
+ runId: run.runId,
2007
+ state: run.issueState
2008
+ }
2009
+ );
2436
2010
  }
2437
- const retryKind = await this.classifyRetryKind(tenant, run, trackerDependencies);
2011
+ const retryKind = await this.classifyRetryKind(
2012
+ tenant,
2013
+ run,
2014
+ trackerDependencies
2015
+ );
2438
2016
  const failureRetryCount = retryKind === "failure" ? (this.resolveFailureRetryCount(issueRecords, run.issueId) ?? 0) + 1 : this.resolveFailureRetryCount(issueRecords, run.issueId) ?? 0;
2439
- const maxFailureRetries = await this.loadMaxFailureRetries(tenant, run.repository);
2017
+ const maxFailureRetries = await this.loadMaxFailureRetries(
2018
+ tenant,
2019
+ run.repository
2020
+ );
2440
2021
  if (retryKind === "failure" && failureRetryCount >= maxFailureRetries) {
2441
2022
  const lastError = [
2442
2023
  `Run suppressed: ${MAX_FAILURE_RETRIES_EXCEEDED_REASON}.`,
@@ -2463,16 +2044,21 @@ var OrchestratorService = class {
2463
2044
  issueId: run.issueId,
2464
2045
  reason: MAX_FAILURE_RETRIES_EXCEEDED_REASON
2465
2046
  });
2466
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
2047
+ this.logVerbose(
2048
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
2049
+ );
2467
2050
  return {
2468
2051
  issueRecords: upsertIssueOrchestration(issueRecords, {
2469
2052
  issueId: run.issueId,
2470
2053
  identifier: run.issueIdentifier,
2471
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2472
- projectId: tenant.projectId,
2473
- adapter: tenant.tracker.adapter,
2474
- issueSubjectId: run.issueSubjectId
2475
- }, run.issueIdentifier),
2054
+ workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey(
2055
+ {
2056
+ projectId: tenant.projectId,
2057
+ adapter: tenant.tracker.adapter,
2058
+ issueSubjectId: run.issueSubjectId
2059
+ },
2060
+ run.issueIdentifier
2061
+ ),
2476
2062
  state: "released",
2477
2063
  failureRetryCount,
2478
2064
  currentRunId: null,
@@ -2484,7 +2070,9 @@ var OrchestratorService = class {
2484
2070
  }
2485
2071
  let nextRetryAt;
2486
2072
  if (retryKind === "continuation") {
2487
- nextRetryAt = new Date(now.getTime() + CONTINUATION_RETRY_DELAY_MS).toISOString();
2073
+ nextRetryAt = new Date(
2074
+ now.getTime() + CONTINUATION_RETRY_DELAY_MS
2075
+ ).toISOString();
2488
2076
  } else {
2489
2077
  const retryOptions = await this.loadRetryPolicy(tenant, run.repository);
2490
2078
  const backoffMs = this.dependencies.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
@@ -2505,16 +2093,23 @@ var OrchestratorService = class {
2505
2093
  lastError: retryKind === "continuation" ? null : "Worker process exited unexpectedly."
2506
2094
  };
2507
2095
  await this.store.saveRun(retryRecord);
2508
- this.logVerbose(`[retry-scheduled] ${retryRecord.runId} kind=${retryKind} attempt=${retryRecord.attempt} nextAt=${nextRetryAt}`);
2509
- this.logVerbose(`[run-completed] ${retryRecord.runId} status=${retryRecord.status}`);
2096
+ this.logVerbose(
2097
+ `[retry-scheduled] ${retryRecord.runId} kind=${retryKind} attempt=${retryRecord.attempt} nextAt=${nextRetryAt}`
2098
+ );
2099
+ this.logVerbose(
2100
+ `[run-completed] ${retryRecord.runId} status=${retryRecord.status}`
2101
+ );
2510
2102
  issueRecords = upsertIssueOrchestration(issueRecords, {
2511
2103
  issueId: run.issueId,
2512
2104
  identifier: run.issueIdentifier,
2513
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2514
- projectId: tenant.projectId,
2515
- adapter: tenant.tracker.adapter,
2516
- issueSubjectId: run.issueSubjectId
2517
- }, run.issueIdentifier),
2105
+ workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey(
2106
+ {
2107
+ projectId: tenant.projectId,
2108
+ adapter: tenant.tracker.adapter,
2109
+ issueSubjectId: run.issueSubjectId
2110
+ },
2111
+ run.issueIdentifier
2112
+ ),
2518
2113
  state: "retry_queued",
2519
2114
  completedOnce: retryKind === "continuation" ? true : void 0,
2520
2115
  failureRetryCount,
@@ -2573,9 +2168,13 @@ var OrchestratorService = class {
2573
2168
  if (!isOrchestratorChannelEvent(parsed)) {
2574
2169
  return;
2575
2170
  }
2576
- void this.runSerialized(() => this.applyWorkerChannelEvent(runId, parsed)).catch((error) => {
2171
+ void this.runSerialized(
2172
+ () => this.applyWorkerChannelEvent(runId, parsed)
2173
+ ).catch((error) => {
2577
2174
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2578
- this.writeStderr(`[orchestrator] failed to apply worker channel event for ${runId}: ${message}`);
2175
+ this.writeStderr(
2176
+ `[orchestrator] failed to apply worker channel event for ${runId}: ${message}`
2177
+ );
2579
2178
  });
2580
2179
  } catch {
2581
2180
  }
@@ -2592,15 +2191,29 @@ var OrchestratorService = class {
2592
2191
  ...run,
2593
2192
  updatedAt: nowIso2,
2594
2193
  lastEvent: "heartbeat",
2595
- lastTurnSummary: resolveLastTurnSummary(run.lastTurnSummary, event.lastError),
2194
+ lastTurnSummary: resolveLastTurnSummary(
2195
+ run.lastTurnSummary,
2196
+ event.lastError
2197
+ ),
2596
2198
  lastEventAt: persistedLastEventAt,
2597
2199
  lastEventAtSource: event.lastEventAt != null ? "event-channel" : run.lastEventAtSource ?? null,
2598
2200
  tokenUsage: event.tokenUsage,
2599
2201
  rateLimits: event.rateLimits,
2600
- runtimeSession: buildRuntimeSession(run.runtimeSession, resolveChannelSessionId(event.sessionInfo), event.sessionInfo?.threadId ?? null, "active", run.startedAt ?? run.runtimeSession?.startedAt ?? nowIso2, nowIso2, event.sessionInfo?.exitClassification ?? null),
2202
+ runtimeSession: buildRuntimeSession(
2203
+ run.runtimeSession,
2204
+ resolveChannelSessionId(event.sessionInfo),
2205
+ event.sessionInfo?.threadId ?? null,
2206
+ "active",
2207
+ run.startedAt ?? run.runtimeSession?.startedAt ?? nowIso2,
2208
+ nowIso2,
2209
+ event.sessionInfo?.exitClassification ?? null
2210
+ ),
2601
2211
  threadId: event.sessionInfo?.threadId ?? run.threadId ?? run.runtimeSession?.threadId ?? null,
2602
2212
  turnCount: event.sessionInfo && event.sessionInfo.turnCount != null ? event.sessionInfo.turnCount : run.turnCount,
2603
- cumulativeTurnCount: resolveCumulativeTurnCount(run, event.sessionInfo?.turnCount ?? null),
2213
+ cumulativeTurnCount: resolveCumulativeTurnCount(
2214
+ run,
2215
+ event.sessionInfo?.turnCount ?? null
2216
+ ),
2604
2217
  executionPhase: event.executionPhase ?? run.executionPhase,
2605
2218
  runPhase: event.runPhase ?? run.runPhase,
2606
2219
  lastError: event.lastError
@@ -2661,15 +2274,29 @@ var OrchestratorService = class {
2661
2274
  ...run,
2662
2275
  updatedAt: nowIso,
2663
2276
  lastEvent: event.event ?? run.lastEvent ?? null,
2664
- lastTurnSummary: resolveLastTurnSummary(run.lastTurnSummary, resolveLastTurnSummaryCandidate(event.event, event.lastError)),
2277
+ lastTurnSummary: resolveLastTurnSummary(
2278
+ run.lastTurnSummary,
2279
+ resolveLastTurnSummaryCandidate(event.event, event.lastError)
2280
+ ),
2665
2281
  lastEventAt: event.lastEventAt,
2666
2282
  lastEventAtSource: "event-channel",
2667
2283
  tokenUsage: event.tokenUsage ?? run.tokenUsage,
2668
2284
  rateLimits: event.rateLimits ?? run.rateLimits ?? null,
2669
- runtimeSession: buildRuntimeSession(run.runtimeSession, resolveChannelSessionId(event.sessionInfo), event.sessionInfo?.threadId ?? run.runtimeSession?.threadId ?? null, "active", run.startedAt ?? run.runtimeSession?.startedAt ?? nowIso, nowIso, event.sessionInfo?.exitClassification ?? null),
2285
+ runtimeSession: buildRuntimeSession(
2286
+ run.runtimeSession,
2287
+ resolveChannelSessionId(event.sessionInfo),
2288
+ event.sessionInfo?.threadId ?? run.runtimeSession?.threadId ?? null,
2289
+ "active",
2290
+ run.startedAt ?? run.runtimeSession?.startedAt ?? nowIso,
2291
+ nowIso,
2292
+ event.sessionInfo?.exitClassification ?? null
2293
+ ),
2670
2294
  threadId: event.sessionInfo?.threadId ?? run.threadId ?? run.runtimeSession?.threadId ?? null,
2671
2295
  turnCount: event.sessionInfo && event.sessionInfo.turnCount != null ? event.sessionInfo.turnCount : run.turnCount,
2672
- cumulativeTurnCount: resolveCumulativeTurnCount(run, event.sessionInfo?.turnCount ?? null),
2296
+ cumulativeTurnCount: resolveCumulativeTurnCount(
2297
+ run,
2298
+ event.sessionInfo?.turnCount ?? null
2299
+ ),
2673
2300
  executionPhase: event.executionPhase ?? run.executionPhase ?? null,
2674
2301
  runPhase: event.runPhase ?? run.runPhase ?? null,
2675
2302
  lastError: event.lastError ?? run.lastError
@@ -2734,7 +2361,11 @@ var OrchestratorService = class {
2734
2361
  */
2735
2362
  async classifyRetryKind(tenant, run, trackerDependencies = {}) {
2736
2363
  try {
2737
- const eligibleContext = await this.fetchTrackedIssueEligibilityContext(tenant, run.issueIdentifier, trackerDependencies);
2364
+ const eligibleContext = await this.fetchTrackedIssueEligibilityContext(
2365
+ tenant,
2366
+ run.issueIdentifier,
2367
+ trackerDependencies
2368
+ );
2738
2369
  if (!eligibleContext) {
2739
2370
  return "failure";
2740
2371
  }
@@ -2742,17 +2373,22 @@ var OrchestratorService = class {
2742
2373
  if (!isUsableWorkflowResolution(resolution)) {
2743
2374
  return "failure";
2744
2375
  }
2745
- return this.isIssueCandidateEligible(eligibleContext.issue, resolution.lifecycle, eligibleContext.issues) ? "continuation" : "failure";
2376
+ return this.isIssueCandidateEligible(
2377
+ eligibleContext.issue,
2378
+ resolution.lifecycle,
2379
+ eligibleContext.issues
2380
+ ) ? "continuation" : "failure";
2746
2381
  } catch {
2747
2382
  return "failure";
2748
2383
  }
2749
2384
  }
2750
2385
  async resolveRetryRestartAction(tenant, run, trackerDependencies = {}) {
2751
2386
  try {
2752
- if (isIssueBudgetExceeded(resolveIssueBudgetSnapshot((await this.store.loadAllRuns()).filter((candidate) => candidate.projectId === tenant.projectId), run.issueId), this.now())) {
2753
- return "release";
2754
- }
2755
- const eligibleContext = await this.fetchTrackedIssueEligibilityContext(tenant, run.issueIdentifier, trackerDependencies);
2387
+ const eligibleContext = await this.fetchTrackedIssueEligibilityContext(
2388
+ tenant,
2389
+ run.issueIdentifier,
2390
+ trackerDependencies
2391
+ );
2756
2392
  if (!eligibleContext) {
2757
2393
  return "release";
2758
2394
  }
@@ -2760,7 +2396,11 @@ var OrchestratorService = class {
2760
2396
  if (!isUsableWorkflowResolution(resolution)) {
2761
2397
  return "restart";
2762
2398
  }
2763
- return this.isIssueCandidateEligible(eligibleContext.issue, resolution.lifecycle, eligibleContext.issues) ? "restart" : "release";
2399
+ return this.isIssueCandidateEligible(
2400
+ eligibleContext.issue,
2401
+ resolution.lifecycle,
2402
+ eligibleContext.issues
2403
+ ) ? "restart" : "release";
2764
2404
  } catch {
2765
2405
  return "restart";
2766
2406
  }
@@ -2771,7 +2411,9 @@ var OrchestratorService = class {
2771
2411
  fetchImpl: this.dependencies.fetchImpl,
2772
2412
  ...trackerDependencies
2773
2413
  });
2774
- const issue = issues.find((candidate) => candidate.identifier === issueIdentifier);
2414
+ const issue = issues.find(
2415
+ (candidate) => candidate.identifier === issueIdentifier
2416
+ );
2775
2417
  return issue ? { issue, issues } : null;
2776
2418
  }
2777
2419
  async fetchWorkerRunInfo(run) {
@@ -2795,12 +2437,20 @@ var OrchestratorService = class {
2795
2437
  async readPersistedWorkerTokenUsage(run) {
2796
2438
  const artifactPaths = [
2797
2439
  join3(run.workspaceRuntimeDir, "token-usage.json"),
2798
- join3(run.workspaceRuntimeDir, ".orchestrator", "runs", run.runId, "token-usage.json")
2440
+ join3(
2441
+ run.workspaceRuntimeDir,
2442
+ ".orchestrator",
2443
+ "runs",
2444
+ run.runId,
2445
+ "token-usage.json"
2446
+ )
2799
2447
  ];
2800
2448
  for (const artifactPath of artifactPaths) {
2801
2449
  try {
2802
2450
  const raw = await readFile3(artifactPath, "utf8");
2803
- const tokenUsage = JSON.parse(raw);
2451
+ const tokenUsage = JSON.parse(
2452
+ raw
2453
+ );
2804
2454
  if (hasTokenUsage(tokenUsage)) {
2805
2455
  return tokenUsage;
2806
2456
  }
@@ -2821,7 +2471,10 @@ var OrchestratorService = class {
2821
2471
  if (!isUsableWorkflowResolution(workflowResolution)) {
2822
2472
  return null;
2823
2473
  }
2824
- const hookEnv = this.buildProjectExecutionEnv(tenant.projectId, buildHookEnv(context));
2474
+ const hookEnv = this.buildProjectExecutionEnv(
2475
+ tenant.projectId,
2476
+ buildHookEnv(context)
2477
+ );
2825
2478
  return executeWorkspaceHook({
2826
2479
  kind,
2827
2480
  hooks: workflowResolution.workflow.hooks,
@@ -2839,14 +2492,24 @@ var OrchestratorService = class {
2839
2492
  return readEnvFile(envPath);
2840
2493
  } catch (error) {
2841
2494
  const message = error instanceof Error ? error.message : "Unknown error occurred.";
2842
- (this.dependencies.stderr ?? process.stderr).write(`[warn] Failed to load project env for ${projectId} from ${envPath}: ${message}
2843
- `);
2495
+ (this.dependencies.stderr ?? process.stderr).write(
2496
+ `[warn] Failed to load project env for ${projectId} from ${envPath}: ${message}
2497
+ `
2498
+ );
2844
2499
  return {};
2845
2500
  }
2846
2501
  }
2847
2502
  buildProjectExecutionEnv(projectId, env) {
2848
- const inheritedEnv = Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string"));
2849
- const explicitEnv = Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
2503
+ const inheritedEnv = Object.fromEntries(
2504
+ Object.entries(process.env).filter(
2505
+ (entry) => typeof entry[1] === "string"
2506
+ )
2507
+ );
2508
+ const explicitEnv = Object.fromEntries(
2509
+ Object.entries(env).filter(
2510
+ (entry) => typeof entry[1] === "string"
2511
+ )
2512
+ );
2850
2513
  return {
2851
2514
  ...this.readProjectEnv(projectId),
2852
2515
  ...inheritedEnv,
@@ -2862,19 +2525,18 @@ var OrchestratorService = class {
2862
2525
  lastError: "Superseded by recovered run."
2863
2526
  };
2864
2527
  await this.store.saveRun(supersededRecord);
2865
- const issue = resolveTrackerAdapter2(tenant.tracker).reviveIssue(tenant, run);
2866
- const restarted = await this.startRun(tenant, issue, {
2867
- threadId: run.threadId ?? run.runtimeSession?.threadId ?? null,
2868
- cumulativeTurnCount: resolvePersistedCumulativeTurnCount(run),
2869
- lastTurnSummary: run.lastTurnSummary ?? null
2870
- });
2528
+ const issue = resolveTrackerAdapter2(tenant.tracker).reviveIssue(
2529
+ tenant,
2530
+ run
2531
+ );
2532
+ const restarted = await this.startRun(tenant, issue);
2871
2533
  const recoveredRecord = {
2872
2534
  ...restarted,
2873
2535
  attempt: run.attempt,
2874
2536
  retryKind: run.retryKind ?? "recovery",
2875
2537
  createdAt: run.createdAt,
2876
2538
  issueWorkspaceKey: run.issueWorkspaceKey,
2877
- threadId: run.threadId ?? run.runtimeSession?.threadId ?? null,
2539
+ threadId: null,
2878
2540
  cumulativeTurnCount: resolvePersistedCumulativeTurnCount(run),
2879
2541
  lastTurnSummary: run.lastTurnSummary ?? null,
2880
2542
  turnCount: 0
@@ -2892,11 +2554,14 @@ var OrchestratorService = class {
2892
2554
  issueRecords: upsertIssueOrchestration(issueRecords, {
2893
2555
  issueId: recoveredRecord.issueId,
2894
2556
  identifier: recoveredRecord.issueIdentifier,
2895
- workspaceKey: recoveredRecord.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2896
- projectId: tenant.projectId,
2897
- adapter: tenant.tracker.adapter,
2898
- issueSubjectId: recoveredRecord.issueSubjectId
2899
- }, recoveredRecord.issueIdentifier),
2557
+ workspaceKey: recoveredRecord.issueWorkspaceKey ?? deriveIssueWorkspaceKey(
2558
+ {
2559
+ projectId: tenant.projectId,
2560
+ adapter: tenant.tracker.adapter,
2561
+ issueSubjectId: recoveredRecord.issueSubjectId
2562
+ },
2563
+ recoveredRecord.issueIdentifier
2564
+ ),
2900
2565
  state: "running",
2901
2566
  currentRunId: recoveredRecord.runId,
2902
2567
  retryEntry: null,
@@ -2917,34 +2582,40 @@ var OrchestratorService = class {
2917
2582
  lastError: "Retry canceled because the tracker issue is no longer actionable."
2918
2583
  };
2919
2584
  await this.store.saveRun(suppressedRun);
2920
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
2585
+ this.logVerbose(
2586
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
2587
+ );
2921
2588
  return {
2922
2589
  issueRecords: releaseIssueOrchestration(issueRecords, run.issueId, now),
2923
2590
  recovered: false
2924
2591
  };
2925
2592
  }
2926
2593
  async loadProjectPollInterval(tenant) {
2927
- const intervals = await Promise.all(tenant.repositories.map(async (repository) => {
2928
- const resolution = await this.loadProjectWorkflow(tenant, repository);
2929
- return isUsableWorkflowResolution(resolution) ? resolution.workflow.polling.intervalMs : NaN;
2930
- }));
2931
- const validIntervals = intervals.filter((value) => Number.isFinite(value) && value > 0);
2594
+ const intervals = await Promise.all(
2595
+ tenant.repositories.map(async (repository) => {
2596
+ const resolution = await this.loadProjectWorkflow(tenant, repository);
2597
+ return isUsableWorkflowResolution(resolution) ? resolution.workflow.polling.intervalMs : NaN;
2598
+ })
2599
+ );
2600
+ const validIntervals = intervals.filter(
2601
+ (value) => Number.isFinite(value) && value > 0
2602
+ );
2932
2603
  return validIntervals.length ? Math.min(...validIntervals) : DEFAULT_POLL_INTERVAL_MS;
2933
2604
  }
2934
2605
  async loadProjectMaxConcurrentByState(tenant) {
2935
2606
  const result = {};
2936
- const resolutions = await Promise.all(tenant.repositories.map(async (repository) => {
2937
- try {
2938
- return await this.loadProjectWorkflow(tenant, repository);
2939
- } catch {
2940
- return null;
2941
- }
2942
- }));
2607
+ const resolutions = await Promise.all(
2608
+ tenant.repositories.map(async (repository) => {
2609
+ try {
2610
+ return await this.loadProjectWorkflow(tenant, repository);
2611
+ } catch {
2612
+ return null;
2613
+ }
2614
+ })
2615
+ );
2943
2616
  for (const resolution of resolutions) {
2944
- if (!resolution)
2945
- continue;
2946
- if (!isUsableWorkflowResolution(resolution))
2947
- continue;
2617
+ if (!resolution) continue;
2618
+ if (!isUsableWorkflowResolution(resolution)) continue;
2948
2619
  const stateLimits = resolution.workflow.agent.maxConcurrentAgentsByState;
2949
2620
  for (const [state, limit] of Object.entries(stateLimits)) {
2950
2621
  const existing = result[state];
@@ -2963,7 +2634,7 @@ var OrchestratorService = class {
2963
2634
  return {
2964
2635
  baseDelayMs: this.dependencies.retryBackoffMs ?? resolution.workflow.agent.retryBaseDelayMs,
2965
2636
  maxDelayMs: this.dependencies.retryBackoffMs ?? resolution.workflow.agent.maxRetryBackoffMs,
2966
- stallTimeoutMs: resolution.workflow.codex.stallTimeoutMs
2637
+ stallTimeoutMs: resolveWorkflowRuntimeTimeouts(resolution.workflow).stallTimeoutMs
2967
2638
  };
2968
2639
  } catch {
2969
2640
  if (!this.dependencies.retryBackoffMs) {
@@ -2980,15 +2651,22 @@ var OrchestratorService = class {
2980
2651
  if (this.dependencies.concurrency !== void 0) {
2981
2652
  return this.dependencies.concurrency;
2982
2653
  }
2983
- const limits = await Promise.all(project.repositories.map(async (repository) => {
2984
- try {
2985
- const resolution = await this.loadProjectWorkflow(project, repository);
2986
- return isUsableWorkflowResolution(resolution) ? resolution.workflow.agent.maxConcurrentAgents : NaN;
2987
- } catch {
2988
- return NaN;
2989
- }
2990
- }));
2991
- const validLimits = limits.filter((value) => Number.isFinite(value) && value >= 0);
2654
+ const limits = await Promise.all(
2655
+ project.repositories.map(async (repository) => {
2656
+ try {
2657
+ const resolution = await this.loadProjectWorkflow(
2658
+ project,
2659
+ repository
2660
+ );
2661
+ return isUsableWorkflowResolution(resolution) ? resolution.workflow.agent.maxConcurrentAgents : NaN;
2662
+ } catch {
2663
+ return NaN;
2664
+ }
2665
+ })
2666
+ );
2667
+ const validLimits = limits.filter(
2668
+ (value) => Number.isFinite(value) && value >= 0
2669
+ );
2992
2670
  return validLimits.length ? Math.min(...validLimits) : DEFAULT_CONCURRENCY;
2993
2671
  }
2994
2672
  async resolveWorkflowResolution(repository, cacheRoot, resolution, changed) {
@@ -3002,7 +2680,10 @@ var OrchestratorService = class {
3002
2680
  };
3003
2681
  let workflowPath = effectiveResolution.workflowPath;
3004
2682
  try {
3005
- workflowPath = await this.persistLastKnownGoodWorkflow(cacheRoot, effectiveResolution) ?? effectiveResolution.workflowPath;
2683
+ workflowPath = await this.persistLastKnownGoodWorkflow(
2684
+ cacheRoot,
2685
+ effectiveResolution
2686
+ ) ?? effectiveResolution.workflowPath;
3006
2687
  } catch {
3007
2688
  workflowPath = effectiveResolution.workflowPath;
3008
2689
  }
@@ -3017,8 +2698,10 @@ var OrchestratorService = class {
3017
2698
  const message = resolution.validationError ?? "Invalid repository WORKFLOW.md";
3018
2699
  const previousMessage = this.lastReportedWorkflowErrors.get(cacheKey);
3019
2700
  if (changed || previousMessage !== message) {
3020
- process.stderr.write(`[orchestrator] failed to reload WORKFLOW.md for ${repository.owner}/${repository.name}: ${message}
3021
- `);
2701
+ process.stderr.write(
2702
+ `[orchestrator] failed to reload WORKFLOW.md for ${repository.owner}/${repository.name}: ${message}
2703
+ `
2704
+ );
3022
2705
  this.lastReportedWorkflowErrors.set(cacheKey, message);
3023
2706
  }
3024
2707
  if (!cached) {
@@ -3108,10 +2791,22 @@ var OrchestratorService = class {
3108
2791
  adapter: issue.tracker.adapter,
3109
2792
  issueSubjectId
3110
2793
  };
3111
- const preferredWorkspaceKey = deriveIssueWorkspaceKey(identity, issue.identifier);
2794
+ const preferredWorkspaceKey = deriveIssueWorkspaceKey(
2795
+ identity,
2796
+ issue.identifier
2797
+ );
3112
2798
  const legacyWorkspaceKey = deriveLegacyIssueWorkspaceKey(identity);
3113
2799
  const orchestrationRecord = (await this.store.loadProjectIssueOrchestrations(tenant.projectId)).find((record) => record.issueId === issue.id);
3114
- const workspaceRecord = (orchestrationRecord ? await this.store.loadIssueWorkspace(tenant.projectId, orchestrationRecord.workspaceKey) : null) ?? await this.store.loadIssueWorkspace(tenant.projectId, preferredWorkspaceKey) ?? (legacyWorkspaceKey === preferredWorkspaceKey ? null : await this.store.loadIssueWorkspace(tenant.projectId, legacyWorkspaceKey));
2800
+ const workspaceRecord = (orchestrationRecord ? await this.store.loadIssueWorkspace(
2801
+ tenant.projectId,
2802
+ orchestrationRecord.workspaceKey
2803
+ ) : null) ?? await this.store.loadIssueWorkspace(
2804
+ tenant.projectId,
2805
+ preferredWorkspaceKey
2806
+ ) ?? (legacyWorkspaceKey === preferredWorkspaceKey ? null : await this.store.loadIssueWorkspace(
2807
+ tenant.projectId,
2808
+ legacyWorkspaceKey
2809
+ ));
3115
2810
  if (!workspaceRecord || workspaceRecord.status === "removed") {
3116
2811
  return;
3117
2812
  }
@@ -3121,17 +2816,26 @@ var OrchestratorService = class {
3121
2816
  updatedAt: now.toISOString()
3122
2817
  };
3123
2818
  await this.store.saveIssueWorkspace(pendingRecord);
3124
- const hookResult = await this.runHook("before_remove", tenant, workspaceRecord.repositoryPath, issue.repository, {
3125
- projectId: tenant.projectId,
3126
- workspaceKey: workspaceRecord.workspaceKey,
3127
- issueSubjectId,
3128
- issueIdentifier: issue.identifier,
3129
- workspacePath: workspaceRecord.workspacePath,
3130
- repositoryPath: workspaceRecord.repositoryPath
3131
- }, workflowResolution);
2819
+ const hookResult = await this.runHook(
2820
+ "before_remove",
2821
+ tenant,
2822
+ workspaceRecord.repositoryPath,
2823
+ issue.repository,
2824
+ {
2825
+ projectId: tenant.projectId,
2826
+ workspaceKey: workspaceRecord.workspaceKey,
2827
+ issueSubjectId,
2828
+ issueIdentifier: issue.identifier,
2829
+ workspacePath: workspaceRecord.workspacePath,
2830
+ repositoryPath: workspaceRecord.repositoryPath
2831
+ },
2832
+ workflowResolution
2833
+ );
3132
2834
  if (hookResult && hookResult.outcome !== "success" && hookResult.outcome !== "skipped") {
3133
2835
  const errorMessage = hookResult.error ?? `before_remove hook ${hookResult.outcome}`;
3134
- console.warn(`[orchestrator] before_remove hook failed for ${issue.identifier}; continuing cleanup: ${errorMessage}`);
2836
+ console.warn(
2837
+ `[orchestrator] before_remove hook failed for ${issue.identifier}; continuing cleanup: ${errorMessage}`
2838
+ );
3135
2839
  }
3136
2840
  try {
3137
2841
  await rm3(workspaceRecord.workspacePath, { recursive: true, force: true });
@@ -3149,19 +2853,26 @@ var OrchestratorService = class {
3149
2853
  return issueRecords.find((record) => record.issueId === issueId)?.failureRetryCount ?? null;
3150
2854
  }
3151
2855
  async isFailureRetrySuppressedIssue(tenant, issue, issueRecords, latestRun) {
3152
- const issueRecord = issueRecords.find((record) => record.issueId === issue.id || record.identifier === issue.identifier) ?? null;
2856
+ const issueRecord = issueRecords.find(
2857
+ (record) => record.issueId === issue.id || record.identifier === issue.identifier
2858
+ ) ?? null;
3153
2859
  if (!issueRecord || issueRecord.failureRetryCount <= 0) {
3154
2860
  return false;
3155
2861
  }
3156
- const maxFailureRetries = await this.loadMaxFailureRetries(tenant, issue.repository);
2862
+ const maxFailureRetries = await this.loadMaxFailureRetries(
2863
+ tenant,
2864
+ issue.repository
2865
+ );
3157
2866
  if (issueRecord.failureRetryCount < maxFailureRetries) {
3158
2867
  return false;
3159
2868
  }
3160
2869
  if (!latestRun || latestRun.status !== "suppressed" || latestRun.issueState !== issue.state || !latestRun.lastError?.includes(MAX_FAILURE_RETRIES_EXCEEDED_REASON)) {
3161
2870
  return false;
3162
2871
  }
3163
- const issueUpdatedAtMs = parseTimestampMs2(issue.updatedAt);
3164
- const suppressedAtMs = parseTimestampMs2(latestRun.completedAt ?? latestRun.updatedAt);
2872
+ const issueUpdatedAtMs = parseTimestampMs(issue.updatedAt);
2873
+ const suppressedAtMs = parseTimestampMs(
2874
+ latestRun.completedAt ?? latestRun.updatedAt
2875
+ );
3165
2876
  if (issueUpdatedAtMs === null || suppressedAtMs === null) {
3166
2877
  return true;
3167
2878
  }
@@ -3177,7 +2888,9 @@ var OrchestratorService = class {
3177
2888
  }
3178
2889
  };
3179
2890
  function hasTokenUsage(tokenUsage) {
3180
- return Boolean(tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0 || tokenUsage.totalTokens > 0));
2891
+ return Boolean(
2892
+ tokenUsage && (tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0 || tokenUsage.totalTokens > 0)
2893
+ );
3181
2894
  }
3182
2895
  function isRecord(value) {
3183
2896
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -3189,7 +2902,9 @@ function resolveProjectRateLimits(runs, issues) {
3189
2902
  if (!isRecord(run.rateLimits)) {
3190
2903
  continue;
3191
2904
  }
3192
- const timestamp = parseTimestampMs2(run.lastEventAt ?? run.updatedAt ?? run.startedAt);
2905
+ const timestamp = parseTimestampMs(
2906
+ run.lastEventAt ?? run.updatedAt ?? run.startedAt
2907
+ );
3193
2908
  const sortableTimestamp = timestamp ?? -Infinity;
3194
2909
  if (sortableTimestamp >= latestRunTimestamp) {
3195
2910
  latestRunTimestamp = sortableTimestamp;
@@ -3268,56 +2983,21 @@ function resolvePersistedCumulativeTurnCount(run) {
3268
2983
  return run.cumulativeTurnCount ?? run.turnCount ?? 0;
3269
2984
  }
3270
2985
  function hasConvergenceLockedRun(runs, issueId, issueState, issueUpdatedAt) {
3271
- const latestRun = runs.filter((run) => run.issueId === issueId).sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
2986
+ const latestRun = runs.filter((run) => run.issueId === issueId).sort(
2987
+ (left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime()
2988
+ )[0];
3272
2989
  if (latestRun?.runtimeSession?.exitClassification !== "convergence-detected" || latestRun.issueState !== issueState) {
3273
2990
  return false;
3274
2991
  }
3275
- const convergedAtMs = parseTimestampMs2(latestRun.completedAt ?? latestRun.updatedAt);
3276
- const issueUpdatedAtMs = parseTimestampMs2(issueUpdatedAt);
2992
+ const convergedAtMs = parseTimestampMs(
2993
+ latestRun.completedAt ?? latestRun.updatedAt
2994
+ );
2995
+ const issueUpdatedAtMs = parseTimestampMs(issueUpdatedAt);
3277
2996
  if (convergedAtMs === null || issueUpdatedAtMs === null) {
3278
2997
  return true;
3279
2998
  }
3280
2999
  return issueUpdatedAtMs <= convergedAtMs;
3281
3000
  }
3282
- function resolveIssueBudgetSnapshot(runs, issueId) {
3283
- const issueRuns = runs.filter((run) => run.issueId === issueId);
3284
- const startedAtCandidates = issueRuns.map((run) => run.startedAt).filter((value) => typeof value === "string");
3285
- return {
3286
- cumulativeTurnCount: issueRuns.reduce((total, run) => total + resolvePersistedCumulativeTurnCount(run), 0),
3287
- tokenUsage: issueRuns.reduce((total, run) => ({
3288
- inputTokens: total.inputTokens + (run.tokenUsage?.inputTokens ?? 0),
3289
- outputTokens: total.outputTokens + (run.tokenUsage?.outputTokens ?? 0),
3290
- totalTokens: total.totalTokens + (run.tokenUsage?.totalTokens ?? 0)
3291
- }), {
3292
- inputTokens: 0,
3293
- outputTokens: 0,
3294
- totalTokens: 0
3295
- }),
3296
- sessionStartedAt: startedAtCandidates.sort((left, right) => left.localeCompare(right))[0] ?? null
3297
- };
3298
- }
3299
- function isIssueBudgetExceeded(snapshot, now, env = process.env) {
3300
- const globalMaxTurns = parsePositiveInteger(env.SYMPHONY_GLOBAL_MAX_TURNS ?? "") ?? DEFAULT_GLOBAL_MAX_TURNS;
3301
- if (snapshot.cumulativeTurnCount >= globalMaxTurns) {
3302
- return true;
3303
- }
3304
- const maxTokens = parsePositiveInteger(env.SYMPHONY_MAX_TOKENS ?? "") ?? DEFAULT_MAX_TOKENS;
3305
- if (snapshot.tokenUsage.totalTokens >= maxTokens) {
3306
- return true;
3307
- }
3308
- const sessionTimeoutMs = parsePositiveInteger(env.SYMPHONY_SESSION_TIMEOUT_MS ?? "");
3309
- if (sessionTimeoutMs === null || snapshot.sessionStartedAt === null) {
3310
- return false;
3311
- }
3312
- return now.getTime() - new Date(snapshot.sessionStartedAt).getTime() >= sessionTimeoutMs;
3313
- }
3314
- function parsePositiveInteger(value) {
3315
- const parsed = Number(value);
3316
- if (!Number.isFinite(parsed) || parsed <= 0) {
3317
- return null;
3318
- }
3319
- return Math.floor(parsed);
3320
- }
3321
3001
  function resolveCumulativeTurnCount(run, turnCount) {
3322
3002
  const carriedTotal = resolvePersistedCumulativeTurnCount(run);
3323
3003
  if (turnCount === null) {
@@ -3360,7 +3040,10 @@ function resolveWorkerCommand() {
3360
3040
  return `node ${fileURLToPath(workerUrl)}`;
3361
3041
  } catch {
3362
3042
  try {
3363
- const bundledWorker = join3(fileURLToPath(new URL(".", import.meta.url)), "worker-entry.js");
3043
+ const bundledWorker = join3(
3044
+ fileURLToPath(new URL(".", import.meta.url)),
3045
+ "worker-entry.js"
3046
+ );
3364
3047
  return `node ${bundledWorker}`;
3365
3048
  } catch {
3366
3049
  return DEFAULT_WORKER_COMMAND;
@@ -3373,17 +3056,13 @@ function createStore(runtimeRoot = ".runtime", options = {}) {
3373
3056
  function sortCandidatesForDispatch(candidates) {
3374
3057
  return [...candidates].sort((a, b) => {
3375
3058
  if (a.priority !== b.priority) {
3376
- if (a.priority === null)
3377
- return 1;
3378
- if (b.priority === null)
3379
- return -1;
3059
+ if (a.priority === null) return 1;
3060
+ if (b.priority === null) return -1;
3380
3061
  return a.priority - b.priority;
3381
3062
  }
3382
3063
  if (a.createdAt !== b.createdAt) {
3383
- if (a.createdAt === null)
3384
- return 1;
3385
- if (b.createdAt === null)
3386
- return -1;
3064
+ if (a.createdAt === null) return 1;
3065
+ if (b.createdAt === null) return -1;
3387
3066
  return a.createdAt < b.createdAt ? -1 : 1;
3388
3067
  }
3389
3068
  return a.identifier.localeCompare(b.identifier);
@@ -3424,8 +3103,8 @@ function buildLatestRunMapByIssueId(runs) {
3424
3103
  latestRuns.set(run.issueId, run);
3425
3104
  continue;
3426
3105
  }
3427
- const runUpdatedAtMs = parseTimestampMs2(run.updatedAt) ?? -Infinity;
3428
- const existingUpdatedAtMs = parseTimestampMs2(existing.updatedAt) ?? -Infinity;
3106
+ const runUpdatedAtMs = parseTimestampMs(run.updatedAt) ?? -Infinity;
3107
+ const existingUpdatedAtMs = parseTimestampMs(existing.updatedAt) ?? -Infinity;
3429
3108
  if (runUpdatedAtMs > existingUpdatedAtMs) {
3430
3109
  latestRuns.set(run.issueId, run);
3431
3110
  }
@@ -3437,7 +3116,9 @@ function isIssueOrchestrationClaimed(state) {
3437
3116
  }
3438
3117
  function upsertIssueOrchestration(issueRecords, nextRecord) {
3439
3118
  const existingRecord = issueRecords.find((record) => record.issueId === nextRecord.issueId) ?? null;
3440
- const remaining = issueRecords.filter((record) => record.issueId !== nextRecord.issueId);
3119
+ const remaining = issueRecords.filter(
3120
+ (record) => record.issueId !== nextRecord.issueId
3121
+ );
3441
3122
  return [
3442
3123
  ...remaining,
3443
3124
  {
@@ -3448,19 +3129,21 @@ function upsertIssueOrchestration(issueRecords, nextRecord) {
3448
3129
  ];
3449
3130
  }
3450
3131
  function releaseIssueOrchestration(issueRecords, issueId, now) {
3451
- return issueRecords.map((record) => record.issueId === issueId ? {
3452
- ...record,
3453
- state: "released",
3454
- currentRunId: null,
3455
- retryEntry: null,
3456
- updatedAt: now.toISOString()
3457
- } : record);
3132
+ return issueRecords.map(
3133
+ (record) => record.issueId === issueId ? {
3134
+ ...record,
3135
+ state: "released",
3136
+ currentRunId: null,
3137
+ retryEntry: null,
3138
+ updatedAt: now.toISOString()
3139
+ } : record
3140
+ );
3458
3141
  }
3459
3142
  function isActiveRunStatus(status) {
3460
3143
  return status === "pending" || status === "starting" || status === "running" || status === "retrying";
3461
3144
  }
3462
3145
 
3463
- // ../orchestrator/dist/lock.js
3146
+ // ../orchestrator/src/lock.ts
3464
3147
  import { randomUUID as randomUUID2 } from "crypto";
3465
3148
  import { mkdir as mkdir4, open as open2, readFile as readFile4, rm as rm4 } from "fs/promises";
3466
3149
  import { dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
@@ -3498,14 +3181,18 @@ async function acquireProjectLock(input) {
3498
3181
  if (existing.status === "invalid") {
3499
3182
  invalidReadAttempts += 1;
3500
3183
  if (invalidReadAttempts >= LOCK_READ_RETRY_LIMIT) {
3501
- throw new Error(`Project "${input.projectId}" lock file is unreadable at "${lockPath}".`);
3184
+ throw new Error(
3185
+ `Project "${input.projectId}" lock file is unreadable at "${lockPath}".`
3186
+ );
3502
3187
  }
3503
3188
  await delay(LOCK_READ_RETRY_DELAY_MS);
3504
3189
  continue;
3505
3190
  }
3506
3191
  invalidReadAttempts = 0;
3507
3192
  if ((input.isProcessRunning ?? isProcessRunning)(existing.record.pid)) {
3508
- throw new Error(`Project "${input.projectId}" is already running (PID ${existing.record.pid}).`);
3193
+ throw new Error(
3194
+ `Project "${input.projectId}" is already running (PID ${existing.record.pid}).`
3195
+ );
3509
3196
  }
3510
3197
  await rm4(lockPath, { force: true });
3511
3198
  }
@@ -3544,7 +3231,9 @@ async function readProjectLock(lockPath) {
3544
3231
  }
3545
3232
  function assertValidProjectId(projectId) {
3546
3233
  if (projectId.length === 0 || projectId === "." || projectId === ".." || projectId.includes("/") || projectId.includes("\\")) {
3547
- throw new Error(`Invalid project ID "${projectId}". Project IDs must not contain path separators or traversal segments.`);
3234
+ throw new Error(
3235
+ `Invalid project ID "${projectId}". Project IDs must not contain path separators or traversal segments.`
3236
+ );
3548
3237
  }
3549
3238
  }
3550
3239
  function resolveProjectLockPath(runtimeRoot, projectId) {
@@ -3553,7 +3242,9 @@ function resolveProjectLockPath(runtimeRoot, projectId) {
3553
3242
  const projectDir = resolve2(store.projectDir(projectId));
3554
3243
  const relativeProjectDir = relative2(projectsRoot, projectDir);
3555
3244
  if (relativeProjectDir.length === 0 || relativeProjectDir.startsWith("..") || isAbsolute(relativeProjectDir)) {
3556
- throw new Error(`Invalid project ID "${projectId}". Project lock path must stay within "${projectsRoot}".`);
3245
+ throw new Error(
3246
+ `Invalid project ID "${projectId}". Project lock path must stay within "${projectsRoot}".`
3247
+ );
3557
3248
  }
3558
3249
  return join4(projectDir, ".lock");
3559
3250
  }
@@ -3581,15 +3272,24 @@ function isProcessRunning(pid) {
3581
3272
  }
3582
3273
  }
3583
3274
  function isAlreadyExistsError2(error) {
3584
- return Boolean(error && typeof error === "object" && "code" in error && error.code === "EEXIST");
3275
+ return Boolean(
3276
+ error && typeof error === "object" && "code" in error && error.code === "EEXIST"
3277
+ );
3585
3278
  }
3586
3279
  function isMissingFileError2(error) {
3587
- return Boolean(error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"));
3280
+ return Boolean(
3281
+ error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")
3282
+ );
3588
3283
  }
3589
3284
 
3590
- // ../orchestrator/dist/index.js
3285
+ // ../orchestrator/src/index.ts
3591
3286
  import { pathToFileURL } from "url";
3592
3287
  import { resolve as resolve3 } from "path";
3288
+
3289
+ // ../orchestrator/src/runtime-factory.ts
3290
+ import { join as join5 } from "path";
3291
+
3292
+ // ../orchestrator/src/index.ts
3593
3293
  function resolveOrchestratorLogLevel(value) {
3594
3294
  if (!value || value === "normal") {
3595
3295
  return "normal";
@@ -3597,7 +3297,9 @@ function resolveOrchestratorLogLevel(value) {
3597
3297
  if (value === "verbose") {
3598
3298
  return "verbose";
3599
3299
  }
3600
- throw new Error(`Unsupported log level: ${value}. Supported values: normal, verbose.`);
3300
+ throw new Error(
3301
+ `Unsupported log level: ${value}. Supported values: normal, verbose.`
3302
+ );
3601
3303
  }
3602
3304
  async function runCli(argv, dependencies = {}) {
3603
3305
  const [command = "run-once", ...args] = argv;
@@ -3607,8 +3309,12 @@ async function runCli(argv, dependencies = {}) {
3607
3309
  }
3608
3310
  const runtimeRoot = resolve3(parsed.runtimeRoot ?? ".runtime");
3609
3311
  const stderr = dependencies.stderr ?? process.stderr;
3610
- const eventsDir = resolveOptionalPath(parsed.eventsDir ?? process.env.SYMPHONY_EVENTS_DIR);
3611
- const logLevel = resolveOrchestratorLogLevel(parsed.logLevel ?? process.env.SYMPHONY_LOG_LEVEL);
3312
+ const eventsDir = resolveOptionalPath(
3313
+ parsed.eventsDir ?? process.env.SYMPHONY_EVENTS_DIR
3314
+ );
3315
+ const logLevel = resolveOrchestratorLogLevel(
3316
+ parsed.logLevel ?? process.env.SYMPHONY_LOG_LEVEL
3317
+ );
3612
3318
  const service = await dependencies.createService?.(runtimeRoot, parsed.projectId, {
3613
3319
  eventsDir,
3614
3320
  logLevel,
@@ -3656,8 +3362,10 @@ async function runCli(argv, dependencies = {}) {
3656
3362
  let exitCode = 0;
3657
3363
  void cleanup().catch((error) => {
3658
3364
  exitCode = 1;
3659
- stderr.write(`Failed to shut down orchestrator after ${signal}: ${error instanceof Error ? error.message : String(error)}
3660
- `);
3365
+ stderr.write(
3366
+ `Failed to shut down orchestrator after ${signal}: ${error instanceof Error ? error.message : String(error)}
3367
+ `
3368
+ );
3661
3369
  }).finally(() => {
3662
3370
  exitProcess(exitCode);
3663
3371
  });
@@ -3784,8 +3492,10 @@ function resolveOptionalPath(value) {
3784
3492
  }
3785
3493
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
3786
3494
  main().catch((error) => {
3787
- process.stderr.write(`${error instanceof Error ? error.message : "Unknown error"}
3788
- `);
3495
+ process.stderr.write(
3496
+ `${error instanceof Error ? error.message : "Unknown error"}
3497
+ `
3498
+ );
3789
3499
  process.exitCode = 1;
3790
3500
  });
3791
3501
  }