@ai-sdk/harness-claude-code 1.0.137 → 1.0.138

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