@gh-symphony/cli 0.0.21 → 0.0.22

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 (38) hide show
  1. package/README.md +36 -0
  2. package/dist/{chunk-SXGT7LOF.js → chunk-2TSM3INR.js} +26 -1
  3. package/dist/{chunk-A67CMOYE.js → chunk-2UW7NQLX.js} +1 -1
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  6. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  7. package/dist/{chunk-KY6WKH66.js → chunk-E7HYEEZD.js} +70 -52
  8. package/dist/{chunk-QEONJ5DZ.js → chunk-EEQQWTXS.js} +1288 -92
  9. package/dist/chunk-GDE6FYN4.js +26 -0
  10. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  11. package/dist/{chunk-JN3TQVFV.js → chunk-HMLBBZNY.js} +11 -2
  12. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  13. package/dist/{chunk-MYVJ6HK4.js → chunk-PUDXVBSN.js} +706 -376
  14. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  15. package/dist/{chunk-S6VIK4FF.js → chunk-ZHOKYUO3.js} +337 -13
  16. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  17. package/dist/{doctor-4HBRICHP.js → doctor-EJUMPBMW.js} +4 -4
  18. package/dist/index.js +88 -21
  19. package/dist/{init-HZ3JEDGQ.js → init-54HMKNYI.js} +3 -3
  20. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  21. package/dist/project-RMYMZSFV.js +25 -0
  22. package/dist/{recover-L3MJHHDA.js → recover-LTLKMTRX.js} +7 -7
  23. package/dist/repo-WI7GF6XQ.js +749 -0
  24. package/dist/{run-XJQ6BF7U.js → run-IHN3ZL35.js} +21 -9
  25. package/dist/{setup-B2SVLW2R.js → setup-TZJSM3QV.js} +14 -13
  26. package/dist/start-RTAHQMR2.js +19 -0
  27. package/dist/status-F4D52OVK.js +12 -0
  28. package/dist/stop-MDKMJPVR.js +10 -0
  29. package/dist/{upgrade-OJXPZRYE.js → upgrade-O33S2SJK.js} +2 -2
  30. package/dist/{version-TBDCTKDO.js → version-CW54Q7BK.js} +1 -1
  31. package/dist/worker-entry.js +369 -13
  32. package/dist/{workflow-BLJH2HC3.js → workflow-L3KT6HB7.js} +5 -5
  33. package/package.json +3 -3
  34. package/dist/project-25NQ4J4Y.js +0 -24
  35. package/dist/repo-TDCWQR6P.js +0 -379
  36. package/dist/start-I2CC7BLW.js +0 -18
  37. package/dist/status-QSCFVGRQ.js +0 -11
  38. package/dist/stop-7MFCBQVW.js +0 -9
@@ -583,6 +583,73 @@ function matchOptionalSection(markdown, heading) {
583
583
  return match?.[1]?.trim() ?? null;
584
584
  }
585
585
 
586
+ // ../core/src/workflow/loader.ts
587
+ import { createHash } from "crypto";
588
+ import { access, readFile, stat } from "fs/promises";
589
+ import { constants } from "fs";
590
+ var WorkflowConfigStore = class {
591
+ cache = /* @__PURE__ */ new Map();
592
+ async load(workflowPath, env = process.env) {
593
+ await access(workflowPath, constants.R_OK);
594
+ const fileStat = await stat(workflowPath);
595
+ const cached = this.cache.get(workflowPath);
596
+ const markdown = await readFile(workflowPath, "utf8");
597
+ const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}:${createHash("sha256").update(markdown).digest("hex")}`;
598
+ if (cached && cached.fingerprint === fingerprint) {
599
+ return toWorkflowResolution(workflowPath, cached.workflow, {
600
+ isValid: true,
601
+ usedLastKnownGood: false,
602
+ validationError: null
603
+ });
604
+ }
605
+ try {
606
+ const workflow = parseWorkflowMarkdown(markdown, env);
607
+ this.cache.set(workflowPath, {
608
+ fingerprint,
609
+ workflow,
610
+ loadedAt: (/* @__PURE__ */ new Date()).toISOString()
611
+ });
612
+ return toWorkflowResolution(workflowPath, workflow, {
613
+ isValid: true,
614
+ usedLastKnownGood: false,
615
+ validationError: null
616
+ });
617
+ } catch (error) {
618
+ if (cached) {
619
+ return toWorkflowResolution(workflowPath, cached.workflow, {
620
+ isValid: false,
621
+ usedLastKnownGood: true,
622
+ validationError: error instanceof Error ? error.message : "Invalid workflow definition."
623
+ });
624
+ }
625
+ throw error;
626
+ }
627
+ }
628
+ };
629
+ function createDefaultWorkflowResolution() {
630
+ return createInvalidWorkflowResolution(null, "missing_workflow_file");
631
+ }
632
+ function createInvalidWorkflowResolution(workflowPath, validationError) {
633
+ return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
634
+ isValid: false,
635
+ usedLastKnownGood: false,
636
+ validationError
637
+ });
638
+ }
639
+ function toWorkflowResolution(workflowPath, workflow, metadata) {
640
+ return {
641
+ workflowPath,
642
+ workflow,
643
+ lifecycle: workflow.lifecycle,
644
+ promptTemplate: workflow.promptTemplate,
645
+ agentCommand: workflow.agentCommand,
646
+ hookPath: workflow.hookPath ?? "",
647
+ isValid: metadata.isValid,
648
+ usedLastKnownGood: metadata.usedLastKnownGood,
649
+ validationError: metadata.validationError
650
+ };
651
+ }
652
+
586
653
  // ../core/src/workflow/render.ts
587
654
  import {
588
655
  Liquid,
@@ -806,73 +873,6 @@ function isOrchestratorChannelEvent(value) {
806
873
  return false;
807
874
  }
808
875
 
809
- // ../core/src/workflow/loader.ts
810
- import { createHash } from "crypto";
811
- import { access, readFile, stat } from "fs/promises";
812
- import { constants } from "fs";
813
- var WorkflowConfigStore = class {
814
- cache = /* @__PURE__ */ new Map();
815
- async load(workflowPath, env = process.env) {
816
- await access(workflowPath, constants.R_OK);
817
- const fileStat = await stat(workflowPath);
818
- const fingerprint = `${fileStat.mtimeMs}:${fileStat.size}`;
819
- const cached = this.cache.get(workflowPath);
820
- if (cached && cached.fingerprint === fingerprint) {
821
- return toWorkflowResolution(workflowPath, cached.workflow, {
822
- isValid: true,
823
- usedLastKnownGood: false,
824
- validationError: null
825
- });
826
- }
827
- const markdown = await readFile(workflowPath, "utf8");
828
- try {
829
- const workflow = parseWorkflowMarkdown(markdown, env);
830
- this.cache.set(workflowPath, {
831
- fingerprint,
832
- workflow,
833
- loadedAt: (/* @__PURE__ */ new Date()).toISOString()
834
- });
835
- return toWorkflowResolution(workflowPath, workflow, {
836
- isValid: true,
837
- usedLastKnownGood: false,
838
- validationError: null
839
- });
840
- } catch (error) {
841
- if (cached) {
842
- return toWorkflowResolution(workflowPath, cached.workflow, {
843
- isValid: false,
844
- usedLastKnownGood: true,
845
- validationError: error instanceof Error ? error.message : "Invalid workflow definition."
846
- });
847
- }
848
- throw error;
849
- }
850
- }
851
- };
852
- function createDefaultWorkflowResolution() {
853
- return createInvalidWorkflowResolution(null, "missing_workflow_file");
854
- }
855
- function createInvalidWorkflowResolution(workflowPath, validationError) {
856
- return toWorkflowResolution(workflowPath, DEFAULT_WORKFLOW_DEFINITION, {
857
- isValid: false,
858
- usedLastKnownGood: false,
859
- validationError
860
- });
861
- }
862
- function toWorkflowResolution(workflowPath, workflow, metadata) {
863
- return {
864
- workflowPath,
865
- workflow,
866
- lifecycle: workflow.lifecycle,
867
- promptTemplate: workflow.promptTemplate,
868
- agentCommand: workflow.agentCommand,
869
- hookPath: workflow.hookPath ?? "",
870
- isValid: metadata.isValid,
871
- usedLastKnownGood: metadata.usedLastKnownGood,
872
- validationError: metadata.validationError
873
- };
874
- }
875
-
876
876
  // ../core/src/workflow/exit-classification.ts
877
877
  function classifySessionExit(params) {
878
878
  if (params.userInputRequired) {
@@ -917,9 +917,22 @@ var CODEX_ENV_KEYS = [
917
917
  "OPENAI_ORG_ID",
918
918
  "OPENAI_PROJECT"
919
919
  ];
920
+ var AgentRuntimeCredentialError = class extends Error {
921
+ };
920
922
  function extractEnvForCodex(env) {
921
923
  return pickRuntimeEnv(env, CODEX_ENV_KEYS);
922
924
  }
925
+ function extractEnvForClaude(env, envKey = "ANTHROPIC_API_KEY") {
926
+ const apiKey = env[envKey];
927
+ if (!apiKey) {
928
+ throw new AgentRuntimeCredentialError(
929
+ `${envKey} is required in the credential broker response.`
930
+ );
931
+ }
932
+ return {
933
+ [envKey]: apiKey
934
+ };
935
+ }
923
936
  function toAgentCredentialCacheEntry(brokerResponse, now = /* @__PURE__ */ new Date()) {
924
937
  return {
925
938
  env: brokerResponse.env,
@@ -1024,37 +1037,49 @@ import { resolve } from "path";
1024
1037
  // ../core/src/workspace/identity.ts
1025
1038
  import { resolve as resolve2, join } from "path";
1026
1039
  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, "");
1040
+ var RESERVED_WORKSPACE_KEYS = /* @__PURE__ */ new Set([
1041
+ "cache",
1042
+ "issues.json",
1043
+ "project.json",
1044
+ "runs",
1045
+ "status.json"
1046
+ ]);
1047
+ function deriveWorkspaceKey(identifier) {
1048
+ const sanitized = identifier.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
1035
1049
  if (!sanitized || /^[.]+$/.test(sanitized)) {
1036
1050
  return "issue";
1037
1051
  }
1038
1052
  return sanitized;
1039
1053
  }
1040
- function deriveLegacyIssueWorkspaceKey(identity) {
1041
- const input = [
1042
- identity.projectId,
1043
- identity.adapter,
1044
- identity.issueSubjectId
1045
- ].join(":");
1054
+ var deriveIssueWorkspaceKeyFromIdentifier = deriveWorkspaceKey;
1055
+ function deriveIssueWorkspaceKey(identityOrIdentifier, issueIdentifier) {
1056
+ if (typeof identityOrIdentifier === "string") {
1057
+ return deriveWorkspaceKey(identityOrIdentifier);
1058
+ }
1059
+ return deriveWorkspaceKey(
1060
+ issueIdentifier ?? identityOrIdentifier.issueSubjectId
1061
+ );
1062
+ }
1063
+ function deriveLegacyIssueWorkspaceKey(identity, projectId) {
1064
+ const input = [projectId, identity.adapter, identity.issueSubjectId].filter((part) => typeof part === "string").join(":");
1046
1065
  return createHash2("sha256").update(input).digest("hex").slice(0, 16);
1047
1066
  }
1048
- function resolveIssueWorkspaceDirectory(projectDirectory, workspaceKey) {
1049
- const normalizedProjectDirectory = resolve2(projectDirectory);
1050
- const candidate = resolve2(normalizedProjectDirectory, "issues", workspaceKey);
1051
- if (!candidate.startsWith(`${normalizedProjectDirectory}/`)) {
1067
+ function resolveIssueWorkspaceDirectory(runtimeRoot, workspaceKey) {
1068
+ const normalizedRuntimeRoot = resolve2(runtimeRoot);
1069
+ const candidate = resolve2(normalizedRuntimeRoot, workspaceKey);
1070
+ if (!candidate.startsWith(`${normalizedRuntimeRoot}/`)) {
1052
1071
  throw new Error(
1053
- "Issue workspace path escapes the configured project directory."
1072
+ "Issue workspace path escapes the configured runtime root."
1054
1073
  );
1055
1074
  }
1075
+ if (isReservedWorkspaceKey(workspaceKey)) {
1076
+ throw new Error("Issue workspace key is reserved by the runtime layout.");
1077
+ }
1056
1078
  return candidate;
1057
1079
  }
1080
+ function isReservedWorkspaceKey(workspaceKey) {
1081
+ return workspaceKey.startsWith(".") || RESERVED_WORKSPACE_KEYS.has(workspaceKey);
1082
+ }
1058
1083
 
1059
1084
  // ../core/src/workspace/hooks.ts
1060
1085
  import { spawn } from "child_process";
@@ -1206,11 +1231,11 @@ function buildProjectSnapshot(input) {
1206
1231
  allRuns ?? activeRuns
1207
1232
  );
1208
1233
  return {
1209
- projectId: project.projectId,
1210
- slug: project.slug,
1234
+ repository: project.repository,
1211
1235
  tracker: {
1212
1236
  adapter: project.tracker.adapter,
1213
- bindingId: project.tracker.bindingId
1237
+ bindingId: project.tracker.bindingId,
1238
+ settings: project.tracker.settings
1214
1239
  },
1215
1240
  lastTickAt,
1216
1241
  health: lastError ? "degraded" : activeRuns.length > 0 ? "running" : "idle",
@@ -1405,9 +1430,9 @@ function parseRunEventLine(line) {
1405
1430
  }
1406
1431
 
1407
1432
  // ../core/src/observability/status-assembler.ts
1408
- function isMatchingIssueRun(run, projectId, issueId, issueIdentifier) {
1433
+ function isMatchingIssueRun(run, issueId, issueIdentifier) {
1409
1434
  return Boolean(
1410
- run && run.projectId === projectId && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1435
+ run && (run.issueId === issueId || run.issueIdentifier === issueIdentifier)
1411
1436
  );
1412
1437
  }
1413
1438
  function mapIssueOrchestrationStateToStatus(state) {
@@ -1717,6 +1742,77 @@ import { randomUUID } from "crypto";
1717
1742
  import { rm } from "fs/promises";
1718
1743
  import { join as join5 } from "path";
1719
1744
 
1745
+ // ../runtime-claude/src/argv.ts
1746
+ var DEFAULT_CLAUDE_PRINT_ARGS = [
1747
+ "-p",
1748
+ "--output-format",
1749
+ "stream-json",
1750
+ "--input-format",
1751
+ "stream-json",
1752
+ "--include-partial-messages",
1753
+ // Claude stream-json output requires verbose mode when partial message
1754
+ // events are included; keep this even when callers provide custom args.
1755
+ "--verbose",
1756
+ "--permission-mode",
1757
+ "bypassPermissions"
1758
+ ];
1759
+ function buildClaudePrintArgv(options = {}) {
1760
+ const args = options.baseArgs ? withRequiredClaudePrintArgs(options.baseArgs) : [...DEFAULT_CLAUDE_PRINT_ARGS];
1761
+ const { session, isolation, extraArgs } = options;
1762
+ if (session?.mode === "start") {
1763
+ ensureFlagValue(args, "--session-id", session.sessionId);
1764
+ }
1765
+ if (session?.mode === "resume") {
1766
+ ensureFlagValue(args, "--resume", session.sessionId);
1767
+ if (session.forkSession) {
1768
+ ensureFlag(args, "--fork-session");
1769
+ }
1770
+ }
1771
+ if (isolation?.bare) {
1772
+ ensureFlag(args, "--bare");
1773
+ }
1774
+ if (isolation?.strictMcpConfig) {
1775
+ ensureFlag(args, "--strict-mcp-config");
1776
+ if (isolation.mcpConfigPath) {
1777
+ ensureFlagValue(args, "--mcp-config", isolation.mcpConfigPath);
1778
+ }
1779
+ }
1780
+ if (extraArgs?.length) {
1781
+ args.push(...extraArgs);
1782
+ }
1783
+ return args;
1784
+ }
1785
+ function withRequiredClaudePrintArgs(baseArgs) {
1786
+ const args = [...baseArgs];
1787
+ ensureFlag(args, "-p");
1788
+ ensureFlagValue(args, "--output-format", "stream-json");
1789
+ ensureFlagValue(args, "--input-format", "stream-json");
1790
+ ensureFlag(args, "--include-partial-messages");
1791
+ ensureFlag(args, "--verbose");
1792
+ ensureFlagValue(args, "--permission-mode", "bypassPermissions");
1793
+ return args;
1794
+ }
1795
+ function ensureFlag(args, flag) {
1796
+ if (!args.includes(flag)) {
1797
+ args.push(flag);
1798
+ }
1799
+ }
1800
+ function ensureFlagValue(args, flag, value) {
1801
+ const index = args.indexOf(flag);
1802
+ if (index === -1) {
1803
+ args.push(flag, value);
1804
+ return;
1805
+ }
1806
+ const existingValue = args[index + 1];
1807
+ if (existingValue?.startsWith("-")) {
1808
+ args.splice(index + 1, 0, value);
1809
+ return;
1810
+ }
1811
+ if (existingValue !== value) {
1812
+ args.splice(index + 1, existingValue === void 0 ? 0 : 1, value);
1813
+ }
1814
+ }
1815
+
1720
1816
  // ../runtime-claude/src/mcp-compose.ts
1721
1817
  import { mkdir, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1722
1818
  import { basename, dirname, join as join3, resolve as resolve4 } from "path";
@@ -2006,13 +2102,1111 @@ function createGitHubGraphQLMcpServerEntry(options = {}) {
2006
2102
  };
2007
2103
  }
2008
2104
 
2105
+ // ../runtime-claude/src/mcp-compose.ts
2106
+ async function composeClaudeMcpConfig(workspaceRoot, strictMode, symphonyTokenEnv = {}) {
2107
+ const workspaceMcpPath = join3(workspaceRoot, ".mcp.json");
2108
+ const finalPath = strictMode ? resolveStrictMcpConfigPath(workspaceRoot, symphonyTokenEnv) : workspaceMcpPath;
2109
+ const baseConfig = await readBaseMcpConfig(workspaceMcpPath);
2110
+ const mergedConfig = mergeGitHubGraphQLMcpServer(baseConfig, symphonyTokenEnv);
2111
+ await mkdir(dirname(finalPath), { recursive: true });
2112
+ await writeFile3(finalPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf8");
2113
+ return {
2114
+ finalPath,
2115
+ extraArgv: strictMode ? ["--strict-mcp-config", "--mcp-config", finalPath] : [],
2116
+ ...strictMode ? { cleanupPath: finalPath } : {}
2117
+ };
2118
+ }
2119
+ async function readBaseMcpConfig(workspaceMcpPath) {
2120
+ try {
2121
+ const raw = await readFile6(workspaceMcpPath, "utf8");
2122
+ const parsed = JSON.parse(raw);
2123
+ return isRecord3(parsed) ? parsed : { mcpServers: {} };
2124
+ } catch (error) {
2125
+ if (isNodeError(error) && error.code === "ENOENT") {
2126
+ return { mcpServers: {} };
2127
+ }
2128
+ throw error;
2129
+ }
2130
+ }
2131
+ function mergeGitHubGraphQLMcpServer(baseConfig, env) {
2132
+ const mcpServers = isRecord3(baseConfig.mcpServers) ? baseConfig.mcpServers : {};
2133
+ return {
2134
+ ...baseConfig,
2135
+ mcpServers: {
2136
+ ...mcpServers,
2137
+ github_graphql: createGitHubGraphQLMcpServerEntry({
2138
+ githubToken: env.GITHUB_GRAPHQL_TOKEN,
2139
+ githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
2140
+ githubTokenBrokerUrl: env.GITHUB_TOKEN_BROKER_URL,
2141
+ githubTokenBrokerSecret: env.GITHUB_TOKEN_BROKER_SECRET,
2142
+ githubTokenCachePath: env.GITHUB_TOKEN_CACHE_PATH,
2143
+ githubProjectId: env.GITHUB_PROJECT_ID
2144
+ })
2145
+ }
2146
+ };
2147
+ }
2148
+ function resolveStrictMcpConfigPath(workspaceRoot, env) {
2149
+ const normalizedWorkspaceRoot = resolve4(workspaceRoot);
2150
+ const runtimeDir = env.WORKSPACE_RUNTIME_DIR ?? join3(normalizedWorkspaceRoot, ".runtime", basename(normalizedWorkspaceRoot));
2151
+ return join3(runtimeDir, "mcp.json");
2152
+ }
2153
+ function isRecord3(value) {
2154
+ return value != null && typeof value === "object" && !Array.isArray(value);
2155
+ }
2156
+ function isNodeError(error) {
2157
+ return error instanceof Error && "code" in error;
2158
+ }
2159
+
2009
2160
  // ../runtime-claude/src/spawn.ts
2010
2161
  import { spawn as spawn2 } from "child_process";
2011
2162
  import { finished } from "stream/promises";
2012
2163
 
2164
+ // ../runtime-claude/src/internal.ts
2165
+ function asRecord(value) {
2166
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2167
+ }
2168
+ function getString(value) {
2169
+ if (typeof value === "string") {
2170
+ return value;
2171
+ }
2172
+ if (typeof value === "number") {
2173
+ return String(value);
2174
+ }
2175
+ return void 0;
2176
+ }
2177
+
2178
+ // ../runtime-claude/src/events.ts
2179
+ var CLAUDE_OBSERVABILITY_PREFIX = "claude-print/";
2180
+ function parseClaudePrintNdjsonLine(line) {
2181
+ const trimmedLine = line.trim();
2182
+ if (!trimmedLine) {
2183
+ return null;
2184
+ }
2185
+ try {
2186
+ const parsed = JSON.parse(trimmedLine);
2187
+ if (!asRecord(parsed)) {
2188
+ return {
2189
+ line: trimmedLine,
2190
+ parseError: "Claude stream-json line is not a JSON object."
2191
+ };
2192
+ }
2193
+ return {
2194
+ line: trimmedLine,
2195
+ message: parsed
2196
+ };
2197
+ } catch (error) {
2198
+ return {
2199
+ line: trimmedLine,
2200
+ parseError: error instanceof Error ? error.message : "Unknown JSON parse error."
2201
+ };
2202
+ }
2203
+ }
2204
+ var ClaudePrintEventMapper = class {
2205
+ constructor(options = {}) {
2206
+ this.options = options;
2207
+ }
2208
+ hasStartedTurn = false;
2209
+ latestResultEvent = null;
2210
+ // Claude -p stream-json does not define an ordered collection of error
2211
+ // records for one turn; keep the terminal/latest error for exit classification.
2212
+ latestErrorEvent = null;
2213
+ sawRateLimit = false;
2214
+ mapLine(line) {
2215
+ const record = parseClaudePrintNdjsonLine(line);
2216
+ if (!record?.message) {
2217
+ return [];
2218
+ }
2219
+ return this.mapMessage(record.message);
2220
+ }
2221
+ mapMessage(message) {
2222
+ const type = getEventType(message);
2223
+ const events = [];
2224
+ if (type === "message_start") {
2225
+ events.push(this.buildTurnStartedEvent(message, type));
2226
+ this.hasStartedTurn = true;
2227
+ return events;
2228
+ }
2229
+ if (type === "content_block_start" || type === "tool_use") {
2230
+ if (!this.hasStartedTurn) {
2231
+ events.push(this.buildTurnStartedEvent(message, type));
2232
+ this.hasStartedTurn = true;
2233
+ }
2234
+ const toolUseEvent = mapToolUseEvent(message, this.options);
2235
+ if (toolUseEvent) {
2236
+ events.push(toolUseEvent);
2237
+ }
2238
+ }
2239
+ if (type === "content_block_delta") {
2240
+ if (!this.hasStartedTurn) {
2241
+ events.push(this.buildTurnStartedEvent(message, type));
2242
+ this.hasStartedTurn = true;
2243
+ }
2244
+ events.push({
2245
+ name: "agent.messageDelta",
2246
+ payload: {
2247
+ observabilityEvent: observabilityEventName(type),
2248
+ params: message,
2249
+ delta: extractDeltaText(message),
2250
+ itemId: extractItemId(message)
2251
+ }
2252
+ });
2253
+ }
2254
+ if (type === "result") {
2255
+ this.latestResultEvent = message;
2256
+ const rateLimit = extractRateLimit(message);
2257
+ if (rateLimit) {
2258
+ this.sawRateLimit = true;
2259
+ events.push({
2260
+ name: "agent.rateLimit",
2261
+ payload: {
2262
+ observabilityEvent: observabilityEventName(type),
2263
+ params: {
2264
+ source: "claude",
2265
+ rate_limit: rateLimit,
2266
+ usage: asRecord(message.usage),
2267
+ result: message
2268
+ }
2269
+ }
2270
+ });
2271
+ }
2272
+ if (isClaudeResultError(message)) {
2273
+ events.push(buildClaudeErrorEvent(message, type));
2274
+ } else {
2275
+ events.push({
2276
+ name: "agent.turnCompleted",
2277
+ payload: {
2278
+ observabilityEvent: observabilityEventName(type),
2279
+ params: message,
2280
+ inputRequired: false
2281
+ }
2282
+ });
2283
+ }
2284
+ }
2285
+ if (type === "error") {
2286
+ this.latestErrorEvent = message;
2287
+ events.push(buildClaudeErrorEvent(message, type));
2288
+ }
2289
+ return events;
2290
+ }
2291
+ snapshot() {
2292
+ return {
2293
+ hasStartedTurn: this.hasStartedTurn,
2294
+ latestResultEvent: this.latestResultEvent,
2295
+ latestErrorEvent: this.latestErrorEvent,
2296
+ sawRateLimit: this.sawRateLimit
2297
+ };
2298
+ }
2299
+ buildTurnStartedEvent(message, type) {
2300
+ return {
2301
+ name: "agent.turnStarted",
2302
+ payload: {
2303
+ observabilityEvent: observabilityEventName(type),
2304
+ params: message
2305
+ }
2306
+ };
2307
+ }
2308
+ };
2309
+ function isClaudeResultError(message) {
2310
+ const subtype = getString(message.subtype);
2311
+ const stopReason = getString(message.stop_reason);
2312
+ return message.is_error === true || subtype !== void 0 && subtype.startsWith("error") || stopReason !== void 0 && stopReason.startsWith("error");
2313
+ }
2314
+ function extractRateLimit(message) {
2315
+ const usage = asRecord(message.usage);
2316
+ const rateLimit = usage ? asRecord(usage.rate_limit) : null;
2317
+ if (rateLimit) {
2318
+ return rateLimit;
2319
+ }
2320
+ return asRecord(message.rate_limit);
2321
+ }
2322
+ function getClaudeResultStatus(message) {
2323
+ if (!message) {
2324
+ return void 0;
2325
+ }
2326
+ return getString(message.subtype) ?? getString(message.stop_reason);
2327
+ }
2328
+ function mapToolUseEvent(message, options) {
2329
+ const type = getEventType(message);
2330
+ const contentBlock = asRecord(message.content_block);
2331
+ const toolUse = type === "tool_use" ? message : contentBlock && getString(contentBlock.type) === "tool_use" ? contentBlock : null;
2332
+ if (!toolUse) {
2333
+ return null;
2334
+ }
2335
+ const input = toolUse.input !== void 0 ? toolUse.input : toolUse.arguments;
2336
+ return {
2337
+ name: "agent.toolCallRequested",
2338
+ payload: {
2339
+ observabilityEvent: observabilityEventName(type),
2340
+ params: message,
2341
+ callId: getString(toolUse.id) ?? "",
2342
+ toolName: getString(toolUse.name) ?? "",
2343
+ threadId: options.threadId ?? getString(message.thread_id),
2344
+ turnId: options.turnId ?? getString(message.turn_id),
2345
+ arguments: input
2346
+ }
2347
+ };
2348
+ }
2349
+ function buildClaudeErrorEvent(message, type) {
2350
+ return {
2351
+ name: "agent.error",
2352
+ payload: {
2353
+ observabilityEvent: observabilityEventName(type),
2354
+ params: message,
2355
+ error: describeClaudeError(message)
2356
+ }
2357
+ };
2358
+ }
2359
+ function describeClaudeError(message) {
2360
+ const error = asRecord(message.error);
2361
+ return getString(error?.message) ?? getString(error?.type) ?? getString(message.message) ?? getString(message.subtype) ?? getString(message.stop_reason) ?? JSON.stringify(message);
2362
+ }
2363
+ function extractDeltaText(message) {
2364
+ const delta = asRecord(message.delta);
2365
+ return getString(delta?.text) ?? getString(delta?.partial_json) ?? getString(message.text) ?? "";
2366
+ }
2367
+ function extractItemId(message) {
2368
+ return getString(message.item_id) ?? getString(message.content_block_id) ?? getString(message.index) ?? "";
2369
+ }
2370
+ function getEventType(message) {
2371
+ return getString(message.type) ?? "";
2372
+ }
2373
+ function observabilityEventName(type) {
2374
+ return `${CLAUDE_OBSERVABILITY_PREFIX}${type || "unknown"}`;
2375
+ }
2376
+
2377
+ // ../runtime-claude/src/exit-classifier.ts
2378
+ var TRANSIENT_ERROR_PATTERNS = [
2379
+ /rate.?limit/i,
2380
+ /\b429\b/,
2381
+ /timeout/i,
2382
+ /timed?.?out/i,
2383
+ /temporar/i,
2384
+ /overload/i,
2385
+ /unavailable/i,
2386
+ /ECONNRESET/,
2387
+ /ETIMEDOUT/,
2388
+ /EAI_AGAIN/
2389
+ ];
2390
+ function classifyClaudeTurnExit(input) {
2391
+ const resultStatus = getClaudeResultStatus(input.resultEvent);
2392
+ if (input.exitCode === 0 && input.resultEvent && !isClaudeResultError(input.resultEvent)) {
2393
+ return {
2394
+ kind: "success",
2395
+ transient: false,
2396
+ reason: "result_success",
2397
+ resultStatus
2398
+ };
2399
+ }
2400
+ if (input.exitCode === 0 && !input.resultEvent) {
2401
+ return {
2402
+ kind: "app-error",
2403
+ transient: false,
2404
+ reason: "missing_result",
2405
+ resultStatus
2406
+ };
2407
+ }
2408
+ if (input.exitCode === 0 && input.resultEvent && isClaudeResultError(input.resultEvent)) {
2409
+ return {
2410
+ kind: "app-error",
2411
+ transient: isTransientClaudeFailure(input),
2412
+ reason: resultStatus ?? "result_error",
2413
+ resultStatus
2414
+ };
2415
+ }
2416
+ return {
2417
+ kind: "process-error",
2418
+ transient: isTransientClaudeFailure(input),
2419
+ reason: describeProcessFailure(input),
2420
+ resultStatus
2421
+ };
2422
+ }
2423
+ function isTransientClaudeFailure(input) {
2424
+ if (input.sawRateLimit || input.resultEvent && (extractRateLimit(input.resultEvent) !== null || getClaudeResultStatus(input.resultEvent) === "error_rate_limit")) {
2425
+ return true;
2426
+ }
2427
+ if (input.signal === "SIGTERM") {
2428
+ return true;
2429
+ }
2430
+ const text = [
2431
+ input.spawnErrorMessage,
2432
+ extractFailureMessage(input.errorEvent),
2433
+ extractFailureMessage(input.resultEvent)
2434
+ ].join("\n");
2435
+ return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(text));
2436
+ }
2437
+ function describeProcessFailure(input) {
2438
+ if (input.signal) {
2439
+ return `signal_${input.signal}`;
2440
+ }
2441
+ if (input.spawnErrorMessage) {
2442
+ return input.spawnErrorMessage;
2443
+ }
2444
+ if (typeof input.exitCode === "number") {
2445
+ return `exit_${input.exitCode}`;
2446
+ }
2447
+ return "process_error";
2448
+ }
2449
+ function extractFailureMessage(event) {
2450
+ if (!event) {
2451
+ return "";
2452
+ }
2453
+ const error = asRecord(event.error);
2454
+ return [
2455
+ getString(error?.message),
2456
+ getString(error?.type),
2457
+ getString(event.message)
2458
+ ].filter((value) => value !== void 0).join("\n");
2459
+ }
2460
+
2461
+ // ../runtime-claude/src/spawn.ts
2462
+ async function spawnClaudeTurn(input, dependencies = {}) {
2463
+ const command = input.command ?? "claude";
2464
+ const child = (dependencies.spawnImpl ?? spawn2)(command, input.args, {
2465
+ cwd: input.cwd,
2466
+ env: input.env,
2467
+ stdio: "pipe"
2468
+ });
2469
+ dependencies.onSpawned?.(child);
2470
+ const records = [];
2471
+ const eventMapper = new ClaudePrintEventMapper();
2472
+ let emittedErrorEvent = false;
2473
+ const emitEvent = (event) => {
2474
+ if (event.name === "agent.error") {
2475
+ emittedErrorEvent = true;
2476
+ }
2477
+ dependencies.onEvent?.(event);
2478
+ };
2479
+ const stdoutDone = collectNdjsonStream(
2480
+ child.stdout,
2481
+ "stdout",
2482
+ records,
2483
+ eventMapper,
2484
+ emitEvent
2485
+ );
2486
+ const stderrDone = collectNdjsonStream(
2487
+ child.stderr,
2488
+ "stderr",
2489
+ records,
2490
+ null,
2491
+ null
2492
+ );
2493
+ const exitDone = waitForChildExit(child, records);
2494
+ const stdinMessages = Array.isArray(input.stdinMessages) ? input.stdinMessages : [input.stdinMessages];
2495
+ for (const message of stdinMessages) {
2496
+ const didWrite = await writeToStdin(
2497
+ child.stdin,
2498
+ `${JSON.stringify(message)}
2499
+ `
2500
+ );
2501
+ if (!didWrite) {
2502
+ break;
2503
+ }
2504
+ }
2505
+ if (child.stdin && !child.stdin.destroyed && !child.stdin.writableEnded && !child.stdin.writableFinished) {
2506
+ child.stdin.end();
2507
+ }
2508
+ const outcome = await exitDone;
2509
+ await Promise.all([stdoutDone, stderrDone]);
2510
+ const mapperState = eventMapper.snapshot();
2511
+ const classification = classifyClaudeTurnExit({
2512
+ exitCode: outcome.exitCode,
2513
+ signal: outcome.signal,
2514
+ resultEvent: mapperState.latestResultEvent,
2515
+ errorEvent: mapperState.latestErrorEvent,
2516
+ sawRateLimit: mapperState.sawRateLimit,
2517
+ spawnErrorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2518
+ });
2519
+ if ((classification.kind === "app-error" || classification.kind === "process-error") && !emittedErrorEvent) {
2520
+ emitEvent({
2521
+ name: "agent.error",
2522
+ payload: {
2523
+ observabilityEvent: classification.kind === "app-error" ? "claude-print/app-error" : "claude-print/process-exit",
2524
+ params: {
2525
+ exitCode: outcome.exitCode,
2526
+ signal: outcome.signal,
2527
+ classification,
2528
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2529
+ },
2530
+ error: classification.reason
2531
+ }
2532
+ });
2533
+ }
2534
+ return {
2535
+ command,
2536
+ args: [...input.args],
2537
+ cwd: input.cwd,
2538
+ records,
2539
+ exitCode: outcome.exitCode,
2540
+ signal: outcome.signal,
2541
+ result: classification.kind,
2542
+ classification,
2543
+ errorMessage: "errorMessage" in outcome ? outcome.errorMessage : void 0
2544
+ };
2545
+ }
2546
+ async function collectNdjsonStream(stream, channel, records, eventMapper, onEvent) {
2547
+ if (!stream) {
2548
+ return;
2549
+ }
2550
+ let buffer = "";
2551
+ stream.setEncoding("utf8");
2552
+ stream.on("data", (chunk) => {
2553
+ buffer += chunk;
2554
+ while (true) {
2555
+ const newlineIndex = buffer.indexOf("\n");
2556
+ if (newlineIndex === -1) {
2557
+ break;
2558
+ }
2559
+ const line = buffer.slice(0, newlineIndex).trim();
2560
+ buffer = buffer.slice(newlineIndex + 1);
2561
+ if (line.length === 0) {
2562
+ continue;
2563
+ }
2564
+ records.push(parseClaudeRecord(channel, line, eventMapper, onEvent));
2565
+ }
2566
+ });
2567
+ try {
2568
+ await finished(stream);
2569
+ } catch (error) {
2570
+ records.push({
2571
+ stream: channel,
2572
+ line: "",
2573
+ parseError: error instanceof Error ? error.message : "Unknown stream error."
2574
+ });
2575
+ }
2576
+ const trailingLine = buffer.trim();
2577
+ if (trailingLine.length > 0) {
2578
+ records.push(
2579
+ parseClaudeRecord(channel, trailingLine, eventMapper, onEvent)
2580
+ );
2581
+ }
2582
+ }
2583
+ function parseClaudeRecord(stream, line, eventMapper, onEvent) {
2584
+ const record = parseClaudePrintNdjsonLine(line);
2585
+ if (record.message) {
2586
+ if (!eventMapper) {
2587
+ return {
2588
+ stream,
2589
+ line: record.line,
2590
+ message: record.message
2591
+ };
2592
+ }
2593
+ for (const event of eventMapper.mapMessage(record.message)) {
2594
+ onEvent?.(event);
2595
+ }
2596
+ return {
2597
+ stream,
2598
+ line: record.line,
2599
+ message: record.message
2600
+ };
2601
+ }
2602
+ return {
2603
+ stream,
2604
+ line: record.line,
2605
+ parseError: record.parseError
2606
+ };
2607
+ }
2608
+ async function writeToStdin(stream, line) {
2609
+ if (!stream || stream.destroyed || stream.writableEnded) {
2610
+ return false;
2611
+ }
2612
+ if (stream.write(line)) {
2613
+ return true;
2614
+ }
2615
+ return waitForDrainOrClosure(stream);
2616
+ }
2617
+ function waitForDrainOrClosure(stream) {
2618
+ return new Promise((resolve5) => {
2619
+ const cleanup = () => {
2620
+ stream.removeListener("drain", handleDrain);
2621
+ stream.removeListener("close", handleClose);
2622
+ stream.removeListener("finish", handleFinish);
2623
+ stream.removeListener("error", handleError);
2624
+ };
2625
+ const handleDrain = () => {
2626
+ cleanup();
2627
+ resolve5(true);
2628
+ };
2629
+ const handleClose = () => {
2630
+ cleanup();
2631
+ resolve5(false);
2632
+ };
2633
+ const handleFinish = () => {
2634
+ cleanup();
2635
+ resolve5(false);
2636
+ };
2637
+ const handleError = () => {
2638
+ cleanup();
2639
+ resolve5(false);
2640
+ };
2641
+ stream.once("drain", handleDrain);
2642
+ stream.once("close", handleClose);
2643
+ stream.once("finish", handleFinish);
2644
+ stream.once("error", handleError);
2645
+ });
2646
+ }
2647
+ function waitForChildExit(child, records) {
2648
+ return new Promise((resolve5) => {
2649
+ const handleClose = (exitCode, signal) => {
2650
+ cleanup();
2651
+ resolve5({ exitCode, signal });
2652
+ };
2653
+ const handleError = (error) => {
2654
+ cleanup();
2655
+ records.push({
2656
+ stream: "stderr",
2657
+ line: "",
2658
+ parseError: error.message
2659
+ });
2660
+ resolve5({
2661
+ exitCode: null,
2662
+ signal: null,
2663
+ errorMessage: error.message
2664
+ });
2665
+ };
2666
+ const cleanup = () => {
2667
+ child.removeListener("close", handleClose);
2668
+ child.removeListener("error", handleError);
2669
+ };
2670
+ child.on("close", handleClose);
2671
+ child.on("error", handleError);
2672
+ });
2673
+ }
2674
+
2013
2675
  // ../runtime-claude/src/session-store.ts
2014
2676
  import { mkdir as mkdir2, readFile as readFile7, rename, writeFile as writeFile4 } from "fs/promises";
2015
2677
  import { dirname as dirname2, join as join4 } from "path";
2678
+ var CLAUDE_SESSION_PROTOCOL = "claude-print";
2679
+ var CLAUDE_SESSION_FILENAME = "claude-session.json";
2680
+ var ClaudeSessionStore = class {
2681
+ constructor(options) {
2682
+ this.options = options;
2683
+ }
2684
+ sessionFilePath(options) {
2685
+ return join4(
2686
+ options.runDirectory ?? this.runDirectory(options.runId),
2687
+ CLAUDE_SESSION_FILENAME
2688
+ );
2689
+ }
2690
+ async load(options) {
2691
+ let raw;
2692
+ try {
2693
+ raw = await readFile7(this.sessionFilePath(options), "utf8");
2694
+ } catch (error) {
2695
+ if (isFileNotFoundError(error)) {
2696
+ return null;
2697
+ }
2698
+ throw error;
2699
+ }
2700
+ return parseClaudeSessionFile(JSON.parse(raw));
2701
+ }
2702
+ async save(options) {
2703
+ const session = {
2704
+ protocol: CLAUDE_SESSION_PROTOCOL,
2705
+ sessionId: options.sessionId,
2706
+ createdAt: options.createdAt,
2707
+ protocolState: options.protocolState ?? {}
2708
+ };
2709
+ if (options.parentRunId) {
2710
+ session.parentRunId = options.parentRunId;
2711
+ }
2712
+ const path = this.sessionFilePath(options);
2713
+ await mkdir2(dirname2(path), { recursive: true });
2714
+ await writeFile4(`${path}.tmp`, `${JSON.stringify(session, null, 2)}
2715
+ `, "utf8");
2716
+ await rename(`${path}.tmp`, path);
2717
+ return session;
2718
+ }
2719
+ runDirectory(runId) {
2720
+ return join4(this.options.runtimeRoot, "runs", runId);
2721
+ }
2722
+ };
2723
+ function parseClaudeSessionFile(value) {
2724
+ if (!isRecord4(value)) {
2725
+ throw new Error("Claude session file must be a JSON object.");
2726
+ }
2727
+ if (value.protocol !== CLAUDE_SESSION_PROTOCOL) {
2728
+ throw new Error(
2729
+ `Claude session file protocol must be ${CLAUDE_SESSION_PROTOCOL}.`
2730
+ );
2731
+ }
2732
+ if (typeof value.sessionId !== "string" || value.sessionId.length === 0) {
2733
+ throw new Error("Claude session file sessionId must be a non-empty string.");
2734
+ }
2735
+ if (typeof value.createdAt !== "string" || value.createdAt.length === 0) {
2736
+ throw new Error("Claude session file createdAt must be a non-empty string.");
2737
+ }
2738
+ if ("parentRunId" in value && value.parentRunId !== void 0 && typeof value.parentRunId !== "string") {
2739
+ throw new Error("Claude session file parentRunId must be a string.");
2740
+ }
2741
+ if ("protocolState" in value && value.protocolState !== void 0 && !isRecord4(value.protocolState)) {
2742
+ throw new Error("Claude session file protocolState must be an object.");
2743
+ }
2744
+ return {
2745
+ protocol: CLAUDE_SESSION_PROTOCOL,
2746
+ sessionId: value.sessionId,
2747
+ createdAt: value.createdAt,
2748
+ parentRunId: typeof value.parentRunId === "string" ? value.parentRunId : void 0,
2749
+ protocolState: isRecord4(value.protocolState) ? value.protocolState : {}
2750
+ };
2751
+ }
2752
+ function isRecord4(value) {
2753
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2754
+ }
2755
+ function isFileNotFoundError(error) {
2756
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
2757
+ }
2758
+
2759
+ // ../runtime-claude/src/adapter.ts
2760
+ var ClaudePrintRuntimeAdapter = class {
2761
+ constructor(config, dependencies = {}) {
2762
+ this.config = config;
2763
+ this.dependencies = dependencies;
2764
+ this.sessionStore = new ClaudeSessionStore({
2765
+ runtimeRoot: config.runtimeRoot ?? join5(config.workingDirectory, ".runtime", "orchestrator")
2766
+ });
2767
+ }
2768
+ activeChild = null;
2769
+ preparedMcpConfig = null;
2770
+ preparedSession = null;
2771
+ eventHandlers = /* @__PURE__ */ new Set();
2772
+ pendingEvents = [];
2773
+ sessionStore;
2774
+ async prepare(context) {
2775
+ await this.cleanupPreparedMcpConfig();
2776
+ this.pendingEvents.length = 0;
2777
+ this.preparedSession = await this.prepareSession(context);
2778
+ this.preparedMcpConfig = await composeClaudeMcpConfig(
2779
+ this.config.workingDirectory,
2780
+ this.config.isolation?.strictMcpConfig === true,
2781
+ buildClaudeMcpTokenEnvironment({
2782
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
2783
+ configEnv: this.config.env,
2784
+ runtimeDirectory: this.config.runtimeDirectory
2785
+ })
2786
+ );
2787
+ }
2788
+ async spawnTurn(input) {
2789
+ if (this.activeChild) {
2790
+ throw new Error(
2791
+ "TODO(#8): Claude print runtime adapter supports only one in-flight turn."
2792
+ );
2793
+ }
2794
+ const session = input.session ?? this.preparedSession?.session;
2795
+ const argv = buildClaudePrintArgv(this.buildArgvOptions(input, session));
2796
+ try {
2797
+ const result = await this.spawnWithArgv(input, argv);
2798
+ if (this.shouldInvalidatePreparedResume(session, result)) {
2799
+ return await this.retryWithFreshSession(input, result);
2800
+ }
2801
+ await this.persistStartedSessionId(result);
2802
+ await this.persistForkedSessionId(result);
2803
+ return result;
2804
+ } finally {
2805
+ this.activeChild = null;
2806
+ }
2807
+ }
2808
+ onEvent(handler) {
2809
+ this.eventHandlers.add(handler);
2810
+ for (const event of this.pendingEvents) {
2811
+ handler(event);
2812
+ }
2813
+ return () => {
2814
+ this.eventHandlers.delete(handler);
2815
+ };
2816
+ }
2817
+ resolveCredentials(brokerResponse) {
2818
+ return extractEnvForClaude(brokerResponse.env, this.config.authEnvKey);
2819
+ }
2820
+ async shutdown() {
2821
+ this.stopActiveChild();
2822
+ await this.cleanupPreparedMcpConfig();
2823
+ }
2824
+ async cancel(_reason) {
2825
+ this.stopActiveChild();
2826
+ await this.cleanupPreparedMcpConfig();
2827
+ }
2828
+ buildArgvOptions(input, session) {
2829
+ const isolation = {
2830
+ ...this.config.isolation,
2831
+ ...input.isolation
2832
+ };
2833
+ const configuredExtraArgs = input.extraArgs ?? this.config.extraArgs ?? [];
2834
+ if (this.preparedMcpConfig) {
2835
+ return {
2836
+ baseArgs: this.config.args,
2837
+ session,
2838
+ // prepare() owns MCP argv injection through extraArgv; suppress the
2839
+ // isolation flag here so buildClaudePrintArgv does not add it twice.
2840
+ // Any input mcpConfigPath is intentionally ignored while a prepared
2841
+ // composition result is active.
2842
+ isolation: {
2843
+ ...isolation,
2844
+ strictMcpConfig: false,
2845
+ mcpConfigPath: void 0
2846
+ },
2847
+ extraArgs: [
2848
+ ...this.preparedMcpConfig.extraArgv,
2849
+ ...configuredExtraArgs
2850
+ ]
2851
+ };
2852
+ }
2853
+ if (isolation.strictMcpConfig && !isolation.mcpConfigPath) {
2854
+ throw new Error(
2855
+ "Claude strict MCP config requires prepare() or an explicit mcpConfigPath."
2856
+ );
2857
+ }
2858
+ return {
2859
+ baseArgs: this.config.args,
2860
+ session,
2861
+ isolation,
2862
+ extraArgs: configuredExtraArgs
2863
+ };
2864
+ }
2865
+ async prepareSession(context) {
2866
+ const currentOptions = {
2867
+ runId: context.runId,
2868
+ runDirectory: context.runDirectory
2869
+ };
2870
+ const parentRunId = context.previousRunId;
2871
+ try {
2872
+ const current = await this.sessionStore.load(currentOptions);
2873
+ if (current) {
2874
+ return {
2875
+ runId: context.runId,
2876
+ runDirectory: context.runDirectory,
2877
+ sessionFile: current,
2878
+ session: {
2879
+ mode: "resume",
2880
+ sessionId: current.sessionId
2881
+ }
2882
+ };
2883
+ }
2884
+ } catch (error) {
2885
+ return await this.createFreshSession(context, {
2886
+ reason: `session file could not be read or parsed: ${formatErrorMessage(error)}`,
2887
+ invalidatedSessionId: "unknown",
2888
+ parentRunId
2889
+ });
2890
+ }
2891
+ if (context.previousRunId) {
2892
+ try {
2893
+ const previous = await this.sessionStore.load({
2894
+ runId: context.previousRunId,
2895
+ runDirectory: context.previousRunDirectory
2896
+ });
2897
+ if (previous) {
2898
+ const sessionFile = await this.sessionStore.save({
2899
+ ...currentOptions,
2900
+ sessionId: previous.sessionId,
2901
+ createdAt: this.nowIso(),
2902
+ parentRunId: context.previousRunId
2903
+ });
2904
+ return {
2905
+ runId: context.runId,
2906
+ runDirectory: context.runDirectory,
2907
+ sessionFile,
2908
+ session: {
2909
+ mode: "resume",
2910
+ sessionId: previous.sessionId,
2911
+ forkSession: true
2912
+ }
2913
+ };
2914
+ }
2915
+ } catch (error) {
2916
+ return await this.createFreshSession(context, {
2917
+ reason: `parent session file could not be read or parsed: ${formatErrorMessage(error)}`,
2918
+ invalidatedSessionId: "unknown",
2919
+ parentRunId
2920
+ });
2921
+ }
2922
+ }
2923
+ return await this.createFreshSession(context, { parentRunId });
2924
+ }
2925
+ async createFreshSession(context, options = {}) {
2926
+ const replacementSessionId = this.createSessionId();
2927
+ const sessionFile = await this.sessionStore.save({
2928
+ runId: context.runId,
2929
+ runDirectory: context.runDirectory,
2930
+ sessionId: replacementSessionId,
2931
+ createdAt: this.nowIso(),
2932
+ parentRunId: options.parentRunId
2933
+ });
2934
+ if (options.reason) {
2935
+ this.emitSessionInvalidated({
2936
+ runId: context.runId,
2937
+ sessionId: options.invalidatedSessionId ?? "unknown",
2938
+ replacementSessionId,
2939
+ reason: options.reason
2940
+ });
2941
+ }
2942
+ return {
2943
+ runId: context.runId,
2944
+ runDirectory: context.runDirectory,
2945
+ sessionFile,
2946
+ session: {
2947
+ mode: "start",
2948
+ sessionId: replacementSessionId
2949
+ }
2950
+ };
2951
+ }
2952
+ async retryWithFreshSession(input, failedResult) {
2953
+ if (!this.preparedSession) {
2954
+ return failedResult;
2955
+ }
2956
+ const invalidatedSessionId = this.preparedSession.session.sessionId;
2957
+ const replacementSessionId = this.createSessionId();
2958
+ const parentRunId = this.preparedSession.sessionFile.parentRunId;
2959
+ const sessionFile = await this.sessionStore.save({
2960
+ runId: this.preparedSession.runId,
2961
+ runDirectory: this.preparedSession.runDirectory,
2962
+ sessionId: replacementSessionId,
2963
+ createdAt: this.nowIso(),
2964
+ parentRunId
2965
+ });
2966
+ this.preparedSession = {
2967
+ ...this.preparedSession,
2968
+ sessionFile,
2969
+ session: {
2970
+ mode: "start",
2971
+ sessionId: replacementSessionId
2972
+ }
2973
+ };
2974
+ this.emitSessionInvalidated({
2975
+ runId: this.preparedSession.runId,
2976
+ sessionId: invalidatedSessionId,
2977
+ replacementSessionId,
2978
+ reason: "claude resume session was rejected with a 4xx response"
2979
+ });
2980
+ const retryArgv = buildClaudePrintArgv(
2981
+ this.buildArgvOptions(input, this.preparedSession.session)
2982
+ );
2983
+ const retryResult = await this.spawnWithArgv(input, retryArgv);
2984
+ await this.persistStartedSessionId(retryResult);
2985
+ return retryResult;
2986
+ }
2987
+ async spawnWithArgv(input, argv) {
2988
+ return await spawnClaudeTurn(
2989
+ {
2990
+ command: input.command ?? this.config.command,
2991
+ args: argv,
2992
+ cwd: input.cwd ?? this.config.workingDirectory,
2993
+ env: buildClaudeSpawnEnv({
2994
+ inheritProcessEnv: this.config.inheritProcessEnv === true,
2995
+ configEnv: this.config.env,
2996
+ inputEnv: input.env
2997
+ }),
2998
+ stdinMessages: input.messages
2999
+ },
3000
+ {
3001
+ ...this.dependencies,
3002
+ onSpawned: (child) => {
3003
+ this.activeChild = child;
3004
+ this.dependencies.onSpawned?.(child);
3005
+ },
3006
+ onEvent: (event) => {
3007
+ this.emitEvent(event);
3008
+ try {
3009
+ this.dependencies.onEvent?.(event);
3010
+ } catch {
3011
+ }
3012
+ }
3013
+ }
3014
+ );
3015
+ }
3016
+ async persistForkedSessionId(result) {
3017
+ if (this.preparedSession?.session.mode !== "resume" || !this.preparedSession.session.forkSession) {
3018
+ return;
3019
+ }
3020
+ const forkedSessionId = findSessionIdInResult(result);
3021
+ const sessionId = forkedSessionId ?? this.preparedSession.session.sessionId;
3022
+ this.preparedSession = {
3023
+ ...this.preparedSession,
3024
+ sessionFile: await this.sessionStore.save({
3025
+ runId: this.preparedSession.runId,
3026
+ runDirectory: this.preparedSession.runDirectory,
3027
+ sessionId,
3028
+ createdAt: this.preparedSession.sessionFile.createdAt,
3029
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3030
+ protocolState: this.preparedSession.sessionFile.protocolState
3031
+ }),
3032
+ session: {
3033
+ mode: "resume",
3034
+ sessionId
3035
+ }
3036
+ };
3037
+ }
3038
+ async persistStartedSessionId(result) {
3039
+ if (this.preparedSession?.session.mode !== "start") {
3040
+ return;
3041
+ }
3042
+ if (result.result !== "success") {
3043
+ return;
3044
+ }
3045
+ const sessionId = findSessionIdInResult(result) ?? this.preparedSession.session.sessionId;
3046
+ this.preparedSession = {
3047
+ ...this.preparedSession,
3048
+ sessionFile: await this.sessionStore.save({
3049
+ runId: this.preparedSession.runId,
3050
+ runDirectory: this.preparedSession.runDirectory,
3051
+ sessionId,
3052
+ createdAt: this.preparedSession.sessionFile.createdAt,
3053
+ parentRunId: this.preparedSession.sessionFile.parentRunId,
3054
+ protocolState: this.preparedSession.sessionFile.protocolState
3055
+ }),
3056
+ session: {
3057
+ mode: "resume",
3058
+ sessionId
3059
+ }
3060
+ };
3061
+ }
3062
+ shouldInvalidatePreparedResume(session, result) {
3063
+ return session === this.preparedSession?.session && session?.mode === "resume" && isResumeRejectedWith4xx(result);
3064
+ }
3065
+ emitSessionInvalidated(payload) {
3066
+ const event = {
3067
+ name: "agent.sessionInvalidated",
3068
+ payload: {
3069
+ params: {},
3070
+ ...payload,
3071
+ observabilityEvent: "session_invalidated"
3072
+ }
3073
+ };
3074
+ if (this.eventHandlers.size === 0) {
3075
+ this.pendingEvents.push(event);
3076
+ } else {
3077
+ for (const handler of this.eventHandlers) {
3078
+ handler(event);
3079
+ }
3080
+ }
3081
+ }
3082
+ createSessionId() {
3083
+ return this.dependencies.createSessionId?.() ?? randomUUID();
3084
+ }
3085
+ nowIso() {
3086
+ return (this.dependencies.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
3087
+ }
3088
+ stopActiveChild() {
3089
+ if (!this.activeChild || this.activeChild.killed) {
3090
+ this.activeChild = null;
3091
+ return;
3092
+ }
3093
+ this.activeChild.kill("SIGTERM");
3094
+ this.activeChild = null;
3095
+ }
3096
+ async cleanupPreparedMcpConfig() {
3097
+ const cleanupPath = this.preparedMcpConfig?.cleanupPath;
3098
+ this.preparedMcpConfig = null;
3099
+ if (!cleanupPath) {
3100
+ return;
3101
+ }
3102
+ await rm(cleanupPath, { force: true });
3103
+ }
3104
+ emitEvent(event) {
3105
+ for (const handler of this.eventHandlers) {
3106
+ try {
3107
+ handler(event);
3108
+ } catch {
3109
+ }
3110
+ }
3111
+ }
3112
+ };
3113
+ function createClaudePrintRuntimeAdapter(config, dependencies = {}) {
3114
+ return new ClaudePrintRuntimeAdapter(config, dependencies);
3115
+ }
3116
+ var DEFAULT_INHERITED_ENV_KEYS = [
3117
+ "HOME",
3118
+ "LANG",
3119
+ "PATH",
3120
+ "SHELL",
3121
+ "SYSTEMROOT",
3122
+ "TEMP",
3123
+ "TERM",
3124
+ "TMP",
3125
+ "TMPDIR",
3126
+ "USER",
3127
+ "USERPROFILE"
3128
+ ];
3129
+ function buildClaudeSpawnEnv(options) {
3130
+ if (options.inheritProcessEnv) {
3131
+ return {
3132
+ ...process.env,
3133
+ ...options.configEnv,
3134
+ ...options.inputEnv
3135
+ };
3136
+ }
3137
+ const env = {};
3138
+ for (const key of DEFAULT_INHERITED_ENV_KEYS) {
3139
+ const value = process.env[key];
3140
+ if (value !== void 0) {
3141
+ env[key] = value;
3142
+ }
3143
+ }
3144
+ Object.assign(env, options.configEnv, options.inputEnv);
3145
+ return env;
3146
+ }
3147
+ function findSessionIdInResult(result) {
3148
+ for (const record of result.records) {
3149
+ const sessionId = findSessionId(record.message);
3150
+ if (sessionId) {
3151
+ return sessionId;
3152
+ }
3153
+ }
3154
+ return null;
3155
+ }
3156
+ function findSessionId(value, depth = 0) {
3157
+ if (depth > 5) {
3158
+ return null;
3159
+ }
3160
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3161
+ return null;
3162
+ }
3163
+ const record = value;
3164
+ if (typeof record.sessionId === "string") {
3165
+ return record.sessionId;
3166
+ }
3167
+ if (typeof record.session_id === "string") {
3168
+ return record.session_id;
3169
+ }
3170
+ for (const nested of Object.values(record)) {
3171
+ const sessionId = findSessionId(nested, depth + 1);
3172
+ if (sessionId) {
3173
+ return sessionId;
3174
+ }
3175
+ }
3176
+ return null;
3177
+ }
3178
+ function isResumeRejectedWith4xx(result) {
3179
+ if (result.result !== "process-error") {
3180
+ return false;
3181
+ }
3182
+ return result.records.some((record) => {
3183
+ const text = record.line.toLowerCase();
3184
+ return text.includes("resume") && /\b4\d\d\b/.test(text);
3185
+ });
3186
+ }
3187
+ function formatErrorMessage(error) {
3188
+ if (error instanceof Error) {
3189
+ return error.message;
3190
+ }
3191
+ return String(error);
3192
+ }
3193
+ function buildClaudeMcpTokenEnvironment(options) {
3194
+ const source = options.inheritProcessEnv ? {
3195
+ ...process.env,
3196
+ ...options.configEnv
3197
+ } : {
3198
+ ...options.configEnv
3199
+ };
3200
+ return {
3201
+ GITHUB_GRAPHQL_TOKEN: source.GITHUB_GRAPHQL_TOKEN,
3202
+ GITHUB_GRAPHQL_API_URL: source.GITHUB_GRAPHQL_API_URL,
3203
+ GITHUB_TOKEN_BROKER_URL: source.GITHUB_TOKEN_BROKER_URL,
3204
+ GITHUB_TOKEN_BROKER_SECRET: source.GITHUB_TOKEN_BROKER_SECRET,
3205
+ GITHUB_TOKEN_CACHE_PATH: source.GITHUB_TOKEN_CACHE_PATH,
3206
+ GITHUB_PROJECT_ID: source.GITHUB_PROJECT_ID,
3207
+ WORKSPACE_RUNTIME_DIR: options.runtimeDirectory ?? source.WORKSPACE_RUNTIME_DIR
3208
+ };
3209
+ }
2016
3210
 
2017
3211
  export {
2018
3212
  isOrchestratorChannelEvent,
@@ -2032,14 +3226,15 @@ export {
2032
3226
  classifySessionExit,
2033
3227
  scheduleRetryAt,
2034
3228
  extractEnvForCodex,
3229
+ extractEnvForClaude,
2035
3230
  shouldReuseAgentCredentialCache,
2036
3231
  readAgentCredentialCache,
2037
3232
  writeAgentCredentialCache,
2038
3233
  DEFAULT_AGENT_INPUT_REQUIRED_REASON,
2039
3234
  buildAgentInputRequiredReason,
2040
3235
  readEnvFile,
2041
- deriveIssueWorkspaceKey,
2042
3236
  deriveIssueWorkspaceKeyFromIdentifier,
3237
+ deriveIssueWorkspaceKey,
2043
3238
  deriveLegacyIssueWorkspaceKey,
2044
3239
  resolveIssueWorkspaceDirectory,
2045
3240
  buildHookEnv,
@@ -2053,6 +3248,7 @@ export {
2053
3248
  mapIssueOrchestrationStateToStatus,
2054
3249
  resolveGitHubGraphQLToken,
2055
3250
  createGitHubGraphQLMcpServerEntry,
3251
+ createClaudePrintRuntimeAdapter,
2056
3252
  runClaudePreflight,
2057
3253
  formatClaudePreflightText,
2058
3254
  isClaudeRuntimeCommand,