@gh-symphony/cli 0.0.20 → 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,18 +660,17 @@ 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;
@@ -1392,7 +683,7 @@ var STUCK_WORKER_TIMEOUT_MS = 30 * 60 * 1e3;
1392
683
  function isUsableWorkflowResolution(resolution) {
1393
684
  return resolution.isValid || resolution.usedLastKnownGood;
1394
685
  }
1395
- function parseTimestampMs2(value) {
686
+ function parseTimestampMs(value) {
1396
687
  if (!value) {
1397
688
  return null;
1398
689
  }
@@ -1410,9 +701,11 @@ function parseFiniteNumber(value) {
1410
701
  return null;
1411
702
  }
1412
703
  var OrchestratorService = class {
1413
- store;
1414
- projectConfig;
1415
- dependencies;
704
+ constructor(store, projectConfig, dependencies = {}) {
705
+ this.store = store;
706
+ this.projectConfig = projectConfig;
707
+ this.dependencies = dependencies;
708
+ }
1416
709
  projectPollIntervals = /* @__PURE__ */ new Map();
1417
710
  activeWorkerPids = /* @__PURE__ */ new Set();
1418
711
  workerStderrBuffers = /* @__PURE__ */ new Map();
@@ -1427,23 +720,25 @@ var OrchestratorService = class {
1427
720
  sleepResolver = null;
1428
721
  reconcilePromise = Promise.resolve();
1429
722
  reconcileRequested = false;
1430
- constructor(store, projectConfig, dependencies = {}) {
1431
- this.store = store;
1432
- this.projectConfig = projectConfig;
1433
- this.dependencies = dependencies;
1434
- }
1435
723
  async run(options = {}) {
1436
724
  this.running = true;
1437
- await this.runSerialized(() => this.performStartupCleanup(this.createTrackerDependencies()));
725
+ await this.runSerialized(
726
+ () => this.performStartupCleanup(this.createTrackerDependencies())
727
+ );
1438
728
  while (this.running) {
1439
729
  try {
1440
- const snapshot = await this.runOnceInternal(options.issueIdentifier, this.createTrackerDependencies());
730
+ const snapshot = await this.runOnceInternal(
731
+ options.issueIdentifier,
732
+ this.createTrackerDependencies()
733
+ );
1441
734
  await this.notifyTick(snapshot);
1442
735
  } catch (error) {
1443
736
  if (options.once) {
1444
737
  throw error;
1445
738
  }
1446
- 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
+ );
1447
742
  }
1448
743
  if (options.once || !this.running) {
1449
744
  return;
@@ -1452,20 +747,39 @@ var OrchestratorService = class {
1452
747
  }
1453
748
  }
1454
749
  async runOnce(options = {}) {
1455
- return this.runOnceInternal(options.issueIdentifier, this.createTrackerDependencies());
750
+ return this.runOnceInternal(
751
+ options.issueIdentifier,
752
+ this.createTrackerDependencies()
753
+ );
1456
754
  }
1457
755
  async status() {
1458
756
  return this.store.loadProjectStatus(this.projectConfig.projectId);
1459
757
  }
1460
758
  async statusForIssue(issueIdentifier) {
1461
- const issueRecords = await this.store.loadProjectIssueOrchestrations(this.projectConfig.projectId);
1462
- 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
+ );
1463
765
  if (!issueRecord) {
1464
766
  return null;
1465
767
  }
1466
- const currentRunCandidate = issueRecord.currentRunId ? await this.store.loadRun(issueRecord.currentRunId, this.projectConfig.projectId) : null;
1467
- const currentRun = isMatchingIssueRun(currentRunCandidate, this.projectConfig.projectId, issueRecord.issueId, issueIdentifier) ? currentRunCandidate : await this.findLatestRunForIssue(issueRecord.issueId, issueIdentifier);
1468
- 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
+ );
1469
783
  const latestEventMessage = recentEvents[recentEvents.length - 1]?.message ?? null;
1470
784
  const currentAttempt = currentRun?.attempt ?? issueRecord.retryEntry?.attempt ?? 0;
1471
785
  return {
@@ -1502,7 +816,10 @@ var OrchestratorService = class {
1502
816
  codex_session_logs: currentRun === null ? [] : [
1503
817
  {
1504
818
  label: "worker",
1505
- 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
+ ),
1506
823
  url: null
1507
824
  }
1508
825
  ]
@@ -1564,7 +881,9 @@ var OrchestratorService = class {
1564
881
  if (this.dependencies.pollIntervalMs) {
1565
882
  return this.dependencies.pollIntervalMs;
1566
883
  }
1567
- 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
+ );
1568
887
  return configuredIntervals.length ? Math.min(...configuredIntervals) : DEFAULT_POLL_INTERVAL_MS;
1569
888
  }
1570
889
  async reconcileProject(tenant, issueIdentifier, trackerDependencies = {}) {
@@ -1577,27 +896,57 @@ var OrchestratorService = class {
1577
896
  let pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;
1578
897
  let rateLimits = null;
1579
898
  let trackerRateLimits = null;
1580
- let issueRecords = await this.store.loadProjectIssueOrchestrations(tenant.projectId);
1581
- 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
+ );
1582
905
  const activeRuns = allRuns.filter((run) => isActiveRunStatus(run.status));
1583
906
  for (const run of activeRuns) {
1584
- const outcome = await this.reconcileRun(tenant, run, issueRecords, trackerDependencies);
907
+ const outcome = await this.reconcileRun(
908
+ tenant,
909
+ run,
910
+ issueRecords,
911
+ trackerDependencies
912
+ );
1585
913
  issueRecords = outcome.issueRecords;
1586
914
  if (outcome.recovered) {
1587
915
  recovered += 1;
1588
916
  }
1589
917
  }
1590
- const reconciledRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status));
1591
- 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
+ );
1592
924
  rateLimits = resolveProjectRateLimits(reconciledRuns, []);
1593
925
  try {
1594
926
  pollIntervalMs = await this.loadProjectPollInterval(tenant);
1595
- const currentActiveRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId && isActiveRunStatus(run.status));
1596
- const { runs: syncedActiveRuns, issuesByIdentifier: syncedIssuesByIdentifier } = await this.syncActiveRunIssueStates(tenant, trackerAdapter, currentActiveRuns, now);
1597
- const issues = await trackerAdapter.listIssues(tenant, trackerDependencies);
1598
- 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;
1599
946
  const { candidates: actionableCandidates, lifecycle } = await this.resolveActionableCandidates(tenant, filteredIssues);
1600
- const trackedIssuesByIdentifier = new Map(syncedIssuesByIdentifier);
947
+ const trackedIssuesByIdentifier = new Map(
948
+ syncedIssuesByIdentifier
949
+ );
1601
950
  for (const issue of filteredIssues) {
1602
951
  const existing = trackedIssuesByIdentifier.get(issue.identifier);
1603
952
  trackedIssuesByIdentifier.set(issue.identifier, {
@@ -1618,17 +967,33 @@ var OrchestratorService = class {
1618
967
  rateLimits: existing.rateLimits ?? issue.rateLimits ?? null
1619
968
  });
1620
969
  }
1621
- rateLimits = resolveProjectRateLimits(syncedActiveRuns, trackedIssuesByIdentifier.values());
1622
- trackerRateLimits = resolveTrackerRateLimits(trackedIssuesByIdentifier.values());
970
+ rateLimits = resolveProjectRateLimits(
971
+ syncedActiveRuns,
972
+ trackedIssuesByIdentifier.values()
973
+ );
974
+ trackerRateLimits = resolveTrackerRateLimits(
975
+ trackedIssuesByIdentifier.values()
976
+ );
1623
977
  const concurrency = await this.getProjectConcurrency(tenant);
1624
- const currentlyActive = issueRecords.filter((record) => isIssueOrchestrationClaimed(record.state)).length;
978
+ const currentlyActive = issueRecords.filter(
979
+ (record) => isIssueOrchestrationClaimed(record.state)
980
+ ).length;
1625
981
  const availableSlots = Math.max(0, concurrency - currentlyActive);
1626
- const latestRunsByIssueId = buildLatestRunMapByIssueId(projectRunsAfterReconcile);
982
+ const latestRunsByIssueId = buildLatestRunMapByIssueId(
983
+ projectRunsAfterReconcile
984
+ );
1627
985
  const unscheduledCandidates = actionableCandidates.filter((issue) => {
1628
- if (hasConvergenceLockedRun(projectRunsAfterReconcile, issue.id, issue.state, issue.updatedAt)) {
986
+ if (hasConvergenceLockedRun(
987
+ projectRunsAfterReconcile,
988
+ issue.id,
989
+ issue.state,
990
+ issue.updatedAt
991
+ )) {
1629
992
  return false;
1630
993
  }
1631
- 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
+ );
1632
997
  });
1633
998
  const sortedCandidates = sortCandidatesForDispatch(unscheduledCandidates);
1634
999
  const activeByState = /* @__PURE__ */ new Map();
@@ -1643,9 +1008,13 @@ var OrchestratorService = class {
1643
1008
  if (this.shuttingDown) {
1644
1009
  break;
1645
1010
  }
1646
- if (slotsRemaining <= 0)
1647
- break;
1648
- if (await this.isFailureRetrySuppressedIssue(tenant, issue, issueRecords, latestRunsByIssueId.get(issue.id) ?? null)) {
1011
+ if (slotsRemaining <= 0) break;
1012
+ if (await this.isFailureRetrySuppressedIssue(
1013
+ tenant,
1014
+ issue,
1015
+ issueRecords,
1016
+ latestRunsByIssueId.get(issue.id) ?? null
1017
+ )) {
1649
1018
  continue;
1650
1019
  }
1651
1020
  const stateLimit = maxConcurrentByState[issue.state];
@@ -1655,11 +1024,14 @@ var OrchestratorService = class {
1655
1024
  continue;
1656
1025
  }
1657
1026
  }
1658
- const preferredWorkspaceKey = deriveIssueWorkspaceKey({
1659
- projectId: tenant.projectId,
1660
- adapter: issue.tracker.adapter,
1661
- issueSubjectId: issue.id
1662
- }, 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
+ );
1663
1035
  issueRecords = upsertIssueOrchestration(issueRecords, {
1664
1036
  issueId: issue.id,
1665
1037
  identifier: issue.identifier,
@@ -1695,10 +1067,15 @@ var OrchestratorService = class {
1695
1067
  issueId: run.issueId,
1696
1068
  issueState: issue.state
1697
1069
  });
1698
- this.logVerbose(`[dispatch] Issue ${issue.identifier} \u2192 run ${run.runId}`);
1070
+ this.logVerbose(
1071
+ `[dispatch] Issue ${issue.identifier} \u2192 run ${run.runId}`
1072
+ );
1699
1073
  dispatched += 1;
1700
1074
  slotsRemaining -= 1;
1701
- activeByState.set(issue.state, (activeByState.get(issue.state) ?? 0) + 1);
1075
+ activeByState.set(
1076
+ issue.state,
1077
+ (activeByState.get(issue.state) ?? 0) + 1
1078
+ );
1702
1079
  }
1703
1080
  for (const issueRecord of issueRecords) {
1704
1081
  if (!isIssueOrchestrationClaimed(issueRecord.state)) {
@@ -1709,8 +1086,17 @@ var OrchestratorService = class {
1709
1086
  continue;
1710
1087
  }
1711
1088
  const persistedRun = issueRecord.currentRunId ? await this.store.loadRun(issueRecord.currentRunId, tenant.projectId) : null;
1712
- const activeRun = syncedActiveRuns.find((run) => isMatchingIssueRun(run, tenant.projectId, issueRecord.issueId, issueRecord.identifier)) ?? persistedRun;
1713
- 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
+ );
1714
1100
  if (resolvedIssue) {
1715
1101
  continue;
1716
1102
  }
@@ -1729,9 +1115,15 @@ var OrchestratorService = class {
1729
1115
  lastError: "Run suppressed because the tracker state is no longer actionable."
1730
1116
  };
1731
1117
  await this.store.saveRun(suppressedRun);
1732
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
1118
+ this.logVerbose(
1119
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
1120
+ );
1733
1121
  }
1734
- issueRecords = releaseIssueOrchestration(issueRecords, issueRecord.issueId, now);
1122
+ issueRecords = releaseIssueOrchestration(
1123
+ issueRecords,
1124
+ issueRecord.issueId,
1125
+ now
1126
+ );
1735
1127
  suppressed += 1;
1736
1128
  }
1737
1129
  const terminalIssuesByIdentifier = /* @__PURE__ */ new Map();
@@ -1747,14 +1139,28 @@ var OrchestratorService = class {
1747
1139
  } catch (error) {
1748
1140
  lastError = error instanceof Error ? error.message : "Unknown orchestration error";
1749
1141
  }
1750
- const effectivePollIntervalMs = resolveAdaptivePollIntervalMs(pollIntervalMs, trackerRateLimits);
1142
+ const effectivePollIntervalMs = resolveAdaptivePollIntervalMs(
1143
+ pollIntervalMs,
1144
+ trackerRateLimits
1145
+ );
1751
1146
  if (effectivePollIntervalMs > pollIntervalMs && isLowRateLimit(trackerRateLimits, LOW_RATE_LIMIT_WARNING_THRESHOLD)) {
1752
- 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
+ );
1753
1152
  }
1754
1153
  this.projectPollIntervals.set(tenant.projectId, effectivePollIntervalMs);
1755
- await this.store.saveProjectIssueOrchestrations(tenant.projectId, issueRecords);
1756
- const allTenantRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
1757
- 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
+ );
1758
1164
  rateLimits = rateLimits ?? resolveProjectRateLimits(latestRuns, []);
1759
1165
  const status = buildProjectSnapshot({
1760
1166
  project: tenant,
@@ -1771,7 +1177,9 @@ var OrchestratorService = class {
1771
1177
  async performStartupCleanup(trackerDependencies = {}) {
1772
1178
  const tenant = this.projectConfig;
1773
1179
  const now = this.now();
1774
- const workspaceRecords = await this.store.loadIssueWorkspaces(tenant.projectId);
1180
+ const workspaceRecords = await this.store.loadIssueWorkspaces(
1181
+ tenant.projectId
1182
+ );
1775
1183
  if (workspaceRecords.length === 0) {
1776
1184
  return;
1777
1185
  }
@@ -1779,10 +1187,20 @@ var OrchestratorService = class {
1779
1187
  const workflowCache = /* @__PURE__ */ new Map();
1780
1188
  let issues;
1781
1189
  try {
1782
- 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
+ );
1783
1199
  } catch (error) {
1784
1200
  const message = error instanceof Error ? error.message : "Unknown tracker error";
1785
- 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
+ );
1786
1204
  return;
1787
1205
  }
1788
1206
  const issuesById = new Map(issues.map((issue) => [issue.id, issue]));
@@ -1795,17 +1213,28 @@ var OrchestratorService = class {
1795
1213
  continue;
1796
1214
  }
1797
1215
  try {
1798
- const resolution = await this.loadStartupCleanupWorkflow(tenant, issue.repository, workflowCache);
1216
+ const resolution = await this.loadStartupCleanupWorkflow(
1217
+ tenant,
1218
+ issue.repository,
1219
+ workflowCache
1220
+ );
1799
1221
  if (!resolution.isValid) {
1800
1222
  continue;
1801
1223
  }
1802
1224
  if (!isStateTerminal(issue.state, resolution.lifecycle)) {
1803
1225
  continue;
1804
1226
  }
1805
- await this.cleanupTerminalIssueWorkspace(tenant, issue, now, resolution);
1227
+ await this.cleanupTerminalIssueWorkspace(
1228
+ tenant,
1229
+ issue,
1230
+ now,
1231
+ resolution
1232
+ );
1806
1233
  } catch (error) {
1807
1234
  const message = error instanceof Error ? error.message : "Unknown startup cleanup error";
1808
- 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
+ );
1809
1238
  }
1810
1239
  }
1811
1240
  }
@@ -1816,7 +1245,9 @@ var OrchestratorService = class {
1816
1245
  try {
1817
1246
  await this.dependencies.onTick(snapshot);
1818
1247
  } catch (error) {
1819
- this.writeStderr(`[orchestrator] onTick callback failed: ${this.formatErrorMessage(error)}`);
1248
+ this.writeStderr(
1249
+ `[orchestrator] onTick callback failed: ${this.formatErrorMessage(error)}`
1250
+ );
1820
1251
  }
1821
1252
  }
1822
1253
  formatErrorMessage(error) {
@@ -1827,11 +1258,18 @@ var OrchestratorService = class {
1827
1258
  }
1828
1259
  async resolveStartupCleanupTerminalStates(tenant, workspaceRecords, workflowCache) {
1829
1260
  const terminalStates = /* @__PURE__ */ new Map();
1830
- const repositories = this.resolveStartupCleanupRepositories(tenant, workspaceRecords);
1261
+ const repositories = this.resolveStartupCleanupRepositories(
1262
+ tenant,
1263
+ workspaceRecords
1264
+ );
1831
1265
  for (const repository of repositories) {
1832
1266
  let resolution;
1833
1267
  try {
1834
- resolution = await this.loadStartupCleanupWorkflow(tenant, repository, workflowCache);
1268
+ resolution = await this.loadStartupCleanupWorkflow(
1269
+ tenant,
1270
+ repository,
1271
+ workflowCache
1272
+ );
1835
1273
  } catch {
1836
1274
  continue;
1837
1275
  }
@@ -1855,14 +1293,20 @@ var OrchestratorService = class {
1855
1293
  resolveStartupCleanupRepositories(tenant, workspaceRecords) {
1856
1294
  const repositories = /* @__PURE__ */ new Map();
1857
1295
  for (const repository of tenant.repositories) {
1858
- repositories.set(this.startupCleanupRepositoryKey(repository.owner, repository.name), repository);
1296
+ repositories.set(
1297
+ this.startupCleanupRepositoryKey(repository.owner, repository.name),
1298
+ repository
1299
+ );
1859
1300
  }
1860
1301
  for (const workspaceRecord of workspaceRecords) {
1861
1302
  const repository = this.parseWorkspaceRepositoryRef(workspaceRecord);
1862
1303
  if (!repository) {
1863
1304
  continue;
1864
1305
  }
1865
- const key = this.startupCleanupRepositoryKey(repository.owner, repository.name);
1306
+ const key = this.startupCleanupRepositoryKey(
1307
+ repository.owner,
1308
+ repository.name
1309
+ );
1866
1310
  if (!repositories.has(key)) {
1867
1311
  repositories.set(key, repository);
1868
1312
  }
@@ -1870,7 +1314,9 @@ var OrchestratorService = class {
1870
1314
  return [...repositories.values()];
1871
1315
  }
1872
1316
  parseWorkspaceRepositoryRef(workspaceRecord) {
1873
- const match = workspaceRecord.issueIdentifier.match(/^([^/]+)\/([^#]+)#\d+$/);
1317
+ const match = workspaceRecord.issueIdentifier.match(
1318
+ /^([^/]+)\/([^#]+)#\d+$/
1319
+ );
1874
1320
  if (!match) {
1875
1321
  return null;
1876
1322
  }
@@ -1894,7 +1340,9 @@ var OrchestratorService = class {
1894
1340
  if (cachedResolution) {
1895
1341
  return cachedResolution;
1896
1342
  }
1897
- 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);
1898
1346
  workflowCache.set(cacheKey, resolutionPromise);
1899
1347
  return resolutionPromise;
1900
1348
  }
@@ -1916,7 +1364,11 @@ var OrchestratorService = class {
1916
1364
  const workflowResolutionCache = /* @__PURE__ */ new Map();
1917
1365
  this.workflowResolutionCache = workflowResolutionCache;
1918
1366
  try {
1919
- return await this.reconcileProject(this.projectConfig, issueIdentifier, trackerDependencies);
1367
+ return await this.reconcileProject(
1368
+ this.projectConfig,
1369
+ issueIdentifier,
1370
+ trackerDependencies
1371
+ );
1920
1372
  } finally {
1921
1373
  if (this.workflowResolutionCache === workflowResolutionCache) {
1922
1374
  this.workflowResolutionCache = null;
@@ -1931,14 +1383,21 @@ var OrchestratorService = class {
1931
1383
  };
1932
1384
  }
1933
1385
  async findLatestRunForIssue(issueId, issueIdentifier) {
1934
- 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
+ );
1935
1391
  return matchingRuns[0] ?? null;
1936
1392
  }
1937
1393
  async resolveActionableCandidates(tenant, issues) {
1938
1394
  const candidates = [];
1939
1395
  let lifecycle = null;
1940
1396
  for (const issue of issues) {
1941
- const resolution = await this.loadProjectWorkflow(tenant, issue.repository);
1397
+ const resolution = await this.loadProjectWorkflow(
1398
+ tenant,
1399
+ issue.repository
1400
+ );
1942
1401
  if (!isUsableWorkflowResolution(resolution)) {
1943
1402
  continue;
1944
1403
  }
@@ -1951,7 +1410,10 @@ var OrchestratorService = class {
1951
1410
  candidates.push(issue);
1952
1411
  }
1953
1412
  if (!lifecycle && tenant.repositories.length > 0) {
1954
- const resolution = await this.loadProjectWorkflow(tenant, tenant.repositories[0]);
1413
+ const resolution = await this.loadProjectWorkflow(
1414
+ tenant,
1415
+ tenant.repositories[0]
1416
+ );
1955
1417
  if (isUsableWorkflowResolution(resolution)) {
1956
1418
  lifecycle = resolution.lifecycle;
1957
1419
  }
@@ -1978,7 +1440,9 @@ var OrchestratorService = class {
1978
1440
  return false;
1979
1441
  }
1980
1442
  if (blockerRef.identifier) {
1981
- const blockerIssue = issues.find((candidate) => candidate.identifier === blockerRef.identifier);
1443
+ const blockerIssue = issues.find(
1444
+ (candidate) => candidate.identifier === blockerRef.identifier
1445
+ );
1982
1446
  if (blockerIssue?.state) {
1983
1447
  return !isStateTerminal(blockerIssue.state, lifecycle);
1984
1448
  }
@@ -1994,24 +1458,42 @@ var OrchestratorService = class {
1994
1458
  if (cachedResolution) {
1995
1459
  return cachedResolution;
1996
1460
  }
1997
- const resolutionPromise = this.loadProjectWorkflowUncached(tenant, repository);
1461
+ const resolutionPromise = this.loadProjectWorkflowUncached(
1462
+ tenant,
1463
+ repository
1464
+ );
1998
1465
  pendingCache.set(cacheKey, resolutionPromise);
1999
1466
  return resolutionPromise;
2000
1467
  }
2001
1468
  return this.loadProjectWorkflowUncached(tenant, repository);
2002
1469
  }
2003
1470
  async loadProjectWorkflowUncached(tenant, repository) {
2004
- 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
+ );
2005
1477
  const { repositoryDirectory, changed } = await syncRepositoryForRun({
2006
1478
  repository,
2007
1479
  targetDirectory: cacheRoot
2008
1480
  });
2009
- const resolution = await loadRepositoryWorkflow(repositoryDirectory, repository);
2010
- 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
+ );
2011
1491
  }
2012
1492
  async startRun(tenant, issue) {
2013
1493
  if (this.shuttingDown || !this.running) {
2014
- 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
+ );
2015
1497
  }
2016
1498
  const trackerAdapter = resolveTrackerAdapter2(tenant.tracker);
2017
1499
  const now = this.now();
@@ -2024,12 +1506,24 @@ var OrchestratorService = class {
2024
1506
  adapter: issue.tracker.adapter,
2025
1507
  issueSubjectId
2026
1508
  };
2027
- const preferredWorkspaceKey = deriveIssueWorkspaceKey(identity, issue.identifier);
1509
+ const preferredWorkspaceKey = deriveIssueWorkspaceKey(
1510
+ identity,
1511
+ issue.identifier
1512
+ );
2028
1513
  const legacyWorkspaceKey = deriveLegacyIssueWorkspaceKey(identity);
2029
- 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
+ ));
2030
1521
  const workspaceKey = existingWorkspaceRecord?.workspaceKey ?? preferredWorkspaceKey;
2031
1522
  const projectDir = this.store.projectDir(tenant.projectId);
2032
- const issueWorkspacePath = resolveIssueWorkspaceDirectory(projectDir, workspaceKey);
1523
+ const issueWorkspacePath = resolveIssueWorkspaceDirectory(
1524
+ projectDir,
1525
+ workspaceKey
1526
+ );
2033
1527
  const repositoryDirectory = await ensureIssueWorkspaceRepository({
2034
1528
  repository: issue.repository,
2035
1529
  issueWorkspacePath
@@ -2049,14 +1543,20 @@ var OrchestratorService = class {
2049
1543
  lastError: null
2050
1544
  };
2051
1545
  await this.store.saveIssueWorkspace(workspaceRecord);
2052
- const afterCreateResult = await this.runHook("after_create", tenant, repositoryDirectory, issue.repository, {
2053
- projectId: tenant.projectId,
2054
- workspaceKey,
2055
- issueSubjectId,
2056
- issueIdentifier: issue.identifier,
2057
- workspacePath: issueWorkspacePath,
2058
- repositoryPath: repositoryDirectory
2059
- });
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
+ );
2060
1560
  if (afterCreateResult && afterCreateResult.outcome !== "success" && afterCreateResult.outcome !== "skipped") {
2061
1561
  await this.store.appendRunEvent(runId, {
2062
1562
  at: now.toISOString(),
@@ -2069,23 +1569,35 @@ var OrchestratorService = class {
2069
1569
  }
2070
1570
  const workflow = await this.loadProjectWorkflow(tenant, issue.repository);
2071
1571
  if (!isUsableWorkflowResolution(workflow)) {
2072
- throw new Error(workflow.validationError ?? "Invalid repository WORKFLOW.md");
1572
+ throw new Error(
1573
+ workflow.validationError ?? "Invalid repository WORKFLOW.md"
1574
+ );
2073
1575
  }
2074
1576
  const promptVariables = buildPromptVariables(issue, {
2075
1577
  attempt: null
2076
1578
  // first execution
2077
1579
  });
2078
- const renderedPrompt = renderPrompt(workflow.promptTemplate, promptVariables);
2079
- await this.runHook("before_run", tenant, repositoryDirectory, issue.repository, {
2080
- projectId: tenant.projectId,
2081
- workspaceKey,
2082
- issueSubjectId,
2083
- issueIdentifier: issue.identifier,
2084
- workspacePath: issueWorkspacePath,
2085
- repositoryPath: repositoryDirectory,
2086
- runId,
2087
- state: issue.state
2088
- });
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);
2089
1601
  mkdirSync(runDir, { recursive: true });
2090
1602
  const workerLogStream = (this.dependencies.createWriteStreamImpl ?? createWriteStream)(join3(runDir, "worker.log"), {
2091
1603
  flags: "a"
@@ -2108,57 +1620,69 @@ var OrchestratorService = class {
2108
1620
  }
2109
1621
  workerLogAvailable = false;
2110
1622
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2111
- 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
+ );
2112
1626
  };
2113
- const child = (this.dependencies.spawnImpl ?? spawn2)("bash", ["-lc", resolveWorkerCommand()], {
2114
- cwd: process.cwd(),
2115
- env: this.buildProjectExecutionEnv(tenant.projectId, {
2116
- GITHUB_GRAPHQL_TOKEN: process.env.GITHUB_GRAPHQL_TOKEN ?? "",
2117
- CODEX_PROJECT_ID: tenant.projectId,
2118
- PROJECT_ID: tenant.projectId,
2119
- WORKING_DIRECTORY: repositoryDirectory,
2120
- WORKSPACE_RUNTIME_DIR: workspaceRuntimeDir,
2121
- SYMPHONY_RUN_ID: runId,
2122
- SYMPHONY_ISSUE_STATE: issue.state,
2123
- SYMPHONY_ISSUE_ID: issue.id,
2124
- SYMPHONY_ISSUE_IDENTIFIER: issue.identifier,
2125
- SYMPHONY_ISSUE_TITLE: issue.title,
2126
- SYMPHONY_ISSUE_SUBJECT_ID: issueSubjectId,
2127
- SYMPHONY_ISSUE_WORKSPACE_KEY: workspaceKey,
2128
- SYMPHONY_TRACKER_ADAPTER: issue.tracker.adapter,
2129
- SYMPHONY_TRACKER_BINDING_ID: issue.tracker.bindingId,
2130
- SYMPHONY_TRACKER_ITEM_ID: issue.tracker.itemId,
2131
- TARGET_REPOSITORY_CLONE_URL: issue.repository.cloneUrl,
2132
- TARGET_REPOSITORY_OWNER: issue.repository.owner,
2133
- TARGET_REPOSITORY_NAME: issue.repository.name,
2134
- TARGET_REPOSITORY_URL: issue.repository.url,
2135
- ...trackerAdapter.buildWorkerEnvironment(tenant, issue),
2136
- SYMPHONY_RENDERED_PROMPT: renderedPrompt,
2137
- SYMPHONY_WORKFLOW_PATH: workflow.workflowPath ?? "",
2138
- SYMPHONY_AGENT_COMMAND: workflow.workflow.codex.command,
2139
- SYMPHONY_APPROVAL_POLICY: workflow.workflow.codex.approvalPolicy ?? "",
2140
- SYMPHONY_THREAD_SANDBOX: workflow.workflow.codex.threadSandbox ?? "",
2141
- SYMPHONY_TURN_SANDBOX_POLICY: workflow.workflow.codex.turnSandboxPolicy ?? "",
2142
- SYMPHONY_MAX_TURNS: String(workflow.workflow.agent.maxTurns),
2143
- SYMPHONY_MAX_NONPRODUCTIVE_TURNS: process.env.SYMPHONY_MAX_NONPRODUCTIVE_TURNS ?? String(DEFAULT_MAX_NONPRODUCTIVE_TURNS),
2144
- // Clear legacy resume/budget env so fresh worker sessions do not
2145
- // inherit stale process-level values.
2146
- SYMPHONY_GLOBAL_MAX_TURNS: "",
2147
- SYMPHONY_MAX_TOKENS: "",
2148
- SYMPHONY_SESSION_TIMEOUT_MS: "",
2149
- SYMPHONY_RESUME_THREAD_ID: "",
2150
- SYMPHONY_CUMULATIVE_TURN_COUNT: "0",
2151
- SYMPHONY_CUMULATIVE_INPUT_TOKENS: "0",
2152
- SYMPHONY_CUMULATIVE_OUTPUT_TOKENS: "0",
2153
- SYMPHONY_CUMULATIVE_TOTAL_TOKENS: "0",
2154
- SYMPHONY_LAST_TURN_SUMMARY: "",
2155
- SYMPHONY_SESSION_STARTED_AT: "",
2156
- SYMPHONY_READ_TIMEOUT_MS: String(workflow.workflow.codex.readTimeoutMs),
2157
- SYMPHONY_TURN_TIMEOUT_MS: String(workflow.workflow.codex.turnTimeoutMs)
2158
- }),
2159
- detached: true,
2160
- stdio: ["ignore", "ignore", "pipe"]
2161
- });
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
+ );
2162
1686
  const handleWorkerStderrChunk = (chunk) => {
2163
1687
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8");
2164
1688
  if (workerLogAvailable) {
@@ -2195,7 +1719,9 @@ var OrchestratorService = class {
2195
1719
  if (child.pid) {
2196
1720
  this.retireWorkerPid(child.pid);
2197
1721
  }
2198
- 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
+ );
2199
1725
  };
2200
1726
  const finalizeWorkerStderr = (code, signal) => {
2201
1727
  if (workerExited || workerStderrFinalizing) {
@@ -2235,7 +1761,9 @@ var OrchestratorService = class {
2235
1761
  }
2236
1762
  child.on?.("error", (error) => {
2237
1763
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2238
- this.writeStderr(`[orchestrator] worker process error for ${runId}: ${message}`);
1764
+ this.writeStderr(
1765
+ `[orchestrator] worker process error for ${runId}: ${message}`
1766
+ );
2239
1767
  finalizeWorkerStderr(null, null);
2240
1768
  });
2241
1769
  child.on?.("close", (code, signal) => {
@@ -2282,14 +1810,24 @@ var OrchestratorService = class {
2282
1810
  issuesByIdentifier: /* @__PURE__ */ new Map()
2283
1811
  };
2284
1812
  }
2285
- const issues = await trackerAdapter.fetchIssueStatesByIds(tenant, activeIssueIds, {
2286
- fetchImpl: this.dependencies.fetchImpl
2287
- });
2288
- const issuesByIdentifier = new Map(issues.map((issue) => [issue.identifier, issue]));
2289
- 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
+ );
2290
1826
  const syncedRuns = [];
2291
1827
  for (const run of activeRuns) {
2292
- const currentTrackerState = issueStateByIdentifier.get(run.issueIdentifier);
1828
+ const currentTrackerState = issueStateByIdentifier.get(
1829
+ run.issueIdentifier
1830
+ );
2293
1831
  if (!currentTrackerState || currentTrackerState === run.issueState) {
2294
1832
  syncedRuns.push(run);
2295
1833
  continue;
@@ -2312,8 +1850,10 @@ var OrchestratorService = class {
2312
1850
  if (run.processId && this.isProcessRunning(run.processId)) {
2313
1851
  const retryPolicy = await this.loadRetryPolicy(tenant, run.repository);
2314
1852
  const configuredStallTimeoutMs = retryPolicy?.stallTimeoutMs ?? null;
2315
- const lastActivityAtMs = parseTimestampMs2(run.lastEventAt ?? run.startedAt);
2316
- const startedAtMs = parseTimestampMs2(run.startedAt);
1853
+ const lastActivityAtMs = parseTimestampMs(
1854
+ run.lastEventAt ?? run.startedAt
1855
+ );
1856
+ const startedAtMs = parseTimestampMs(run.startedAt);
2317
1857
  const elapsedSinceLastActivityMs = lastActivityAtMs === null ? null : now.getTime() - lastActivityAtMs;
2318
1858
  const runningSinceMs = startedAtMs === null ? null : now.getTime() - startedAtMs;
2319
1859
  const isStalledByWorkflowTimeout = configuredStallTimeoutMs !== null && configuredStallTimeoutMs > 0 && elapsedSinceLastActivityMs !== null && elapsedSinceLastActivityMs > configuredStallTimeoutMs;
@@ -2324,9 +1864,13 @@ var OrchestratorService = class {
2324
1864
  const elapsedSeconds = Math.round((elapsedMs ?? 0) / 1e3);
2325
1865
  const timeoutSeconds = Math.round((timeoutMs ?? 0) / 1e3);
2326
1866
  if (this.isVerboseLoggingEnabled()) {
2327
- 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
+ );
2328
1870
  } else {
2329
- 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
+ );
2330
1874
  }
2331
1875
  this.sendSignal(run.processId, "SIGTERM");
2332
1876
  } else {
@@ -2339,11 +1883,14 @@ var OrchestratorService = class {
2339
1883
  issueRecords = upsertIssueOrchestration(issueRecords, {
2340
1884
  issueId: run.issueId,
2341
1885
  identifier: run.issueIdentifier,
2342
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2343
- projectId: tenant.projectId,
2344
- adapter: tenant.tracker.adapter,
2345
- issueSubjectId: run.issueSubjectId
2346
- }, 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
+ ),
2347
1894
  state: "running",
2348
1895
  currentRunId: run.runId,
2349
1896
  retryEntry: null,
@@ -2361,12 +1908,29 @@ var OrchestratorService = class {
2361
1908
  const workerInfo = await this.fetchWorkerRunInfo(run);
2362
1909
  const runWithTokens = {
2363
1910
  ...run,
2364
- 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
+ ),
2365
1920
  threadId: workerInfo.threadId ?? run.threadId ?? null,
2366
- cumulativeTurnCount: resolveCumulativeTurnCount(run, workerInfo.turnCount ?? null),
1921
+ cumulativeTurnCount: resolveCumulativeTurnCount(
1922
+ run,
1923
+ workerInfo.turnCount ?? null
1924
+ ),
2367
1925
  tokenUsage: workerInfo.tokenUsage ?? run.tokenUsage,
2368
1926
  lastEvent: workerInfo.lastEvent ?? run.lastEvent,
2369
- 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
+ ),
2370
1934
  lastEventAt: workerInfo.lastEventAt ?? run.lastEventAt ?? void 0,
2371
1935
  lastEventAtSource: workerInfo.lastEventAtSource ?? run.lastEventAtSource ?? void 0,
2372
1936
  executionPhase: workerInfo.executionPhase ?? run.executionPhase ?? null,
@@ -2392,7 +1956,11 @@ var OrchestratorService = class {
2392
1956
  recovered: false
2393
1957
  };
2394
1958
  }
2395
- if (await this.resolveRetryRestartAction(tenant, run, trackerDependencies) === "release") {
1959
+ if (await this.resolveRetryRestartAction(
1960
+ tenant,
1961
+ run,
1962
+ trackerDependencies
1963
+ ) === "release") {
2396
1964
  return this.releaseRetryingRun(runWithTokens, issueRecords, now);
2397
1965
  }
2398
1966
  return this.restartRun(tenant, run, issueRecords, now, workerSessionId);
@@ -2410,28 +1978,46 @@ var OrchestratorService = class {
2410
1978
  runPhase: runWithTokens.runPhase ?? "failed"
2411
1979
  };
2412
1980
  await this.store.saveRun(completedRun);
2413
- this.logVerbose(`[run-completed] ${completedRun.runId} status=${completedRun.status}`);
1981
+ this.logVerbose(
1982
+ `[run-completed] ${completedRun.runId} status=${completedRun.status}`
1983
+ );
2414
1984
  return {
2415
1985
  issueRecords: releaseIssueOrchestration(issueRecords, run.issueId, now),
2416
1986
  recovered: false
2417
1987
  };
2418
1988
  }
2419
1989
  if (run.issueWorkspaceKey) {
2420
- const issueWorkspacePath = resolveIssueWorkspaceDirectory(this.store.projectDir(tenant.projectId), run.issueWorkspaceKey);
2421
- await this.runHook("after_run", tenant, run.workingDirectory, run.repository, {
2422
- projectId: run.projectId,
2423
- workspaceKey: run.issueWorkspaceKey,
2424
- issueSubjectId: run.issueSubjectId,
2425
- issueIdentifier: run.issueIdentifier,
2426
- workspacePath: issueWorkspacePath,
2427
- repositoryPath: run.workingDirectory,
2428
- runId: run.runId,
2429
- state: run.issueState
2430
- });
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
+ );
2431
2010
  }
2432
- const retryKind = await this.classifyRetryKind(tenant, run, trackerDependencies);
2011
+ const retryKind = await this.classifyRetryKind(
2012
+ tenant,
2013
+ run,
2014
+ trackerDependencies
2015
+ );
2433
2016
  const failureRetryCount = retryKind === "failure" ? (this.resolveFailureRetryCount(issueRecords, run.issueId) ?? 0) + 1 : this.resolveFailureRetryCount(issueRecords, run.issueId) ?? 0;
2434
- const maxFailureRetries = await this.loadMaxFailureRetries(tenant, run.repository);
2017
+ const maxFailureRetries = await this.loadMaxFailureRetries(
2018
+ tenant,
2019
+ run.repository
2020
+ );
2435
2021
  if (retryKind === "failure" && failureRetryCount >= maxFailureRetries) {
2436
2022
  const lastError = [
2437
2023
  `Run suppressed: ${MAX_FAILURE_RETRIES_EXCEEDED_REASON}.`,
@@ -2458,16 +2044,21 @@ var OrchestratorService = class {
2458
2044
  issueId: run.issueId,
2459
2045
  reason: MAX_FAILURE_RETRIES_EXCEEDED_REASON
2460
2046
  });
2461
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
2047
+ this.logVerbose(
2048
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
2049
+ );
2462
2050
  return {
2463
2051
  issueRecords: upsertIssueOrchestration(issueRecords, {
2464
2052
  issueId: run.issueId,
2465
2053
  identifier: run.issueIdentifier,
2466
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2467
- projectId: tenant.projectId,
2468
- adapter: tenant.tracker.adapter,
2469
- issueSubjectId: run.issueSubjectId
2470
- }, 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
+ ),
2471
2062
  state: "released",
2472
2063
  failureRetryCount,
2473
2064
  currentRunId: null,
@@ -2479,7 +2070,9 @@ var OrchestratorService = class {
2479
2070
  }
2480
2071
  let nextRetryAt;
2481
2072
  if (retryKind === "continuation") {
2482
- nextRetryAt = new Date(now.getTime() + CONTINUATION_RETRY_DELAY_MS).toISOString();
2073
+ nextRetryAt = new Date(
2074
+ now.getTime() + CONTINUATION_RETRY_DELAY_MS
2075
+ ).toISOString();
2483
2076
  } else {
2484
2077
  const retryOptions = await this.loadRetryPolicy(tenant, run.repository);
2485
2078
  const backoffMs = this.dependencies.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS;
@@ -2500,16 +2093,23 @@ var OrchestratorService = class {
2500
2093
  lastError: retryKind === "continuation" ? null : "Worker process exited unexpectedly."
2501
2094
  };
2502
2095
  await this.store.saveRun(retryRecord);
2503
- this.logVerbose(`[retry-scheduled] ${retryRecord.runId} kind=${retryKind} attempt=${retryRecord.attempt} nextAt=${nextRetryAt}`);
2504
- 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
+ );
2505
2102
  issueRecords = upsertIssueOrchestration(issueRecords, {
2506
2103
  issueId: run.issueId,
2507
2104
  identifier: run.issueIdentifier,
2508
- workspaceKey: run.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2509
- projectId: tenant.projectId,
2510
- adapter: tenant.tracker.adapter,
2511
- issueSubjectId: run.issueSubjectId
2512
- }, 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
+ ),
2513
2113
  state: "retry_queued",
2514
2114
  completedOnce: retryKind === "continuation" ? true : void 0,
2515
2115
  failureRetryCount,
@@ -2568,9 +2168,13 @@ var OrchestratorService = class {
2568
2168
  if (!isOrchestratorChannelEvent(parsed)) {
2569
2169
  return;
2570
2170
  }
2571
- void this.runSerialized(() => this.applyWorkerChannelEvent(runId, parsed)).catch((error) => {
2171
+ void this.runSerialized(
2172
+ () => this.applyWorkerChannelEvent(runId, parsed)
2173
+ ).catch((error) => {
2572
2174
  const message = error instanceof Error ? error.message : String(error ?? "unknown");
2573
- 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
+ );
2574
2178
  });
2575
2179
  } catch {
2576
2180
  }
@@ -2587,15 +2191,29 @@ var OrchestratorService = class {
2587
2191
  ...run,
2588
2192
  updatedAt: nowIso2,
2589
2193
  lastEvent: "heartbeat",
2590
- lastTurnSummary: resolveLastTurnSummary(run.lastTurnSummary, event.lastError),
2194
+ lastTurnSummary: resolveLastTurnSummary(
2195
+ run.lastTurnSummary,
2196
+ event.lastError
2197
+ ),
2591
2198
  lastEventAt: persistedLastEventAt,
2592
2199
  lastEventAtSource: event.lastEventAt != null ? "event-channel" : run.lastEventAtSource ?? null,
2593
2200
  tokenUsage: event.tokenUsage,
2594
2201
  rateLimits: event.rateLimits,
2595
- 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
+ ),
2596
2211
  threadId: event.sessionInfo?.threadId ?? run.threadId ?? run.runtimeSession?.threadId ?? null,
2597
2212
  turnCount: event.sessionInfo && event.sessionInfo.turnCount != null ? event.sessionInfo.turnCount : run.turnCount,
2598
- cumulativeTurnCount: resolveCumulativeTurnCount(run, event.sessionInfo?.turnCount ?? null),
2213
+ cumulativeTurnCount: resolveCumulativeTurnCount(
2214
+ run,
2215
+ event.sessionInfo?.turnCount ?? null
2216
+ ),
2599
2217
  executionPhase: event.executionPhase ?? run.executionPhase,
2600
2218
  runPhase: event.runPhase ?? run.runPhase,
2601
2219
  lastError: event.lastError
@@ -2656,15 +2274,29 @@ var OrchestratorService = class {
2656
2274
  ...run,
2657
2275
  updatedAt: nowIso,
2658
2276
  lastEvent: event.event ?? run.lastEvent ?? null,
2659
- lastTurnSummary: resolveLastTurnSummary(run.lastTurnSummary, resolveLastTurnSummaryCandidate(event.event, event.lastError)),
2277
+ lastTurnSummary: resolveLastTurnSummary(
2278
+ run.lastTurnSummary,
2279
+ resolveLastTurnSummaryCandidate(event.event, event.lastError)
2280
+ ),
2660
2281
  lastEventAt: event.lastEventAt,
2661
2282
  lastEventAtSource: "event-channel",
2662
2283
  tokenUsage: event.tokenUsage ?? run.tokenUsage,
2663
2284
  rateLimits: event.rateLimits ?? run.rateLimits ?? null,
2664
- 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
+ ),
2665
2294
  threadId: event.sessionInfo?.threadId ?? run.threadId ?? run.runtimeSession?.threadId ?? null,
2666
2295
  turnCount: event.sessionInfo && event.sessionInfo.turnCount != null ? event.sessionInfo.turnCount : run.turnCount,
2667
- cumulativeTurnCount: resolveCumulativeTurnCount(run, event.sessionInfo?.turnCount ?? null),
2296
+ cumulativeTurnCount: resolveCumulativeTurnCount(
2297
+ run,
2298
+ event.sessionInfo?.turnCount ?? null
2299
+ ),
2668
2300
  executionPhase: event.executionPhase ?? run.executionPhase ?? null,
2669
2301
  runPhase: event.runPhase ?? run.runPhase ?? null,
2670
2302
  lastError: event.lastError ?? run.lastError
@@ -2729,7 +2361,11 @@ var OrchestratorService = class {
2729
2361
  */
2730
2362
  async classifyRetryKind(tenant, run, trackerDependencies = {}) {
2731
2363
  try {
2732
- const eligibleContext = await this.fetchTrackedIssueEligibilityContext(tenant, run.issueIdentifier, trackerDependencies);
2364
+ const eligibleContext = await this.fetchTrackedIssueEligibilityContext(
2365
+ tenant,
2366
+ run.issueIdentifier,
2367
+ trackerDependencies
2368
+ );
2733
2369
  if (!eligibleContext) {
2734
2370
  return "failure";
2735
2371
  }
@@ -2737,14 +2373,22 @@ var OrchestratorService = class {
2737
2373
  if (!isUsableWorkflowResolution(resolution)) {
2738
2374
  return "failure";
2739
2375
  }
2740
- 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";
2741
2381
  } catch {
2742
2382
  return "failure";
2743
2383
  }
2744
2384
  }
2745
2385
  async resolveRetryRestartAction(tenant, run, trackerDependencies = {}) {
2746
2386
  try {
2747
- const eligibleContext = await this.fetchTrackedIssueEligibilityContext(tenant, run.issueIdentifier, trackerDependencies);
2387
+ const eligibleContext = await this.fetchTrackedIssueEligibilityContext(
2388
+ tenant,
2389
+ run.issueIdentifier,
2390
+ trackerDependencies
2391
+ );
2748
2392
  if (!eligibleContext) {
2749
2393
  return "release";
2750
2394
  }
@@ -2752,7 +2396,11 @@ var OrchestratorService = class {
2752
2396
  if (!isUsableWorkflowResolution(resolution)) {
2753
2397
  return "restart";
2754
2398
  }
2755
- 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";
2756
2404
  } catch {
2757
2405
  return "restart";
2758
2406
  }
@@ -2763,7 +2411,9 @@ var OrchestratorService = class {
2763
2411
  fetchImpl: this.dependencies.fetchImpl,
2764
2412
  ...trackerDependencies
2765
2413
  });
2766
- const issue = issues.find((candidate) => candidate.identifier === issueIdentifier);
2414
+ const issue = issues.find(
2415
+ (candidate) => candidate.identifier === issueIdentifier
2416
+ );
2767
2417
  return issue ? { issue, issues } : null;
2768
2418
  }
2769
2419
  async fetchWorkerRunInfo(run) {
@@ -2787,12 +2437,20 @@ var OrchestratorService = class {
2787
2437
  async readPersistedWorkerTokenUsage(run) {
2788
2438
  const artifactPaths = [
2789
2439
  join3(run.workspaceRuntimeDir, "token-usage.json"),
2790
- 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
+ )
2791
2447
  ];
2792
2448
  for (const artifactPath of artifactPaths) {
2793
2449
  try {
2794
2450
  const raw = await readFile3(artifactPath, "utf8");
2795
- const tokenUsage = JSON.parse(raw);
2451
+ const tokenUsage = JSON.parse(
2452
+ raw
2453
+ );
2796
2454
  if (hasTokenUsage(tokenUsage)) {
2797
2455
  return tokenUsage;
2798
2456
  }
@@ -2813,7 +2471,10 @@ var OrchestratorService = class {
2813
2471
  if (!isUsableWorkflowResolution(workflowResolution)) {
2814
2472
  return null;
2815
2473
  }
2816
- const hookEnv = this.buildProjectExecutionEnv(tenant.projectId, buildHookEnv(context));
2474
+ const hookEnv = this.buildProjectExecutionEnv(
2475
+ tenant.projectId,
2476
+ buildHookEnv(context)
2477
+ );
2817
2478
  return executeWorkspaceHook({
2818
2479
  kind,
2819
2480
  hooks: workflowResolution.workflow.hooks,
@@ -2831,14 +2492,24 @@ var OrchestratorService = class {
2831
2492
  return readEnvFile(envPath);
2832
2493
  } catch (error) {
2833
2494
  const message = error instanceof Error ? error.message : "Unknown error occurred.";
2834
- (this.dependencies.stderr ?? process.stderr).write(`[warn] Failed to load project env for ${projectId} from ${envPath}: ${message}
2835
- `);
2495
+ (this.dependencies.stderr ?? process.stderr).write(
2496
+ `[warn] Failed to load project env for ${projectId} from ${envPath}: ${message}
2497
+ `
2498
+ );
2836
2499
  return {};
2837
2500
  }
2838
2501
  }
2839
2502
  buildProjectExecutionEnv(projectId, env) {
2840
- const inheritedEnv = Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string"));
2841
- 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
+ );
2842
2513
  return {
2843
2514
  ...this.readProjectEnv(projectId),
2844
2515
  ...inheritedEnv,
@@ -2854,7 +2525,10 @@ var OrchestratorService = class {
2854
2525
  lastError: "Superseded by recovered run."
2855
2526
  };
2856
2527
  await this.store.saveRun(supersededRecord);
2857
- const issue = resolveTrackerAdapter2(tenant.tracker).reviveIssue(tenant, run);
2528
+ const issue = resolveTrackerAdapter2(tenant.tracker).reviveIssue(
2529
+ tenant,
2530
+ run
2531
+ );
2858
2532
  const restarted = await this.startRun(tenant, issue);
2859
2533
  const recoveredRecord = {
2860
2534
  ...restarted,
@@ -2880,11 +2554,14 @@ var OrchestratorService = class {
2880
2554
  issueRecords: upsertIssueOrchestration(issueRecords, {
2881
2555
  issueId: recoveredRecord.issueId,
2882
2556
  identifier: recoveredRecord.issueIdentifier,
2883
- workspaceKey: recoveredRecord.issueWorkspaceKey ?? deriveIssueWorkspaceKey({
2884
- projectId: tenant.projectId,
2885
- adapter: tenant.tracker.adapter,
2886
- issueSubjectId: recoveredRecord.issueSubjectId
2887
- }, 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
+ ),
2888
2565
  state: "running",
2889
2566
  currentRunId: recoveredRecord.runId,
2890
2567
  retryEntry: null,
@@ -2905,34 +2582,40 @@ var OrchestratorService = class {
2905
2582
  lastError: "Retry canceled because the tracker issue is no longer actionable."
2906
2583
  };
2907
2584
  await this.store.saveRun(suppressedRun);
2908
- this.logVerbose(`[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`);
2585
+ this.logVerbose(
2586
+ `[run-completed] ${suppressedRun.runId} status=${suppressedRun.status}`
2587
+ );
2909
2588
  return {
2910
2589
  issueRecords: releaseIssueOrchestration(issueRecords, run.issueId, now),
2911
2590
  recovered: false
2912
2591
  };
2913
2592
  }
2914
2593
  async loadProjectPollInterval(tenant) {
2915
- const intervals = await Promise.all(tenant.repositories.map(async (repository) => {
2916
- const resolution = await this.loadProjectWorkflow(tenant, repository);
2917
- return isUsableWorkflowResolution(resolution) ? resolution.workflow.polling.intervalMs : NaN;
2918
- }));
2919
- 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
+ );
2920
2603
  return validIntervals.length ? Math.min(...validIntervals) : DEFAULT_POLL_INTERVAL_MS;
2921
2604
  }
2922
2605
  async loadProjectMaxConcurrentByState(tenant) {
2923
2606
  const result = {};
2924
- const resolutions = await Promise.all(tenant.repositories.map(async (repository) => {
2925
- try {
2926
- return await this.loadProjectWorkflow(tenant, repository);
2927
- } catch {
2928
- return null;
2929
- }
2930
- }));
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
+ );
2931
2616
  for (const resolution of resolutions) {
2932
- if (!resolution)
2933
- continue;
2934
- if (!isUsableWorkflowResolution(resolution))
2935
- continue;
2617
+ if (!resolution) continue;
2618
+ if (!isUsableWorkflowResolution(resolution)) continue;
2936
2619
  const stateLimits = resolution.workflow.agent.maxConcurrentAgentsByState;
2937
2620
  for (const [state, limit] of Object.entries(stateLimits)) {
2938
2621
  const existing = result[state];
@@ -2951,7 +2634,7 @@ var OrchestratorService = class {
2951
2634
  return {
2952
2635
  baseDelayMs: this.dependencies.retryBackoffMs ?? resolution.workflow.agent.retryBaseDelayMs,
2953
2636
  maxDelayMs: this.dependencies.retryBackoffMs ?? resolution.workflow.agent.maxRetryBackoffMs,
2954
- stallTimeoutMs: resolution.workflow.codex.stallTimeoutMs
2637
+ stallTimeoutMs: resolveWorkflowRuntimeTimeouts(resolution.workflow).stallTimeoutMs
2955
2638
  };
2956
2639
  } catch {
2957
2640
  if (!this.dependencies.retryBackoffMs) {
@@ -2968,15 +2651,22 @@ var OrchestratorService = class {
2968
2651
  if (this.dependencies.concurrency !== void 0) {
2969
2652
  return this.dependencies.concurrency;
2970
2653
  }
2971
- const limits = await Promise.all(project.repositories.map(async (repository) => {
2972
- try {
2973
- const resolution = await this.loadProjectWorkflow(project, repository);
2974
- return isUsableWorkflowResolution(resolution) ? resolution.workflow.agent.maxConcurrentAgents : NaN;
2975
- } catch {
2976
- return NaN;
2977
- }
2978
- }));
2979
- 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
+ );
2980
2670
  return validLimits.length ? Math.min(...validLimits) : DEFAULT_CONCURRENCY;
2981
2671
  }
2982
2672
  async resolveWorkflowResolution(repository, cacheRoot, resolution, changed) {
@@ -2990,7 +2680,10 @@ var OrchestratorService = class {
2990
2680
  };
2991
2681
  let workflowPath = effectiveResolution.workflowPath;
2992
2682
  try {
2993
- workflowPath = await this.persistLastKnownGoodWorkflow(cacheRoot, effectiveResolution) ?? effectiveResolution.workflowPath;
2683
+ workflowPath = await this.persistLastKnownGoodWorkflow(
2684
+ cacheRoot,
2685
+ effectiveResolution
2686
+ ) ?? effectiveResolution.workflowPath;
2994
2687
  } catch {
2995
2688
  workflowPath = effectiveResolution.workflowPath;
2996
2689
  }
@@ -3005,8 +2698,10 @@ var OrchestratorService = class {
3005
2698
  const message = resolution.validationError ?? "Invalid repository WORKFLOW.md";
3006
2699
  const previousMessage = this.lastReportedWorkflowErrors.get(cacheKey);
3007
2700
  if (changed || previousMessage !== message) {
3008
- process.stderr.write(`[orchestrator] failed to reload WORKFLOW.md for ${repository.owner}/${repository.name}: ${message}
3009
- `);
2701
+ process.stderr.write(
2702
+ `[orchestrator] failed to reload WORKFLOW.md for ${repository.owner}/${repository.name}: ${message}
2703
+ `
2704
+ );
3010
2705
  this.lastReportedWorkflowErrors.set(cacheKey, message);
3011
2706
  }
3012
2707
  if (!cached) {
@@ -3096,10 +2791,22 @@ var OrchestratorService = class {
3096
2791
  adapter: issue.tracker.adapter,
3097
2792
  issueSubjectId
3098
2793
  };
3099
- const preferredWorkspaceKey = deriveIssueWorkspaceKey(identity, issue.identifier);
2794
+ const preferredWorkspaceKey = deriveIssueWorkspaceKey(
2795
+ identity,
2796
+ issue.identifier
2797
+ );
3100
2798
  const legacyWorkspaceKey = deriveLegacyIssueWorkspaceKey(identity);
3101
2799
  const orchestrationRecord = (await this.store.loadProjectIssueOrchestrations(tenant.projectId)).find((record) => record.issueId === issue.id);
3102
- 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
+ ));
3103
2810
  if (!workspaceRecord || workspaceRecord.status === "removed") {
3104
2811
  return;
3105
2812
  }
@@ -3109,17 +2816,26 @@ var OrchestratorService = class {
3109
2816
  updatedAt: now.toISOString()
3110
2817
  };
3111
2818
  await this.store.saveIssueWorkspace(pendingRecord);
3112
- const hookResult = await this.runHook("before_remove", tenant, workspaceRecord.repositoryPath, issue.repository, {
3113
- projectId: tenant.projectId,
3114
- workspaceKey: workspaceRecord.workspaceKey,
3115
- issueSubjectId,
3116
- issueIdentifier: issue.identifier,
3117
- workspacePath: workspaceRecord.workspacePath,
3118
- repositoryPath: workspaceRecord.repositoryPath
3119
- }, 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
+ );
3120
2834
  if (hookResult && hookResult.outcome !== "success" && hookResult.outcome !== "skipped") {
3121
2835
  const errorMessage = hookResult.error ?? `before_remove hook ${hookResult.outcome}`;
3122
- 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
+ );
3123
2839
  }
3124
2840
  try {
3125
2841
  await rm3(workspaceRecord.workspacePath, { recursive: true, force: true });
@@ -3137,19 +2853,26 @@ var OrchestratorService = class {
3137
2853
  return issueRecords.find((record) => record.issueId === issueId)?.failureRetryCount ?? null;
3138
2854
  }
3139
2855
  async isFailureRetrySuppressedIssue(tenant, issue, issueRecords, latestRun) {
3140
- 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;
3141
2859
  if (!issueRecord || issueRecord.failureRetryCount <= 0) {
3142
2860
  return false;
3143
2861
  }
3144
- const maxFailureRetries = await this.loadMaxFailureRetries(tenant, issue.repository);
2862
+ const maxFailureRetries = await this.loadMaxFailureRetries(
2863
+ tenant,
2864
+ issue.repository
2865
+ );
3145
2866
  if (issueRecord.failureRetryCount < maxFailureRetries) {
3146
2867
  return false;
3147
2868
  }
3148
2869
  if (!latestRun || latestRun.status !== "suppressed" || latestRun.issueState !== issue.state || !latestRun.lastError?.includes(MAX_FAILURE_RETRIES_EXCEEDED_REASON)) {
3149
2870
  return false;
3150
2871
  }
3151
- const issueUpdatedAtMs = parseTimestampMs2(issue.updatedAt);
3152
- const suppressedAtMs = parseTimestampMs2(latestRun.completedAt ?? latestRun.updatedAt);
2872
+ const issueUpdatedAtMs = parseTimestampMs(issue.updatedAt);
2873
+ const suppressedAtMs = parseTimestampMs(
2874
+ latestRun.completedAt ?? latestRun.updatedAt
2875
+ );
3153
2876
  if (issueUpdatedAtMs === null || suppressedAtMs === null) {
3154
2877
  return true;
3155
2878
  }
@@ -3165,7 +2888,9 @@ var OrchestratorService = class {
3165
2888
  }
3166
2889
  };
3167
2890
  function hasTokenUsage(tokenUsage) {
3168
- 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
+ );
3169
2894
  }
3170
2895
  function isRecord(value) {
3171
2896
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -3177,7 +2902,9 @@ function resolveProjectRateLimits(runs, issues) {
3177
2902
  if (!isRecord(run.rateLimits)) {
3178
2903
  continue;
3179
2904
  }
3180
- const timestamp = parseTimestampMs2(run.lastEventAt ?? run.updatedAt ?? run.startedAt);
2905
+ const timestamp = parseTimestampMs(
2906
+ run.lastEventAt ?? run.updatedAt ?? run.startedAt
2907
+ );
3181
2908
  const sortableTimestamp = timestamp ?? -Infinity;
3182
2909
  if (sortableTimestamp >= latestRunTimestamp) {
3183
2910
  latestRunTimestamp = sortableTimestamp;
@@ -3256,12 +2983,16 @@ function resolvePersistedCumulativeTurnCount(run) {
3256
2983
  return run.cumulativeTurnCount ?? run.turnCount ?? 0;
3257
2984
  }
3258
2985
  function hasConvergenceLockedRun(runs, issueId, issueState, issueUpdatedAt) {
3259
- 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];
3260
2989
  if (latestRun?.runtimeSession?.exitClassification !== "convergence-detected" || latestRun.issueState !== issueState) {
3261
2990
  return false;
3262
2991
  }
3263
- const convergedAtMs = parseTimestampMs2(latestRun.completedAt ?? latestRun.updatedAt);
3264
- const issueUpdatedAtMs = parseTimestampMs2(issueUpdatedAt);
2992
+ const convergedAtMs = parseTimestampMs(
2993
+ latestRun.completedAt ?? latestRun.updatedAt
2994
+ );
2995
+ const issueUpdatedAtMs = parseTimestampMs(issueUpdatedAt);
3265
2996
  if (convergedAtMs === null || issueUpdatedAtMs === null) {
3266
2997
  return true;
3267
2998
  }
@@ -3309,7 +3040,10 @@ function resolveWorkerCommand() {
3309
3040
  return `node ${fileURLToPath(workerUrl)}`;
3310
3041
  } catch {
3311
3042
  try {
3312
- 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
+ );
3313
3047
  return `node ${bundledWorker}`;
3314
3048
  } catch {
3315
3049
  return DEFAULT_WORKER_COMMAND;
@@ -3322,17 +3056,13 @@ function createStore(runtimeRoot = ".runtime", options = {}) {
3322
3056
  function sortCandidatesForDispatch(candidates) {
3323
3057
  return [...candidates].sort((a, b) => {
3324
3058
  if (a.priority !== b.priority) {
3325
- if (a.priority === null)
3326
- return 1;
3327
- if (b.priority === null)
3328
- return -1;
3059
+ if (a.priority === null) return 1;
3060
+ if (b.priority === null) return -1;
3329
3061
  return a.priority - b.priority;
3330
3062
  }
3331
3063
  if (a.createdAt !== b.createdAt) {
3332
- if (a.createdAt === null)
3333
- return 1;
3334
- if (b.createdAt === null)
3335
- return -1;
3064
+ if (a.createdAt === null) return 1;
3065
+ if (b.createdAt === null) return -1;
3336
3066
  return a.createdAt < b.createdAt ? -1 : 1;
3337
3067
  }
3338
3068
  return a.identifier.localeCompare(b.identifier);
@@ -3373,8 +3103,8 @@ function buildLatestRunMapByIssueId(runs) {
3373
3103
  latestRuns.set(run.issueId, run);
3374
3104
  continue;
3375
3105
  }
3376
- const runUpdatedAtMs = parseTimestampMs2(run.updatedAt) ?? -Infinity;
3377
- const existingUpdatedAtMs = parseTimestampMs2(existing.updatedAt) ?? -Infinity;
3106
+ const runUpdatedAtMs = parseTimestampMs(run.updatedAt) ?? -Infinity;
3107
+ const existingUpdatedAtMs = parseTimestampMs(existing.updatedAt) ?? -Infinity;
3378
3108
  if (runUpdatedAtMs > existingUpdatedAtMs) {
3379
3109
  latestRuns.set(run.issueId, run);
3380
3110
  }
@@ -3386,7 +3116,9 @@ function isIssueOrchestrationClaimed(state) {
3386
3116
  }
3387
3117
  function upsertIssueOrchestration(issueRecords, nextRecord) {
3388
3118
  const existingRecord = issueRecords.find((record) => record.issueId === nextRecord.issueId) ?? null;
3389
- const remaining = issueRecords.filter((record) => record.issueId !== nextRecord.issueId);
3119
+ const remaining = issueRecords.filter(
3120
+ (record) => record.issueId !== nextRecord.issueId
3121
+ );
3390
3122
  return [
3391
3123
  ...remaining,
3392
3124
  {
@@ -3397,19 +3129,21 @@ function upsertIssueOrchestration(issueRecords, nextRecord) {
3397
3129
  ];
3398
3130
  }
3399
3131
  function releaseIssueOrchestration(issueRecords, issueId, now) {
3400
- return issueRecords.map((record) => record.issueId === issueId ? {
3401
- ...record,
3402
- state: "released",
3403
- currentRunId: null,
3404
- retryEntry: null,
3405
- updatedAt: now.toISOString()
3406
- } : 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
+ );
3407
3141
  }
3408
3142
  function isActiveRunStatus(status) {
3409
3143
  return status === "pending" || status === "starting" || status === "running" || status === "retrying";
3410
3144
  }
3411
3145
 
3412
- // ../orchestrator/dist/lock.js
3146
+ // ../orchestrator/src/lock.ts
3413
3147
  import { randomUUID as randomUUID2 } from "crypto";
3414
3148
  import { mkdir as mkdir4, open as open2, readFile as readFile4, rm as rm4 } from "fs/promises";
3415
3149
  import { dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve2 } from "path";
@@ -3447,14 +3181,18 @@ async function acquireProjectLock(input) {
3447
3181
  if (existing.status === "invalid") {
3448
3182
  invalidReadAttempts += 1;
3449
3183
  if (invalidReadAttempts >= LOCK_READ_RETRY_LIMIT) {
3450
- 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
+ );
3451
3187
  }
3452
3188
  await delay(LOCK_READ_RETRY_DELAY_MS);
3453
3189
  continue;
3454
3190
  }
3455
3191
  invalidReadAttempts = 0;
3456
3192
  if ((input.isProcessRunning ?? isProcessRunning)(existing.record.pid)) {
3457
- 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
+ );
3458
3196
  }
3459
3197
  await rm4(lockPath, { force: true });
3460
3198
  }
@@ -3493,7 +3231,9 @@ async function readProjectLock(lockPath) {
3493
3231
  }
3494
3232
  function assertValidProjectId(projectId) {
3495
3233
  if (projectId.length === 0 || projectId === "." || projectId === ".." || projectId.includes("/") || projectId.includes("\\")) {
3496
- 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
+ );
3497
3237
  }
3498
3238
  }
3499
3239
  function resolveProjectLockPath(runtimeRoot, projectId) {
@@ -3502,7 +3242,9 @@ function resolveProjectLockPath(runtimeRoot, projectId) {
3502
3242
  const projectDir = resolve2(store.projectDir(projectId));
3503
3243
  const relativeProjectDir = relative2(projectsRoot, projectDir);
3504
3244
  if (relativeProjectDir.length === 0 || relativeProjectDir.startsWith("..") || isAbsolute(relativeProjectDir)) {
3505
- 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
+ );
3506
3248
  }
3507
3249
  return join4(projectDir, ".lock");
3508
3250
  }
@@ -3530,15 +3272,24 @@ function isProcessRunning(pid) {
3530
3272
  }
3531
3273
  }
3532
3274
  function isAlreadyExistsError2(error) {
3533
- 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
+ );
3534
3278
  }
3535
3279
  function isMissingFileError2(error) {
3536
- 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
+ );
3537
3283
  }
3538
3284
 
3539
- // ../orchestrator/dist/index.js
3285
+ // ../orchestrator/src/index.ts
3540
3286
  import { pathToFileURL } from "url";
3541
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
3542
3293
  function resolveOrchestratorLogLevel(value) {
3543
3294
  if (!value || value === "normal") {
3544
3295
  return "normal";
@@ -3546,7 +3297,9 @@ function resolveOrchestratorLogLevel(value) {
3546
3297
  if (value === "verbose") {
3547
3298
  return "verbose";
3548
3299
  }
3549
- 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
+ );
3550
3303
  }
3551
3304
  async function runCli(argv, dependencies = {}) {
3552
3305
  const [command = "run-once", ...args] = argv;
@@ -3556,8 +3309,12 @@ async function runCli(argv, dependencies = {}) {
3556
3309
  }
3557
3310
  const runtimeRoot = resolve3(parsed.runtimeRoot ?? ".runtime");
3558
3311
  const stderr = dependencies.stderr ?? process.stderr;
3559
- const eventsDir = resolveOptionalPath(parsed.eventsDir ?? process.env.SYMPHONY_EVENTS_DIR);
3560
- 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
+ );
3561
3318
  const service = await dependencies.createService?.(runtimeRoot, parsed.projectId, {
3562
3319
  eventsDir,
3563
3320
  logLevel,
@@ -3605,8 +3362,10 @@ async function runCli(argv, dependencies = {}) {
3605
3362
  let exitCode = 0;
3606
3363
  void cleanup().catch((error) => {
3607
3364
  exitCode = 1;
3608
- stderr.write(`Failed to shut down orchestrator after ${signal}: ${error instanceof Error ? error.message : String(error)}
3609
- `);
3365
+ stderr.write(
3366
+ `Failed to shut down orchestrator after ${signal}: ${error instanceof Error ? error.message : String(error)}
3367
+ `
3368
+ );
3610
3369
  }).finally(() => {
3611
3370
  exitProcess(exitCode);
3612
3371
  });
@@ -3733,8 +3492,10 @@ function resolveOptionalPath(value) {
3733
3492
  }
3734
3493
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
3735
3494
  main().catch((error) => {
3736
- process.stderr.write(`${error instanceof Error ? error.message : "Unknown error"}
3737
- `);
3495
+ process.stderr.write(
3496
+ `${error instanceof Error ? error.message : "Unknown error"}
3497
+ `
3498
+ );
3738
3499
  process.exitCode = 1;
3739
3500
  });
3740
3501
  }