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