@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.
- package/CHANGELOG.md +15 -0
- package/README.md +5 -0
- package/dist/bridge/index.mjs +1619 -1792
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +25747 -25734
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1697 -1969
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/src/bridge/create-emit-stream-event.ts +49 -2
- package/src/bridge/index.ts +8 -2
- package/src/claude-code-bridge-protocol.ts +2 -0
- package/src/claude-code-harness.ts +26 -0
- package/src/index.ts +1 -0
package/dist/bridge/index.mjs
CHANGED
|
@@ -1,1910 +1,1737 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/bridge/compaction-latch.ts
|
|
647
581
|
function createCompactionLatch(emit) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
610
|
+
return {
|
|
611
|
+
type: "preset",
|
|
612
|
+
preset: "claude_code",
|
|
613
|
+
...instructions ? { append: instructions } : {}
|
|
614
|
+
};
|
|
687
615
|
}
|
|
688
|
-
|
|
689
|
-
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region src/bridge/claude-skills-option.ts
|
|
690
618
|
function toClaudeSkillsOption(skills) {
|
|
691
|
-
|
|
619
|
+
return skills && skills.length > 0 ? "all" : void 0;
|
|
692
620
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/bridge/create-emit-stream-event.ts
|
|
696
623
|
function createClaudeStreamEventState() {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
714
|
-
|
|
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
|
-
|
|
655
|
+
return nativeName.startsWith("mcp__") && !nativeName.startsWith(HOST_TOOL_PREFIX);
|
|
717
656
|
}
|
|
718
|
-
function createEmitStreamEvent({
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
958
|
+
return usage != null && typeof usage === "object" ? usage : void 0;
|
|
1012
959
|
}
|
|
1013
960
|
function mergeNonNullUsage(current, update) {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
974
|
+
return entry != null && typeof entry === "object" && "text" in entry;
|
|
1031
975
|
}
|
|
1032
976
|
function stringifyContent(content) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|
-
|
|
982
|
+
return Array.isArray(content) && content.some((entry) => !isTextEntry(entry));
|
|
1043
983
|
}
|
|
1044
|
-
function resolveToolResult({
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
-
|
|
1089
|
-
import { z } from "zod/v4";
|
|
1031
|
+
//#endregion
|
|
1032
|
+
//#region src/bridge/json-schema-to-zod.ts
|
|
1090
1033
|
function jsonSchemaToZodObject(input) {
|
|
1091
|
-
|
|
1092
|
-
return toZodObject(schema);
|
|
1034
|
+
return toZodObject(isJsonSchemaObject(input) ? input : {});
|
|
1093
1035
|
}
|
|
1094
1036
|
function toZodObject(schema) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
|
|
1097
|
+
return Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : [schema.type].filter(Boolean);
|
|
1171
1098
|
}
|
|
1172
1099
|
function isNullable(schema) {
|
|
1173
|
-
|
|
1100
|
+
return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null");
|
|
1174
1101
|
}
|
|
1175
1102
|
function isJsonSchemaObject(input) {
|
|
1176
|
-
|
|
1103
|
+
return input != null && typeof input === "object" && !Array.isArray(input);
|
|
1177
1104
|
}
|
|
1178
1105
|
function isJsonLiteral(value) {
|
|
1179
|
-
|
|
1106
|
+
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
1180
1107
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
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
|
-
|
|
1138
|
+
const PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
|
|
1212
1139
|
function toNativeName(toolName) {
|
|
1213
|
-
|
|
1140
|
+
return PUBLIC_TO_NATIVE[toolName] ?? toolName;
|
|
1214
1141
|
}
|
|
1215
1142
|
function resolveNativeTools(toolFiltering) {
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
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
|
-
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/bridge/question-tool.ts
|
|
1228
1152
|
function claudeCodeQuestionKey(nativeInput) {
|
|
1229
|
-
|
|
1153
|
+
return JSON.stringify(nativeInput.questions);
|
|
1230
1154
|
}
|
|
1231
1155
|
function toHarnessQuestionsInput(nativeInput) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
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(
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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
|
-
|
|
1908
|
-
|
|
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
|