@gh-symphony/cli 0.0.18 → 0.0.20

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.
@@ -26,7 +26,7 @@ import {
26
26
  resolveIssueWorkspaceDirectory,
27
27
  safeReadDir,
28
28
  scheduleRetryAt
29
- } from "./chunk-OL73UN2X.js";
29
+ } from "./chunk-M3IFVLQS.js";
30
30
 
31
31
  // ../orchestrator/dist/service.js
32
32
  import { mkdir as mkdir3, readFile as readFile3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
@@ -501,6 +501,9 @@ function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFE
501
501
  state: normalizeBlockerState(node.state, lifecycle)
502
502
  }
503
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;
504
507
  return {
505
508
  id: item.content.id,
506
509
  identifier: `${repository.owner.login}/${repository.name}#${item.content.number}`,
@@ -514,7 +517,7 @@ function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFE
514
517
  labels: (item.content.labels?.nodes ?? []).flatMap((label) => label?.name ? [label.name.toLowerCase()] : []).sort(),
515
518
  blockedBy,
516
519
  createdAt: item.content.createdAt,
517
- updatedAt: item.content.updatedAt ?? item.updatedAt,
520
+ updatedAt: trackedUpdatedAt,
518
521
  repository: {
519
522
  owner: repository.owner.login,
520
523
  name: repository.name,
@@ -1381,8 +1384,6 @@ var DEFAULT_POLL_INTERVAL_MS = 3e4;
1381
1384
  var DEFAULT_CONCURRENCY = 3;
1382
1385
  var DEFAULT_RETRY_BACKOFF_MS = 3e4;
1383
1386
  var CONTINUATION_RETRY_DELAY_MS = 1e3;
1384
- var DEFAULT_GLOBAL_MAX_TURNS = 100;
1385
- var DEFAULT_MAX_TOKENS = 256e3;
1386
1387
  var DEFAULT_WORKER_COMMAND = "node packages/worker/dist/index.js";
1387
1388
  var DEFAULT_MAX_NONPRODUCTIVE_TURNS = 3;
1388
1389
  var LOW_RATE_LIMIT_WARNING_THRESHOLD = 0.05;
@@ -1624,7 +1625,7 @@ var OrchestratorService = class {
1624
1625
  const availableSlots = Math.max(0, concurrency - currentlyActive);
1625
1626
  const latestRunsByIssueId = buildLatestRunMapByIssueId(projectRunsAfterReconcile);
1626
1627
  const unscheduledCandidates = actionableCandidates.filter((issue) => {
1627
- if (hasConvergenceLockedRun(projectRunsAfterReconcile, issue.id, issue.state)) {
1628
+ if (hasConvergenceLockedRun(projectRunsAfterReconcile, issue.id, issue.state, issue.updatedAt)) {
1628
1629
  return false;
1629
1630
  }
1630
1631
  return !issueRecords.some((record) => record.issueId === issue.id && isIssueOrchestrationClaimed(record.state));
@@ -1647,9 +1648,6 @@ var OrchestratorService = class {
1647
1648
  if (await this.isFailureRetrySuppressedIssue(tenant, issue, issueRecords, latestRunsByIssueId.get(issue.id) ?? null)) {
1648
1649
  continue;
1649
1650
  }
1650
- if (isIssueBudgetExceeded(resolveIssueBudgetSnapshot(projectRunsAfterReconcile, issue.id), now)) {
1651
- continue;
1652
- }
1653
1651
  const stateLimit = maxConcurrentByState[issue.state];
1654
1652
  if (stateLimit !== void 0) {
1655
1653
  const activeInState = activeByState.get(issue.state) ?? 0;
@@ -2011,7 +2009,7 @@ var OrchestratorService = class {
2011
2009
  const resolution = await loadRepositoryWorkflow(repositoryDirectory, repository);
2012
2010
  return this.resolveWorkflowResolution(repository, cacheRoot, resolution, changed);
2013
2011
  }
2014
- async startRun(tenant, issue, resumeContext) {
2012
+ async startRun(tenant, issue) {
2015
2013
  if (this.shuttingDown || !this.running) {
2016
2014
  throw new Error("Orchestrator is shutting down and cannot start new runs.");
2017
2015
  }
@@ -2073,8 +2071,6 @@ var OrchestratorService = class {
2073
2071
  if (!isUsableWorkflowResolution(workflow)) {
2074
2072
  throw new Error(workflow.validationError ?? "Invalid repository WORKFLOW.md");
2075
2073
  }
2076
- const allProjectRuns = (await this.store.loadAllRuns()).filter((run) => run.projectId === tenant.projectId);
2077
- const issueBudgetSnapshot = resolveIssueBudgetSnapshot(allProjectRuns, issue.id);
2078
2074
  const promptVariables = buildPromptVariables(issue, {
2079
2075
  attempt: null
2080
2076
  // first execution
@@ -2144,17 +2140,19 @@ var OrchestratorService = class {
2144
2140
  SYMPHONY_THREAD_SANDBOX: workflow.workflow.codex.threadSandbox ?? "",
2145
2141
  SYMPHONY_TURN_SANDBOX_POLICY: workflow.workflow.codex.turnSandboxPolicy ?? "",
2146
2142
  SYMPHONY_MAX_TURNS: String(workflow.workflow.agent.maxTurns),
2147
- SYMPHONY_GLOBAL_MAX_TURNS: process.env.SYMPHONY_GLOBAL_MAX_TURNS ?? "",
2148
- SYMPHONY_MAX_TOKENS: process.env.SYMPHONY_MAX_TOKENS ?? "",
2149
2143
  SYMPHONY_MAX_NONPRODUCTIVE_TURNS: process.env.SYMPHONY_MAX_NONPRODUCTIVE_TURNS ?? String(DEFAULT_MAX_NONPRODUCTIVE_TURNS),
2150
- SYMPHONY_SESSION_TIMEOUT_MS: process.env.SYMPHONY_SESSION_TIMEOUT_MS ?? "",
2151
- SYMPHONY_RESUME_THREAD_ID: resumeContext?.threadId ?? "",
2152
- SYMPHONY_CUMULATIVE_TURN_COUNT: String(Math.max(0, resumeContext?.cumulativeTurnCount ?? issueBudgetSnapshot.cumulativeTurnCount)),
2153
- SYMPHONY_CUMULATIVE_INPUT_TOKENS: String(issueBudgetSnapshot.tokenUsage.inputTokens),
2154
- SYMPHONY_CUMULATIVE_OUTPUT_TOKENS: String(issueBudgetSnapshot.tokenUsage.outputTokens),
2155
- SYMPHONY_CUMULATIVE_TOTAL_TOKENS: String(issueBudgetSnapshot.tokenUsage.totalTokens),
2156
- SYMPHONY_LAST_TURN_SUMMARY: resumeContext?.lastTurnSummary ?? "",
2157
- SYMPHONY_SESSION_STARTED_AT: issueBudgetSnapshot.sessionStartedAt ?? "",
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: "",
2158
2156
  SYMPHONY_READ_TIMEOUT_MS: String(workflow.workflow.codex.readTimeoutMs),
2159
2157
  SYMPHONY_TURN_TIMEOUT_MS: String(workflow.workflow.codex.turnTimeoutMs)
2160
2158
  }),
@@ -2399,17 +2397,17 @@ var OrchestratorService = class {
2399
2397
  }
2400
2398
  return this.restartRun(tenant, run, issueRecords, now, workerSessionId);
2401
2399
  }
2402
- if (workerInfo.exitClassification === "budget-exceeded" || workerInfo.exitClassification === "convergence-detected") {
2400
+ if (workerInfo.exitClassification === "convergence-detected") {
2403
2401
  const completedRun = {
2404
2402
  ...runWithTokens,
2405
- status: workerInfo.exitClassification === "budget-exceeded" ? "succeeded" : "failed",
2403
+ status: "failed",
2406
2404
  processId: null,
2407
2405
  updatedAt: now.toISOString(),
2408
2406
  completedAt: now.toISOString(),
2409
2407
  nextRetryAt: null,
2410
2408
  retryKind: null,
2411
- lastError: workerInfo.exitClassification === "budget-exceeded" ? null : runWithTokens.lastError,
2412
- runPhase: runWithTokens.runPhase ?? (workerInfo.exitClassification === "budget-exceeded" ? "succeeded" : "failed")
2409
+ lastError: runWithTokens.lastError,
2410
+ runPhase: runWithTokens.runPhase ?? "failed"
2413
2411
  };
2414
2412
  await this.store.saveRun(completedRun);
2415
2413
  this.logVerbose(`[run-completed] ${completedRun.runId} status=${completedRun.status}`);
@@ -2746,9 +2744,6 @@ var OrchestratorService = class {
2746
2744
  }
2747
2745
  async resolveRetryRestartAction(tenant, run, trackerDependencies = {}) {
2748
2746
  try {
2749
- if (isIssueBudgetExceeded(resolveIssueBudgetSnapshot((await this.store.loadAllRuns()).filter((candidate) => candidate.projectId === tenant.projectId), run.issueId), this.now())) {
2750
- return "release";
2751
- }
2752
2747
  const eligibleContext = await this.fetchTrackedIssueEligibilityContext(tenant, run.issueIdentifier, trackerDependencies);
2753
2748
  if (!eligibleContext) {
2754
2749
  return "release";
@@ -2860,18 +2855,14 @@ var OrchestratorService = class {
2860
2855
  };
2861
2856
  await this.store.saveRun(supersededRecord);
2862
2857
  const issue = resolveTrackerAdapter2(tenant.tracker).reviveIssue(tenant, run);
2863
- const restarted = await this.startRun(tenant, issue, {
2864
- threadId: run.threadId ?? run.runtimeSession?.threadId ?? null,
2865
- cumulativeTurnCount: resolvePersistedCumulativeTurnCount(run),
2866
- lastTurnSummary: run.lastTurnSummary ?? null
2867
- });
2858
+ const restarted = await this.startRun(tenant, issue);
2868
2859
  const recoveredRecord = {
2869
2860
  ...restarted,
2870
2861
  attempt: run.attempt,
2871
2862
  retryKind: run.retryKind ?? "recovery",
2872
2863
  createdAt: run.createdAt,
2873
2864
  issueWorkspaceKey: run.issueWorkspaceKey,
2874
- threadId: run.threadId ?? run.runtimeSession?.threadId ?? null,
2865
+ threadId: null,
2875
2866
  cumulativeTurnCount: resolvePersistedCumulativeTurnCount(run),
2876
2867
  lastTurnSummary: run.lastTurnSummary ?? null,
2877
2868
  turnCount: 0
@@ -3264,48 +3255,17 @@ function buildRuntimeSession(existing, sessionId, threadId, status, startedAt, u
3264
3255
  function resolvePersistedCumulativeTurnCount(run) {
3265
3256
  return run.cumulativeTurnCount ?? run.turnCount ?? 0;
3266
3257
  }
3267
- function hasConvergenceLockedRun(runs, issueId, issueState) {
3258
+ function hasConvergenceLockedRun(runs, issueId, issueState, issueUpdatedAt) {
3268
3259
  const latestRun = runs.filter((run) => run.issueId === issueId).sort((left, right) => new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime())[0];
3269
- return latestRun?.runtimeSession?.exitClassification === "convergence-detected" && latestRun.issueState === issueState;
3270
- }
3271
- function resolveIssueBudgetSnapshot(runs, issueId) {
3272
- const issueRuns = runs.filter((run) => run.issueId === issueId);
3273
- const startedAtCandidates = issueRuns.map((run) => run.startedAt).filter((value) => typeof value === "string");
3274
- return {
3275
- cumulativeTurnCount: issueRuns.reduce((total, run) => total + resolvePersistedCumulativeTurnCount(run), 0),
3276
- tokenUsage: issueRuns.reduce((total, run) => ({
3277
- inputTokens: total.inputTokens + (run.tokenUsage?.inputTokens ?? 0),
3278
- outputTokens: total.outputTokens + (run.tokenUsage?.outputTokens ?? 0),
3279
- totalTokens: total.totalTokens + (run.tokenUsage?.totalTokens ?? 0)
3280
- }), {
3281
- inputTokens: 0,
3282
- outputTokens: 0,
3283
- totalTokens: 0
3284
- }),
3285
- sessionStartedAt: startedAtCandidates.sort((left, right) => left.localeCompare(right))[0] ?? null
3286
- };
3287
- }
3288
- function isIssueBudgetExceeded(snapshot, now, env = process.env) {
3289
- const globalMaxTurns = parsePositiveInteger(env.SYMPHONY_GLOBAL_MAX_TURNS ?? "") ?? DEFAULT_GLOBAL_MAX_TURNS;
3290
- if (snapshot.cumulativeTurnCount >= globalMaxTurns) {
3291
- return true;
3292
- }
3293
- const maxTokens = parsePositiveInteger(env.SYMPHONY_MAX_TOKENS ?? "") ?? DEFAULT_MAX_TOKENS;
3294
- if (snapshot.tokenUsage.totalTokens >= maxTokens) {
3295
- return true;
3296
- }
3297
- const sessionTimeoutMs = parsePositiveInteger(env.SYMPHONY_SESSION_TIMEOUT_MS ?? "");
3298
- if (sessionTimeoutMs === null || snapshot.sessionStartedAt === null) {
3260
+ if (latestRun?.runtimeSession?.exitClassification !== "convergence-detected" || latestRun.issueState !== issueState) {
3299
3261
  return false;
3300
3262
  }
3301
- return now.getTime() - new Date(snapshot.sessionStartedAt).getTime() >= sessionTimeoutMs;
3302
- }
3303
- function parsePositiveInteger(value) {
3304
- const parsed = Number(value);
3305
- if (!Number.isFinite(parsed) || parsed <= 0) {
3306
- return null;
3263
+ const convergedAtMs = parseTimestampMs2(latestRun.completedAt ?? latestRun.updatedAt);
3264
+ const issueUpdatedAtMs = parseTimestampMs2(issueUpdatedAt);
3265
+ if (convergedAtMs === null || issueUpdatedAtMs === null) {
3266
+ return true;
3307
3267
  }
3308
- return Math.floor(parsed);
3268
+ return issueUpdatedAtMs <= convergedAtMs;
3309
3269
  }
3310
3270
  function resolveCumulativeTurnCount(run, turnCount) {
3311
3271
  const carriedTotal = resolvePersistedCumulativeTurnCount(run);