reeval 0.0.1
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/LICENSE +202 -0
- package/THIRD_PARTY_NOTICES.md +2160 -0
- package/assets/package.json +9 -0
- package/assets/pnpm-lock.yaml +1336 -0
- package/dist/agent-DVHkx8aT.js +532 -0
- package/dist/agent-DVHkx8aT.js.map +1 -0
- package/dist/analysis-CxSCt1YY.js +16 -0
- package/dist/analysis-CxSCt1YY.js.map +1 -0
- package/dist/assemble-B-nmz8oH.js +695 -0
- package/dist/assemble-B-nmz8oH.js.map +1 -0
- package/dist/assemble-CmRVDZ2Q.js +2 -0
- package/dist/attempt-BdRQ9jKX.js +176 -0
- package/dist/attempt-BdRQ9jKX.js.map +1 -0
- package/dist/bridge/index.mjs +1911 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1041 -0
- package/dist/bridge/pnpm-workspace.yaml +2 -0
- package/dist/budget-DBUrH4jX.js +8 -0
- package/dist/budget-DBUrH4jX.js.map +1 -0
- package/dist/cli--CQ3Iy9q.js +1865 -0
- package/dist/cli--CQ3Iy9q.js.map +1 -0
- package/dist/cli-spec-DKAu09Gi.js +478 -0
- package/dist/cli-spec-DKAu09Gi.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors-QV9dFVhN.js +34 -0
- package/dist/colors-QV9dFVhN.js.map +1 -0
- package/dist/command-BSJzHMtP.js +280 -0
- package/dist/command-BSJzHMtP.js.map +1 -0
- package/dist/command-CEm8PZ1N.js +1043 -0
- package/dist/command-CEm8PZ1N.js.map +1 -0
- package/dist/command-D-jnQWVG.js +673 -0
- package/dist/command-D-jnQWVG.js.map +1 -0
- package/dist/command-DQAvbG7d.js +432 -0
- package/dist/command-DQAvbG7d.js.map +1 -0
- package/dist/commands-A4udfyKZ.js +2 -0
- package/dist/commands-CF8mOfiu.js +207 -0
- package/dist/commands-CF8mOfiu.js.map +1 -0
- package/dist/config-Cuj0kVYj.js +415 -0
- package/dist/config-Cuj0kVYj.js.map +1 -0
- package/dist/context-loi9OCZv.js +106 -0
- package/dist/context-loi9OCZv.js.map +1 -0
- package/dist/core-tLN-z1Be.js +96 -0
- package/dist/core-tLN-z1Be.js.map +1 -0
- package/dist/db-Cjj2ZsfQ.js +2 -0
- package/dist/db-DxDzMhtB.js +827 -0
- package/dist/db-DxDzMhtB.js.map +1 -0
- package/dist/debug-D24NOQH1.js +89 -0
- package/dist/debug-D24NOQH1.js.map +1 -0
- package/dist/debug-SEmW4w8f.js +2 -0
- package/dist/dist-BZXYHN2X.js +1673 -0
- package/dist/dist-BZXYHN2X.js.map +1 -0
- package/dist/docker-DQDZWXF9.js +20 -0
- package/dist/docker-DQDZWXF9.js.map +1 -0
- package/dist/docker-DnWAVdEm.js +2 -0
- package/dist/docker-oSbCExHL.js +153 -0
- package/dist/docker-oSbCExHL.js.map +1 -0
- package/dist/doctor-CxlH1pDM.js +525 -0
- package/dist/doctor-CxlH1pDM.js.map +1 -0
- package/dist/egress-73SQfKdx.js +619 -0
- package/dist/egress-73SQfKdx.js.map +1 -0
- package/dist/env-BQqj93gu.js +21 -0
- package/dist/env-BQqj93gu.js.map +1 -0
- package/dist/environment-Mz7wvrtD.js +552 -0
- package/dist/environment-Mz7wvrtD.js.map +1 -0
- package/dist/errors-C9w_ud7g.js +160 -0
- package/dist/errors-C9w_ud7g.js.map +1 -0
- package/dist/errors-CSxfJ4qu.js +11 -0
- package/dist/errors-CSxfJ4qu.js.map +1 -0
- package/dist/files--6CEUXm7.js +2 -0
- package/dist/files-BLlrhU4-.js +62 -0
- package/dist/files-BLlrhU4-.js.map +1 -0
- package/dist/files-Bd25OAQH.js +2 -0
- package/dist/files-BvHCFWgD.js +2 -0
- package/dist/files-D2-qn_Dl.js +275 -0
- package/dist/files-D2-qn_Dl.js.map +1 -0
- package/dist/files-bF2fPuT9.js +99 -0
- package/dist/files-bF2fPuT9.js.map +1 -0
- package/dist/git-D6hMeieS.js +44 -0
- package/dist/git-D6hMeieS.js.map +1 -0
- package/dist/harness-ls5aX1UO.js +131 -0
- package/dist/harness-ls5aX1UO.js.map +1 -0
- package/dist/harness.d.ts +74 -0
- package/dist/harness.js +2 -0
- package/dist/header-DzFDoMCI.js +78 -0
- package/dist/header-DzFDoMCI.js.map +1 -0
- package/dist/ids-Bp6ut7ZD.js +49 -0
- package/dist/ids-Bp6ut7ZD.js.map +1 -0
- package/dist/images-Cc1-coFc.js +286 -0
- package/dist/images-Cc1-coFc.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +1959 -0
- package/dist/index.js.map +1 -0
- package/dist/init-CFljS8BJ.js +344 -0
- package/dist/init-CFljS8BJ.js.map +1 -0
- package/dist/init-z2ZPn9Tj.js +2 -0
- package/dist/invocations-rXktgSAn.js +157 -0
- package/dist/invocations-rXktgSAn.js.map +1 -0
- package/dist/jiti-BOLXpKyG.js +46 -0
- package/dist/jiti-BOLXpKyG.js.map +1 -0
- package/dist/judge-D8Uf4dA9.js +146 -0
- package/dist/judge-D8Uf4dA9.js.map +1 -0
- package/dist/judge.d.ts +21 -0
- package/dist/judge.js +19 -0
- package/dist/judge.js.map +1 -0
- package/dist/live-Bs3LReMV.js +98 -0
- package/dist/live-Bs3LReMV.js.map +1 -0
- package/dist/matchers-DyLvm7EG.js +374 -0
- package/dist/matchers-DyLvm7EG.js.map +1 -0
- package/dist/model-BdO79gWz.js +44 -0
- package/dist/model-BdO79gWz.js.map +1 -0
- package/dist/model-CDL98xue.js +725 -0
- package/dist/model-CDL98xue.js.map +1 -0
- package/dist/model-v-0_Ik7S.js +2 -0
- package/dist/node-kG64XxdE.js +506 -0
- package/dist/node-kG64XxdE.js.map +1 -0
- package/dist/opencode-providers-DZuD_oYt.js +3149 -0
- package/dist/opencode-providers-DZuD_oYt.js.map +1 -0
- package/dist/options-CAQhu--C.js +452 -0
- package/dist/options-CAQhu--C.js.map +1 -0
- package/dist/patch-BkKje4cp.js +69 -0
- package/dist/patch-BkKje4cp.js.map +1 -0
- package/dist/paths-CBvm6pap.js +72 -0
- package/dist/paths-CBvm6pap.js.map +1 -0
- package/dist/plan-gwk6dJ8c.js +237 -0
- package/dist/plan-gwk6dJ8c.js.map +1 -0
- package/dist/preflight-BtubXKQ8.js +2 -0
- package/dist/preflight-BypZMNBG.js +185 -0
- package/dist/preflight-BypZMNBG.js.map +1 -0
- package/dist/prepare-Cwv0bHs_.js +3 -0
- package/dist/prepare-D6Uzcubb.js +213 -0
- package/dist/prepare-D6Uzcubb.js.map +1 -0
- package/dist/provenance-Dfb8YY5Y.js +129 -0
- package/dist/provenance-Dfb8YY5Y.js.map +1 -0
- package/dist/provider-BDbNOQhQ.js +2 -0
- package/dist/provider-CmAYTQQs.js +14 -0
- package/dist/provider-CmAYTQQs.js.map +1 -0
- package/dist/providers-2p3ylMHI.js +107 -0
- package/dist/providers-2p3ylMHI.js.map +1 -0
- package/dist/providers-BUOxitcT.js +2 -0
- package/dist/query-CFATHZmV.js +91 -0
- package/dist/query-CFATHZmV.js.map +1 -0
- package/dist/query-DR4m6CXf.js +2 -0
- package/dist/record-DQ8wzyco.js +99 -0
- package/dist/record-DQ8wzyco.js.map +1 -0
- package/dist/redact-C4fL_bZP.js +167 -0
- package/dist/redact-C4fL_bZP.js.map +1 -0
- package/dist/results-CeXb8Wde.js +37 -0
- package/dist/results-CeXb8Wde.js.map +1 -0
- package/dist/root-CFDjpWuc.js +110 -0
- package/dist/root-CFDjpWuc.js.map +1 -0
- package/dist/sandbox-By2gkdkC.js +133 -0
- package/dist/sandbox-By2gkdkC.js.map +1 -0
- package/dist/sandbox.d.ts +41 -0
- package/dist/sandbox.js +2 -0
- package/dist/server-Bi61uiY0.js +350 -0
- package/dist/server-Bi61uiY0.js.map +1 -0
- package/dist/server-CsSBL8NC.js +2 -0
- package/dist/source-BXzrmof5.js +2 -0
- package/dist/source-Bsfa3LZH.js +40 -0
- package/dist/source-Bsfa3LZH.js.map +1 -0
- package/dist/src-55j1zNHH.js +3 -0
- package/dist/src-AEwxd1UP.js +602 -0
- package/dist/src-AEwxd1UP.js.map +1 -0
- package/dist/src-BDwSuyIo.js +3 -0
- package/dist/src-BQyMiWri.js +490 -0
- package/dist/src-BQyMiWri.js.map +1 -0
- package/dist/src-C-LHyTSV.js +3 -0
- package/dist/src-Cmp-7YrD.js +1136 -0
- package/dist/src-Cmp-7YrD.js.map +1 -0
- package/dist/src-D0RdqpNb.js +252 -0
- package/dist/src-D0RdqpNb.js.map +1 -0
- package/dist/src-D8KYzxBX.js +793 -0
- package/dist/src-D8KYzxBX.js.map +1 -0
- package/dist/src-DEgff9Z7.js +4 -0
- package/dist/src-DXx_29pq.js +567 -0
- package/dist/src-DXx_29pq.js.map +1 -0
- package/dist/src-DwwiVLqv.js +2937 -0
- package/dist/src-DwwiVLqv.js.map +1 -0
- package/dist/src-N7E_mfcZ.js +2 -0
- package/dist/src-W9g-y5X5.js +3 -0
- package/dist/src-daS0wKH2.js +3 -0
- package/dist/state-Cyw3QTsN.js +2 -0
- package/dist/state-DZ7eqb1F.js +167 -0
- package/dist/state-DZ7eqb1F.js.map +1 -0
- package/dist/stats-BNAHlNMD.js +114 -0
- package/dist/stats-BNAHlNMD.js.map +1 -0
- package/dist/studio-app/assets/index-D6XyfFSH.js +58 -0
- package/dist/studio-app/assets/index-xwzWdcE_.css +2 -0
- package/dist/studio-app/favicon.svg +1 -0
- package/dist/studio-app/index.html +15 -0
- package/dist/summary-D7Ttky0z.js +166 -0
- package/dist/summary-D7Ttky0z.js.map +1 -0
- package/dist/text-gEcKjAyp.js +29 -0
- package/dist/text-gEcKjAyp.js.map +1 -0
- package/dist/types-C5vI3g1C.js +9 -0
- package/dist/types-C5vI3g1C.js.map +1 -0
- package/dist/types-DLec-6Cx.d.ts +1535 -0
- package/dist/usage-DsYUqIUM.js +24 -0
- package/dist/usage-DsYUqIUM.js.map +1 -0
- package/dist/view/index.d.ts +645 -0
- package/dist/view/index.js +8 -0
- package/dist/views-BrHOv6wg.js +220 -0
- package/dist/views-BrHOv6wg.js.map +1 -0
- package/dist/views-CwCWLWqo.js +3 -0
- package/dist/worker-entry-DnYkANPG.js +506 -0
- package/dist/worker-entry-DnYkANPG.js.map +1 -0
- package/dist/workspace-C2S0Z6gl.js +327 -0
- package/dist/workspace-C2S0Z6gl.js.map +1 -0
- package/dist/workspace-CyoXG6CY.js +2 -0
- package/docker/Dockerfile +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1,1911 @@
|
|
|
1
|
+
// Modified by Invertase with patches/@ai-sdk__harness-claude-code@1.0.128.patch; see THIRD_PARTY_NOTICES.md.
|
|
2
|
+
// ../harness/dist/bridge/index.js
|
|
3
|
+
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
import { env as procEnv, pid, stdout } from "process";
|
|
7
|
+
import { WebSocketServer } from "ws";
|
|
8
|
+
var DEBUG_LEVEL_WEIGHT = {
|
|
9
|
+
error: 0,
|
|
10
|
+
warn: 1,
|
|
11
|
+
info: 2,
|
|
12
|
+
debug: 3,
|
|
13
|
+
trace: 4
|
|
14
|
+
};
|
|
15
|
+
function subsystemMatches(filters, subsystem) {
|
|
16
|
+
if (!filters || filters.length === 0) return true;
|
|
17
|
+
return filters.some(
|
|
18
|
+
(filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function formatBridgeError(err) {
|
|
22
|
+
if (err instanceof Error) {
|
|
23
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
24
|
+
}
|
|
25
|
+
if (typeof err === "string") {
|
|
26
|
+
return { message: err };
|
|
27
|
+
}
|
|
28
|
+
if (err !== null && typeof err === "object") {
|
|
29
|
+
try {
|
|
30
|
+
return { message: JSON.stringify(err) };
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { message: String(err) };
|
|
35
|
+
}
|
|
36
|
+
function createBridgeUserMessageQueue(options) {
|
|
37
|
+
const messages = [];
|
|
38
|
+
const waiters = [];
|
|
39
|
+
const entries = /* @__PURE__ */ new Map();
|
|
40
|
+
let closed = false;
|
|
41
|
+
let pendingCount = 0;
|
|
42
|
+
const enqueue = (input) => {
|
|
43
|
+
const existing = entries.get(input.messageId);
|
|
44
|
+
if (existing != null) {
|
|
45
|
+
if (existing.response != null) {
|
|
46
|
+
options.respond(existing.response);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let settled = false;
|
|
51
|
+
const settle = (response) => {
|
|
52
|
+
if (settled) return;
|
|
53
|
+
settled = true;
|
|
54
|
+
pendingCount--;
|
|
55
|
+
const entry = entries.get(input.messageId);
|
|
56
|
+
if (entry != null) entry.response = response;
|
|
57
|
+
options.respond(response);
|
|
58
|
+
};
|
|
59
|
+
const message = {
|
|
60
|
+
messageId: input.messageId,
|
|
61
|
+
text: input.text,
|
|
62
|
+
accept: () => {
|
|
63
|
+
settle({
|
|
64
|
+
type: "user-message-response",
|
|
65
|
+
messageId: input.messageId,
|
|
66
|
+
accepted: true
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
reject: (error) => {
|
|
70
|
+
settle({
|
|
71
|
+
type: "user-message-response",
|
|
72
|
+
messageId: input.messageId,
|
|
73
|
+
accepted: false,
|
|
74
|
+
error: { message: formatBridgeError(error).message }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
entries.set(input.messageId, {
|
|
79
|
+
reject: message.reject
|
|
80
|
+
});
|
|
81
|
+
pendingCount++;
|
|
82
|
+
if (closed) {
|
|
83
|
+
message.reject(
|
|
84
|
+
new Error("The bridge turn is no longer accepting user messages.")
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const waiter = waiters.shift();
|
|
89
|
+
if (waiter != null) {
|
|
90
|
+
waiter({ done: false, value: message });
|
|
91
|
+
} else {
|
|
92
|
+
messages.push(message);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const close = (error) => {
|
|
96
|
+
if (closed) return;
|
|
97
|
+
closed = true;
|
|
98
|
+
const reason = error ?? new Error("The bridge turn ended before accepting the user message.");
|
|
99
|
+
for (const entry of entries.values()) {
|
|
100
|
+
if (entry.response == null) entry.reject(reason);
|
|
101
|
+
}
|
|
102
|
+
messages.length = 0;
|
|
103
|
+
while (waiters.length > 0) {
|
|
104
|
+
waiters.shift()({ done: true, value: void 0 });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
get pendingCount() {
|
|
109
|
+
return pendingCount;
|
|
110
|
+
},
|
|
111
|
+
enqueue,
|
|
112
|
+
close,
|
|
113
|
+
[Symbol.asyncIterator]() {
|
|
114
|
+
return {
|
|
115
|
+
next: () => {
|
|
116
|
+
const message = messages.shift();
|
|
117
|
+
if (message != null) {
|
|
118
|
+
return Promise.resolve({ done: false, value: message });
|
|
119
|
+
}
|
|
120
|
+
if (closed) {
|
|
121
|
+
return Promise.resolve({
|
|
122
|
+
done: true,
|
|
123
|
+
value: void 0
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return new Promise(
|
|
127
|
+
(resolve) => {
|
|
128
|
+
waiters.push(resolve);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function parseEnvList(value) {
|
|
137
|
+
if (!value) return void 0;
|
|
138
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
139
|
+
return items.length > 0 ? items : void 0;
|
|
140
|
+
}
|
|
141
|
+
var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
142
|
+
var WS_OPEN = 1;
|
|
143
|
+
async function runBridge(options) {
|
|
144
|
+
const { bridgeType, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
|
|
145
|
+
const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
|
|
146
|
+
const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
|
|
147
|
+
const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
|
|
148
|
+
const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
|
|
149
|
+
const startConfigPath = `${bridgeStateDir2}/start-config.json`;
|
|
150
|
+
const rerunStartConfigPath = `${bridgeStateDir2}/rerun-start-config.json`;
|
|
151
|
+
const eventLogPath = `${bridgeStateDir2}/event-log.ndjson`;
|
|
152
|
+
try {
|
|
153
|
+
await mkdir(bridgeStateDir2, { recursive: true });
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
let currentBoundPort = 0;
|
|
157
|
+
let currentTurnState = "init";
|
|
158
|
+
let activeSocket;
|
|
159
|
+
let isFirstTurn = true;
|
|
160
|
+
let turnAbort;
|
|
161
|
+
let currentUserMessages;
|
|
162
|
+
let activeTurn;
|
|
163
|
+
let debugConfig;
|
|
164
|
+
let consoleCaptureInstalled = false;
|
|
165
|
+
const envDebugEnabled = ENV_TRUTHY.has(
|
|
166
|
+
(procEnv.HARNESS_DEBUG ?? "").toLowerCase()
|
|
167
|
+
);
|
|
168
|
+
let seqCounter = 0;
|
|
169
|
+
let eventLog = [];
|
|
170
|
+
let diskBuffer = "";
|
|
171
|
+
let flushPromise = null;
|
|
172
|
+
const flushEventsToDisk = async () => {
|
|
173
|
+
while (diskBuffer.length > 0) {
|
|
174
|
+
const buf = diskBuffer;
|
|
175
|
+
diskBuffer = "";
|
|
176
|
+
await appendFile(eventLogPath, buf).catch(() => {
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const scheduleEventFlush = () => {
|
|
181
|
+
if (flushPromise) return;
|
|
182
|
+
flushPromise = new Promise((resolve) => {
|
|
183
|
+
setImmediate(() => {
|
|
184
|
+
void flushEventsToDisk().finally(resolve);
|
|
185
|
+
});
|
|
186
|
+
}).finally(() => {
|
|
187
|
+
flushPromise = null;
|
|
188
|
+
if (diskBuffer.length > 0) {
|
|
189
|
+
scheduleEventFlush();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
const flushPendingEventsToDisk = async () => {
|
|
194
|
+
if (diskBuffer.length > 0 && !flushPromise) {
|
|
195
|
+
scheduleEventFlush();
|
|
196
|
+
}
|
|
197
|
+
let inFlight = flushPromise;
|
|
198
|
+
while (inFlight) {
|
|
199
|
+
await inFlight;
|
|
200
|
+
inFlight = flushPromise;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const replayFromDisk = procEnv.BRIDGE_REPLAY_FROM_DISK === "1";
|
|
204
|
+
if (replayFromDisk && existsSync(eventLogPath)) {
|
|
205
|
+
try {
|
|
206
|
+
const lines = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
|
|
207
|
+
eventLog = lines.map((line) => ({
|
|
208
|
+
seq: JSON.parse(line).seq,
|
|
209
|
+
line
|
|
210
|
+
}));
|
|
211
|
+
seqCounter = eventLog.at(-1)?.seq ?? 0;
|
|
212
|
+
} catch {
|
|
213
|
+
eventLog = [];
|
|
214
|
+
seqCounter = 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
218
|
+
const bufferedToolResults = [];
|
|
219
|
+
const pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
220
|
+
const writeBridgeMeta = async (state) => {
|
|
221
|
+
try {
|
|
222
|
+
await writeFile(
|
|
223
|
+
bridgeMetaPath,
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
type: bridgeType,
|
|
226
|
+
port: currentBoundPort,
|
|
227
|
+
state,
|
|
228
|
+
pid
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const writeStartConfig = async (start) => {
|
|
235
|
+
try {
|
|
236
|
+
const serialized = JSON.stringify(start);
|
|
237
|
+
await writeFile(startConfigPath, serialized);
|
|
238
|
+
if (!existsSync(rerunStartConfigPath)) {
|
|
239
|
+
await writeFile(rerunStartConfigPath, serialized);
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const emit = (event) => {
|
|
245
|
+
const seq = ++seqCounter;
|
|
246
|
+
const line = JSON.stringify({ ...event, seq });
|
|
247
|
+
eventLog.push({ seq, line });
|
|
248
|
+
diskBuffer += `${line}
|
|
249
|
+
`;
|
|
250
|
+
scheduleEventFlush();
|
|
251
|
+
if (activeSocket?.readyState === WS_OPEN) {
|
|
252
|
+
try {
|
|
253
|
+
activeSocket.send(line);
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const replay = (ws, afterSeq) => {
|
|
259
|
+
for (const entry of eventLog) {
|
|
260
|
+
if (entry.seq > afterSeq && ws.readyState === WS_OPEN) {
|
|
261
|
+
ws.send(entry.line);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const shouldEmitDebugEvent = (level, subsystem) => {
|
|
266
|
+
if (!debugConfig?.enabled) return false;
|
|
267
|
+
const threshold = debugConfig.level ?? "debug";
|
|
268
|
+
if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
|
|
269
|
+
return subsystemMatches(debugConfig.subsystems, subsystem);
|
|
270
|
+
};
|
|
271
|
+
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
272
|
+
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
273
|
+
const writeErrorToStderr = (input) => {
|
|
274
|
+
try {
|
|
275
|
+
const formatted = formatBridgeError(input.error);
|
|
276
|
+
rawStderrWrite(
|
|
277
|
+
`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}
|
|
278
|
+
`
|
|
279
|
+
);
|
|
280
|
+
if (formatted.stack) {
|
|
281
|
+
rawStderrWrite(`${formatted.stack}
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const emitWarning = (input) => {
|
|
288
|
+
try {
|
|
289
|
+
for (const line of input.message.split("\n")) {
|
|
290
|
+
if (line.trim().length > 0) {
|
|
291
|
+
rawStderrWrite(`[harness:${bridgeType}:warn] ${line}
|
|
292
|
+
`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const emitError = (input) => {
|
|
299
|
+
writeErrorToStderr({
|
|
300
|
+
message: input.message ?? "bridge error",
|
|
301
|
+
error: input.error
|
|
302
|
+
});
|
|
303
|
+
emit({ type: "error", error: serialiseError(input.error) });
|
|
304
|
+
};
|
|
305
|
+
const installConsoleCapture = () => {
|
|
306
|
+
if (consoleCaptureInstalled) return;
|
|
307
|
+
consoleCaptureInstalled = true;
|
|
308
|
+
const buffers = {
|
|
309
|
+
stdout: "",
|
|
310
|
+
stderr: ""
|
|
311
|
+
};
|
|
312
|
+
const patch = (stream, raw) => (chunk, encoding, cb) => {
|
|
313
|
+
if (debugConfig?.enabled) {
|
|
314
|
+
try {
|
|
315
|
+
const enc = typeof encoding === "string" ? encoding : "utf8";
|
|
316
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(
|
|
317
|
+
enc
|
|
318
|
+
);
|
|
319
|
+
const combined = buffers[stream] + text.replace(/\r\n/g, "\n");
|
|
320
|
+
const parts = combined.split("\n");
|
|
321
|
+
buffers[stream] = parts.pop() ?? "";
|
|
322
|
+
for (const line of parts) {
|
|
323
|
+
const trimmed = line.replace(/\s+$/, "");
|
|
324
|
+
if (trimmed) {
|
|
325
|
+
emit({
|
|
326
|
+
type: "sandbox-log",
|
|
327
|
+
source: bridgeType,
|
|
328
|
+
stream,
|
|
329
|
+
line: trimmed
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return raw(
|
|
337
|
+
chunk,
|
|
338
|
+
encoding,
|
|
339
|
+
cb
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
process.stdout.write = patch(
|
|
343
|
+
"stdout",
|
|
344
|
+
rawStdoutWrite
|
|
345
|
+
);
|
|
346
|
+
process.stderr.write = patch(
|
|
347
|
+
"stderr",
|
|
348
|
+
rawStderrWrite
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
const handleInbound = async (msg, ws) => {
|
|
352
|
+
switch (msg.type) {
|
|
353
|
+
case "start": {
|
|
354
|
+
for (; ; ) {
|
|
355
|
+
const pendingTurn = activeTurn;
|
|
356
|
+
if (pendingTurn == null) break;
|
|
357
|
+
turnAbort?.abort();
|
|
358
|
+
currentUserMessages?.close(
|
|
359
|
+
new Error("A new bridge turn replaced the active turn.")
|
|
360
|
+
);
|
|
361
|
+
let graceTimer;
|
|
362
|
+
const settled = await Promise.race([
|
|
363
|
+
pendingTurn.then(() => true),
|
|
364
|
+
new Promise((resolve) => {
|
|
365
|
+
graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
|
|
366
|
+
graceTimer.unref?.();
|
|
367
|
+
})
|
|
368
|
+
]);
|
|
369
|
+
clearTimeout(graceTimer);
|
|
370
|
+
if (!settled) break;
|
|
371
|
+
}
|
|
372
|
+
let turnFinished;
|
|
373
|
+
const thisTurn = new Promise((resolve) => turnFinished = resolve);
|
|
374
|
+
activeTurn = thisTurn;
|
|
375
|
+
activeSocket = ws;
|
|
376
|
+
const firstTurn = isFirstTurn;
|
|
377
|
+
isFirstTurn = false;
|
|
378
|
+
eventLog = [];
|
|
379
|
+
diskBuffer = "";
|
|
380
|
+
void writeFile(eventLogPath, "").catch(() => {
|
|
381
|
+
});
|
|
382
|
+
turnAbort = new AbortController();
|
|
383
|
+
currentTurnState = "running";
|
|
384
|
+
void writeStartConfig(msg);
|
|
385
|
+
void writeBridgeMeta("running");
|
|
386
|
+
const startDebug = msg.debug;
|
|
387
|
+
debugConfig = {
|
|
388
|
+
enabled: startDebug?.enabled ?? envDebugEnabled,
|
|
389
|
+
level: startDebug?.level ?? procEnv.HARNESS_DEBUG_LEVEL,
|
|
390
|
+
subsystems: startDebug?.subsystems ?? parseEnvList(procEnv.HARNESS_DEBUG_SUBSYSTEMS)
|
|
391
|
+
};
|
|
392
|
+
if (debugConfig.enabled) {
|
|
393
|
+
installConsoleCapture();
|
|
394
|
+
}
|
|
395
|
+
const userMessages = createBridgeUserMessageQueue({ respond: emit });
|
|
396
|
+
const turn = {
|
|
397
|
+
emit,
|
|
398
|
+
requestToolResult: (requestInput) => {
|
|
399
|
+
const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
|
|
400
|
+
const bufferedIndex = bufferedToolResults.findIndex(
|
|
401
|
+
(buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true
|
|
402
|
+
);
|
|
403
|
+
if (bufferedIndex >= 0) {
|
|
404
|
+
return Promise.resolve(
|
|
405
|
+
bufferedToolResults.splice(bufferedIndex, 1)[0].result
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
pendingToolResults.set(request.toolCallId, {
|
|
410
|
+
resolve,
|
|
411
|
+
matches: request.matches
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
requestToolApproval: (approvalId) => new Promise((resolve) => {
|
|
416
|
+
pendingToolApprovals.set(approvalId, resolve);
|
|
417
|
+
}),
|
|
418
|
+
experimental_userMessages: userMessages,
|
|
419
|
+
abortSignal: turnAbort.signal,
|
|
420
|
+
firstTurn,
|
|
421
|
+
bridgeLog: (input) => {
|
|
422
|
+
const level = input.level ?? "debug";
|
|
423
|
+
if (!shouldEmitDebugEvent(level, input.subsystem)) return;
|
|
424
|
+
emit({
|
|
425
|
+
type: "debug-event",
|
|
426
|
+
level,
|
|
427
|
+
subsystem: input.subsystem,
|
|
428
|
+
message: input.message,
|
|
429
|
+
...input.attrs ? { attrs: input.attrs } : {},
|
|
430
|
+
...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
emitWarning,
|
|
434
|
+
emitError
|
|
435
|
+
};
|
|
436
|
+
currentUserMessages = userMessages;
|
|
437
|
+
try {
|
|
438
|
+
await onStart(msg, turn);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
emitError({ error: err, message: "bridge turn failed" });
|
|
441
|
+
} finally {
|
|
442
|
+
userMessages.close();
|
|
443
|
+
if (currentUserMessages === userMessages) {
|
|
444
|
+
currentUserMessages = void 0;
|
|
445
|
+
}
|
|
446
|
+
if (activeTurn === thisTurn) {
|
|
447
|
+
activeTurn = void 0;
|
|
448
|
+
currentTurnState = "waiting";
|
|
449
|
+
void writeBridgeMeta("waiting");
|
|
450
|
+
}
|
|
451
|
+
turnFinished();
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
case "tool-result": {
|
|
456
|
+
const result = {
|
|
457
|
+
output: msg.output,
|
|
458
|
+
isError: msg.isError,
|
|
459
|
+
toolResult: msg.toolResult
|
|
460
|
+
};
|
|
461
|
+
const exactPending = pendingToolResults.get(msg.toolCallId);
|
|
462
|
+
const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(
|
|
463
|
+
([, pending2]) => pending2.matches?.(result) === true
|
|
464
|
+
) : void 0;
|
|
465
|
+
const pending = exactPending ?? matchingPending?.[1];
|
|
466
|
+
const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
|
|
467
|
+
if (pending != null && pendingId != null) {
|
|
468
|
+
pendingToolResults.delete(pendingId);
|
|
469
|
+
pending.resolve(result);
|
|
470
|
+
} else {
|
|
471
|
+
bufferedToolResults.push({
|
|
472
|
+
toolCallId: msg.toolCallId,
|
|
473
|
+
result
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
case "tool-approval-response": {
|
|
479
|
+
const resolver = pendingToolApprovals.get(msg.approvalId);
|
|
480
|
+
if (resolver) {
|
|
481
|
+
pendingToolApprovals.delete(msg.approvalId);
|
|
482
|
+
resolver({ approved: msg.approved, reason: msg.reason });
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
case "user-message": {
|
|
487
|
+
const messageId = msg.messageId ?? randomUUID();
|
|
488
|
+
if (currentUserMessages == null) {
|
|
489
|
+
sendControl(ws, {
|
|
490
|
+
type: "user-message-response",
|
|
491
|
+
messageId,
|
|
492
|
+
accepted: false,
|
|
493
|
+
error: { message: "The bridge has no active turn to steer." }
|
|
494
|
+
});
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (ws !== activeSocket) {
|
|
498
|
+
sendControl(ws, {
|
|
499
|
+
type: "user-message-response",
|
|
500
|
+
messageId,
|
|
501
|
+
accepted: false,
|
|
502
|
+
error: {
|
|
503
|
+
message: "The connection does not own the active bridge turn."
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
currentUserMessages.enqueue({
|
|
509
|
+
messageId,
|
|
510
|
+
text: msg.text
|
|
511
|
+
});
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
case "abort":
|
|
515
|
+
turnAbort?.abort();
|
|
516
|
+
return;
|
|
517
|
+
case "resume":
|
|
518
|
+
activeSocket = ws;
|
|
519
|
+
replay(ws, msg.lastSeenEventId);
|
|
520
|
+
return;
|
|
521
|
+
case "destroy":
|
|
522
|
+
currentTurnState = "done";
|
|
523
|
+
void writeBridgeMeta("done");
|
|
524
|
+
await onDestroy?.();
|
|
525
|
+
drainThenExit(ws, 1e3, "destroy");
|
|
526
|
+
return;
|
|
527
|
+
case "stop": {
|
|
528
|
+
currentTurnState = "done";
|
|
529
|
+
void writeBridgeMeta("done");
|
|
530
|
+
const data = await onStop?.() ?? {};
|
|
531
|
+
sendControl(ws, { type: "bridge-stop", data });
|
|
532
|
+
drainThenExit(ws, 1e3, "stop");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
void writeBridgeMeta("init");
|
|
537
|
+
const wss = new WebSocketServer({ port: bridgeWsPort, host: "0.0.0.0" });
|
|
538
|
+
const exit = () => {
|
|
539
|
+
if (options.onExit) {
|
|
540
|
+
options.onExit();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
wss.close(() => process.exit(0));
|
|
544
|
+
setTimeout(() => process.exit(0), 1e3).unref();
|
|
545
|
+
};
|
|
546
|
+
const drainThenExit = (ws, code, reason) => {
|
|
547
|
+
const start = Date.now();
|
|
548
|
+
const tick = () => {
|
|
549
|
+
const drained = ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN;
|
|
550
|
+
if (drained || Date.now() - start >= 5e3) {
|
|
551
|
+
void flushPendingEventsToDisk().finally(() => {
|
|
552
|
+
try {
|
|
553
|
+
ws.close(code, reason);
|
|
554
|
+
} finally {
|
|
555
|
+
exit();
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
setTimeout(tick, 10).unref();
|
|
561
|
+
};
|
|
562
|
+
tick();
|
|
563
|
+
};
|
|
564
|
+
wss.on("listening", () => {
|
|
565
|
+
const addr = wss.address();
|
|
566
|
+
currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
|
|
567
|
+
currentTurnState = "waiting";
|
|
568
|
+
void writeBridgeMeta("waiting");
|
|
569
|
+
stdout.write(
|
|
570
|
+
JSON.stringify({
|
|
571
|
+
type: "bridge-ready",
|
|
572
|
+
port: currentBoundPort
|
|
573
|
+
}) + "\n"
|
|
574
|
+
);
|
|
575
|
+
options.onListening?.(currentBoundPort);
|
|
576
|
+
});
|
|
577
|
+
wss.on("connection", (ws, req) => {
|
|
578
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
579
|
+
if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
|
|
580
|
+
ws.close(1008, "unauthorized");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
sendControl(ws, {
|
|
584
|
+
type: "bridge-hello",
|
|
585
|
+
state: currentTurnState,
|
|
586
|
+
lastSeq: seqCounter,
|
|
587
|
+
capabilities: { experimental_userMessageResponses: true }
|
|
588
|
+
});
|
|
589
|
+
ws.on("message", (raw) => {
|
|
590
|
+
let parsed;
|
|
591
|
+
try {
|
|
592
|
+
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
593
|
+
parsed = JSON.parse(text);
|
|
594
|
+
} catch (err) {
|
|
595
|
+
sendControl(ws, {
|
|
596
|
+
type: "error",
|
|
597
|
+
error: `protocol parse error: ${err.message}`
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
void handleInbound(parsed, ws);
|
|
602
|
+
});
|
|
603
|
+
ws.on("close", () => {
|
|
604
|
+
if (activeSocket === ws) {
|
|
605
|
+
activeSocket = void 0;
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
ws.on("error", () => {
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
process.on("uncaughtException", (err) => {
|
|
612
|
+
emitError({ error: err, message: "uncaught exception" });
|
|
613
|
+
});
|
|
614
|
+
process.on("unhandledRejection", (err) => {
|
|
615
|
+
emitError({ error: err, message: "unhandled rejection" });
|
|
616
|
+
});
|
|
617
|
+
await new Promise((resolve, reject) => {
|
|
618
|
+
if (wss.address() != null) {
|
|
619
|
+
resolve();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
wss.once("listening", resolve);
|
|
623
|
+
wss.once("error", reject);
|
|
624
|
+
});
|
|
625
|
+
return {
|
|
626
|
+
port: currentBoundPort,
|
|
627
|
+
close: () => new Promise((resolve) => {
|
|
628
|
+
wss.close(() => resolve());
|
|
629
|
+
})
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function sendControl(socket, message) {
|
|
633
|
+
if (socket?.readyState === WS_OPEN) {
|
|
634
|
+
try {
|
|
635
|
+
socket.send(JSON.stringify(message));
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function serialiseError(err) {
|
|
641
|
+
if (err instanceof Error) {
|
|
642
|
+
return { name: err.name, message: err.message, stack: err.stack };
|
|
643
|
+
}
|
|
644
|
+
return err;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/bridge/compaction-latch.ts
|
|
648
|
+
function createCompactionLatch(emit) {
|
|
649
|
+
let boundary;
|
|
650
|
+
let summary;
|
|
651
|
+
const tryEmit = () => {
|
|
652
|
+
if (!boundary || summary === void 0) return;
|
|
653
|
+
emit({
|
|
654
|
+
type: "compaction",
|
|
655
|
+
trigger: boundary.trigger,
|
|
656
|
+
summary,
|
|
657
|
+
...boundary.tokensBefore !== void 0 ? { tokensBefore: boundary.tokensBefore } : {},
|
|
658
|
+
...boundary.tokensAfter !== void 0 ? { tokensAfter: boundary.tokensAfter } : {}
|
|
659
|
+
});
|
|
660
|
+
boundary = void 0;
|
|
661
|
+
summary = void 0;
|
|
662
|
+
};
|
|
663
|
+
return {
|
|
664
|
+
onBoundary(next) {
|
|
665
|
+
boundary = next;
|
|
666
|
+
tryEmit();
|
|
667
|
+
},
|
|
668
|
+
onSummary(next) {
|
|
669
|
+
summary = next;
|
|
670
|
+
tryEmit();
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/bridge/index.ts
|
|
676
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
677
|
+
import { argv, env as procEnv2, stdout as stdout2 } from "process";
|
|
678
|
+
import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
|
|
679
|
+
import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
680
|
+
|
|
681
|
+
// src/bridge/claude-code-system-prompt.ts
|
|
682
|
+
function createClaudeCodeSystemPrompt(instructions) {
|
|
683
|
+
return {
|
|
684
|
+
type: "preset",
|
|
685
|
+
preset: "claude_code",
|
|
686
|
+
...instructions ? { append: instructions } : {}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/bridge/claude-skills-option.ts
|
|
691
|
+
function toClaudeSkillsOption(skills) {
|
|
692
|
+
return skills && skills.length > 0 ? "all" : void 0;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/bridge/create-emit-stream-event.ts
|
|
696
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
697
|
+
function createClaudeStreamEventState() {
|
|
698
|
+
return {
|
|
699
|
+
nativeToolCallNames: /* @__PURE__ */ new Map(),
|
|
700
|
+
approvalRequestedToolUseIds: /* @__PURE__ */ new Set(),
|
|
701
|
+
partialBlocks: /* @__PURE__ */ new Map(),
|
|
702
|
+
stepUsage: void 0,
|
|
703
|
+
pendingStepToolUseIds: /* @__PURE__ */ new Set(),
|
|
704
|
+
pendingStepAssistantUsage: void 0,
|
|
705
|
+
pendingStepDeltaUsage: void 0,
|
|
706
|
+
pendingStepUsage: void 0,
|
|
707
|
+
stepOpen: false,
|
|
708
|
+
mcpToolUseIds: /* @__PURE__ */ new Set(),
|
|
709
|
+
externalMcpToolUseIds: /* @__PURE__ */ new Set(),
|
|
710
|
+
structuredOutputToolUseIds: /* @__PURE__ */ new Set(),
|
|
711
|
+
observedTerminalError: void 0
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
715
|
+
var HOST_TOOL_PREFIX = "mcp__harness-tools__";
|
|
716
|
+
function isExternalMcpTool(nativeName) {
|
|
717
|
+
return nativeName.startsWith("mcp__") && !nativeName.startsWith(HOST_TOOL_PREFIX);
|
|
718
|
+
}
|
|
719
|
+
function createEmitStreamEvent({
|
|
720
|
+
state,
|
|
721
|
+
emit,
|
|
722
|
+
emitWarning,
|
|
723
|
+
emitTerminalError,
|
|
724
|
+
onCompactionBoundary,
|
|
725
|
+
toCommonName: toCommonName2
|
|
726
|
+
}) {
|
|
727
|
+
let streamStarted = false;
|
|
728
|
+
return (msg) => {
|
|
729
|
+
const type = msg.type;
|
|
730
|
+
if (!streamStarted && type !== "command_lifecycle") {
|
|
731
|
+
const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
|
|
732
|
+
emit({
|
|
733
|
+
type: "stream-start",
|
|
734
|
+
...initModel ? { modelId: initModel } : {}
|
|
735
|
+
});
|
|
736
|
+
streamStarted = true;
|
|
737
|
+
}
|
|
738
|
+
if (type === "system" && msg.subtype === "api_retry") {
|
|
739
|
+
if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
|
|
740
|
+
emitTerminalError(
|
|
741
|
+
`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
|
|
742
|
+
);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
emitWarning({ message: formatApiRetryWarning(msg) });
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (typeof msg.error === "string" && msg.error.trim()) {
|
|
749
|
+
state.observedTerminalError = msg.error.trim();
|
|
750
|
+
}
|
|
751
|
+
if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
|
|
752
|
+
emitTerminalError(msg.error);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
|
|
756
|
+
emitTerminalError(msg.patch.error);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (type === "system" && msg.subtype === "compact_boundary") {
|
|
760
|
+
const meta = msg.compact_metadata;
|
|
761
|
+
if (meta) {
|
|
762
|
+
onCompactionBoundary({
|
|
763
|
+
trigger: meta.trigger,
|
|
764
|
+
...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
|
|
765
|
+
...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (msg.parent_tool_use_id != null) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (type === "stream_event") {
|
|
774
|
+
handleStreamEvent({
|
|
775
|
+
event: msg.event,
|
|
776
|
+
state,
|
|
777
|
+
send: emit,
|
|
778
|
+
toCommonName: toCommonName2
|
|
779
|
+
});
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const messageContent = msg.message?.content;
|
|
783
|
+
if (type === "assistant" && Array.isArray(messageContent)) {
|
|
784
|
+
const usage = toUsageRecord(msg.message?.usage);
|
|
785
|
+
const toolUseIds = [];
|
|
786
|
+
let opensStep = false;
|
|
787
|
+
for (const block of messageContent) {
|
|
788
|
+
if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
789
|
+
toolUseIds.push(block.id);
|
|
790
|
+
if (block.name === "StructuredOutput") {
|
|
791
|
+
state.structuredOutputToolUseIds.add(block.id);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (block.name.startsWith(HOST_TOOL_PREFIX)) {
|
|
795
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
796
|
+
state.mcpToolUseIds.add(block.id);
|
|
797
|
+
opensStep = true;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
state.nativeToolCallNames.set(block.id, block.name);
|
|
801
|
+
if (block.name === "AskUserQuestion") {
|
|
802
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
803
|
+
opensStep = true;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const dynamic = isExternalMcpTool(block.name);
|
|
807
|
+
if (dynamic) state.externalMcpToolUseIds.add(block.id);
|
|
808
|
+
if (state.approvalRequestedToolUseIds.has(block.id)) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
state.pendingStepToolUseIds.add(block.id);
|
|
812
|
+
opensStep = true;
|
|
813
|
+
emit({
|
|
814
|
+
type: "tool-call",
|
|
815
|
+
toolCallId: block.id,
|
|
816
|
+
toolName: toCommonName2(block.name),
|
|
817
|
+
nativeName: block.name,
|
|
818
|
+
input: JSON.stringify(block.input ?? {}),
|
|
819
|
+
providerExecuted: true,
|
|
820
|
+
...dynamic ? { dynamic: true } : {}
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (opensStep || toolUseIds.length === 0) {
|
|
825
|
+
state.stepOpen = true;
|
|
826
|
+
if (usage) {
|
|
827
|
+
state.pendingStepAssistantUsage = usage;
|
|
828
|
+
updatePendingStepUsage(state);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (type === "user" && Array.isArray(messageContent)) {
|
|
834
|
+
const toolResultBlocks = messageContent.filter(
|
|
835
|
+
(block) => block.type === "tool_result"
|
|
836
|
+
);
|
|
837
|
+
const toolUseResult = toolResultBlocks.length === 1 ? msg.tool_use_result : void 0;
|
|
838
|
+
for (const block of messageContent) {
|
|
839
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
840
|
+
if (state.structuredOutputToolUseIds.delete(block.tool_use_id)) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (state.mcpToolUseIds.has(block.tool_use_id)) {
|
|
844
|
+
state.mcpToolUseIds.delete(block.tool_use_id);
|
|
845
|
+
state.pendingStepToolUseIds.delete(block.tool_use_id);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
state.approvalRequestedToolUseIds.delete(block.tool_use_id);
|
|
849
|
+
const nativeName = state.nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
|
|
850
|
+
state.nativeToolCallNames.delete(block.tool_use_id);
|
|
851
|
+
const toolName = toCommonName2(nativeName);
|
|
852
|
+
const dynamic = state.externalMcpToolUseIds.delete(block.tool_use_id);
|
|
853
|
+
const isError = !!block.is_error;
|
|
854
|
+
const result = toolUseResult !== void 0 ? toolUseResult : resolveToolResult({
|
|
855
|
+
toolName,
|
|
856
|
+
dynamic,
|
|
857
|
+
isError,
|
|
858
|
+
rawContent: block.content
|
|
859
|
+
});
|
|
860
|
+
emit({
|
|
861
|
+
type: "tool-result",
|
|
862
|
+
toolCallId: block.tool_use_id,
|
|
863
|
+
toolName,
|
|
864
|
+
result,
|
|
865
|
+
isError,
|
|
866
|
+
...dynamic ? { dynamic: true } : {}
|
|
867
|
+
});
|
|
868
|
+
state.pendingStepToolUseIds.delete(block.tool_use_id);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
closeStepIfReady({ state, emit });
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function finishApprovalStep({
|
|
876
|
+
state,
|
|
877
|
+
emit,
|
|
878
|
+
approvalId
|
|
879
|
+
}) {
|
|
880
|
+
state.stepOpen = true;
|
|
881
|
+
state.pendingStepToolUseIds.delete(approvalId);
|
|
882
|
+
closeStepIfReady({ state, emit });
|
|
883
|
+
}
|
|
884
|
+
function emitFinishStep({
|
|
885
|
+
state,
|
|
886
|
+
emit,
|
|
887
|
+
usage
|
|
888
|
+
}) {
|
|
889
|
+
emit({
|
|
890
|
+
type: "finish-step",
|
|
891
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
892
|
+
usage: usage ?? defaultUsage()
|
|
893
|
+
});
|
|
894
|
+
state.stepUsage = usage ?? state.stepUsage;
|
|
895
|
+
state.pendingStepAssistantUsage = void 0;
|
|
896
|
+
state.pendingStepDeltaUsage = void 0;
|
|
897
|
+
state.pendingStepUsage = void 0;
|
|
898
|
+
state.pendingStepToolUseIds = /* @__PURE__ */ new Set();
|
|
899
|
+
state.stepOpen = false;
|
|
900
|
+
}
|
|
901
|
+
function closeStepIfReady({
|
|
902
|
+
state,
|
|
903
|
+
emit
|
|
904
|
+
}) {
|
|
905
|
+
if (!state.stepOpen || state.pendingStepToolUseIds.size > 0 || state.partialBlocks.size > 0) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
emitFinishStep({ state, emit, usage: state.pendingStepUsage });
|
|
909
|
+
}
|
|
910
|
+
function formatApiRetryWarning(msg) {
|
|
911
|
+
const details = [];
|
|
912
|
+
if (typeof msg.attempt === "number") {
|
|
913
|
+
const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
|
|
914
|
+
details.push(`attempt ${msg.attempt}${maxRetries}`);
|
|
915
|
+
}
|
|
916
|
+
if (typeof msg.error_status === "number") {
|
|
917
|
+
details.push(`HTTP ${msg.error_status}`);
|
|
918
|
+
}
|
|
919
|
+
if (typeof msg.retry_delay_ms === "number") {
|
|
920
|
+
details.push(`retrying in ${msg.retry_delay_ms}ms`);
|
|
921
|
+
}
|
|
922
|
+
if (msg.error) details.push(msg.error);
|
|
923
|
+
return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
|
|
924
|
+
}
|
|
925
|
+
function handleStreamEvent({
|
|
926
|
+
event,
|
|
927
|
+
state,
|
|
928
|
+
send,
|
|
929
|
+
toCommonName: toCommonName2
|
|
930
|
+
}) {
|
|
931
|
+
if (!event) return;
|
|
932
|
+
if (event.type === "message_delta") {
|
|
933
|
+
const usage = toUsageRecord(event.usage);
|
|
934
|
+
if (usage) {
|
|
935
|
+
state.pendingStepDeltaUsage = mergeNonNullUsage(
|
|
936
|
+
state.pendingStepDeltaUsage,
|
|
937
|
+
usage
|
|
938
|
+
);
|
|
939
|
+
updatePendingStepUsage(state);
|
|
940
|
+
}
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (typeof event.index !== "number") return;
|
|
944
|
+
const index = event.index;
|
|
945
|
+
const partialBlocks = state.partialBlocks;
|
|
946
|
+
if (event.type === "content_block_start") {
|
|
947
|
+
const blockType = event.content_block?.type;
|
|
948
|
+
if (blockType === "text") {
|
|
949
|
+
const id = randomUUID2();
|
|
950
|
+
partialBlocks.set(index, { id, kind: "text" });
|
|
951
|
+
send({ type: "text-start", id });
|
|
952
|
+
} else if (blockType === "thinking") {
|
|
953
|
+
const id = randomUUID2();
|
|
954
|
+
partialBlocks.set(index, { id, kind: "thinking" });
|
|
955
|
+
send({ type: "reasoning-start", id });
|
|
956
|
+
} else if (blockType === "tool_use" && typeof event.content_block?.id === "string" && typeof event.content_block.name === "string") {
|
|
957
|
+
const id = event.content_block.id;
|
|
958
|
+
const nativeName = event.content_block.name;
|
|
959
|
+
if (nativeName === "StructuredOutput") {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (nativeName === "AskUserQuestion") {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX) ? nativeName.slice(HOST_TOOL_PREFIX.length) : void 0;
|
|
966
|
+
const dynamic = isExternalMcpTool(nativeName);
|
|
967
|
+
partialBlocks.set(index, { id, kind: "tool-input" });
|
|
968
|
+
send({
|
|
969
|
+
type: "tool-input-start",
|
|
970
|
+
id,
|
|
971
|
+
toolName: hostToolName ?? toCommonName2(nativeName),
|
|
972
|
+
providerExecuted: hostToolName === void 0,
|
|
973
|
+
...dynamic ? { dynamic: true } : {}
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
if (event.type === "content_block_delta") {
|
|
979
|
+
const block = partialBlocks.get(index);
|
|
980
|
+
if (!block) return;
|
|
981
|
+
if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
|
|
982
|
+
send({ type: "text-delta", id: block.id, delta: event.delta.text });
|
|
983
|
+
} else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
|
|
984
|
+
send({
|
|
985
|
+
type: "reasoning-delta",
|
|
986
|
+
id: block.id,
|
|
987
|
+
delta: event.delta.thinking
|
|
988
|
+
});
|
|
989
|
+
} else if (block.kind === "tool-input" && event.delta?.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
|
|
990
|
+
send({
|
|
991
|
+
type: "tool-input-delta",
|
|
992
|
+
id: block.id,
|
|
993
|
+
delta: event.delta.partial_json
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (event.type === "content_block_stop") {
|
|
999
|
+
const block = partialBlocks.get(index);
|
|
1000
|
+
if (!block) return;
|
|
1001
|
+
partialBlocks.delete(index);
|
|
1002
|
+
if (block.kind === "text") {
|
|
1003
|
+
send({ type: "text-end", id: block.id });
|
|
1004
|
+
} else if (block.kind === "thinking") {
|
|
1005
|
+
send({ type: "reasoning-end", id: block.id });
|
|
1006
|
+
} else {
|
|
1007
|
+
send({ type: "tool-input-end", id: block.id });
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function toUsageRecord(usage) {
|
|
1012
|
+
return usage != null && typeof usage === "object" ? usage : void 0;
|
|
1013
|
+
}
|
|
1014
|
+
function mergeNonNullUsage(current, update) {
|
|
1015
|
+
const merged = { ...current };
|
|
1016
|
+
for (const [key, value] of Object.entries(update)) {
|
|
1017
|
+
if (value != null) {
|
|
1018
|
+
merged[key] = value;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return merged;
|
|
1022
|
+
}
|
|
1023
|
+
function updatePendingStepUsage(state) {
|
|
1024
|
+
const assistantUsage = state.pendingStepAssistantUsage;
|
|
1025
|
+
const deltaUsage = state.pendingStepDeltaUsage;
|
|
1026
|
+
state.pendingStepUsage = mapUsage(
|
|
1027
|
+
assistantUsage || deltaUsage ? { ...assistantUsage, ...deltaUsage } : void 0
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
function isTextEntry(entry) {
|
|
1031
|
+
return entry != null && typeof entry === "object" && "text" in entry;
|
|
1032
|
+
}
|
|
1033
|
+
function stringifyContent(content) {
|
|
1034
|
+
if (typeof content === "string") return content;
|
|
1035
|
+
if (Array.isArray(content)) {
|
|
1036
|
+
return content.map(
|
|
1037
|
+
(entry) => isTextEntry(entry) ? String(entry.text ?? "") : JSON.stringify(entry)
|
|
1038
|
+
).join("");
|
|
1039
|
+
}
|
|
1040
|
+
return JSON.stringify(content);
|
|
1041
|
+
}
|
|
1042
|
+
function hasNonTextContent(content) {
|
|
1043
|
+
return Array.isArray(content) && content.some((entry) => !isTextEntry(entry));
|
|
1044
|
+
}
|
|
1045
|
+
function resolveToolResult({
|
|
1046
|
+
toolName,
|
|
1047
|
+
dynamic,
|
|
1048
|
+
isError,
|
|
1049
|
+
rawContent
|
|
1050
|
+
}) {
|
|
1051
|
+
if (toolName === "bash") {
|
|
1052
|
+
return { exitCode: isError ? 1 : 0, stdout: stringifyContent(rawContent) };
|
|
1053
|
+
}
|
|
1054
|
+
if (hasNonTextContent(rawContent)) return rawContent;
|
|
1055
|
+
const content = stringifyContent(rawContent);
|
|
1056
|
+
return dynamic ? parseMcpToolResult(content) : content;
|
|
1057
|
+
}
|
|
1058
|
+
function parseMcpToolResult(content) {
|
|
1059
|
+
try {
|
|
1060
|
+
const parsed = JSON.parse(content);
|
|
1061
|
+
return parsed !== null && typeof parsed === "object" ? parsed : content;
|
|
1062
|
+
} catch {
|
|
1063
|
+
return content;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function mapUsage(usage) {
|
|
1067
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
1068
|
+
const u = usage;
|
|
1069
|
+
return {
|
|
1070
|
+
inputTokens: {
|
|
1071
|
+
total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
|
|
1072
|
+
noCache: u.input_tokens ?? 0,
|
|
1073
|
+
cacheRead: u.cache_read_input_tokens ?? 0,
|
|
1074
|
+
cacheWrite: u.cache_creation_input_tokens ?? 0
|
|
1075
|
+
},
|
|
1076
|
+
outputTokens: {
|
|
1077
|
+
total: u.output_tokens ?? 0,
|
|
1078
|
+
text: u.output_tokens ?? 0
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function defaultUsage() {
|
|
1083
|
+
return {
|
|
1084
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1085
|
+
outputTokens: { total: 0, text: 0 }
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/bridge/json-schema-to-zod.ts
|
|
1090
|
+
import { z } from "zod/v4";
|
|
1091
|
+
function jsonSchemaToZodObject(input) {
|
|
1092
|
+
const schema = isJsonSchemaObject(input) ? input : {};
|
|
1093
|
+
return toZodObject(schema);
|
|
1094
|
+
}
|
|
1095
|
+
function toZodObject(schema) {
|
|
1096
|
+
const object = z.object(toZodShape(schema));
|
|
1097
|
+
if (schema.additionalProperties === false) return object.strict();
|
|
1098
|
+
return object.catchall(
|
|
1099
|
+
isJsonSchemaObject(schema.additionalProperties) ? toZodType(schema.additionalProperties) : z.unknown()
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
function toZodShape(schema) {
|
|
1103
|
+
if (!schema?.properties) return {};
|
|
1104
|
+
const required = new Set(schema.required);
|
|
1105
|
+
const shape = {};
|
|
1106
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
1107
|
+
const propType = toZodType(propSchema);
|
|
1108
|
+
let propertyType = required.has(key) ? propType : propType.optional();
|
|
1109
|
+
if (propSchema.description) {
|
|
1110
|
+
propertyType = propertyType.describe(propSchema.description);
|
|
1111
|
+
}
|
|
1112
|
+
shape[key] = propertyType;
|
|
1113
|
+
}
|
|
1114
|
+
return shape;
|
|
1115
|
+
}
|
|
1116
|
+
function toZodType(schema) {
|
|
1117
|
+
if (!schema) return z.any();
|
|
1118
|
+
let zType = zodForConst(schema) ?? zodForEnum(schema) ?? zodForUnion(schema) ?? zodForType(schema);
|
|
1119
|
+
if (isNullable(schema)) zType = zType.nullable();
|
|
1120
|
+
if (schema.description) zType = zType.describe(schema.description);
|
|
1121
|
+
return zType;
|
|
1122
|
+
}
|
|
1123
|
+
function zodForType(schema) {
|
|
1124
|
+
const types = getNonNullTypes(schema);
|
|
1125
|
+
if (types.length > 1) return z.any();
|
|
1126
|
+
if (Array.isArray(schema.type) && types.length === 0) return z.null();
|
|
1127
|
+
const type = types[0] ?? (schema.properties ? "object" : void 0);
|
|
1128
|
+
switch (type) {
|
|
1129
|
+
case "string":
|
|
1130
|
+
return z.string();
|
|
1131
|
+
case "number":
|
|
1132
|
+
return z.number();
|
|
1133
|
+
case "integer":
|
|
1134
|
+
return z.number().int();
|
|
1135
|
+
case "boolean":
|
|
1136
|
+
return z.boolean();
|
|
1137
|
+
case "array":
|
|
1138
|
+
return z.array(
|
|
1139
|
+
Array.isArray(schema.items) ? z.any() : toZodType(schema.items)
|
|
1140
|
+
);
|
|
1141
|
+
case "object":
|
|
1142
|
+
return toZodObject(schema);
|
|
1143
|
+
case "null":
|
|
1144
|
+
return z.null();
|
|
1145
|
+
default:
|
|
1146
|
+
return z.any();
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function zodForConst(schema) {
|
|
1150
|
+
if (!("const" in schema)) return void 0;
|
|
1151
|
+
return isJsonLiteral(schema.const) ? z.literal(schema.const) : z.any();
|
|
1152
|
+
}
|
|
1153
|
+
function zodForEnum(schema) {
|
|
1154
|
+
if (!Array.isArray(schema.enum)) return void 0;
|
|
1155
|
+
if (!schema.enum.every(isJsonLiteral)) return z.any();
|
|
1156
|
+
return zodForLiterals(schema.enum);
|
|
1157
|
+
}
|
|
1158
|
+
function zodForUnion(schema) {
|
|
1159
|
+
const unionSchemas = schema.anyOf ?? schema.oneOf;
|
|
1160
|
+
if (!unionSchemas || unionSchemas.length < 2) return void 0;
|
|
1161
|
+
const options = unionSchemas.map((item) => toZodType(item));
|
|
1162
|
+
return z.union(options);
|
|
1163
|
+
}
|
|
1164
|
+
function zodForLiterals(values) {
|
|
1165
|
+
if (values.length === 0) return z.any();
|
|
1166
|
+
if (values.length === 1) return z.literal(values[0]);
|
|
1167
|
+
const literals = values.map((value) => z.literal(value));
|
|
1168
|
+
return z.union(literals);
|
|
1169
|
+
}
|
|
1170
|
+
function getNonNullTypes(schema) {
|
|
1171
|
+
return Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : [schema.type].filter(Boolean);
|
|
1172
|
+
}
|
|
1173
|
+
function isNullable(schema) {
|
|
1174
|
+
return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null");
|
|
1175
|
+
}
|
|
1176
|
+
function isJsonSchemaObject(input) {
|
|
1177
|
+
return input != null && typeof input === "object" && !Array.isArray(input);
|
|
1178
|
+
}
|
|
1179
|
+
function isJsonLiteral(value) {
|
|
1180
|
+
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/bridge/tool-filtering.ts
|
|
1184
|
+
var PUBLIC_TO_NATIVE = {
|
|
1185
|
+
read: "Read",
|
|
1186
|
+
write: "Write",
|
|
1187
|
+
edit: "Edit",
|
|
1188
|
+
bash: "Bash",
|
|
1189
|
+
glob: "Glob",
|
|
1190
|
+
grep: "Grep",
|
|
1191
|
+
webSearch: "WebSearch",
|
|
1192
|
+
WebFetch: "WebFetch",
|
|
1193
|
+
NotebookEdit: "NotebookEdit",
|
|
1194
|
+
TodoWrite: "TodoWrite",
|
|
1195
|
+
Agent: "Agent",
|
|
1196
|
+
TaskCreate: "TaskCreate",
|
|
1197
|
+
TaskGet: "TaskGet",
|
|
1198
|
+
TaskUpdate: "TaskUpdate",
|
|
1199
|
+
TaskList: "TaskList",
|
|
1200
|
+
TaskStop: "TaskStop",
|
|
1201
|
+
TaskOutput: "TaskOutput",
|
|
1202
|
+
Monitor: "Monitor",
|
|
1203
|
+
ListMcpResources: "ListMcpResources",
|
|
1204
|
+
ReadMcpResource: "ReadMcpResource",
|
|
1205
|
+
ExitPlanMode: "ExitPlanMode",
|
|
1206
|
+
EnterWorktree: "EnterWorktree",
|
|
1207
|
+
ExitWorktree: "ExitWorktree",
|
|
1208
|
+
askUserQuestions: "AskUserQuestion",
|
|
1209
|
+
Skill: "Skill",
|
|
1210
|
+
ToolSearch: "ToolSearch"
|
|
1211
|
+
};
|
|
1212
|
+
var PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
|
|
1213
|
+
function toNativeName(toolName) {
|
|
1214
|
+
return PUBLIC_TO_NATIVE[toolName] ?? toolName;
|
|
1215
|
+
}
|
|
1216
|
+
function resolveNativeTools(toolFiltering) {
|
|
1217
|
+
if (toolFiltering == null || toolFiltering.mode === "deny") return void 0;
|
|
1218
|
+
return toolFiltering.toolNames.map((name) => toNativeName(name));
|
|
1219
|
+
}
|
|
1220
|
+
function resolveInactiveNativeTools(toolFiltering) {
|
|
1221
|
+
if (toolFiltering == null) return [];
|
|
1222
|
+
const inactiveToolNames = toolFiltering.mode === "allow" ? PUBLIC_TOOL_NAMES.filter(
|
|
1223
|
+
(name) => !toolFiltering.toolNames.includes(name)
|
|
1224
|
+
) : toolFiltering.toolNames;
|
|
1225
|
+
return inactiveToolNames.map((name) => toNativeName(name));
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/bridge/question-tool.ts
|
|
1229
|
+
function claudeCodeQuestionKey(nativeInput) {
|
|
1230
|
+
return JSON.stringify(nativeInput.questions);
|
|
1231
|
+
}
|
|
1232
|
+
function toHarnessQuestionsInput(nativeInput) {
|
|
1233
|
+
return {
|
|
1234
|
+
allowPartialAnswers: true,
|
|
1235
|
+
questions: nativeInput.questions.map((question, questionIndex) => ({
|
|
1236
|
+
id: `question-${questionIndex + 1}`,
|
|
1237
|
+
question: question.question,
|
|
1238
|
+
header: question.header,
|
|
1239
|
+
options: question.options.map((option, optionIndex) => ({
|
|
1240
|
+
id: `option-${optionIndex + 1}`,
|
|
1241
|
+
label: option.label,
|
|
1242
|
+
description: option.description,
|
|
1243
|
+
...option.preview !== void 0 ? { preview: option.preview } : {}
|
|
1244
|
+
})),
|
|
1245
|
+
allowMultiple: question.multiSelect,
|
|
1246
|
+
allowFreeForm: true
|
|
1247
|
+
}))
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
function toClaudeCodeQuestionResult(input) {
|
|
1251
|
+
const output = input.output;
|
|
1252
|
+
if (output.action === "declined" || output.action === "cancelled") {
|
|
1253
|
+
return {
|
|
1254
|
+
behavior: "deny",
|
|
1255
|
+
message: `The user ${output.action} the questions.`
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
const canonicalInput = toHarnessQuestionsInput(input.nativeInput);
|
|
1259
|
+
const answers = Object.fromEntries(
|
|
1260
|
+
canonicalInput.questions.flatMap((question, questionIndex) => {
|
|
1261
|
+
const answer = output.answers[question.id];
|
|
1262
|
+
if (answer == null) return [];
|
|
1263
|
+
const nativeQuestion = input.nativeInput.questions[questionIndex];
|
|
1264
|
+
return [
|
|
1265
|
+
[
|
|
1266
|
+
nativeQuestion.question,
|
|
1267
|
+
toClaudeCodeAnswer({
|
|
1268
|
+
nativeQuestion,
|
|
1269
|
+
optionIds: answer.optionIds,
|
|
1270
|
+
freeform: answer.freeform
|
|
1271
|
+
})
|
|
1272
|
+
]
|
|
1273
|
+
];
|
|
1274
|
+
})
|
|
1275
|
+
);
|
|
1276
|
+
return {
|
|
1277
|
+
behavior: "allow",
|
|
1278
|
+
updatedInput: { ...input.nativeInput, answers }
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function toClaudeCodeAnswer(input) {
|
|
1282
|
+
if (input.freeform !== void 0) return input.freeform;
|
|
1283
|
+
const labels = input.optionIds.flatMap((optionId) => {
|
|
1284
|
+
const index = positionalIdIndex({ id: optionId, prefix: "option-" });
|
|
1285
|
+
const option = index == null ? void 0 : input.nativeQuestion.options[index];
|
|
1286
|
+
return option == null ? [] : [option.label];
|
|
1287
|
+
});
|
|
1288
|
+
return input.nativeQuestion.multiSelect ? labels.join(", ") : labels[0] ?? "";
|
|
1289
|
+
}
|
|
1290
|
+
function positionalIdIndex(input) {
|
|
1291
|
+
if (!input.id.startsWith(input.prefix)) return void 0;
|
|
1292
|
+
const oneBasedIndex = Number(input.id.slice(input.prefix.length));
|
|
1293
|
+
return Number.isInteger(oneBasedIndex) && oneBasedIndex > 0 ? oneBasedIndex - 1 : void 0;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/bridge/index.ts
|
|
1297
|
+
var NATIVE_TO_COMMON = {
|
|
1298
|
+
Read: "read",
|
|
1299
|
+
Write: "write",
|
|
1300
|
+
Edit: "edit",
|
|
1301
|
+
Bash: "bash",
|
|
1302
|
+
Glob: "glob",
|
|
1303
|
+
Grep: "grep",
|
|
1304
|
+
WebSearch: "webSearch",
|
|
1305
|
+
AskUserQuestion: "askUserQuestions"
|
|
1306
|
+
};
|
|
1307
|
+
var NATIVE_TOOL_KINDS = {
|
|
1308
|
+
Read: "readonly",
|
|
1309
|
+
Glob: "readonly",
|
|
1310
|
+
Grep: "readonly",
|
|
1311
|
+
WebSearch: "readonly",
|
|
1312
|
+
WebFetch: "readonly",
|
|
1313
|
+
TaskGet: "readonly",
|
|
1314
|
+
TaskList: "readonly",
|
|
1315
|
+
TaskOutput: "readonly",
|
|
1316
|
+
ListMcpResources: "readonly",
|
|
1317
|
+
ReadMcpResource: "readonly",
|
|
1318
|
+
Write: "edit",
|
|
1319
|
+
Edit: "edit",
|
|
1320
|
+
NotebookEdit: "edit",
|
|
1321
|
+
TodoWrite: "edit",
|
|
1322
|
+
TaskCreate: "edit",
|
|
1323
|
+
TaskUpdate: "edit",
|
|
1324
|
+
TaskStop: "edit",
|
|
1325
|
+
EnterWorktree: "edit",
|
|
1326
|
+
ExitWorktree: "edit",
|
|
1327
|
+
ExitPlanMode: "edit",
|
|
1328
|
+
Skill: "readonly",
|
|
1329
|
+
AskUserQuestion: "readonly",
|
|
1330
|
+
ToolSearch: "readonly",
|
|
1331
|
+
Bash: "bash",
|
|
1332
|
+
Monitor: "bash"
|
|
1333
|
+
};
|
|
1334
|
+
function toCommonName(nativeName) {
|
|
1335
|
+
return NATIVE_TO_COMMON[nativeName] ?? nativeName;
|
|
1336
|
+
}
|
|
1337
|
+
var args = parseArgs(argv.slice(2));
|
|
1338
|
+
var workdir = args.workdir;
|
|
1339
|
+
var bridgeStateDir = args.bridgeStateDir;
|
|
1340
|
+
if (!workdir) {
|
|
1341
|
+
emitFatal("Missing --workdir argument.");
|
|
1342
|
+
}
|
|
1343
|
+
if (!bridgeStateDir) {
|
|
1344
|
+
emitFatal("Missing --bridge-state-dir argument.");
|
|
1345
|
+
}
|
|
1346
|
+
var claudeSdk = claudeAgentSdk;
|
|
1347
|
+
var mcpModule = mcpServerModule;
|
|
1348
|
+
var lastClaudeSessionId;
|
|
1349
|
+
await runBridge({
|
|
1350
|
+
bridgeType: "claude-code",
|
|
1351
|
+
bridgeStateDir,
|
|
1352
|
+
onStart: runTurn,
|
|
1353
|
+
// Claude Code's conversation state lives in the runtime's own store, keyed
|
|
1354
|
+
// by working directory. The resume payload names the exact conversation so a
|
|
1355
|
+
// later resume does not have to fall back to "most recent in this workdir".
|
|
1356
|
+
onStop: () => lastClaudeSessionId == null ? {} : { claudeSessionId: lastClaudeSessionId }
|
|
1357
|
+
});
|
|
1358
|
+
function createPermissionOptions(input) {
|
|
1359
|
+
const permissionMode = input.start.permissionMode ?? "allow-all";
|
|
1360
|
+
const inactiveNativeTools = new Set(input.inactiveNativeTools);
|
|
1361
|
+
const permissionSettings = createPermissionSettings({
|
|
1362
|
+
permissionMode,
|
|
1363
|
+
inactiveNativeTools
|
|
1364
|
+
});
|
|
1365
|
+
const baseOptions = {
|
|
1366
|
+
permissionMode: permissionMode === "allow-all" ? "bypassPermissions" : permissionMode === "allow-edits" ? "acceptEdits" : "default",
|
|
1367
|
+
allowDangerouslySkipPermissions: permissionMode === "allow-all",
|
|
1368
|
+
...permissionSettings ? { settings: permissionSettings } : {}
|
|
1369
|
+
};
|
|
1370
|
+
if (permissionMode === "allow-all") {
|
|
1371
|
+
return {
|
|
1372
|
+
...baseOptions,
|
|
1373
|
+
/*
|
|
1374
|
+
* Claude Code exposes AskUserQuestion in headless SDK sessions only
|
|
1375
|
+
* when a permission prompt tool is configured. The stdio prompt tool
|
|
1376
|
+
* preserves that tool surface without supplying the canUseTool callback
|
|
1377
|
+
* that bypassPermissions guarantees it will never invoke.
|
|
1378
|
+
*/
|
|
1379
|
+
permissionPromptToolName: "stdio"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
return {
|
|
1383
|
+
...baseOptions,
|
|
1384
|
+
canUseTool: async (toolName, toolInput, options) => {
|
|
1385
|
+
if (toolName.startsWith("mcp__harness-tools__")) {
|
|
1386
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
1387
|
+
}
|
|
1388
|
+
if (!inactiveNativeTools.has(toolName) && !nativeToolRequiresApproval({
|
|
1389
|
+
nativeName: toolName,
|
|
1390
|
+
permissionMode
|
|
1391
|
+
})) {
|
|
1392
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
1393
|
+
}
|
|
1394
|
+
const approvalId = options.toolUseID;
|
|
1395
|
+
input.approvalRequestedToolUseIds.add(approvalId);
|
|
1396
|
+
input.nativeToolCallNames.set(approvalId, toolName);
|
|
1397
|
+
input.emit({
|
|
1398
|
+
type: "tool-call",
|
|
1399
|
+
toolCallId: approvalId,
|
|
1400
|
+
toolName: toCommonName(toolName),
|
|
1401
|
+
nativeName: toolName,
|
|
1402
|
+
input: JSON.stringify(toolInput ?? {}),
|
|
1403
|
+
providerExecuted: true,
|
|
1404
|
+
...isExternalMcpTool(toolName) ? { dynamic: true } : {}
|
|
1405
|
+
});
|
|
1406
|
+
input.emit({
|
|
1407
|
+
type: "tool-approval-request",
|
|
1408
|
+
approvalId,
|
|
1409
|
+
toolCallId: approvalId
|
|
1410
|
+
});
|
|
1411
|
+
input.finishApprovalStep(approvalId);
|
|
1412
|
+
const decision = await input.turn.requestToolApproval(approvalId);
|
|
1413
|
+
return decision.approved ? { behavior: "allow", updatedInput: toolInput, toolUseID: approvalId } : {
|
|
1414
|
+
behavior: "deny",
|
|
1415
|
+
message: decision.reason ?? "Denied",
|
|
1416
|
+
toolUseID: approvalId
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
function createQuestionPreToolUseHook(input) {
|
|
1422
|
+
return async (hookInput, toolUseID) => {
|
|
1423
|
+
if (hookInput.hook_event_name !== "PreToolUse" || hookInput.tool_name !== "AskUserQuestion") {
|
|
1424
|
+
return {};
|
|
1425
|
+
}
|
|
1426
|
+
const nativeInput = hookInput.tool_input;
|
|
1427
|
+
const canonicalInput = toHarnessQuestionsInput(nativeInput);
|
|
1428
|
+
const toolCallId = toolUseID ?? hookInput.tool_use_id;
|
|
1429
|
+
input.nativeToolCallNames.set(toolCallId, hookInput.tool_name);
|
|
1430
|
+
input.emit({
|
|
1431
|
+
type: "tool-call",
|
|
1432
|
+
toolCallId,
|
|
1433
|
+
toolName: "askUserQuestions",
|
|
1434
|
+
nativeName: hookInput.tool_name,
|
|
1435
|
+
input: JSON.stringify(canonicalInput),
|
|
1436
|
+
providerExecuted: false,
|
|
1437
|
+
providerMetadata: {
|
|
1438
|
+
"claude-code": {
|
|
1439
|
+
nativeRequest: nativeInput
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
const questionKey = claudeCodeQuestionKey(nativeInput);
|
|
1444
|
+
const result = await input.turn.requestToolResult({
|
|
1445
|
+
toolCallId,
|
|
1446
|
+
matches: (candidate) => {
|
|
1447
|
+
const nativeRequest = candidate.toolResult?.providerOptions?.["claude-code"]?.nativeRequest;
|
|
1448
|
+
return nativeRequest != null && claudeCodeQuestionKey(nativeRequest) === questionKey;
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
const nativeResult = toClaudeCodeQuestionResult({
|
|
1452
|
+
nativeInput,
|
|
1453
|
+
output: result.output
|
|
1454
|
+
});
|
|
1455
|
+
return {
|
|
1456
|
+
hookSpecificOutput: {
|
|
1457
|
+
hookEventName: "PreToolUse",
|
|
1458
|
+
...nativeResult.behavior === "allow" ? {
|
|
1459
|
+
permissionDecision: "allow",
|
|
1460
|
+
updatedInput: {
|
|
1461
|
+
...nativeResult.updatedInput
|
|
1462
|
+
}
|
|
1463
|
+
} : {
|
|
1464
|
+
permissionDecision: "deny",
|
|
1465
|
+
permissionDecisionReason: nativeResult.message
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function createPermissionSettings(input) {
|
|
1472
|
+
const askRules = /* @__PURE__ */ new Set();
|
|
1473
|
+
for (const [nativeName, kind] of Object.entries(NATIVE_TOOL_KINDS)) {
|
|
1474
|
+
if (input.inactiveNativeTools.has(nativeName) || (input.permissionMode === "allow-reads" ? kind === "edit" || kind === "bash" : input.permissionMode === "allow-edits" ? kind === "bash" : false)) {
|
|
1475
|
+
askRules.add(`${nativeName}(*)`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
if (askRules.size === 0) return void 0;
|
|
1479
|
+
return {
|
|
1480
|
+
permissions: { ask: [...askRules] },
|
|
1481
|
+
sandbox: { autoAllowBashIfSandboxed: false }
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function nativeToolRequiresApproval(input) {
|
|
1485
|
+
if (input.permissionMode === "allow-all") return false;
|
|
1486
|
+
const kind = NATIVE_TOOL_KINDS[input.nativeName] ?? "edit";
|
|
1487
|
+
if (input.permissionMode === "allow-edits") return kind === "bash";
|
|
1488
|
+
return kind === "edit" || kind === "bash";
|
|
1489
|
+
}
|
|
1490
|
+
async function runTurn(start, turn) {
|
|
1491
|
+
const emit = (msg) => turn.emit(msg);
|
|
1492
|
+
const abortCtl = new AbortController();
|
|
1493
|
+
let gracefulAbort;
|
|
1494
|
+
let hardAbortTimer;
|
|
1495
|
+
const onHostAbort = () => {
|
|
1496
|
+
if (gracefulAbort) {
|
|
1497
|
+
gracefulAbort();
|
|
1498
|
+
} else {
|
|
1499
|
+
abortCtl.abort();
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
if (turn.abortSignal.aborted) {
|
|
1503
|
+
abortCtl.abort();
|
|
1504
|
+
} else {
|
|
1505
|
+
turn.abortSignal.addEventListener("abort", onHostAbort, { once: true });
|
|
1506
|
+
}
|
|
1507
|
+
const streamEventState = createClaudeStreamEventState();
|
|
1508
|
+
const mcpServers = { ...start.mcpServers };
|
|
1509
|
+
if (start.tools && start.tools.length > 0) {
|
|
1510
|
+
const server = new mcpModule.McpServer({
|
|
1511
|
+
name: "harness-tools",
|
|
1512
|
+
version: "1.0.0"
|
|
1513
|
+
});
|
|
1514
|
+
for (const tool of start.tools) {
|
|
1515
|
+
server.registerTool(
|
|
1516
|
+
tool.name,
|
|
1517
|
+
{
|
|
1518
|
+
description: tool.description ?? "",
|
|
1519
|
+
inputSchema: jsonSchemaToZodObject(tool.inputSchema)
|
|
1520
|
+
},
|
|
1521
|
+
async (...handlerArgs) => {
|
|
1522
|
+
const [input, extra] = handlerArgs;
|
|
1523
|
+
const metadataToolCallId = extra._meta?.["claudecode/toolUseId"];
|
|
1524
|
+
const toolCallId = typeof metadataToolCallId === "string" ? metadataToolCallId : randomUUID3();
|
|
1525
|
+
emit({
|
|
1526
|
+
type: "tool-call",
|
|
1527
|
+
toolCallId,
|
|
1528
|
+
toolName: tool.name,
|
|
1529
|
+
input: JSON.stringify(input),
|
|
1530
|
+
providerExecuted: false
|
|
1531
|
+
});
|
|
1532
|
+
const { output, isError } = await turn.requestToolResult(toolCallId);
|
|
1533
|
+
emit({
|
|
1534
|
+
type: "tool-result",
|
|
1535
|
+
toolCallId,
|
|
1536
|
+
toolName: tool.name,
|
|
1537
|
+
result: output ?? null,
|
|
1538
|
+
isError: !!isError
|
|
1539
|
+
});
|
|
1540
|
+
return {
|
|
1541
|
+
content: [{ type: "text", text: JSON.stringify(output ?? null) }],
|
|
1542
|
+
isError
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
mcpServers["harness-tools"] = {
|
|
1548
|
+
type: "sdk",
|
|
1549
|
+
name: "harness-tools",
|
|
1550
|
+
instance: server
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
const compaction = createCompactionLatch((event) => emit(event));
|
|
1554
|
+
const queryInput = createQueryInput({
|
|
1555
|
+
initialUserMessage: start.prompt,
|
|
1556
|
+
userMessages: turn.experimental_userMessages,
|
|
1557
|
+
abortSignal: abortCtl.signal
|
|
1558
|
+
});
|
|
1559
|
+
const skillsOption = toClaudeSkillsOption(start.skills);
|
|
1560
|
+
const nativeTools = resolveNativeTools(start.builtinToolFiltering);
|
|
1561
|
+
const inactiveNativeTools = resolveInactiveNativeTools(
|
|
1562
|
+
start.builtinToolFiltering
|
|
1563
|
+
);
|
|
1564
|
+
const permissionOptions = createPermissionOptions({
|
|
1565
|
+
start,
|
|
1566
|
+
inactiveNativeTools,
|
|
1567
|
+
turn,
|
|
1568
|
+
emit,
|
|
1569
|
+
finishApprovalStep: (approvalId) => {
|
|
1570
|
+
finishApprovalStep({ state: streamEventState, emit, approvalId });
|
|
1571
|
+
},
|
|
1572
|
+
nativeToolCallNames: streamEventState.nativeToolCallNames,
|
|
1573
|
+
approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds
|
|
1574
|
+
});
|
|
1575
|
+
const questionPreToolUseHook = createQuestionPreToolUseHook({
|
|
1576
|
+
turn,
|
|
1577
|
+
emit,
|
|
1578
|
+
nativeToolCallNames: streamEventState.nativeToolCallNames
|
|
1579
|
+
});
|
|
1580
|
+
const q = claudeSdk.query({
|
|
1581
|
+
prompt: queryInput.input,
|
|
1582
|
+
options: {
|
|
1583
|
+
...start.model ? { model: start.model } : {},
|
|
1584
|
+
...start.maxTurns !== void 0 ? { maxTurns: start.maxTurns } : {},
|
|
1585
|
+
...start.env !== void 0 ? { env: { ...procEnv2, ...start.env } } : {},
|
|
1586
|
+
...skillsOption ? { skills: skillsOption } : {},
|
|
1587
|
+
...nativeTools !== void 0 ? { tools: nativeTools } : {},
|
|
1588
|
+
...inactiveNativeTools.length > 0 ? { disallowedTools: inactiveNativeTools } : {},
|
|
1589
|
+
systemPrompt: createClaudeCodeSystemPrompt(start.instructions),
|
|
1590
|
+
thinking: start.thinking,
|
|
1591
|
+
...start.effort !== void 0 ? { effort: start.effort } : {},
|
|
1592
|
+
...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? {
|
|
1593
|
+
outputFormat: {
|
|
1594
|
+
type: "json_schema",
|
|
1595
|
+
schema: start.responseFormat.schema
|
|
1596
|
+
}
|
|
1597
|
+
} : {},
|
|
1598
|
+
includePartialMessages: true,
|
|
1599
|
+
// The `PostCompact` hook carries the compaction summary, which the
|
|
1600
|
+
// `compact_boundary` system message does not. Latch it for the unified
|
|
1601
|
+
// `compaction` event; return an empty output so compaction proceeds.
|
|
1602
|
+
hooks: {
|
|
1603
|
+
PreToolUse: [
|
|
1604
|
+
{
|
|
1605
|
+
matcher: "AskUserQuestion",
|
|
1606
|
+
hooks: [questionPreToolUseHook]
|
|
1607
|
+
}
|
|
1608
|
+
],
|
|
1609
|
+
PostCompact: [
|
|
1610
|
+
{
|
|
1611
|
+
hooks: [
|
|
1612
|
+
async (input) => {
|
|
1613
|
+
if (typeof input?.compact_summary === "string") {
|
|
1614
|
+
compaction.onSummary(input.compact_summary);
|
|
1615
|
+
}
|
|
1616
|
+
return {};
|
|
1617
|
+
}
|
|
1618
|
+
]
|
|
1619
|
+
}
|
|
1620
|
+
]
|
|
1621
|
+
},
|
|
1622
|
+
// Continuation rule, most specific first.
|
|
1623
|
+
//
|
|
1624
|
+
// `resumeSessionId` names the exact conversation and is what a
|
|
1625
|
+
// cross-process resume should use: `continue` means "most recent thread
|
|
1626
|
+
// in this workdir", which silently picks the wrong one once anything
|
|
1627
|
+
// else has run there. The bridge also retains the id observed during its
|
|
1628
|
+
// previous query, so every later query stays pinned to that conversation
|
|
1629
|
+
// even when the host detached and reattached between turns. `resume` and
|
|
1630
|
+
// `continue` are mutually exclusive in the SDK.
|
|
1631
|
+
//
|
|
1632
|
+
// Otherwise the host can force-continue by setting `start.continue`,
|
|
1633
|
+
// and turns after the first fall back to the legacy cwd-based behavior
|
|
1634
|
+
// when no exact id was observed.
|
|
1635
|
+
...start.resumeSessionId ?? lastClaudeSessionId ? { resume: start.resumeSessionId ?? lastClaudeSessionId } : start.continue === true || !turn.firstTurn ? { continue: true } : {},
|
|
1636
|
+
...permissionOptions,
|
|
1637
|
+
mcpServers,
|
|
1638
|
+
cwd: workdir,
|
|
1639
|
+
abortSignal: abortCtl.signal
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
gracefulAbort = () => {
|
|
1643
|
+
hardAbortTimer = setTimeout(() => abortCtl.abort(), 5e3);
|
|
1644
|
+
hardAbortTimer.unref?.();
|
|
1645
|
+
void Promise.resolve().then(() => q.interrupt()).catch(() => abortCtl.abort());
|
|
1646
|
+
};
|
|
1647
|
+
let turnUsage;
|
|
1648
|
+
let totalCostUsd;
|
|
1649
|
+
let emittedTerminalError = false;
|
|
1650
|
+
let emittedTerminalFinish = false;
|
|
1651
|
+
const emitTerminalError = (message) => {
|
|
1652
|
+
const normalized = message?.trim();
|
|
1653
|
+
if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
|
|
1654
|
+
streamEventState.observedTerminalError = normalized;
|
|
1655
|
+
emittedTerminalError = true;
|
|
1656
|
+
if (!turn.abortSignal.aborted) {
|
|
1657
|
+
turn.emitError({
|
|
1658
|
+
error: normalized,
|
|
1659
|
+
message: "claude-code terminal error"
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
queryInput.close();
|
|
1663
|
+
abortCtl.abort();
|
|
1664
|
+
};
|
|
1665
|
+
const emitStreamEvent = createEmitStreamEvent({
|
|
1666
|
+
state: streamEventState,
|
|
1667
|
+
emit,
|
|
1668
|
+
emitWarning: turn.emitWarning,
|
|
1669
|
+
emitTerminalError,
|
|
1670
|
+
onCompactionBoundary: (boundary) => compaction.onBoundary(boundary),
|
|
1671
|
+
toCommonName
|
|
1672
|
+
});
|
|
1673
|
+
try {
|
|
1674
|
+
for await (const msg of q) {
|
|
1675
|
+
if (abortCtl.signal.aborted) break;
|
|
1676
|
+
const type = msg.type;
|
|
1677
|
+
if (type === "command_lifecycle") {
|
|
1678
|
+
queryInput.handleLifecycle(msg);
|
|
1679
|
+
}
|
|
1680
|
+
const sessionId = msg.session_id;
|
|
1681
|
+
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
1682
|
+
lastClaudeSessionId = sessionId;
|
|
1683
|
+
}
|
|
1684
|
+
emitStreamEvent(msg);
|
|
1685
|
+
if (type === "result") {
|
|
1686
|
+
if (msg.subtype === "success") {
|
|
1687
|
+
if (msg.is_error) {
|
|
1688
|
+
emitTerminalError(
|
|
1689
|
+
msg.result?.trim() || streamEventState.observedTerminalError || (typeof msg.api_error_status === "number" ? `Claude Code reported an API error (HTTP ${msg.api_error_status})` : "Claude Code reported a failed result")
|
|
1690
|
+
);
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
const emptyResult = !msg.result?.trim?.();
|
|
1694
|
+
if (emptyResult && streamEventState.observedTerminalError) {
|
|
1695
|
+
emitTerminalError(streamEventState.observedTerminalError);
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
const usage = msg.usage ?? msg.message?.usage;
|
|
1699
|
+
const harnessUsage = mapUsage(usage);
|
|
1700
|
+
if (harnessUsage) turnUsage = addUsage(turnUsage, harnessUsage);
|
|
1701
|
+
if (typeof msg.total_cost_usd === "number") {
|
|
1702
|
+
totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
|
|
1703
|
+
}
|
|
1704
|
+
if (start.responseFormat?.type === "json" && msg.structured_output !== void 0) {
|
|
1705
|
+
const id = randomUUID3();
|
|
1706
|
+
emit({ type: "text-start", id });
|
|
1707
|
+
emit({
|
|
1708
|
+
type: "text-delta",
|
|
1709
|
+
id,
|
|
1710
|
+
delta: JSON.stringify(msg.structured_output)
|
|
1711
|
+
});
|
|
1712
|
+
emit({ type: "text-end", id });
|
|
1713
|
+
streamEventState.stepOpen = true;
|
|
1714
|
+
}
|
|
1715
|
+
if (streamEventState.stepOpen) {
|
|
1716
|
+
emitFinishStep({
|
|
1717
|
+
state: streamEventState,
|
|
1718
|
+
emit,
|
|
1719
|
+
usage: streamEventState.pendingStepUsage ?? harnessUsage
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
queryInput.observeResult();
|
|
1723
|
+
if (!queryInput.hasActiveUserMessages()) {
|
|
1724
|
+
queryInput.close();
|
|
1725
|
+
break;
|
|
1726
|
+
}
|
|
1727
|
+
} else {
|
|
1728
|
+
emitTerminalError(
|
|
1729
|
+
(Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || streamEventState.observedTerminalError || msg.result || "Unknown error"
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
if (queryInput.hasObservedResult && !queryInput.hasActiveUserMessages()) {
|
|
1735
|
+
queryInput.close();
|
|
1736
|
+
break;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
if (!turn.abortSignal.aborted && !(abortCtl.signal.aborted && emittedTerminalError)) {
|
|
1741
|
+
turn.emitError({ error: err, message: "claude-code turn failed" });
|
|
1742
|
+
}
|
|
1743
|
+
return;
|
|
1744
|
+
} finally {
|
|
1745
|
+
gracefulAbort = void 0;
|
|
1746
|
+
if (hardAbortTimer != null) clearTimeout(hardAbortTimer);
|
|
1747
|
+
turn.abortSignal.removeEventListener("abort", onHostAbort);
|
|
1748
|
+
queryInput.close();
|
|
1749
|
+
try {
|
|
1750
|
+
await q.return?.(
|
|
1751
|
+
void 0
|
|
1752
|
+
);
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
if (emittedTerminalError) return;
|
|
1757
|
+
emittedTerminalFinish = true;
|
|
1758
|
+
void emittedTerminalFinish;
|
|
1759
|
+
emit({
|
|
1760
|
+
type: "finish",
|
|
1761
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
1762
|
+
totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
|
|
1763
|
+
...totalCostUsd !== void 0 || lastClaudeSessionId !== void 0 ? {
|
|
1764
|
+
harnessMetadata: {
|
|
1765
|
+
"claude-code": {
|
|
1766
|
+
...totalCostUsd !== void 0 ? { costUsd: totalCostUsd } : {},
|
|
1767
|
+
// The conversation this turn belongs to, resumable outside the
|
|
1768
|
+
// SDK with `claude --resume <sessionId>` and captured by the
|
|
1769
|
+
// adapter for exact cross-process resume.
|
|
1770
|
+
...lastClaudeSessionId !== void 0 ? { sessionId: lastClaudeSessionId } : {}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
} : {}
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
function createQueryInput({
|
|
1777
|
+
initialUserMessage,
|
|
1778
|
+
userMessages,
|
|
1779
|
+
abortSignal
|
|
1780
|
+
}) {
|
|
1781
|
+
let closed = false;
|
|
1782
|
+
let observedResult = false;
|
|
1783
|
+
const submittedMessages = /* @__PURE__ */ new Map();
|
|
1784
|
+
const close = (error) => {
|
|
1785
|
+
if (closed) return;
|
|
1786
|
+
closed = true;
|
|
1787
|
+
userMessages.close(error);
|
|
1788
|
+
};
|
|
1789
|
+
if (abortSignal.aborted) {
|
|
1790
|
+
close(abortSignal.reason);
|
|
1791
|
+
} else {
|
|
1792
|
+
abortSignal.addEventListener("abort", () => close(abortSignal.reason), {
|
|
1793
|
+
once: true
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
const toUserMessage = (options) => ({
|
|
1797
|
+
type: "user",
|
|
1798
|
+
message: {
|
|
1799
|
+
role: "user",
|
|
1800
|
+
content: [{ type: "text", text: options.text }]
|
|
1801
|
+
},
|
|
1802
|
+
parent_tool_use_id: null,
|
|
1803
|
+
uuid: options.messageId,
|
|
1804
|
+
...options.priority == null ? {} : { priority: options.priority }
|
|
1805
|
+
});
|
|
1806
|
+
const messageIterator = userMessages[Symbol.asyncIterator]();
|
|
1807
|
+
return {
|
|
1808
|
+
close,
|
|
1809
|
+
handleLifecycle: (message) => {
|
|
1810
|
+
const lifecycle = message;
|
|
1811
|
+
if (lifecycle.command_uuid == null || lifecycle.state == null) return;
|
|
1812
|
+
const submitted = submittedMessages.get(lifecycle.command_uuid);
|
|
1813
|
+
if (submitted == null) return;
|
|
1814
|
+
if (lifecycle.state === "queued" || lifecycle.state === "started") {
|
|
1815
|
+
submitted.accept();
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (lifecycle.state === "cancelled" || lifecycle.state === "discarded") {
|
|
1819
|
+
submitted.reject(
|
|
1820
|
+
new Error(`Claude Code ${lifecycle.state} the user message.`)
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
submittedMessages.delete(lifecycle.command_uuid);
|
|
1824
|
+
},
|
|
1825
|
+
hasActiveUserMessages: () => submittedMessages.size > 0 || userMessages.pendingCount > 0,
|
|
1826
|
+
observeResult: () => {
|
|
1827
|
+
observedResult = true;
|
|
1828
|
+
},
|
|
1829
|
+
get hasObservedResult() {
|
|
1830
|
+
return observedResult;
|
|
1831
|
+
},
|
|
1832
|
+
input: {
|
|
1833
|
+
[Symbol.asyncIterator]() {
|
|
1834
|
+
let sentInitial = false;
|
|
1835
|
+
return {
|
|
1836
|
+
async next() {
|
|
1837
|
+
if (closed || abortSignal.aborted) {
|
|
1838
|
+
return {
|
|
1839
|
+
value: void 0,
|
|
1840
|
+
done: true
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
if (!sentInitial) {
|
|
1844
|
+
sentInitial = true;
|
|
1845
|
+
return {
|
|
1846
|
+
value: toUserMessage({
|
|
1847
|
+
text: initialUserMessage,
|
|
1848
|
+
messageId: randomUUID3()
|
|
1849
|
+
}),
|
|
1850
|
+
done: false
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
const nextMessage = await messageIterator.next();
|
|
1854
|
+
if (nextMessage.done) {
|
|
1855
|
+
return {
|
|
1856
|
+
value: void 0,
|
|
1857
|
+
done: true
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
submittedMessages.set(
|
|
1861
|
+
nextMessage.value.messageId,
|
|
1862
|
+
nextMessage.value
|
|
1863
|
+
);
|
|
1864
|
+
return {
|
|
1865
|
+
value: toUserMessage({
|
|
1866
|
+
text: nextMessage.value.text,
|
|
1867
|
+
messageId: nextMessage.value.messageId,
|
|
1868
|
+
priority: "next"
|
|
1869
|
+
}),
|
|
1870
|
+
done: false
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
function addUsage(total, usage) {
|
|
1879
|
+
if (total == null) return usage;
|
|
1880
|
+
const result = { ...total };
|
|
1881
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
1882
|
+
const previous = result[key];
|
|
1883
|
+
if (typeof value === "number" && typeof previous === "number") {
|
|
1884
|
+
result[key] = previous + value;
|
|
1885
|
+
} else if (value != null && previous != null && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous)) {
|
|
1886
|
+
result[key] = addUsage(
|
|
1887
|
+
previous,
|
|
1888
|
+
value
|
|
1889
|
+
);
|
|
1890
|
+
} else {
|
|
1891
|
+
result[key] = value;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return result;
|
|
1895
|
+
}
|
|
1896
|
+
function parseArgs(args2) {
|
|
1897
|
+
const out = {};
|
|
1898
|
+
for (let i = 0; i < args2.length; i++) {
|
|
1899
|
+
if (args2[i] === "--workdir" && i + 1 < args2.length) {
|
|
1900
|
+
out.workdir = args2[++i];
|
|
1901
|
+
} else if (args2[i] === "--bridge-state-dir" && i + 1 < args2.length) {
|
|
1902
|
+
out.bridgeStateDir = args2[++i];
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return out;
|
|
1906
|
+
}
|
|
1907
|
+
function emitFatal(message) {
|
|
1908
|
+
stdout2.write(JSON.stringify({ type: "bridge-fatal", message }) + "\n");
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
//# sourceMappingURL=index.mjs.map
|