@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.
@@ -3,7 +3,7 @@ import {
3
3
  classifySessionExit,
4
4
  parseWorkflowMarkdown,
5
5
  readEnvFile
6
- } from "./chunk-OL73UN2X.js";
6
+ } from "./chunk-M3IFVLQS.js";
7
7
 
8
8
  // ../worker/dist/index.js
9
9
  import { spawn as spawn2 } from "child_process";
@@ -92,8 +92,7 @@ function buildCodexRuntimePlan(config) {
92
92
  ...gitCredentialHelper,
93
93
  ...tool.env
94
94
  },
95
- tools: [tool],
96
- resumeThreadId: config.resumeThreadId?.trim() || null
95
+ tools: [tool]
97
96
  };
98
97
  }
99
98
  function launchCodexAppServer(plan, spawnImpl = spawn) {
@@ -196,8 +195,7 @@ function resolveLocalRuntimeLaunchConfig(env = process.env) {
196
195
  agentCredentialCachePath: env.AGENT_CREDENTIAL_CACHE_PATH,
197
196
  githubProjectId: env.GITHUB_PROJECT_ID,
198
197
  githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
199
- agentCommand: env.SYMPHONY_AGENT_COMMAND,
200
- resumeThreadId: env.SYMPHONY_RESUME_THREAD_ID
198
+ agentCommand: env.SYMPHONY_AGENT_COMMAND
201
199
  };
202
200
  }
203
201
  async function runLocalRuntimeLauncher(env = process.env) {
@@ -556,111 +554,20 @@ function resolveExitRunPhase(currentRunPhase, exit) {
556
554
  return exit.code === 0 && !exit.signal ? "succeeded" : "failed";
557
555
  }
558
556
 
559
- // ../worker/dist/session-budget.js
560
- function resolveSessionBudgetState(env) {
561
- return {
562
- cumulativeTurnCount: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_TURN_COUNT),
563
- tokenUsageBaseline: {
564
- inputTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_INPUT_TOKENS),
565
- outputTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_OUTPUT_TOKENS),
566
- totalTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_TOTAL_TOKENS)
567
- },
568
- sessionStartedAt: normalizeTimestamp(env.SYMPHONY_SESSION_STARTED_AT),
569
- globalMaxTurns: parsePositiveInteger(env.SYMPHONY_GLOBAL_MAX_TURNS),
570
- maxTokens: parsePositiveInteger(env.SYMPHONY_MAX_TOKENS),
571
- sessionTimeoutMs: parsePositiveInteger(env.SYMPHONY_SESSION_TIMEOUT_MS)
572
- };
573
- }
574
- function resolveBudgetExceededReason(budget, currentSessionTurnCount, currentTokenUsage, now) {
575
- const totalTurns = budget.cumulativeTurnCount + currentSessionTurnCount;
576
- if (budget.globalMaxTurns !== null && totalTurns >= budget.globalMaxTurns) {
577
- return "global-turns";
578
- }
579
- const totalTokens = budget.tokenUsageBaseline.totalTokens + currentTokenUsage.totalTokens;
580
- if (budget.maxTokens !== null && totalTokens >= budget.maxTokens) {
581
- return "tokens";
582
- }
583
- if (budget.sessionTimeoutMs !== null && budget.sessionStartedAt !== null && now.getTime() - new Date(budget.sessionStartedAt).getTime() >= budget.sessionTimeoutMs) {
584
- return "session-timeout";
585
- }
586
- return null;
587
- }
588
- function parsePositiveInteger(value) {
589
- if (!value) {
590
- return null;
591
- }
592
- const parsed = Number(value);
593
- if (!Number.isFinite(parsed) || parsed <= 0) {
594
- return null;
595
- }
596
- return Math.floor(parsed);
597
- }
598
- function parseNonNegativeInteger(value) {
599
- if (!value) {
600
- return 0;
601
- }
602
- const parsed = Number(value);
603
- if (!Number.isFinite(parsed) || parsed < 0) {
604
- return 0;
605
- }
606
- return Math.floor(parsed);
607
- }
608
- function normalizeTimestamp(value) {
609
- if (!value) {
610
- return null;
611
- }
612
- const parsed = Date.parse(value);
613
- return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
614
- }
615
-
616
557
  // ../worker/dist/thread-resume.js
617
558
  var DEFAULT_CONTINUATION_GUIDANCE = "Continue working on the issue. Review your progress and complete any remaining tasks.";
618
- function parseNonNegativeInteger2(value) {
559
+ function parseNonNegativeInteger(value) {
619
560
  const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
620
561
  if (!Number.isFinite(parsed) || parsed <= 0) {
621
562
  return 0;
622
563
  }
623
564
  return Math.floor(parsed);
624
565
  }
625
- function resolveRemainingTurns(maxTurns, cumulativeTurnCount) {
626
- return Math.max(0, parseNonNegativeInteger2(maxTurns) - parseNonNegativeInteger2(cumulativeTurnCount));
627
- }
628
- function buildInitialTurnInput({ renderedPrompt, mode, lastTurnSummary, cumulativeTurnCount = 0, continuationGuidance }) {
629
- if (mode === "fresh") {
630
- return renderedPrompt;
631
- }
632
- const renderedContinuationGuidance = buildContinuationTurnInput({
633
- continuationGuidance,
634
- lastTurnSummary,
635
- cumulativeTurnCount
636
- });
637
- const normalizedSummary = normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.";
638
- const normalizedCumulativeTurnCount = Math.max(0, parseNonNegativeInteger2(cumulativeTurnCount));
639
- if (mode === "resume") {
640
- return [
641
- "Resume work on this issue using the existing thread context.",
642
- `Previous worker turns completed: ${normalizedCumulativeTurnCount}.`,
643
- `Previous session summary: ${normalizedSummary}`,
644
- renderedContinuationGuidance
645
- ].join("\n");
646
- }
647
- return [
648
- "Resume work on this issue from a previous worker session.",
649
- "",
650
- "Original issue instructions:",
651
- renderedPrompt,
652
- "",
653
- "Previous session summary:",
654
- normalizedSummary,
655
- "",
656
- renderedContinuationGuidance
657
- ].join("\n");
658
- }
659
566
  function buildContinuationTurnInput({ continuationGuidance, lastTurnSummary, cumulativeTurnCount = 0 }) {
660
567
  const template = continuationGuidance?.trim() || DEFAULT_CONTINUATION_GUIDANCE;
661
568
  return renderContinuationGuidance(template, {
662
569
  lastTurnSummary: normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.",
663
- cumulativeTurnCount: String(Math.max(0, parseNonNegativeInteger2(cumulativeTurnCount)))
570
+ cumulativeTurnCount: String(Math.max(0, parseNonNegativeInteger(cumulativeTurnCount)))
664
571
  });
665
572
  }
666
573
  function normalizeContinuationVariable(value) {
@@ -668,7 +575,52 @@ function normalizeContinuationVariable(value) {
668
575
  return normalized ? normalized : null;
669
576
  }
670
577
  function renderContinuationGuidance(template, variables) {
671
- return template.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g, (match, key) => variables[key] ?? match);
578
+ if (template.includes("{%") || template.includes("%}")) {
579
+ throw new Error("template_parse_error: continuation guidance does not support Liquid tags.");
580
+ }
581
+ let rendered = "";
582
+ let lastIndex = 0;
583
+ const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g;
584
+ for (const match of template.matchAll(pattern)) {
585
+ const matchedText = match[0];
586
+ const expression = match[1];
587
+ const index = match.index ?? 0;
588
+ rendered += template.slice(lastIndex, index);
589
+ if (!(expression in variables)) {
590
+ throw new Error(`template_render_error: unsupported continuation guidance variable '${expression}'.`);
591
+ }
592
+ rendered += variables[expression] ?? "";
593
+ lastIndex = index + matchedText.length;
594
+ }
595
+ rendered += template.slice(lastIndex);
596
+ const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
597
+ if (strayLiquidExpression) {
598
+ throw new Error(`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`);
599
+ }
600
+ return rendered;
601
+ }
602
+
603
+ // ../worker/dist/turn-limits.js
604
+ var DEFAULT_SESSION_MAX_TURNS = 20;
605
+ function resolveMaxTurns(value) {
606
+ const parsed = typeof value === "number" ? value : Number(value);
607
+ if (!Number.isFinite(parsed)) {
608
+ return {
609
+ maxTurns: DEFAULT_SESSION_MAX_TURNS,
610
+ exhaustedBeforeStart: false
611
+ };
612
+ }
613
+ const normalized = Math.trunc(parsed);
614
+ if (normalized <= 0) {
615
+ return {
616
+ maxTurns: 0,
617
+ exhaustedBeforeStart: true
618
+ };
619
+ }
620
+ return {
621
+ maxTurns: normalized,
622
+ exhaustedBeforeStart: false
623
+ };
672
624
  }
673
625
 
674
626
  // ../worker/dist/token-usage.js
@@ -698,7 +650,6 @@ function resolveTokenUsageArtifactPath(env) {
698
650
 
699
651
  // ../worker/dist/index.js
700
652
  var launcherEnv = loadLauncherEnvironment(process.env);
701
- var sessionBudgetState = resolveSessionBudgetState(launcherEnv);
702
653
  var runtimeState = {
703
654
  status: launcherEnv.SYMPHONY_RUN_ID ? "starting" : "idle",
704
655
  executionPhase: null,
@@ -719,9 +670,9 @@ var runtimeState = {
719
670
  lastError: null
720
671
  } : null,
721
672
  tokenUsage: {
722
- inputTokens: sessionBudgetState.tokenUsageBaseline.inputTokens,
723
- outputTokens: sessionBudgetState.tokenUsageBaseline.outputTokens,
724
- totalTokens: sessionBudgetState.tokenUsageBaseline.totalTokens
673
+ inputTokens: 0,
674
+ outputTokens: 0,
675
+ totalTokens: 0
725
676
  },
726
677
  lastEventAt: null,
727
678
  rateLimits: null,
@@ -916,7 +867,7 @@ function resolveTurnTokenUsageDelta(baseline) {
916
867
  };
917
868
  }
918
869
  function resolveSessionTokenUsageDelta() {
919
- return resolveTurnTokenUsageDelta(sessionBudgetState.tokenUsageBaseline);
870
+ return cloneTokenUsageSnapshot();
920
871
  }
921
872
  async function persistSessionTokenUsageArtifact(env) {
922
873
  await persistTokenUsageArtifact(env, resolveSessionTokenUsageDelta());
@@ -1049,17 +1000,13 @@ async function runCodexClientProtocol(child, plan, env, options) {
1049
1000
  process.stderr.write("[worker] codex process has no stdio pipes; cannot run client protocol\n");
1050
1001
  return;
1051
1002
  }
1052
- const maxTurns = Number(env.SYMPHONY_MAX_TURNS) || 20;
1053
- const cumulativeTurnCount = parseNonNegativeInteger2(env.SYMPHONY_CUMULATIVE_TURN_COUNT);
1054
- const remainingTurns = resolveRemainingTurns(maxTurns, cumulativeTurnCount);
1003
+ const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(env.SYMPHONY_MAX_TURNS);
1055
1004
  const readTimeoutMs = Number(env.SYMPHONY_READ_TIMEOUT_MS) || 5e3;
1056
1005
  const turnTimeoutMs = Number(env.SYMPHONY_TURN_TIMEOUT_MS) || 36e5;
1057
1006
  const maxNonProductiveTurns = resolveMaxNonProductiveTurns(env);
1058
1007
  const issueIdentifier = env.SYMPHONY_ISSUE_IDENTIFIER ?? "";
1059
- const lastTurnSummary = env.SYMPHONY_LAST_TURN_SUMMARY ?? null;
1060
1008
  const continuationGuidance = env.SYMPHONY_CONTINUATION_GUIDANCE ?? options.continuationGuidance;
1061
1009
  const { approvalPolicy, threadSandbox, turnSandboxPolicy } = resolveCodexPolicySettings(env);
1062
- const budgetState = resolveSessionBudgetState(env);
1063
1010
  let previousTurnProgressSnapshot = {
1064
1011
  ...captureTurnWorkspaceSnapshot(plan.cwd),
1065
1012
  lastError: runtimeState.run?.lastError ?? null
@@ -1079,16 +1026,8 @@ async function runCodexClientProtocol(child, plan, env, options) {
1079
1026
  let userInputRequired = false;
1080
1027
  let turnTerminalFailurePhase = null;
1081
1028
  let activeTurnTelemetry = null;
1082
- let budgetExceededReason = null;
1083
1029
  let consecutiveNonProductiveTurns = 0;
1084
1030
  let convergenceDetected = false;
1085
- function checkSessionBudgets(currentSessionTurnCount) {
1086
- return resolveBudgetExceededReason(budgetState, currentSessionTurnCount, {
1087
- inputTokens: runtimeState.tokenUsage.inputTokens - budgetState.tokenUsageBaseline.inputTokens,
1088
- outputTokens: runtimeState.tokenUsage.outputTokens - budgetState.tokenUsageBaseline.outputTokens,
1089
- totalTokens: runtimeState.tokenUsage.totalTokens - budgetState.tokenUsageBaseline.totalTokens
1090
- }, /* @__PURE__ */ new Date());
1091
- }
1092
1031
  function resolvePendingTurnCompletion() {
1093
1032
  if (turnCompletedResolve) {
1094
1033
  turnCompletedResolve();
@@ -1406,27 +1345,6 @@ async function runCodexClientProtocol(child, plan, env, options) {
1406
1345
  env: t.env
1407
1346
  };
1408
1347
  }
1409
- if (remainingTurns <= 0) {
1410
- process.stderr.write(`[worker] max_turns already exhausted by previous sessions (${cumulativeTurnCount}/${maxTurns})
1411
- `);
1412
- runtimeState.status = "completed";
1413
- runtimeState.runPhase = "succeeded";
1414
- runtimeState.sessionInfo.exitClassification = classifySessionExit({
1415
- runPhase: runtimeState.runPhase,
1416
- userInputRequired: false,
1417
- budgetExceeded: false,
1418
- convergenceDetected: false,
1419
- maxTurnsReached: true
1420
- });
1421
- stopOrchestratorHeartbeatTimer();
1422
- emitOrchestratorHeartbeat();
1423
- await persistSessionTokenUsageArtifact(env);
1424
- await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
1425
- setTimeout(() => {
1426
- process.exit(0);
1427
- }, 1500);
1428
- return;
1429
- }
1430
1348
  const baseThreadParams = {
1431
1349
  cwd: plan.cwd,
1432
1350
  developerInstructions: renderedPrompt,
@@ -1436,36 +1354,12 @@ async function runCodexClientProtocol(child, plan, env, options) {
1436
1354
  mcp_servers: mcpServers
1437
1355
  }
1438
1356
  };
1439
- const resumeThreadId = plan.resumeThreadId;
1440
- let threadBootstrapMode = "fresh";
1441
1357
  process.stderr.write(`[worker] starting codex thread (mcp_servers: ${Object.keys(mcpServers).join(", ")})
1442
1358
  `);
1443
- let threadResult;
1444
- if (resumeThreadId) {
1445
- process.stderr.write(`[worker] attempting thread/resume for ${resumeThreadId}
1446
- `);
1447
- try {
1448
- threadResult = await sendRequestWithTimeout("thread-resume-1", "thread/resume", {
1449
- ...baseThreadParams,
1450
- threadId: resumeThreadId
1451
- });
1452
- threadBootstrapMode = "resume";
1453
- } catch (error) {
1454
- const message = error instanceof Error ? error.message : String(error ?? "unknown");
1455
- threadBootstrapMode = "soft-resume";
1456
- process.stderr.write(`[worker] thread/resume failed for ${resumeThreadId}: ${message}; falling back to thread/start
1457
- `);
1458
- threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
1459
- ...baseThreadParams,
1460
- ephemeral: false
1461
- });
1462
- }
1463
- } else {
1464
- threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
1465
- ...baseThreadParams,
1466
- ephemeral: false
1467
- });
1468
- }
1359
+ const threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
1360
+ ...baseThreadParams,
1361
+ ephemeral: false
1362
+ });
1469
1363
  const threadId = threadResult.thread_id ?? threadResult.thread?.id;
1470
1364
  runtimeState.sessionInfo.threadId = threadId ?? null;
1471
1365
  runtimeState.sessionInfo.turnId = null;
@@ -1480,31 +1374,21 @@ async function runCodexClientProtocol(child, plan, env, options) {
1480
1374
  }
1481
1375
  let turnCount = 0;
1482
1376
  let requestIdCounter = 0;
1483
- let maxTurnsReached = false;
1484
- for (let turn = 0; turn < remainingTurns; turn++) {
1485
- budgetExceededReason = checkSessionBudgets(turn);
1486
- if (budgetExceededReason) {
1487
- process.stderr.write(`[worker] session budget exceeded (${budgetExceededReason}) \u2014 exiting
1377
+ let maxTurnsReached = exhaustedBeforeStart;
1378
+ if (exhaustedBeforeStart) {
1379
+ process.stderr.write(`[worker] max_turns (${String(env.SYMPHONY_MAX_TURNS ?? maxTurns)}) does not allow any turns for this worker session \u2014 exiting
1488
1380
  `);
1489
- break;
1490
- }
1381
+ }
1382
+ for (let turn = 0; turn < maxTurns; turn++) {
1491
1383
  turnCount = turn + 1;
1492
- const globalTurnCount = cumulativeTurnCount + turnCount;
1493
1384
  runtimeState.sessionInfo.turnCount = turnCount;
1494
1385
  runtimeState.runPhase = "streaming_turn";
1495
1386
  const isFirstTurn = turn === 0;
1496
- const turnInput = isFirstTurn ? buildInitialTurnInput({
1497
- renderedPrompt,
1498
- mode: threadBootstrapMode,
1499
- lastTurnSummary,
1500
- cumulativeTurnCount,
1501
- continuationGuidance
1502
- }) : buildContinuationTurnInput({
1387
+ const turnInput = isFirstTurn ? renderedPrompt : buildContinuationTurnInput({
1503
1388
  continuationGuidance,
1504
- lastTurnSummary,
1505
- cumulativeTurnCount: globalTurnCount - 1
1389
+ cumulativeTurnCount: turn
1506
1390
  });
1507
- process.stderr.write(`[worker] starting codex turn ${globalTurnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
1391
+ process.stderr.write(`[worker] starting codex turn ${turnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
1508
1392
  `);
1509
1393
  requestIdCounter += 1;
1510
1394
  const turnRequestId = `turn-${requestIdCounter}`;
@@ -1544,15 +1428,9 @@ async function runCodexClientProtocol(child, plan, env, options) {
1544
1428
  `);
1545
1429
  break;
1546
1430
  }
1547
- budgetExceededReason = checkSessionBudgets(turnCount);
1548
- if (budgetExceededReason) {
1549
- process.stderr.write(`[worker] session budget exceeded (${budgetExceededReason}) \u2014 exiting
1550
- `);
1551
- break;
1552
- }
1553
- if (turn + 1 >= remainingTurns) {
1431
+ if (turn + 1 >= maxTurns) {
1554
1432
  maxTurnsReached = true;
1555
- process.stderr.write(`[worker] max_turns (${maxTurns}) reached across sessions \u2014 exiting
1433
+ process.stderr.write(`[worker] max_turns (${maxTurns}) reached for this worker session \u2014 exiting
1556
1434
  `);
1557
1435
  break;
1558
1436
  }
@@ -1600,7 +1478,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
1600
1478
  runtimeState.sessionInfo.exitClassification = classifySessionExit({
1601
1479
  runPhase: runtimeState.runPhase,
1602
1480
  userInputRequired,
1603
- budgetExceeded: budgetExceededReason !== null,
1481
+ budgetExceeded: false,
1604
1482
  convergenceDetected,
1605
1483
  maxTurnsReached
1606
1484
  });