@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.
- package/README.md +36 -0
- package/dist/{chunk-SXGT7LOF.js → chunk-2TSM3INR.js} +26 -1
- package/dist/{chunk-A67CMOYE.js → chunk-2UW7NQLX.js} +1 -1
- package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
- package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
- package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
- package/dist/{chunk-KY6WKH66.js → chunk-E7HYEEZD.js} +70 -52
- package/dist/{chunk-QEONJ5DZ.js → chunk-EEQQWTXS.js} +1288 -92
- package/dist/chunk-GDE6FYN4.js +26 -0
- package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
- package/dist/{chunk-JN3TQVFV.js → chunk-HMLBBZNY.js} +11 -2
- package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
- package/dist/{chunk-MYVJ6HK4.js → chunk-PUDXVBSN.js} +706 -376
- package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
- package/dist/{chunk-S6VIK4FF.js → chunk-ZHOKYUO3.js} +337 -13
- package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
- package/dist/{doctor-4HBRICHP.js → doctor-EJUMPBMW.js} +4 -4
- package/dist/index.js +88 -21
- package/dist/{init-HZ3JEDGQ.js → init-54HMKNYI.js} +3 -3
- package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
- package/dist/project-RMYMZSFV.js +25 -0
- package/dist/{recover-L3MJHHDA.js → recover-LTLKMTRX.js} +7 -7
- package/dist/repo-WI7GF6XQ.js +749 -0
- package/dist/{run-XJQ6BF7U.js → run-IHN3ZL35.js} +21 -9
- package/dist/{setup-B2SVLW2R.js → setup-TZJSM3QV.js} +14 -13
- package/dist/start-RTAHQMR2.js +19 -0
- package/dist/status-F4D52OVK.js +12 -0
- package/dist/stop-MDKMJPVR.js +10 -0
- package/dist/{upgrade-OJXPZRYE.js → upgrade-O33S2SJK.js} +2 -2
- package/dist/{version-TBDCTKDO.js → version-CW54Q7BK.js} +1 -1
- package/dist/worker-entry.js +369 -13
- package/dist/{workflow-BLJH2HC3.js → workflow-L3KT6HB7.js} +5 -5
- package/package.json +3 -3
- package/dist/project-25NQ4J4Y.js +0 -24
- package/dist/repo-TDCWQR6P.js +0 -379
- package/dist/start-I2CC7BLW.js +0 -18
- package/dist/status-QSCFVGRQ.js +0 -11
- 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
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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(
|
|
1049
|
-
const
|
|
1050
|
-
const candidate = resolve2(
|
|
1051
|
-
if (!candidate.startsWith(`${
|
|
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
|
|
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
|
-
|
|
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,
|
|
1433
|
+
function isMatchingIssueRun(run, issueId, issueIdentifier) {
|
|
1409
1434
|
return Boolean(
|
|
1410
|
-
run &&
|
|
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,
|