@gh-symphony/cli 0.0.21 → 0.1.2

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.
Files changed (36) hide show
  1. package/README.md +100 -69
  2. package/dist/chunk-6I753NYO.js +18 -0
  3. package/dist/{workflow-BLJH2HC3.js → chunk-B4ZJMAZL.js} +27 -19
  4. package/dist/{chunk-SXGT7LOF.js → chunk-DLZAJXZL.js} +600 -12
  5. package/dist/chunk-GHVDABFO.js +235 -0
  6. package/dist/{chunk-QEONJ5DZ.js → chunk-GPRCOJDJ.js} +1314 -35
  7. package/dist/{chunk-A67CMOYE.js → chunk-VFHMHHZW.js} +1 -1
  8. package/dist/{chunk-JN3TQVFV.js → chunk-WM2B6BJ7.js} +16 -62
  9. package/dist/{chunk-ROGRTUFI.js → chunk-WOVNN5NW.js} +16 -6
  10. package/dist/{chunk-C67H3OUL.js → chunk-Z3NZOPLZ.js} +0 -81
  11. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-2ADPUYWA.js} +1 -1
  12. package/dist/{doctor-4HBRICHP.js → doctor-EEPNFCGF.js} +464 -40
  13. package/dist/index.js +357 -244
  14. package/dist/repo-RX4OK7XH.js +6783 -0
  15. package/dist/{setup-B2SVLW2R.js → setup-XNHHRBGU.js} +57 -91
  16. package/dist/{upgrade-OJXPZRYE.js → upgrade-NS53EO2B.js} +2 -2
  17. package/dist/{version-TBDCTKDO.js → version-2RHFZ5CI.js} +1 -1
  18. package/dist/worker-entry.js +376 -15
  19. package/dist/workflow-26QNZZWH.js +22 -0
  20. package/package.json +5 -5
  21. package/dist/chunk-5NV3LSAJ.js +0 -11
  22. package/dist/chunk-C7G7RJ4G.js +0 -146
  23. package/dist/chunk-KY6WKH66.js +0 -1300
  24. package/dist/chunk-MYVJ6HK4.js +0 -3510
  25. package/dist/chunk-S6VIK4FF.js +0 -723
  26. package/dist/chunk-XN5ABWZ6.js +0 -486
  27. package/dist/chunk-Y6TYJMNT.js +0 -109
  28. package/dist/init-HZ3JEDGQ.js +0 -38
  29. package/dist/logs-6JKKYDGJ.js +0 -188
  30. package/dist/project-25NQ4J4Y.js +0 -24
  31. package/dist/recover-L3MJHHDA.js +0 -133
  32. package/dist/repo-TDCWQR6P.js +0 -379
  33. package/dist/run-XJQ6BF7U.js +0 -110
  34. package/dist/start-I2CC7BLW.js +0 -18
  35. package/dist/status-QSCFVGRQ.js +0 -11
  36. package/dist/stop-7MFCBQVW.js +0 -9
@@ -592,6 +592,17 @@ import {
592
592
  UndefinedVariableError
593
593
  } from "liquidjs";
594
594
  function buildPromptVariables(issue, options) {
595
+ const contentType = issue.metadata.contentType ?? "Issue";
596
+ const linkedPullRequests = Array.isArray(issue.metadata.linkedPullRequests) ? issue.metadata.linkedPullRequests.map(normalizePullRequestContext) : [];
597
+ const primaryPullRequest = contentType === "PullRequest" ? normalizePullRequestContext(
598
+ issue.metadata.pullRequest ?? linkedPullRequests[0] ?? buildPullRequestContextFromIssue(issue)
599
+ ) : linkedPullRequests[0] ?? null;
600
+ const pullRequestContext = buildPullRequestVariables({
601
+ contentType,
602
+ linkedPullRequests,
603
+ primaryPullRequest,
604
+ issueBranchName: issue.branchName
605
+ });
595
606
  return {
596
607
  issue: {
597
608
  id: issue.id,
@@ -605,13 +616,61 @@ function buildPromptVariables(issue, options) {
605
616
  labels: issue.labels,
606
617
  blocked_by: issue.blockedBy,
607
618
  branch_name: issue.branchName,
619
+ content_type: contentType,
620
+ linked_pull_requests: linkedPullRequests,
621
+ primary_pull_request: primaryPullRequest,
622
+ has_linked_pr: linkedPullRequests.length > 0,
608
623
  created_at: issue.createdAt,
609
624
  updated_at: issue.updatedAt,
610
625
  repository: `${issue.repository.owner}/${issue.repository.name}`
611
626
  },
627
+ pull_request_context: pullRequestContext,
612
628
  attempt: options.attempt
613
629
  };
614
630
  }
631
+ function normalizePullRequestContext(pullRequest) {
632
+ return {
633
+ ...pullRequest,
634
+ state: pullRequest.state ?? null,
635
+ projectState: pullRequest.projectState ?? null,
636
+ isDraft: pullRequest.isDraft ?? null,
637
+ merged: pullRequest.merged ?? null,
638
+ headRefName: pullRequest.headRefName ?? null,
639
+ baseRefName: pullRequest.baseRefName ?? null
640
+ };
641
+ }
642
+ function buildPullRequestVariables(options) {
643
+ const checkoutBranch = options.primaryPullRequest?.headRefName ?? (options.contentType === "PullRequest" ? options.issueBranchName : null);
644
+ const hasPrimaryPr = options.primaryPullRequest !== null;
645
+ return {
646
+ subject_type: options.contentType,
647
+ linked_pull_requests: options.linkedPullRequests,
648
+ primary_pull_request: options.primaryPullRequest,
649
+ has_linked_pr: options.linkedPullRequests.length > 0,
650
+ has_primary_pr: hasPrimaryPr,
651
+ checkout_branch: checkoutBranch,
652
+ // Policy flag for prompt templates: any primary PR means the worker must
653
+ // inspect review threads and checks before editing code.
654
+ review_first: hasPrimaryPr
655
+ };
656
+ }
657
+ function buildPullRequestContextFromIssue(issue) {
658
+ return {
659
+ id: issue.id,
660
+ number: issue.number,
661
+ identifier: issue.identifier,
662
+ url: issue.url,
663
+ state: null,
664
+ projectState: issue.state,
665
+ headRefName: issue.branchName,
666
+ repository: {
667
+ owner: issue.repository.owner,
668
+ name: issue.repository.name,
669
+ url: issue.repository.url ?? "",
670
+ cloneUrl: issue.repository.cloneUrl
671
+ }
672
+ };
673
+ }
615
674
  var STRICT_LIQUID_ENGINE = new Liquid({
616
675
  strictVariables: true,
617
676
  strictFilters: true,
@@ -815,8 +874,9 @@ var WorkflowConfigStore = class {
815
874
  async load(workflowPath, env = process.env) {
816
875
  await access(workflowPath, constants.R_OK);
817
876
  const fileStat = await stat(workflowPath);
818
- const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}`;
819
877
  const cached = this.cache.get(workflowPath);
878
+ const markdown = await readFile(workflowPath, "utf8");
879
+ const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
820
880
  if (cached && cached.fingerprint === fingerprint) {
821
881
  return toWorkflowResolution(workflowPath, cached.workflow, {
822
882
  isValid: true,
@@ -824,7 +884,6 @@ var WorkflowConfigStore = class {
824
884
  validationError: null
825
885
  });
826
886
  }
827
- const markdown = await readFile(workflowPath, "utf8");
828
887
  try {
829
888
  const workflow = parseWorkflowMarkdown(markdown, env);
830
889
  this.cache.set(workflowPath, {
@@ -917,9 +976,22 @@ var CODEX_ENV_KEYS = [
917
976
  "OPENAI_ORG_ID",
918
977
  "OPENAI_PROJECT"
919
978
  ];
979
+ var AgentRuntimeCredentialError = class extends Error {
980
+ };
920
981
  function extractEnvForCodex(env) {
921
982
  return pickRuntimeEnv(env, CODEX_ENV_KEYS);
922
983
  }
984
+ function extractEnvForClaude(env, envKey = "ANTHROPIC_API_KEY") {
985
+ const apiKey = env[envKey];
986
+ if (!apiKey) {
987
+ throw new AgentRuntimeCredentialError(
988
+ `${envKey} is required in the credential broker response.`
989
+ );
990
+ }
991
+ return {
992
+ [envKey]: apiKey
993
+ };
994
+ }
923
995
  function toAgentCredentialCacheEntry(brokerResponse, now = /* @__PURE__ */ new Date()) {
924
996
  return {
925
997
  env: brokerResponse.env,
@@ -1024,37 +1096,49 @@ import { resolve } from "path";
1024
1096
  // ../core/src/workspace/identity.ts
1025
1097
  import { resolve as resolve2, join } from "path";
1026
1098
  import { createHash as createHash2 } from "crypto";
1027
- function deriveIssueWorkspaceKey(identity, issueIdentifier) {
1028
- if (issueIdentifier) {
1029
- return deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier);
1030
- }
1031
- return deriveLegacyIssueWorkspaceKey(identity);
1032
- }
1033
- function deriveIssueWorkspaceKeyFromIdentifier(issueIdentifier) {
1034
- const sanitized = issueIdentifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1099
+ var RESERVED_WORKSPACE_KEYS = /* @__PURE__ */ new Set([
1100
+ "cache",
1101
+ "issues.json",
1102
+ "project.json",
1103
+ "runs",
1104
+ "status.json"
1105
+ ]);
1106
+ function deriveWorkspaceKey(identifier) {
1107
+ const sanitized = identifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1035
1108
  if (!sanitized || /^[.]+$/.test(sanitized)) {
1036
1109
  return "issue";
1037
1110
  }
1038
1111
  return sanitized;
1039
1112
  }
1040
- function deriveLegacyIssueWorkspaceKey(identity) {
1041
- const input = [
1042
- identity.projectId,
1043
- identity.adapter,
1044
- identity.issueSubjectId
1045
- ].join(":");
1113
+ var deriveIssueWorkspaceKeyFromIdentifier = deriveWorkspaceKey;
1114
+ function deriveIssueWorkspaceKey(identityOrIdentifier, issueIdentifier) {
1115
+ if (typeof identityOrIdentifier === "string") {
1116
+ return deriveWorkspaceKey(identityOrIdentifier);
1117
+ }
1118
+ return deriveWorkspaceKey(
1119
+ issueIdentifier ?? identityOrIdentifier.issueSubjectId
1120
+ );
1121
+ }
1122
+ function deriveLegacyIssueWorkspaceKey(identity, projectId) {
1123
+ const input = [projectId, identity.adapter, identity.issueSubjectId].filter((part) => typeof part === "string").join(":");
1046
1124
  return createHash2("sha256").update(input).digest("hex").slice(0, 16);
1047
1125
  }
1048
- function resolveIssueWorkspaceDirectory(projectDirectory, workspaceKey) {
1049
- const normalizedProjectDirectory = resolve2(projectDirectory);
1050
- const candidate = resolve2(normalizedProjectDirectory, "issues", workspaceKey);
1051
- if (!candidate.startsWith(`${normalizedProjectDirectory}/`)) {
1126
+ function resolveIssueWorkspaceDirectory(runtimeRoot, workspaceKey) {
1127
+ const normalizedRuntimeRoot = resolve2(runtimeRoot);
1128
+ const candidate = resolve2(normalizedRuntimeRoot, workspaceKey);
1129
+ if (!candidate.startsWith(`${normalizedRuntimeRoot}/`)) {
1052
1130
  throw new Error(
1053
- "Issue workspace path escapes the configured project directory."
1131
+ "Issue workspace path escapes the configured runtime root."
1054
1132
  );
1055
1133
  }
1134
+ if (isReservedWorkspaceKey(workspaceKey)) {
1135
+ throw new Error("Issue workspace key is reserved by the runtime layout.");
1136
+ }
1056
1137
  return candidate;
1057
1138
  }
1139
+ function isReservedWorkspaceKey(workspaceKey) {
1140
+ return workspaceKey.startsWith(".") || RESERVED_WORKSPACE_KEYS.has(workspaceKey);
1141
+ }
1058
1142
 
1059
1143
  // ../core/src/workspace/hooks.ts
1060
1144
  import { spawn } from "child_process";
@@ -1206,11 +1290,11 @@ function buildProjectSnapshot(input) {
1206
1290
  allRuns ?? activeRuns
1207
1291
  );
1208
1292
  return {
1209
- projectId: project.projectId,
1210
- slug: project.slug,
1293
+ repository: project.repository,
1211
1294
  tracker: {
1212
1295
  adapter: project.tracker.adapter,
1213
- bindingId: project.tracker.bindingId
1296
+ bindingId: project.tracker.bindingId,
1297
+ settings: project.tracker.settings
1214
1298
  },
1215
1299
  lastTickAt,
1216
1300
  health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
@@ -1405,9 +1489,9 @@ function parseRunEventLine(line) {
1405
1489
  }
1406
1490
 
1407
1491
  // ../core/src/observability/status-assembler.ts
1408
- function isMatchingIssueRun(run, projectId, issueId, issueIdentifier) {
1492
+ function isMatchingIssueRun(run, issueId, issueIdentifier) {
1409
1493
  return Boolean(
1410
- run && run.projectId === projectId && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1494
+ run && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1411
1495
  );
1412
1496
  }
1413
1497
  function mapIssueOrchestrationStateToStatus(state) {
@@ -1446,7 +1530,7 @@ async function runClaudePreflight(options, dependencies = {}) {
1446
1530
  const command = resolveRuntimeCommandBinary(options.command) ?? "claude";
1447
1531
  const checks = [];
1448
1532
  checks.push(checkClaudeBinary(command, options.cwd, deps));
1449
- checks.push(await checkAnthropicApiKey(env, options, deps));
1533
+ checks.push(await checkClaudeAuthentication(env, options, deps));
1450
1534
  checks.push(await checkWorkspaceMcpConfig(options.cwd, deps));
1451
1535
  if (options.includeGhAuth) {
1452
1536
  checks.push(checkGhAuthentication(options.cwd, deps));
@@ -1548,11 +1632,12 @@ function checkGhAuthentication(cwd, deps) {
1548
1632
  );
1549
1633
  }
1550
1634
  }
1551
- async function checkAnthropicApiKey(env, options, deps) {
1635
+ async function checkClaudeAuthentication(env, options, deps) {
1636
+ const authMode = options.authMode ?? "api-key-required";
1552
1637
  if (env.ANTHROPIC_API_KEY?.trim()) {
1553
1638
  return pass(
1554
1639
  "anthropic_api_key",
1555
- "Anthropic API key",
1640
+ "Claude authentication",
1556
1641
  "ANTHROPIC_API_KEY is configured in the environment.",
1557
1642
  { source: "env" }
1558
1643
  );
@@ -1560,9 +1645,32 @@ async function checkAnthropicApiKey(env, options, deps) {
1560
1645
  const brokerUrl = env.AGENT_CREDENTIAL_BROKER_URL?.trim();
1561
1646
  const brokerSecret = env.AGENT_CREDENTIAL_BROKER_SECRET?.trim();
1562
1647
  if (!brokerUrl || !brokerSecret) {
1648
+ if (authMode === "local-or-api-key" && !brokerUrl && !brokerSecret) {
1649
+ return warn(
1650
+ "anthropic_api_key",
1651
+ "Claude authentication",
1652
+ "ANTHROPIC_API_KEY and agent credential broker are not configured; Claude Code local login may be used for this non-bare runtime.",
1653
+ "If this runtime will run headlessly or with runtime.isolation.bare: true, set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1654
+ { source: "local" }
1655
+ );
1656
+ }
1657
+ if (brokerUrl || brokerSecret) {
1658
+ const missingName = brokerUrl ? "AGENT_CREDENTIAL_BROKER_SECRET" : "AGENT_CREDENTIAL_BROKER_URL";
1659
+ return fail(
1660
+ "anthropic_api_key",
1661
+ "Claude authentication",
1662
+ `Agent credential broker configuration is incomplete: ${missingName} is not configured.`,
1663
+ `Set ${missingName} or remove the partial broker configuration and use ANTHROPIC_API_KEY instead.`,
1664
+ {
1665
+ source: "broker",
1666
+ brokerUrl: brokerUrl ?? null,
1667
+ missing: missingName
1668
+ }
1669
+ );
1670
+ }
1563
1671
  return fail(
1564
1672
  "anthropic_api_key",
1565
- "Anthropic API key",
1673
+ "Claude authentication",
1566
1674
  "Neither ANTHROPIC_API_KEY nor an agent credential broker is configured.",
1567
1675
  "Set ANTHROPIC_API_KEY or configure AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1568
1676
  { source: "missing" }
@@ -1571,7 +1679,7 @@ async function checkAnthropicApiKey(env, options, deps) {
1571
1679
  if (options.probeCredentialBroker === false) {
1572
1680
  return pass(
1573
1681
  "anthropic_api_key",
1574
- "Anthropic API key",
1682
+ "Claude authentication",
1575
1683
  "Agent credential broker configuration is present.",
1576
1684
  { source: "broker", brokerUrl }
1577
1685
  );
@@ -1589,14 +1697,14 @@ async function checkAnthropicApiKey(env, options, deps) {
1589
1697
  if (response.ok && payload.env?.ANTHROPIC_API_KEY?.trim()) {
1590
1698
  return pass(
1591
1699
  "anthropic_api_key",
1592
- "Anthropic API key",
1700
+ "Claude authentication",
1593
1701
  "Agent credential broker is reachable and returned ANTHROPIC_API_KEY.",
1594
1702
  { source: "broker", brokerUrl }
1595
1703
  );
1596
1704
  }
1597
1705
  return fail(
1598
1706
  "anthropic_api_key",
1599
- "Anthropic API key",
1707
+ "Claude authentication",
1600
1708
  payload.error ? `Agent credential broker did not return ANTHROPIC_API_KEY: ${payload.error}.` : "Agent credential broker did not return ANTHROPIC_API_KEY.",
1601
1709
  "Set ANTHROPIC_API_KEY or configure the credential broker to return ANTHROPIC_API_KEY.",
1602
1710
  { source: "broker", brokerUrl, status: response.status }
@@ -1604,7 +1712,7 @@ async function checkAnthropicApiKey(env, options, deps) {
1604
1712
  } catch (error) {
1605
1713
  return fail(
1606
1714
  "anthropic_api_key",
1607
- "Anthropic API key",
1715
+ "Claude authentication",
1608
1716
  "Agent credential broker could not be reached.",
1609
1717
  "Set ANTHROPIC_API_KEY or fix AGENT_CREDENTIAL_BROKER_URL and AGENT_CREDENTIAL_BROKER_SECRET.",
1610
1718
  {
@@ -1717,6 +1825,77 @@ import { randomUUID } from "crypto";
1717
1825
  import { rm } from "fs/promises";
1718
1826
  import { join as join5 } from "path";
1719
1827
 
1828
+ // ../runtime-claude/src/argv.ts
1829
+ var DEFAULT_CLAUDE_PRINT_ARGS = [
1830
+ "-p",
1831
+ "--output-format",
1832
+ "stream-json",
1833
+ "--input-format",
1834
+ "stream-json",
1835
+ "--include-partial-messages",
1836
+ // Claude stream-json output requires verbose mode when partial message
1837
+ // events are included; keep this even when callers provide custom args.
1838
+ "--verbose",
1839
+ "--permission-mode",
1840
+ "bypassPermissions"
1841
+ ];
1842
+ function buildClaudePrintArgv(options = {}) {
1843
+ const args = options.baseArgs ? withRequiredClaudePrintArgs(options.baseArgs) : [...DEFAULT_CLAUDE_PRINT_ARGS];
1844
+ const { session, isolation, extraArgs } = options;
1845
+ if (session?.mode === "start") {
1846
+ ensureFlagValue(args, "--session-id", session.sessionId);
1847
+ }
1848
+ if (session?.mode === "resume") {
1849
+ ensureFlagValue(args, "--resume", session.sessionId);
1850
+ if (session.forkSession) {
1851
+ ensureFlag(args, "--fork-session");
1852
+ }
1853
+ }
1854
+ if (isolation?.bare) {
1855
+ ensureFlag(args, "--bare");
1856
+ }
1857
+ if (isolation?.strictMcpConfig) {
1858
+ ensureFlag(args, "--strict-mcp-config");
1859
+ if (isolation.mcpConfigPath) {
1860
+ ensureFlagValue(args, "--mcp-config", isolation.mcpConfigPath);
1861
+ }
1862
+ }
1863
+ if (extraArgs?.length) {
1864
+ args.push(...extraArgs);
1865
+ }
1866
+ return args;
1867
+ }
1868
+ function withRequiredClaudePrintArgs(baseArgs) {
1869
+ const args = [...baseArgs];
1870
+ ensureFlag(args, "-p");
1871
+ ensureFlagValue(args, "--output-format", "stream-json");
1872
+ ensureFlagValue(args, "--input-format", "stream-json");
1873
+ ensureFlag(args, "--include-partial-messages");
1874
+ ensureFlag(args, "--verbose");
1875
+ ensureFlagValue(args, "--permission-mode", "bypassPermissions");
1876
+ return args;
1877
+ }
1878
+ function ensureFlag(args, flag) {
1879
+ if (!args.includes(flag)) {
1880
+ args.push(flag);
1881
+ }
1882
+ }
1883
+ function ensureFlagValue(args, flag, value) {
1884
+ const index = args.indexOf(flag);
1885
+ if (index === -1) {
1886
+ args.push(flag, value);
1887
+ return;
1888
+ }
1889
+ const existingValue = args[index + 1];
1890
+ if (existingValue?.startsWith("-")) {
1891
+ args.splice(index + 1, 0, value);
1892
+ return;
1893
+ }
1894
+ if (existingValue !== value) {
1895
+ args.splice(index + 1, existingValue === void 0 ? 0 : 1, value);
1896
+ }
1897
+ }
1898
+
1720
1899
  // ../runtime-claude/src/mcp-compose.ts
1721
1900
  import { mkdir, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1722
1901
  import { basename, dirname, join as join3, resolve as resolve4 } from "path";
@@ -2006,13 +2185,1111 @@ function createGitHubGraphQLMcpServerEntry(options = {}) {
2006
2185
  };
2007
2186
  }
2008
2187
 
2188
+ // ../runtime-claude/src/mcp-compose.ts
2189
+ async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEnv = {}) {
2190
+ const workspaceMcpPath = join3(workspaceRoot, ".mcp.json");
2191
+ const finalPath = strictMode ? resolveStrictMcpConfigPath(workspaceRoot, symphonyTokenEnv) : workspaceMcpPath;
2192
+ const baseConfig = await readBaseMcpConfig(workspaceMcpPath);
2193
+ const mergedConfig = mergeGitHubGraphQLMcpServer(baseConfig, symphonyTokenEnv);
2194
+ await mkdir(dirname(finalPath), { recursive: true });
2195
+ await writeFile3(finalPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf8");
2196
+ return {
2197
+ finalPath,
2198
+ extraArgv: strictMode ? ["--strict-mcp-config", "--mcp-config", finalPath] : [],
2199
+ ...strictMode ? { cleanupPath: finalPath } : {}
2200
+ };
2201
+ }
2202
+ async function readBaseMcpConfig(workspaceMcpPath) {
2203
+ try {
2204
+ const raw = await readFile6(workspaceMcpPath, "utf8");
2205
+ const parsed = JSON.parse(raw);
2206
+ return isRecord3(parsed) ? parsed : { mcpServers: {} };
2207
+ } catch (error) {
2208
+ if (isNodeError(error) && error.code === "ENOENT") {
2209
+ return { mcpServers: {} };
2210
+ }
2211
+ throw error;
2212
+ }
2213
+ }
2214
+ function mergeGitHubGraphQLMcpServer(baseConfig, env) {
2215
+ const mcpServers = isRecord3(baseConfig.mcpServers) ? baseConfig.mcpServers : {};
2216
+ return {
2217
+ ...baseConfig,
2218
+ mcpServers: {
2219
+ ...mcpServers,
2220
+ github_graphql: createGitHubGraphQLMcpServerEntry({
2221
+ githubToken: env.GITHUB_GRAPHQL_TOKEN,
2222
+ githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
2223
+ githubTokenBrokerUrl: env.GITHUB_TOKEN_BROKER_URL,
2224
+ githubTokenBrokerSecret: env.GITHUB_TOKEN_BROKER_SECRET,
2225
+ githubTokenCachePath: env.GITHUB_TOKEN_CACHE_PATH,
2226
+ githubProjectId: env.GITHUB_PROJECT_ID
2227
+ })
2228
+ }
2229
+ };
2230
+ }
2231
+ function resolveStrictMcpConfigPath(workspaceRoot, env) {
2232
+ const normalizedWorkspaceRoot = resolve4(workspaceRoot);
2233
+ const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(normalizedWorkspaceRoot, ".runtime", basename(normalizedWorkspaceRoot));
2234
+ return join3(runtimeDir, "mcp.json");
2235
+ }
2236
+ function isRecord3(value) {
2237
+ return value != null && typeof value === "object" && !Array.isArray(value);
2238
+ }
2239
+ function isNodeError(error) {
2240
+ return error instanceof Error && "code" in error;
2241
+ }
2242
+
2009
2243
  // ../runtime-claude/src/spawn.ts
2010
2244
  import { spawn as spawn2 } from "child_process";
2011
2245
  import { finished } from "stream/promises";
2012
2246
 
2247
+ // ../runtime-claude/src/internal.ts
2248
+ function asRecord(value) {
2249
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2250
+ }
2251
+ function getString(value) {
2252
+ if (typeof value === "string") {
2253
+ return value;
2254
+ }
2255
+ if (typeof value === "number") {
2256
+ return String(value);
2257
+ }
2258
+ return void 0;
2259
+ }
2260
+
2261
+ // ../runtime-claude/src/events.ts
2262
+ var CLAUDE_OBSERVABILITY_PREFIX = "claude-print/";
2263
+ function parseClaudePrintNdjsonLine(line) {
2264
+ const trimmedLine = line.trim();
2265
+ if (!trimmedLine) {
2266
+ return null;
2267
+ }
2268
+ try {
2269
+ const parsed = JSON.parse(trimmedLine);
2270
+ if (!asRecord(parsed)) {
2271
+ return {
2272
+ line: trimmedLine,
2273
+ parseError: "Claude stream-json line is not a JSON object."
2274
+ };
2275
+ }
2276
+ return {
2277
+ line: trimmedLine,
2278
+ message: parsed
2279
+ };
2280
+ } catch (error) {
2281
+ return {
2282
+ line: trimmedLine,
2283
+ parseError: error instanceof Error ? error.message : "Unknown JSON parse error."
2284
+ };
2285
+ }
2286
+ }
2287
+ var ClaudePrintEventMapper = class {
2288
+ constructor(options = {}) {
2289
+ this.options = options;
2290
+ }
2291
+ hasStartedTurn = false;
2292
+ latestResultEvent = null;
2293
+ // Claude -p stream-json does not define an ordered collection of error
2294
+ // records for one turn; keep the terminal/latest error for exit classification.
2295
+ latestErrorEvent = null;
2296
+ sawRateLimit = false;
2297
+ mapLine(line) {
2298
+ const record = parseClaudePrintNdjsonLine(line);
2299
+ if (!record?.message) {
2300
+ return [];
2301
+ }
2302
+ return this.mapMessage(record.message);
2303
+ }
2304
+ mapMessage(message) {
2305
+ const type = getEventType(message);
2306
+ const events = [];
2307
+ if (type === "message_start") {
2308
+ events.push(this.buildTurnStartedEvent(message, type));
2309
+ this.hasStartedTurn = true;
2310
+ return events;
2311
+ }
2312
+ if (type === "content_block_start" || type === "tool_use") {
2313
+ if (!this.hasStartedTurn) {
2314
+ events.push(this.buildTurnStartedEvent(message, type));
2315
+ this.hasStartedTurn = true;
2316
+ }
2317
+ const toolUseEvent = mapToolUseEvent(message, this.options);
2318
+ if (toolUseEvent) {
2319
+ events.push(toolUseEvent);
2320
+ }
2321
+ }
2322
+ if (type === "content_block_delta") {
2323
+ if (!this.hasStartedTurn) {
2324
+ events.push(this.buildTurnStartedEvent(message, type));
2325
+ this.hasStartedTurn = true;
2326
+ }
2327
+ events.push({
2328
+ name: "agent.messageDelta",
2329
+ payload: {
2330
+ observabilityEvent: observabilityEventName(type),
2331
+ params: message,
2332
+ delta: extractDeltaText(message),
2333
+ itemId: extractItemId(message)
2334
+ }
2335
+ });
2336
+ }
2337
+ if (type === "result") {
2338
+ this.latestResultEvent = message;
2339
+ const rateLimit = extractRateLimit(message);
2340
+ if (rateLimit) {
2341
+ this.sawRateLimit = true;
2342
+ events.push({
2343
+ name: "agent.rateLimit",
2344
+ payload: {
2345
+ observabilityEvent: observabilityEventName(type),
2346
+ params: {
2347
+ source: "claude",
2348
+ rate_limit: rateLimit,
2349
+ usage: asRecord(message.usage),
2350
+ result: message
2351
+ }
2352
+ }
2353
+ });
2354
+ }
2355
+ if (isClaudeResultError(message)) {
2356
+ events.push(buildClaudeErrorEvent(message, type));
2357
+ } else {
2358
+ events.push({
2359
+ name: "agent.turnCompleted",
2360
+ payload: {
2361
+ observabilityEvent: observabilityEventName(type),
2362
+ params: message,
2363
+ inputRequired: false
2364
+ }
2365
+ });
2366
+ }
2367
+ }
2368
+ if (type === "error") {
2369
+ this.latestErrorEvent = message;
2370
+ events.push(buildClaudeErrorEvent(message, type));
2371
+ }
2372
+ return events;
2373
+ }
2374
+ snapshot() {
2375
+ return {
2376
+ hasStartedTurn: this.hasStartedTurn,
2377
+ latestResultEvent: this.latestResultEvent,
2378
+ latestErrorEvent: this.latestErrorEvent,
2379
+ sawRateLimit: this.sawRateLimit
2380
+ };
2381
+ }
2382
+ buildTurnStartedEvent(message, type) {
2383
+ return {
2384
+ name: "agent.turnStarted",
2385
+ payload: {
2386
+ observabilityEvent: observabilityEventName(type),
2387
+ params: message
2388
+ }
2389
+ };
2390
+ }
2391
+ };
2392
+ function isClaudeResultError(message) {
2393
+ const subtype = getString(message.subtype);
2394
+ const stopReason = getString(message.stop_reason);
2395
+ return message.is_error === true || subtype !== void 0 && subtype.startsWith("error") || stopReason !== void 0 && stopReason.startsWith("error");
2396
+ }
2397
+ function extractRateLimit(message) {
2398
+ const usage = asRecord(message.usage);
2399
+ const rateLimit = usage ? asRecord(usage.rate_limit) : null;
2400
+ if (rateLimit) {
2401
+ return rateLimit;
2402
+ }
2403
+ return asRecord(message.rate_limit);
2404
+ }
2405
+ function getClaudeResultStatus(message) {
2406
+ if (!message) {
2407
+ return void 0;
2408
+ }
2409
+ return getString(message.subtype) ?? getString(message.stop_reason);
2410
+ }
2411
+ function mapToolUseEvent(message, options) {
2412
+ const type = getEventType(message);
2413
+ const contentBlock = asRecord(message.content_block);
2414
+ const toolUse = type === "tool_use" ? message : contentBlock && getString(contentBlock.type) === "tool_use" ? contentBlock : null;
2415
+ if (!toolUse) {
2416
+ return null;
2417
+ }
2418
+ const input = toolUse.input !== void 0 ? toolUse.input : toolUse.arguments;
2419
+ return {
2420
+ name: "agent.toolCallRequested",
2421
+ payload: {
2422
+ observabilityEvent: observabilityEventName(type),
2423
+ params: message,
2424
+ callId: getString(toolUse.id) ?? "",
2425
+ toolName: getString(toolUse.name) ?? "",
2426
+ threadId: options.threadId ?? getString(message.thread_id),
2427
+ turnId: options.turnId ?? getString(message.turn_id),
2428
+ arguments: input
2429
+ }
2430
+ };
2431
+ }
2432
+ function buildClaudeErrorEvent(message, type) {
2433
+ return {
2434
+ name: "agent.error",
2435
+ payload: {
2436
+ observabilityEvent: observabilityEventName(type),
2437
+ params: message,
2438
+ error: describeClaudeError(message)
2439
+ }
2440
+ };
2441
+ }
2442
+ function describeClaudeError(message) {
2443
+ const error = asRecord(message.error);
2444
+ return getString(error?.message) ?? getString(error?.type) ?? getString(message.message) ?? getString(message.subtype) ?? getString(message.stop_reason) ?? JSON.stringify(message);
2445
+ }
2446
+ function extractDeltaText(message) {
2447
+ const delta = asRecord(message.delta);
2448
+ return getString(delta?.text) ?? getString(delta?.partial_json) ?? getString(message.text) ?? "";
2449
+ }
2450
+ function extractItemId(message) {
2451
+ return getString(message.item_id) ?? getString(message.content_block_id) ?? getString(message.index) ?? "";
2452
+ }
2453
+ function getEventType(message) {
2454
+ return getString(message.type) ?? "";
2455
+ }
2456
+ function observabilityEventName(type) {
2457
+ return `${CLAUDE_OBSERVABILITY_PREFIX}${type || "unknown"}`;
2458
+ }
2459
+
2460
+ // ../runtime-claude/src/exit-classifier.ts
2461
+ var TRANSIENT_ERROR_PATTERNS = [
2462
+ /rate.?limit/i,
2463
+ /\b429\b/,
2464
+ /timeout/i,
2465
+ /timed?.?out/i,
2466
+ /temporar/i,
2467
+ /overload/i,
2468
+ /unavailable/i,
2469
+ /ECONNRESET/,
2470
+ /ETIMEDOUT/,
2471
+ /EAI_AGAIN/
2472
+ ];
2473
+ function classifyClaudeTurnExit(input) {
2474
+ const resultStatus = getClaudeResultStatus(input.resultEvent);
2475
+ if (input.exitCode === 0 && input.resultEvent && !isClaudeResultError(input.resultEvent)) {
2476
+ return {
2477
+ kind: "success",
2478
+ transient: false,
2479
+ reason: "result_success",
2480
+ resultStatus
2481
+ };
2482
+ }
2483
+ if (input.exitCode === 0 && !input.resultEvent) {
2484
+ return {
2485
+ kind: "app-error",
2486
+ transient: false,
2487
+ reason: "missing_result",
2488
+ resultStatus
2489
+ };
2490
+ }
2491
+ if (input.exitCode === 0 && input.resultEvent && isClaudeResultError(input.resultEvent)) {
2492
+ return {
2493
+ kind: "app-error",
2494
+ transient: isTransientClaudeFailure(input),
2495
+ reason: resultStatus ?? "result_error",
2496
+ resultStatus
2497
+ };
2498
+ }
2499
+ return {
2500
+ kind: "process-error",
2501
+ transient: isTransientClaudeFailure(input),
2502
+ reason: describeProcessFailure(input),
2503
+ resultStatus
2504
+ };
2505
+ }
2506
+ function isTransientClaudeFailure(input) {
2507
+ if (input.sawRateLimit || input.resultEvent && (extractRateLimit(input.resultEvent) !== null || getClaudeResultStatus(input.resultEvent) === "error_rate_limit")) {
2508
+ return true;
2509
+ }
2510
+ if (input.signal === "SIGTERM") {
2511
+ return true;
2512
+ }
2513
+ const text = [
2514
+ input.spawnErrorMessage,
2515
+ extractFailureMessage(input.errorEvent),
2516
+ extractFailureMessage(input.resultEvent)
2517
+ ].join("\n");
2518
+ return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(text));
2519
+ }
2520
+ function describeProcessFailure(input) {
2521
+ if (input.signal) {
2522
+ return `signal_${input.signal}`;
2523
+ }
2524
+ if (input.spawnErrorMessage) {
2525
+ return input.spawnErrorMessage;
2526
+ }
2527
+ if (typeof input.exitCode === "number") {
2528
+ return `exit_${input.exitCode}`;
2529
+ }
2530
+ return "process_error";
2531
+ }
2532
+ function extractFailureMessage(event) {
2533
+ if (!event) {
2534
+ return "";
2535
+ }
2536
+ const error = asRecord(event.error);
2537
+ return [
2538
+ getString(error?.message),
2539
+ getString(error?.type),
2540
+ getString(event.message)
2541
+ ].filter((value) => value !== void 0).join("\n");
2542
+ }
2543
+
2544
+ // ../runtime-claude/src/spawn.ts
2545
+ async function spawnClaudeTurn(input, dependencies = {}) {
2546
+ const command = input.command ?? "claude";
2547
+ const child = (dependencies.spawnImpl ?? spawn2)(command, input.args, {
2548
+ cwd: input.cwd,
2549
+ env: input.env,
2550
+ stdio: "pipe"
2551
+ });
2552
+ dependencies.onSpawned?.(child);
2553
+ const records = [];
2554
+ const eventMapper = new ClaudePrintEventMapper();
2555
+ let emittedErrorEvent = false;
2556
+ const emitEvent = (event) => {
2557
+ if (event.name === "agent.error") {
2558
+ emittedErrorEvent = true;
2559
+ }
2560
+ dependencies.onEvent?.(event);
2561
+ };
2562
+ const stdoutDone = collectNdjsonStream(
2563
+ child.stdout,
2564
+ "stdout",
2565
+ records,
2566
+ eventMapper,
2567
+ emitEvent
2568
+ );
2569
+ const stderrDone = collectNdjsonStream(
2570
+ child.stderr,
2571
+ "stderr",
2572
+ records,
2573
+ null,
2574
+ null
2575
+ );
2576
+ const exitDone = waitForChildExit(child, records);
2577
+ const stdinMessages = Array.isArray(input.stdinMessages) ? input.stdinMessages : [input.stdinMessages];
2578
+ for (const message of stdinMessages) {
2579
+ const didWrite = await writeToStdin(
2580
+ child.stdin,
2581
+ `${JSON.stringify(message)}
2582
+ `
2583
+ );
2584
+ if (!didWrite) {
2585
+ break;
2586
+ }
2587
+ }
2588
+ if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded && !child.stdin.writableFinished) {
2589
+ child.stdin.end();
2590
+ }
2591
+ const outcome = await exitDone;
2592
+ await Promise.all([stdoutDone, stderrDone]);
2593
+ const mapperState = eventMapper.snapshot();
2594
+ const classification = classifyClaudeTurnExit({
2595
+ exitCode: outcome.exitCode,
2596
+ signal: outcome.signal,
2597
+ resultEvent: mapperState.latestResultEvent,
2598
+ errorEvent: mapperState.latestErrorEvent,
2599
+ sawRateLimit: mapperState.sawRateLimit,
2600
+ spawnErrorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2601
+ });
2602
+ if ((classification.kind === "app-error" || classification.kind === "process-error") && !emittedErrorEvent) {
2603
+ emitEvent({
2604
+ name: "agent.error",
2605
+ payload: {
2606
+ observabilityEvent: classification.kind === "app-error" ? "claude-print/app-error" : "claude-print/process-exit",
2607
+ params: {
2608
+ exitCode: outcome.exitCode,
2609
+ signal: outcome.signal,
2610
+ classification,
2611
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2612
+ },
2613
+ error: classification.reason
2614
+ }
2615
+ });
2616
+ }
2617
+ return {
2618
+ command,
2619
+ args: [...input.args],
2620
+ cwd: input.cwd,
2621
+ records,
2622
+ exitCode: outcome.exitCode,
2623
+ signal: outcome.signal,
2624
+ result: classification.kind,
2625
+ classification,
2626
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2627
+ };
2628
+ }
2629
+ async function collectNdjsonStream(stream, channel, records, eventMapper, onEvent) {
2630
+ if (!stream) {
2631
+ return;
2632
+ }
2633
+ let buffer = "";
2634
+ stream.setEncoding("utf8");
2635
+ stream.on("data", (chunk) => {
2636
+ buffer += chunk;
2637
+ while (true) {
2638
+ const newlineIndex = buffer.indexOf("\n");
2639
+ if (newlineIndex === -1) {
2640
+ break;
2641
+ }
2642
+ const line = buffer.slice(0, newlineIndex).trim();
2643
+ buffer = buffer.slice(newlineIndex + 1);
2644
+ if (line.length === 0) {
2645
+ continue;
2646
+ }
2647
+ records.push(parseClaudeRecord(channel, line, eventMapper, onEvent));
2648
+ }
2649
+ });
2650
+ try {
2651
+ await finished(stream);
2652
+ } catch (error) {
2653
+ records.push({
2654
+ stream: channel,
2655
+ line: "",
2656
+ parseError: error instanceof Error ? error.message : "Unknown stream error."
2657
+ });
2658
+ }
2659
+ const trailingLine = buffer.trim();
2660
+ if (trailingLine.length > 0) {
2661
+ records.push(
2662
+ parseClaudeRecord(channel, trailingLine, eventMapper, onEvent)
2663
+ );
2664
+ }
2665
+ }
2666
+ function parseClaudeRecord(stream, line, eventMapper, onEvent) {
2667
+ const record = parseClaudePrintNdjsonLine(line);
2668
+ if (record.message) {
2669
+ if (!eventMapper) {
2670
+ return {
2671
+ stream,
2672
+ line: record.line,
2673
+ message: record.message
2674
+ };
2675
+ }
2676
+ for (const event of eventMapper.mapMessage(record.message)) {
2677
+ onEvent?.(event);
2678
+ }
2679
+ return {
2680
+ stream,
2681
+ line: record.line,
2682
+ message: record.message
2683
+ };
2684
+ }
2685
+ return {
2686
+ stream,
2687
+ line: record.line,
2688
+ parseError: record.parseError
2689
+ };
2690
+ }
2691
+ async function writeToStdin(stream, line) {
2692
+ if (!stream || stream.destroyed || stream.writableEnded) {
2693
+ return false;
2694
+ }
2695
+ if (stream.write(line)) {
2696
+ return true;
2697
+ }
2698
+ return waitForDrainOrClosure(stream);
2699
+ }
2700
+ function waitForDrainOrClosure(stream) {
2701
+ return new Promise((resolve5) => {
2702
+ const cleanup = () => {
2703
+ stream.removeListener("drain", handleDrain);
2704
+ stream.removeListener("close", handleClose);
2705
+ stream.removeListener("finish", handleFinish);
2706
+ stream.removeListener("error", handleError);
2707
+ };
2708
+ const handleDrain = () => {
2709
+ cleanup();
2710
+ resolve5(true);
2711
+ };
2712
+ const handleClose = () => {
2713
+ cleanup();
2714
+ resolve5(false);
2715
+ };
2716
+ const handleFinish = () => {
2717
+ cleanup();
2718
+ resolve5(false);
2719
+ };
2720
+ const handleError = () => {
2721
+ cleanup();
2722
+ resolve5(false);
2723
+ };
2724
+ stream.once("drain", handleDrain);
2725
+ stream.once("close", handleClose);
2726
+ stream.once("finish", handleFinish);
2727
+ stream.once("error", handleError);
2728
+ });
2729
+ }
2730
+ function waitForChildExit(child, records) {
2731
+ return new Promise((resolve5) => {
2732
+ const handleClose = (exitCode, signal) => {
2733
+ cleanup();
2734
+ resolve5({ exitCode, signal });
2735
+ };
2736
+ const handleError = (error) => {
2737
+ cleanup();
2738
+ records.push({
2739
+ stream: "stderr",
2740
+ line: "",
2741
+ parseError: error.message
2742
+ });
2743
+ resolve5({
2744
+ exitCode: null,
2745
+ signal: null,
2746
+ errorMessage: error.message
2747
+ });
2748
+ };
2749
+ const cleanup = () => {
2750
+ child.removeListener("close", handleClose);
2751
+ child.removeListener("error", handleError);
2752
+ };
2753
+ child.on("close", handleClose);
2754
+ child.on("error", handleError);
2755
+ });
2756
+ }
2757
+
2013
2758
  // ../runtime-claude/src/session-store.ts
2014
2759
  import { mkdir as mkdir2, readFile as readFile7, rename, writeFile as writeFile4 } from "fs/promises";
2015
2760
  import { dirname as dirname2, join as join4 } from "path";
2761
+ var CLAUDE_SESSION_PROTOCOL = "claude-print";
2762
+ var CLAUDE_SESSION_FILENAME = "claude-session.json";
2763
+ var ClaudeSessionStore = class {
2764
+ constructor(options) {
2765
+ this.options = options;
2766
+ }
2767
+ sessionFilePath(options) {
2768
+ return join4(
2769
+ options.runDirectory ?? this.runDirectory(options.runId),
2770
+ CLAUDE_SESSION_FILENAME
2771
+ );
2772
+ }
2773
+ async load(options) {
2774
+ let raw;
2775
+ try {
2776
+ raw = await readFile7(this.sessionFilePath(options), "utf8");
2777
+ } catch (error) {
2778
+ if (isFileNotFoundError(error)) {
2779
+ return null;
2780
+ }
2781
+ throw error;
2782
+ }
2783
+ return parseClaudeSessionFile(JSON.parse(raw));
2784
+ }
2785
+ async save(options) {
2786
+ const session = {
2787
+ protocol: CLAUDE_SESSION_PROTOCOL,
2788
+ sessionId: options.sessionId,
2789
+ createdAt: options.createdAt,
2790
+ protocolState: options.protocolState ?? {}
2791
+ };
2792
+ if (options.parentRunId) {
2793
+ session.parentRunId = options.parentRunId;
2794
+ }
2795
+ const path = this.sessionFilePath(options);
2796
+ await mkdir2(dirname2(path), { recursive: true });
2797
+ await writeFile4(`${path}.tmp`, `${JSON.stringify(session, null, 2)}
2798
+ `, "utf8");
2799
+ await rename(`${path}.tmp`, path);
2800
+ return session;
2801
+ }
2802
+ runDirectory(runId) {
2803
+ return join4(this.options.runtimeRoot, "runs", runId);
2804
+ }
2805
+ };
2806
+ function parseClaudeSessionFile(value) {
2807
+ if (!isRecord4(value)) {
2808
+ throw new Error("Claude session file must be a JSON object.");
2809
+ }
2810
+ if (value.protocol !== CLAUDE_SESSION_PROTOCOL) {
2811
+ throw new Error(
2812
+ `Claude session file protocol must be ${CLAUDE_SESSION_PROTOCOL}.`
2813
+ );
2814
+ }
2815
+ if (typeof value.sessionId !== "string" || value.sessionId.length === 0) {
2816
+ throw new Error("Claude session file sessionId must be a non-empty string.");
2817
+ }
2818
+ if (typeof value.createdAt !== "string" || value.createdAt.length === 0) {
2819
+ throw new Error("Claude session file createdAt must be a non-empty string.");
2820
+ }
2821
+ if ("parentRunId" in value && value.parentRunId !== void 0 && typeof value.parentRunId !== "string") {
2822
+ throw new Error("Claude session file parentRunId must be a string.");
2823
+ }
2824
+ if ("protocolState" in value && value.protocolState !== void 0 && !isRecord4(value.protocolState)) {
2825
+ throw new Error("Claude session file protocolState must be an object.");
2826
+ }
2827
+ return {
2828
+ protocol: CLAUDE_SESSION_PROTOCOL,
2829
+ sessionId: value.sessionId,
2830
+ createdAt: value.createdAt,
2831
+ parentRunId: typeof value.parentRunId === "string" ? value.parentRunId : void 0,
2832
+ protocolState: isRecord4(value.protocolState) ? value.protocolState : {}
2833
+ };
2834
+ }
2835
+ function isRecord4(value) {
2836
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2837
+ }
2838
+ function isFileNotFoundError(error) {
2839
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
2840
+ }
2841
+
2842
+ // ../runtime-claude/src/adapter.ts
2843
+ var ClaudePrintRuntimeAdapter = class {
2844
+ constructor(config, dependencies = {}) {
2845
+ this.config = config;
2846
+ this.dependencies = dependencies;
2847
+ this.sessionStore = new ClaudeSessionStore({
2848
+ runtimeRoot: config.runtimeRoot ?? join5(config.workingDirectory, ".runtime", "orchestrator")
2849
+ });
2850
+ }
2851
+ activeChild = null;
2852
+ preparedMcpConfig = null;
2853
+ preparedSession = null;
2854
+ eventHandlers = /* @__PURE__ */ new Set();
2855
+ pendingEvents = [];
2856
+ sessionStore;
2857
+ async prepare(context) {
2858
+ await this.cleanupPreparedMcpConfig();
2859
+ this.pendingEvents.length = 0;
2860
+ this.preparedSession = await this.prepareSession(context);
2861
+ this.preparedMcpConfig = await composeClaudeMcpConfig(
2862
+ this.config.workingDirectory,
2863
+ this.config.isolation?.strictMcpConfig === true,
2864
+ buildClaudeMcpTokenEnvironment({
2865
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
2866
+ configEnv: this.config.env,
2867
+ runtimeDirectory: this.config.runtimeDirectory
2868
+ })
2869
+ );
2870
+ }
2871
+ async spawnTurn(input) {
2872
+ if (this.activeChild) {
2873
+ throw new Error(
2874
+ "TODO(#8): Claude print runtime adapter supports only one in-flight turn."
2875
+ );
2876
+ }
2877
+ const session = input.session ?? this.preparedSession?.session;
2878
+ const argv = buildClaudePrintArgv(this.buildArgvOptions(input, session));
2879
+ try {
2880
+ const result = await this.spawnWithArgv(input, argv);
2881
+ if (this.shouldInvalidatePreparedResume(session, result)) {
2882
+ return await this.retryWithFreshSession(input, result);
2883
+ }
2884
+ await this.persistStartedSessionId(result);
2885
+ await this.persistForkedSessionId(result);
2886
+ return result;
2887
+ } finally {
2888
+ this.activeChild = null;
2889
+ }
2890
+ }
2891
+ onEvent(handler) {
2892
+ this.eventHandlers.add(handler);
2893
+ for (const event of this.pendingEvents) {
2894
+ handler(event);
2895
+ }
2896
+ return () => {
2897
+ this.eventHandlers.delete(handler);
2898
+ };
2899
+ }
2900
+ resolveCredentials(brokerResponse) {
2901
+ return extractEnvForClaude(brokerResponse.env, this.config.authEnvKey);
2902
+ }
2903
+ async shutdown() {
2904
+ this.stopActiveChild();
2905
+ await this.cleanupPreparedMcpConfig();
2906
+ }
2907
+ async cancel(_reason) {
2908
+ this.stopActiveChild();
2909
+ await this.cleanupPreparedMcpConfig();
2910
+ }
2911
+ buildArgvOptions(input, session) {
2912
+ const isolation = {
2913
+ ...this.config.isolation,
2914
+ ...input.isolation
2915
+ };
2916
+ const configuredExtraArgs = input.extraArgs ?? this.config.extraArgs ?? [];
2917
+ if (this.preparedMcpConfig) {
2918
+ return {
2919
+ baseArgs: this.config.args,
2920
+ session,
2921
+ // prepare() owns MCP argv injection through extraArgv; suppress the
2922
+ // isolation flag here so buildClaudePrintArgv does not add it twice.
2923
+ // Any input mcpConfigPath is intentionally ignored while a prepared
2924
+ // composition result is active.
2925
+ isolation: {
2926
+ ...isolation,
2927
+ strictMcpConfig: false,
2928
+ mcpConfigPath: void 0
2929
+ },
2930
+ extraArgs: [
2931
+ ...this.preparedMcpConfig.extraArgv,
2932
+ ...configuredExtraArgs
2933
+ ]
2934
+ };
2935
+ }
2936
+ if (isolation.strictMcpConfig && !isolation.mcpConfigPath) {
2937
+ throw new Error(
2938
+ "Claude strict MCP config requires prepare() or an explicit mcpConfigPath."
2939
+ );
2940
+ }
2941
+ return {
2942
+ baseArgs: this.config.args,
2943
+ session,
2944
+ isolation,
2945
+ extraArgs: configuredExtraArgs
2946
+ };
2947
+ }
2948
+ async prepareSession(context) {
2949
+ const currentOptions = {
2950
+ runId: context.runId,
2951
+ runDirectory: context.runDirectory
2952
+ };
2953
+ const parentRunId = context.previousRunId;
2954
+ try {
2955
+ const current = await this.sessionStore.load(currentOptions);
2956
+ if (current) {
2957
+ return {
2958
+ runId: context.runId,
2959
+ runDirectory: context.runDirectory,
2960
+ sessionFile: current,
2961
+ session: {
2962
+ mode: "resume",
2963
+ sessionId: current.sessionId
2964
+ }
2965
+ };
2966
+ }
2967
+ } catch (error) {
2968
+ return await this.createFreshSession(context, {
2969
+ reason: `session file could not be read or parsed: ${formatErrorMessage(error)}`,
2970
+ invalidatedSessionId: "unknown",
2971
+ parentRunId
2972
+ });
2973
+ }
2974
+ if (context.previousRunId) {
2975
+ try {
2976
+ const previous = await this.sessionStore.load({
2977
+ runId: context.previousRunId,
2978
+ runDirectory: context.previousRunDirectory
2979
+ });
2980
+ if (previous) {
2981
+ const sessionFile = await this.sessionStore.save({
2982
+ ...currentOptions,
2983
+ sessionId: previous.sessionId,
2984
+ createdAt: this.nowIso(),
2985
+ parentRunId: context.previousRunId
2986
+ });
2987
+ return {
2988
+ runId: context.runId,
2989
+ runDirectory: context.runDirectory,
2990
+ sessionFile,
2991
+ session: {
2992
+ mode: "resume",
2993
+ sessionId: previous.sessionId,
2994
+ forkSession: true
2995
+ }
2996
+ };
2997
+ }
2998
+ } catch (error) {
2999
+ return await this.createFreshSession(context, {
3000
+ reason: `parent session file could not be read or parsed: ${formatErrorMessage(error)}`,
3001
+ invalidatedSessionId: "unknown",
3002
+ parentRunId
3003
+ });
3004
+ }
3005
+ }
3006
+ return await this.createFreshSession(context, { parentRunId });
3007
+ }
3008
+ async createFreshSession(context, options = {}) {
3009
+ const replacementSessionId = this.createSessionId();
3010
+ const sessionFile = await this.sessionStore.save({
3011
+ runId: context.runId,
3012
+ runDirectory: context.runDirectory,
3013
+ sessionId: replacementSessionId,
3014
+ createdAt: this.nowIso(),
3015
+ parentRunId: options.parentRunId
3016
+ });
3017
+ if (options.reason) {
3018
+ this.emitSessionInvalidated({
3019
+ runId: context.runId,
3020
+ sessionId: options.invalidatedSessionId ?? "unknown",
3021
+ replacementSessionId,
3022
+ reason: options.reason
3023
+ });
3024
+ }
3025
+ return {
3026
+ runId: context.runId,
3027
+ runDirectory: context.runDirectory,
3028
+ sessionFile,
3029
+ session: {
3030
+ mode: "start",
3031
+ sessionId: replacementSessionId
3032
+ }
3033
+ };
3034
+ }
3035
+ async retryWithFreshSession(input, failedResult) {
3036
+ if (!this.preparedSession) {
3037
+ return failedResult;
3038
+ }
3039
+ const invalidatedSessionId = this.preparedSession.session.sessionId;
3040
+ const replacementSessionId = this.createSessionId();
3041
+ const parentRunId = this.preparedSession.sessionFile.parentRunId;
3042
+ const sessionFile = await this.sessionStore.save({
3043
+ runId: this.preparedSession.runId,
3044
+ runDirectory: this.preparedSession.runDirectory,
3045
+ sessionId: replacementSessionId,
3046
+ createdAt: this.nowIso(),
3047
+ parentRunId
3048
+ });
3049
+ this.preparedSession = {
3050
+ ...this.preparedSession,
3051
+ sessionFile,
3052
+ session: {
3053
+ mode: "start",
3054
+ sessionId: replacementSessionId
3055
+ }
3056
+ };
3057
+ this.emitSessionInvalidated({
3058
+ runId: this.preparedSession.runId,
3059
+ sessionId: invalidatedSessionId,
3060
+ replacementSessionId,
3061
+ reason: "claude resume session was rejected with a 4xx response"
3062
+ });
3063
+ const retryArgv = buildClaudePrintArgv(
3064
+ this.buildArgvOptions(input, this.preparedSession.session)
3065
+ );
3066
+ const retryResult = await this.spawnWithArgv(input, retryArgv);
3067
+ await this.persistStartedSessionId(retryResult);
3068
+ return retryResult;
3069
+ }
3070
+ async spawnWithArgv(input, argv) {
3071
+ return await spawnClaudeTurn(
3072
+ {
3073
+ command: input.command ?? this.config.command,
3074
+ args: argv,
3075
+ cwd: input.cwd ?? this.config.workingDirectory,
3076
+ env: buildClaudeSpawnEnv({
3077
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
3078
+ configEnv: this.config.env,
3079
+ inputEnv: input.env
3080
+ }),
3081
+ stdinMessages: input.messages
3082
+ },
3083
+ {
3084
+ ...this.dependencies,
3085
+ onSpawned: (child) => {
3086
+ this.activeChild = child;
3087
+ this.dependencies.onSpawned?.(child);
3088
+ },
3089
+ onEvent: (event) => {
3090
+ this.emitEvent(event);
3091
+ try {
3092
+ this.dependencies.onEvent?.(event);
3093
+ } catch {
3094
+ }
3095
+ }
3096
+ }
3097
+ );
3098
+ }
3099
+ async persistForkedSessionId(result) {
3100
+ if (this.preparedSession?.session.mode !== "resume" || !this.preparedSession.session.forkSession) {
3101
+ return;
3102
+ }
3103
+ const forkedSessionId = findSessionIdInResult(result);
3104
+ const sessionId = forkedSessionId ?? this.preparedSession.session.sessionId;
3105
+ this.preparedSession = {
3106
+ ...this.preparedSession,
3107
+ sessionFile: await this.sessionStore.save({
3108
+ runId: this.preparedSession.runId,
3109
+ runDirectory: this.preparedSession.runDirectory,
3110
+ sessionId,
3111
+ createdAt: this.preparedSession.sessionFile.createdAt,
3112
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3113
+ protocolState: this.preparedSession.sessionFile.protocolState
3114
+ }),
3115
+ session: {
3116
+ mode: "resume",
3117
+ sessionId
3118
+ }
3119
+ };
3120
+ }
3121
+ async persistStartedSessionId(result) {
3122
+ if (this.preparedSession?.session.mode !== "start") {
3123
+ return;
3124
+ }
3125
+ if (result.result !== "success") {
3126
+ return;
3127
+ }
3128
+ const sessionId = findSessionIdInResult(result) ?? this.preparedSession.session.sessionId;
3129
+ this.preparedSession = {
3130
+ ...this.preparedSession,
3131
+ sessionFile: await this.sessionStore.save({
3132
+ runId: this.preparedSession.runId,
3133
+ runDirectory: this.preparedSession.runDirectory,
3134
+ sessionId,
3135
+ createdAt: this.preparedSession.sessionFile.createdAt,
3136
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3137
+ protocolState: this.preparedSession.sessionFile.protocolState
3138
+ }),
3139
+ session: {
3140
+ mode: "resume",
3141
+ sessionId
3142
+ }
3143
+ };
3144
+ }
3145
+ shouldInvalidatePreparedResume(session, result) {
3146
+ return session === this.preparedSession?.session && session?.mode === "resume" && isResumeRejectedWith4xx(result);
3147
+ }
3148
+ emitSessionInvalidated(payload) {
3149
+ const event = {
3150
+ name: "agent.sessionInvalidated",
3151
+ payload: {
3152
+ params: {},
3153
+ ...payload,
3154
+ observabilityEvent: "session_invalidated"
3155
+ }
3156
+ };
3157
+ if (this.eventHandlers.size === 0) {
3158
+ this.pendingEvents.push(event);
3159
+ } else {
3160
+ for (const handler of this.eventHandlers) {
3161
+ handler(event);
3162
+ }
3163
+ }
3164
+ }
3165
+ createSessionId() {
3166
+ return this.dependencies.createSessionId?.() ?? randomUUID();
3167
+ }
3168
+ nowIso() {
3169
+ return (this.dependencies.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
3170
+ }
3171
+ stopActiveChild() {
3172
+ if (!this.activeChild || this.activeChild.killed) {
3173
+ this.activeChild = null;
3174
+ return;
3175
+ }
3176
+ this.activeChild.kill("SIGTERM");
3177
+ this.activeChild = null;
3178
+ }
3179
+ async cleanupPreparedMcpConfig() {
3180
+ const cleanupPath = this.preparedMcpConfig?.cleanupPath;
3181
+ this.preparedMcpConfig = null;
3182
+ if (!cleanupPath) {
3183
+ return;
3184
+ }
3185
+ await rm(cleanupPath, { force: true });
3186
+ }
3187
+ emitEvent(event) {
3188
+ for (const handler of this.eventHandlers) {
3189
+ try {
3190
+ handler(event);
3191
+ } catch {
3192
+ }
3193
+ }
3194
+ }
3195
+ };
3196
+ function createClaudePrintRuntimeAdapter(config, dependencies = {}) {
3197
+ return new ClaudePrintRuntimeAdapter(config, dependencies);
3198
+ }
3199
+ var DEFAULT_INHERITED_ENV_KEYS = [
3200
+ "HOME",
3201
+ "LANG",
3202
+ "PATH",
3203
+ "SHELL",
3204
+ "SYSTEMROOT",
3205
+ "TEMP",
3206
+ "TERM",
3207
+ "TMP",
3208
+ "TMPDIR",
3209
+ "USER",
3210
+ "USERPROFILE"
3211
+ ];
3212
+ function buildClaudeSpawnEnv(options) {
3213
+ if (options.inheritProcessEnv) {
3214
+ return {
3215
+ ...process.env,
3216
+ ...options.configEnv,
3217
+ ...options.inputEnv
3218
+ };
3219
+ }
3220
+ const env = {};
3221
+ for (const key of DEFAULT_INHERITED_ENV_KEYS) {
3222
+ const value = process.env[key];
3223
+ if (value !== void 0) {
3224
+ env[key] = value;
3225
+ }
3226
+ }
3227
+ Object.assign(env, options.configEnv, options.inputEnv);
3228
+ return env;
3229
+ }
3230
+ function findSessionIdInResult(result) {
3231
+ for (const record of result.records) {
3232
+ const sessionId = findSessionId(record.message);
3233
+ if (sessionId) {
3234
+ return sessionId;
3235
+ }
3236
+ }
3237
+ return null;
3238
+ }
3239
+ function findSessionId(value, depth = 0) {
3240
+ if (depth > 5) {
3241
+ return null;
3242
+ }
3243
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3244
+ return null;
3245
+ }
3246
+ const record = value;
3247
+ if (typeof record.sessionId === "string") {
3248
+ return record.sessionId;
3249
+ }
3250
+ if (typeof record.session_id === "string") {
3251
+ return record.session_id;
3252
+ }
3253
+ for (const nested of Object.values(record)) {
3254
+ const sessionId = findSessionId(nested, depth + 1);
3255
+ if (sessionId) {
3256
+ return sessionId;
3257
+ }
3258
+ }
3259
+ return null;
3260
+ }
3261
+ function isResumeRejectedWith4xx(result) {
3262
+ if (result.result !== "process-error") {
3263
+ return false;
3264
+ }
3265
+ return result.records.some((record) => {
3266
+ const text = record.line.toLowerCase();
3267
+ return text.includes("resume") && /\b4\d\d\b/.test(text);
3268
+ });
3269
+ }
3270
+ function formatErrorMessage(error) {
3271
+ if (error instanceof Error) {
3272
+ return error.message;
3273
+ }
3274
+ return String(error);
3275
+ }
3276
+ function buildClaudeMcpTokenEnvironment(options) {
3277
+ const source = options.inheritProcessEnv ? {
3278
+ ...process.env,
3279
+ ...options.configEnv
3280
+ } : {
3281
+ ...options.configEnv
3282
+ };
3283
+ return {
3284
+ GITHUB_GRAPHQL_TOKEN: source.GITHUB_GRAPHQL_TOKEN,
3285
+ GITHUB_GRAPHQL_API_URL: source.GITHUB_GRAPHQL_API_URL,
3286
+ GITHUB_TOKEN_BROKER_URL: source.GITHUB_TOKEN_BROKER_URL,
3287
+ GITHUB_TOKEN_BROKER_SECRET: source.GITHUB_TOKEN_BROKER_SECRET,
3288
+ GITHUB_TOKEN_CACHE_PATH: source.GITHUB_TOKEN_CACHE_PATH,
3289
+ GITHUB_PROJECT_ID: source.GITHUB_PROJECT_ID,
3290
+ WORKSPACE_RUNTIME_DIR: options.runtimeDirectory ?? source.WORKSPACE_RUNTIME_DIR
3291
+ };
3292
+ }
2016
3293
 
2017
3294
  export {
2018
3295
  isOrchestratorChannelEvent,
@@ -2032,14 +3309,15 @@ export {
2032
3309
  classifySessionExit,
2033
3310
  scheduleRetryAt,
2034
3311
  extractEnvForCodex,
3312
+ extractEnvForClaude,
2035
3313
  shouldReuseAgentCredentialCache,
2036
3314
  readAgentCredentialCache,
2037
3315
  writeAgentCredentialCache,
2038
3316
  DEFAULT_AGENT_INPUT_REQUIRED_REASON,
2039
3317
  buildAgentInputRequiredReason,
2040
3318
  readEnvFile,
2041
- deriveIssueWorkspaceKey,
2042
3319
  deriveIssueWorkspaceKeyFromIdentifier,
3320
+ deriveIssueWorkspaceKey,
2043
3321
  deriveLegacyIssueWorkspaceKey,
2044
3322
  resolveIssueWorkspaceDirectory,
2045
3323
  buildHookEnv,
@@ -2053,6 +3331,7 @@ export {
2053
3331
  mapIssueOrchestrationStateToStatus,
2054
3332
  resolveGitHubGraphQLToken,
2055
3333
  createGitHubGraphQLMcpServerEntry,
3334
+ createClaudePrintRuntimeAdapter,
2056
3335
  runClaudePreflight,
2057
3336
  formatClaudePreflightText,
2058
3337
  isClaudeRuntimeCommand,