@ai-sdk/harness 1.0.133 → 1.0.135
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 +25 -0
- package/agent/index.ts +1 -59
- package/bridge/index.ts +1 -15
- package/dist/agent/index.d.ts +1546 -1509
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +4419 -5044
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +149 -148
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +579 -650
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +1447 -1422
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +703 -668
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +551 -522
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +1910 -2465
- package/dist/utils/index.js.map +1 -1
- package/package.json +9 -9
- package/src/agent/index.ts +59 -0
- package/src/bridge/index.ts +1 -1
package/dist/bridge/index.js
CHANGED
|
@@ -1,669 +1,598 @@
|
|
|
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 { env, pid, stdout } from "node:process";
|
|
6
5
|
import { WebSocketServer } from "ws";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
//#region src/bridge/harness-bridge-capability-unsupported-error.ts
|
|
7
|
+
const name = "AI_HarnessBridgeCapabilityUnsupportedError";
|
|
8
|
+
/**
|
|
9
|
+
* Signals an unsupported capability discovered inside a sandbox bridge.
|
|
10
|
+
* `isInstance` also recognizes the serialized error shape received by the
|
|
11
|
+
* host, where it can be translated to `HarnessCapabilityUnsupportedError`.
|
|
12
|
+
*/
|
|
10
13
|
var HarnessBridgeCapabilityUnsupportedError = class extends Error {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
static isInstance(error) {
|
|
22
|
-
return error != null && typeof error === "object" && "name" in error && error.name === name && "message" in error && typeof error.message === "string";
|
|
23
|
-
}
|
|
14
|
+
constructor({ message, harnessId, cause }) {
|
|
15
|
+
super(message);
|
|
16
|
+
Object.defineProperty(this, "name", { value: name });
|
|
17
|
+
this.harnessId = harnessId;
|
|
18
|
+
this.cause = cause;
|
|
19
|
+
}
|
|
20
|
+
static isInstance(error) {
|
|
21
|
+
return error != null && typeof error === "object" && "name" in error && error.name === name && "message" in error && typeof error.message === "string";
|
|
22
|
+
}
|
|
24
23
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/bridge/index.ts
|
|
26
|
+
const DEBUG_LEVEL_WEIGHT = {
|
|
27
|
+
error: 0,
|
|
28
|
+
warn: 1,
|
|
29
|
+
info: 2,
|
|
30
|
+
debug: 3,
|
|
31
|
+
trace: 4
|
|
33
32
|
};
|
|
33
|
+
/** Exact-or-dotted-prefix subsystem match (`'bridge'` matches `'bridge.turn'`). */
|
|
34
34
|
function subsystemMatches(filters, subsystem) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
(filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
|
|
38
|
-
);
|
|
35
|
+
if (!filters || filters.length === 0) return true;
|
|
36
|
+
return filters.some((filter) => subsystem === filter || subsystem.startsWith(`${filter}.`));
|
|
39
37
|
}
|
|
40
38
|
function formatBridgeError(err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { message: String(err) };
|
|
39
|
+
if (err instanceof Error) return {
|
|
40
|
+
name: err.name,
|
|
41
|
+
message: err.message,
|
|
42
|
+
stack: err.stack
|
|
43
|
+
};
|
|
44
|
+
if (typeof err === "string") return { message: err };
|
|
45
|
+
if (err !== null && typeof err === "object") try {
|
|
46
|
+
return { message: JSON.stringify(err) };
|
|
47
|
+
} catch {}
|
|
48
|
+
return { message: String(err) };
|
|
54
49
|
}
|
|
55
50
|
function createBridgeUserMessageQueue(options) {
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
value: void 0
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return new Promise(
|
|
146
|
-
(resolve) => {
|
|
147
|
-
waiters.push(resolve);
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
};
|
|
51
|
+
const messages = [];
|
|
52
|
+
const waiters = [];
|
|
53
|
+
const entries = /* @__PURE__ */ new Map();
|
|
54
|
+
let closed = false;
|
|
55
|
+
let pendingCount = 0;
|
|
56
|
+
const enqueue = (input) => {
|
|
57
|
+
const existing = entries.get(input.messageId);
|
|
58
|
+
if (existing != null) {
|
|
59
|
+
if (existing.response != null) options.respond(existing.response);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let settled = false;
|
|
63
|
+
const settle = (response) => {
|
|
64
|
+
if (settled) return;
|
|
65
|
+
settled = true;
|
|
66
|
+
pendingCount--;
|
|
67
|
+
const entry = entries.get(input.messageId);
|
|
68
|
+
if (entry != null) entry.response = response;
|
|
69
|
+
options.respond(response);
|
|
70
|
+
};
|
|
71
|
+
const message = {
|
|
72
|
+
messageId: input.messageId,
|
|
73
|
+
text: input.text,
|
|
74
|
+
accept: () => {
|
|
75
|
+
settle({
|
|
76
|
+
type: "user-message-response",
|
|
77
|
+
messageId: input.messageId,
|
|
78
|
+
accepted: true
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
reject: (error) => {
|
|
82
|
+
settle({
|
|
83
|
+
type: "user-message-response",
|
|
84
|
+
messageId: input.messageId,
|
|
85
|
+
accepted: false,
|
|
86
|
+
error: { message: formatBridgeError(error).message }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
entries.set(input.messageId, { reject: message.reject });
|
|
91
|
+
pendingCount++;
|
|
92
|
+
if (closed) {
|
|
93
|
+
message.reject(/* @__PURE__ */ new Error("The bridge turn is no longer accepting user messages."));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const waiter = waiters.shift();
|
|
97
|
+
if (waiter != null) waiter({
|
|
98
|
+
done: false,
|
|
99
|
+
value: message
|
|
100
|
+
});
|
|
101
|
+
else messages.push(message);
|
|
102
|
+
};
|
|
103
|
+
const close = (error) => {
|
|
104
|
+
if (closed) return;
|
|
105
|
+
closed = true;
|
|
106
|
+
const reason = error ?? /* @__PURE__ */ new Error("The bridge turn ended before accepting the user message.");
|
|
107
|
+
for (const entry of entries.values()) if (entry.response == null) entry.reject(reason);
|
|
108
|
+
messages.length = 0;
|
|
109
|
+
while (waiters.length > 0) waiters.shift()({
|
|
110
|
+
done: true,
|
|
111
|
+
value: void 0
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
get pendingCount() {
|
|
116
|
+
return pendingCount;
|
|
117
|
+
},
|
|
118
|
+
enqueue,
|
|
119
|
+
close,
|
|
120
|
+
[Symbol.asyncIterator]() {
|
|
121
|
+
return { next: () => {
|
|
122
|
+
const message = messages.shift();
|
|
123
|
+
if (message != null) return Promise.resolve({
|
|
124
|
+
done: false,
|
|
125
|
+
value: message
|
|
126
|
+
});
|
|
127
|
+
if (closed) return Promise.resolve({
|
|
128
|
+
done: true,
|
|
129
|
+
value: void 0
|
|
130
|
+
});
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
waiters.push(resolve);
|
|
133
|
+
});
|
|
134
|
+
} };
|
|
135
|
+
}
|
|
136
|
+
};
|
|
154
137
|
}
|
|
155
138
|
function parseEnvList(value) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
139
|
+
if (!value) return void 0;
|
|
140
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
141
|
+
return items.length > 0 ? items : void 0;
|
|
159
142
|
}
|
|
160
|
-
|
|
161
|
-
|
|
143
|
+
const ENV_TRUTHY = /* @__PURE__ */ new Set([
|
|
144
|
+
"1",
|
|
145
|
+
"true",
|
|
146
|
+
"yes",
|
|
147
|
+
"on"
|
|
148
|
+
]);
|
|
149
|
+
const WS_OPEN = 1;
|
|
162
150
|
async function runBridge(options) {
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
);
|
|
594
|
-
options.onListening?.(currentBoundPort);
|
|
595
|
-
});
|
|
596
|
-
wss.on("connection", (ws, req) => {
|
|
597
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
598
|
-
if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
|
|
599
|
-
ws.close(1008, "unauthorized");
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
sendControl(ws, {
|
|
603
|
-
type: "bridge-hello",
|
|
604
|
-
state: currentTurnState,
|
|
605
|
-
lastSeq: seqCounter,
|
|
606
|
-
capabilities: { experimental_userMessageResponses: true }
|
|
607
|
-
});
|
|
608
|
-
ws.on("message", (raw) => {
|
|
609
|
-
let parsed;
|
|
610
|
-
try {
|
|
611
|
-
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
612
|
-
parsed = JSON.parse(text);
|
|
613
|
-
} catch (err) {
|
|
614
|
-
sendControl(ws, {
|
|
615
|
-
type: "error",
|
|
616
|
-
error: `protocol parse error: ${err.message}`
|
|
617
|
-
});
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
void handleInbound(parsed, ws);
|
|
621
|
-
});
|
|
622
|
-
ws.on("close", () => {
|
|
623
|
-
if (activeSocket === ws) {
|
|
624
|
-
activeSocket = void 0;
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
ws.on("error", () => {
|
|
628
|
-
});
|
|
629
|
-
});
|
|
630
|
-
process.on("uncaughtException", (err) => {
|
|
631
|
-
emitError({ error: err, message: "uncaught exception" });
|
|
632
|
-
});
|
|
633
|
-
process.on("unhandledRejection", (err) => {
|
|
634
|
-
emitError({ error: err, message: "unhandled rejection" });
|
|
635
|
-
});
|
|
636
|
-
await new Promise((resolve, reject) => {
|
|
637
|
-
if (wss.address() != null) {
|
|
638
|
-
resolve();
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
wss.once("listening", resolve);
|
|
642
|
-
wss.once("error", reject);
|
|
643
|
-
});
|
|
644
|
-
return {
|
|
645
|
-
port: currentBoundPort,
|
|
646
|
-
close: () => new Promise((resolve) => {
|
|
647
|
-
wss.close(() => resolve());
|
|
648
|
-
})
|
|
649
|
-
};
|
|
151
|
+
const { bridgeType, bridgeStateDir, onStart, onStop, onDestroy } = options;
|
|
152
|
+
const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
|
|
153
|
+
const expectedToken = options.token ?? env.BRIDGE_CHANNEL_TOKEN ?? "";
|
|
154
|
+
const bridgeWsPort = options.port ?? parseInt(env.BRIDGE_WS_PORT ?? "0", 10);
|
|
155
|
+
const bridgeMetaPath = `${bridgeStateDir}/bridge-meta.json`;
|
|
156
|
+
const startConfigPath = `${bridgeStateDir}/start-config.json`;
|
|
157
|
+
const rerunStartConfigPath = `${bridgeStateDir}/rerun-start-config.json`;
|
|
158
|
+
const eventLogPath = `${bridgeStateDir}/event-log.ndjson`;
|
|
159
|
+
try {
|
|
160
|
+
await mkdir(bridgeStateDir, { recursive: true });
|
|
161
|
+
} catch {}
|
|
162
|
+
let currentBoundPort = 0;
|
|
163
|
+
let currentTurnState = "init";
|
|
164
|
+
let activeSocket;
|
|
165
|
+
let isFirstTurn = true;
|
|
166
|
+
let turnAbort;
|
|
167
|
+
let currentUserMessages;
|
|
168
|
+
/**
|
|
169
|
+
* Settles when the in-flight turn has fully wound down — `onStart`
|
|
170
|
+
* returned or threw AND its completion state was recorded. `undefined`
|
|
171
|
+
* between turns. A new `start` fences on this so turns never overlap.
|
|
172
|
+
*/
|
|
173
|
+
let activeTurn;
|
|
174
|
+
let debugConfig;
|
|
175
|
+
let consoleCaptureInstalled = false;
|
|
176
|
+
const envDebugEnabled = ENV_TRUTHY.has((env.HARNESS_DEBUG ?? "").toLowerCase());
|
|
177
|
+
let seqCounter = 0;
|
|
178
|
+
let eventLog = [];
|
|
179
|
+
let diskBuffer = "";
|
|
180
|
+
let flushPromise = null;
|
|
181
|
+
const flushEventsToDisk = async () => {
|
|
182
|
+
while (diskBuffer.length > 0) {
|
|
183
|
+
const buf = diskBuffer;
|
|
184
|
+
diskBuffer = "";
|
|
185
|
+
await appendFile(eventLogPath, buf).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const scheduleEventFlush = () => {
|
|
189
|
+
if (flushPromise) return;
|
|
190
|
+
flushPromise = new Promise((resolve) => {
|
|
191
|
+
setImmediate(() => {
|
|
192
|
+
flushEventsToDisk().finally(resolve);
|
|
193
|
+
});
|
|
194
|
+
}).finally(() => {
|
|
195
|
+
flushPromise = null;
|
|
196
|
+
if (diskBuffer.length > 0) scheduleEventFlush();
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const flushPendingEventsToDisk = async () => {
|
|
200
|
+
if (diskBuffer.length > 0 && !flushPromise) scheduleEventFlush();
|
|
201
|
+
let inFlight = flushPromise;
|
|
202
|
+
while (inFlight) {
|
|
203
|
+
await inFlight;
|
|
204
|
+
inFlight = flushPromise;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
if (env.BRIDGE_REPLAY_FROM_DISK === "1" && existsSync(eventLogPath)) try {
|
|
208
|
+
eventLog = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => ({
|
|
209
|
+
seq: JSON.parse(line).seq,
|
|
210
|
+
line
|
|
211
|
+
}));
|
|
212
|
+
seqCounter = eventLog.at(-1)?.seq ?? 0;
|
|
213
|
+
} catch {
|
|
214
|
+
eventLog = [];
|
|
215
|
+
seqCounter = 0;
|
|
216
|
+
}
|
|
217
|
+
const pendingToolResults = /* @__PURE__ */ new Map();
|
|
218
|
+
const bufferedToolResults = [];
|
|
219
|
+
const pendingToolApprovals = /* @__PURE__ */ new Map();
|
|
220
|
+
const writeBridgeMeta = async (state) => {
|
|
221
|
+
try {
|
|
222
|
+
await writeFile(bridgeMetaPath, JSON.stringify({
|
|
223
|
+
type: bridgeType,
|
|
224
|
+
port: currentBoundPort,
|
|
225
|
+
state,
|
|
226
|
+
pid
|
|
227
|
+
}));
|
|
228
|
+
} catch {}
|
|
229
|
+
};
|
|
230
|
+
const writeStartConfig = async (start) => {
|
|
231
|
+
try {
|
|
232
|
+
const serialized = JSON.stringify(start);
|
|
233
|
+
await writeFile(startConfigPath, serialized);
|
|
234
|
+
if (!existsSync(rerunStartConfigPath)) await writeFile(rerunStartConfigPath, serialized);
|
|
235
|
+
} catch {}
|
|
236
|
+
};
|
|
237
|
+
const emit = (event) => {
|
|
238
|
+
const seq = ++seqCounter;
|
|
239
|
+
const line = JSON.stringify({
|
|
240
|
+
...event,
|
|
241
|
+
seq
|
|
242
|
+
});
|
|
243
|
+
eventLog.push({
|
|
244
|
+
seq,
|
|
245
|
+
line
|
|
246
|
+
});
|
|
247
|
+
diskBuffer += `${line}\n`;
|
|
248
|
+
scheduleEventFlush();
|
|
249
|
+
if (activeSocket?.readyState === WS_OPEN) try {
|
|
250
|
+
activeSocket.send(line);
|
|
251
|
+
} catch {}
|
|
252
|
+
};
|
|
253
|
+
const replay = (ws, afterSeq) => {
|
|
254
|
+
for (const entry of eventLog) if (entry.seq > afterSeq && ws.readyState === WS_OPEN) ws.send(entry.line);
|
|
255
|
+
};
|
|
256
|
+
const shouldEmitDebugEvent = (level, subsystem) => {
|
|
257
|
+
if (!debugConfig?.enabled) return false;
|
|
258
|
+
const threshold = debugConfig.level ?? "debug";
|
|
259
|
+
if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
|
|
260
|
+
return subsystemMatches(debugConfig.subsystems, subsystem);
|
|
261
|
+
};
|
|
262
|
+
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
263
|
+
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
264
|
+
const writeErrorToStderr = (input) => {
|
|
265
|
+
try {
|
|
266
|
+
const formatted = formatBridgeError(input.error);
|
|
267
|
+
rawStderrWrite(`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}\n`);
|
|
268
|
+
if (formatted.stack) rawStderrWrite(`${formatted.stack}\n`);
|
|
269
|
+
} catch {}
|
|
270
|
+
};
|
|
271
|
+
const emitWarning = (input) => {
|
|
272
|
+
try {
|
|
273
|
+
for (const line of input.message.split("\n")) if (line.trim().length > 0) rawStderrWrite(`[harness:${bridgeType}:warn] ${line}\n`);
|
|
274
|
+
} catch {}
|
|
275
|
+
};
|
|
276
|
+
const emitError = (input) => {
|
|
277
|
+
writeErrorToStderr({
|
|
278
|
+
message: input.message ?? "bridge error",
|
|
279
|
+
error: input.error
|
|
280
|
+
});
|
|
281
|
+
emit({
|
|
282
|
+
type: "error",
|
|
283
|
+
error: serialiseError(input.error)
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const installConsoleCapture = () => {
|
|
287
|
+
if (consoleCaptureInstalled) return;
|
|
288
|
+
consoleCaptureInstalled = true;
|
|
289
|
+
const buffers = {
|
|
290
|
+
stdout: "",
|
|
291
|
+
stderr: ""
|
|
292
|
+
};
|
|
293
|
+
const patch = (stream, raw) => (chunk, encoding, cb) => {
|
|
294
|
+
if (debugConfig?.enabled) try {
|
|
295
|
+
const enc = typeof encoding === "string" ? encoding : "utf8";
|
|
296
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(enc);
|
|
297
|
+
const parts = (buffers[stream] + text.replace(/\r\n/g, "\n")).split("\n");
|
|
298
|
+
buffers[stream] = parts.pop() ?? "";
|
|
299
|
+
for (const line of parts) {
|
|
300
|
+
const trimmed = line.replace(/\s+$/, "");
|
|
301
|
+
if (trimmed) emit({
|
|
302
|
+
type: "sandbox-log",
|
|
303
|
+
source: bridgeType,
|
|
304
|
+
stream,
|
|
305
|
+
line: trimmed
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
} catch {}
|
|
309
|
+
return raw(chunk, encoding, cb);
|
|
310
|
+
};
|
|
311
|
+
process.stdout.write = patch("stdout", rawStdoutWrite);
|
|
312
|
+
process.stderr.write = patch("stderr", rawStderrWrite);
|
|
313
|
+
};
|
|
314
|
+
const handleInbound = async (msg, ws) => {
|
|
315
|
+
switch (msg.type) {
|
|
316
|
+
case "start": {
|
|
317
|
+
for (;;) {
|
|
318
|
+
const pendingTurn = activeTurn;
|
|
319
|
+
if (pendingTurn == null) break;
|
|
320
|
+
turnAbort?.abort();
|
|
321
|
+
currentUserMessages?.close(/* @__PURE__ */ new Error("A new bridge turn replaced the active turn."));
|
|
322
|
+
let graceTimer;
|
|
323
|
+
const settled = await Promise.race([pendingTurn.then(() => true), new Promise((resolve) => {
|
|
324
|
+
graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
|
|
325
|
+
graceTimer.unref?.();
|
|
326
|
+
})]);
|
|
327
|
+
clearTimeout(graceTimer);
|
|
328
|
+
if (!settled) break;
|
|
329
|
+
}
|
|
330
|
+
let turnFinished;
|
|
331
|
+
const thisTurn = new Promise((resolve) => turnFinished = resolve);
|
|
332
|
+
activeTurn = thisTurn;
|
|
333
|
+
activeSocket = ws;
|
|
334
|
+
const firstTurn = isFirstTurn;
|
|
335
|
+
isFirstTurn = false;
|
|
336
|
+
eventLog = [];
|
|
337
|
+
diskBuffer = "";
|
|
338
|
+
writeFile(eventLogPath, "").catch(() => {});
|
|
339
|
+
turnAbort = new AbortController();
|
|
340
|
+
currentTurnState = "running";
|
|
341
|
+
writeStartConfig(msg);
|
|
342
|
+
writeBridgeMeta("running");
|
|
343
|
+
const startDebug = msg.debug;
|
|
344
|
+
debugConfig = {
|
|
345
|
+
enabled: startDebug?.enabled ?? envDebugEnabled,
|
|
346
|
+
level: startDebug?.level ?? env.HARNESS_DEBUG_LEVEL,
|
|
347
|
+
subsystems: startDebug?.subsystems ?? parseEnvList(env.HARNESS_DEBUG_SUBSYSTEMS)
|
|
348
|
+
};
|
|
349
|
+
if (debugConfig.enabled) installConsoleCapture();
|
|
350
|
+
const userMessages = createBridgeUserMessageQueue({ respond: emit });
|
|
351
|
+
const turn = {
|
|
352
|
+
emit,
|
|
353
|
+
requestToolResult: (requestInput) => {
|
|
354
|
+
const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
|
|
355
|
+
const bufferedIndex = bufferedToolResults.findIndex((buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true);
|
|
356
|
+
if (bufferedIndex >= 0) return Promise.resolve(bufferedToolResults.splice(bufferedIndex, 1)[0].result);
|
|
357
|
+
return new Promise((resolve) => {
|
|
358
|
+
pendingToolResults.set(request.toolCallId, {
|
|
359
|
+
resolve,
|
|
360
|
+
matches: request.matches
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
requestToolApproval: (approvalId) => new Promise((resolve) => {
|
|
365
|
+
pendingToolApprovals.set(approvalId, resolve);
|
|
366
|
+
}),
|
|
367
|
+
experimental_userMessages: userMessages,
|
|
368
|
+
abortSignal: turnAbort.signal,
|
|
369
|
+
firstTurn,
|
|
370
|
+
bridgeLog: (input) => {
|
|
371
|
+
const level = input.level ?? "debug";
|
|
372
|
+
if (!shouldEmitDebugEvent(level, input.subsystem)) return;
|
|
373
|
+
emit({
|
|
374
|
+
type: "debug-event",
|
|
375
|
+
level,
|
|
376
|
+
subsystem: input.subsystem,
|
|
377
|
+
message: input.message,
|
|
378
|
+
...input.attrs ? { attrs: input.attrs } : {},
|
|
379
|
+
...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
emitWarning,
|
|
383
|
+
emitError
|
|
384
|
+
};
|
|
385
|
+
currentUserMessages = userMessages;
|
|
386
|
+
try {
|
|
387
|
+
await onStart(msg, turn);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
emitError({
|
|
390
|
+
error: err,
|
|
391
|
+
message: "bridge turn failed"
|
|
392
|
+
});
|
|
393
|
+
} finally {
|
|
394
|
+
userMessages.close();
|
|
395
|
+
if (currentUserMessages === userMessages) currentUserMessages = void 0;
|
|
396
|
+
if (activeTurn === thisTurn) {
|
|
397
|
+
activeTurn = void 0;
|
|
398
|
+
currentTurnState = "waiting";
|
|
399
|
+
writeBridgeMeta("waiting");
|
|
400
|
+
}
|
|
401
|
+
turnFinished();
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
case "tool-result": {
|
|
406
|
+
const result = {
|
|
407
|
+
output: msg.output,
|
|
408
|
+
isError: msg.isError,
|
|
409
|
+
toolResult: msg.toolResult
|
|
410
|
+
};
|
|
411
|
+
const exactPending = pendingToolResults.get(msg.toolCallId);
|
|
412
|
+
const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(([, pending]) => pending.matches?.(result) === true) : void 0;
|
|
413
|
+
const pending = exactPending ?? matchingPending?.[1];
|
|
414
|
+
const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
|
|
415
|
+
if (pending != null && pendingId != null) {
|
|
416
|
+
pendingToolResults.delete(pendingId);
|
|
417
|
+
pending.resolve(result);
|
|
418
|
+
} else bufferedToolResults.push({
|
|
419
|
+
toolCallId: msg.toolCallId,
|
|
420
|
+
result
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
case "tool-approval-response": {
|
|
425
|
+
const resolver = pendingToolApprovals.get(msg.approvalId);
|
|
426
|
+
if (resolver) {
|
|
427
|
+
pendingToolApprovals.delete(msg.approvalId);
|
|
428
|
+
resolver({
|
|
429
|
+
approved: msg.approved,
|
|
430
|
+
reason: msg.reason
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
case "user-message": {
|
|
436
|
+
const messageId = msg.messageId ?? randomUUID();
|
|
437
|
+
if (currentUserMessages == null) {
|
|
438
|
+
sendControl(ws, {
|
|
439
|
+
type: "user-message-response",
|
|
440
|
+
messageId,
|
|
441
|
+
accepted: false,
|
|
442
|
+
error: { message: "The bridge has no active turn to steer." }
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (ws !== activeSocket) {
|
|
447
|
+
sendControl(ws, {
|
|
448
|
+
type: "user-message-response",
|
|
449
|
+
messageId,
|
|
450
|
+
accepted: false,
|
|
451
|
+
error: { message: "The connection does not own the active bridge turn." }
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
currentUserMessages.enqueue({
|
|
456
|
+
messageId,
|
|
457
|
+
text: msg.text
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
case "abort":
|
|
462
|
+
turnAbort?.abort();
|
|
463
|
+
return;
|
|
464
|
+
case "resume":
|
|
465
|
+
activeSocket = ws;
|
|
466
|
+
replay(ws, msg.lastSeenEventId);
|
|
467
|
+
return;
|
|
468
|
+
case "destroy":
|
|
469
|
+
currentTurnState = "done";
|
|
470
|
+
writeBridgeMeta("done");
|
|
471
|
+
await onDestroy?.();
|
|
472
|
+
drainThenExit(ws, 1e3, "destroy");
|
|
473
|
+
return;
|
|
474
|
+
case "stop":
|
|
475
|
+
currentTurnState = "done";
|
|
476
|
+
writeBridgeMeta("done");
|
|
477
|
+
sendControl(ws, {
|
|
478
|
+
type: "bridge-stop",
|
|
479
|
+
data: await onStop?.() ?? {}
|
|
480
|
+
});
|
|
481
|
+
drainThenExit(ws, 1e3, "stop");
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
writeBridgeMeta("init");
|
|
485
|
+
const wss = new WebSocketServer({
|
|
486
|
+
port: bridgeWsPort,
|
|
487
|
+
host: "0.0.0.0"
|
|
488
|
+
});
|
|
489
|
+
const exit = () => {
|
|
490
|
+
if (options.onExit) {
|
|
491
|
+
options.onExit();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
wss.close(() => process.exit(0));
|
|
495
|
+
setTimeout(() => process.exit(0), 1e3).unref();
|
|
496
|
+
};
|
|
497
|
+
const drainThenExit = (ws, code, reason) => {
|
|
498
|
+
const start = Date.now();
|
|
499
|
+
const tick = () => {
|
|
500
|
+
if (ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN || Date.now() - start >= 5e3) {
|
|
501
|
+
flushPendingEventsToDisk().finally(() => {
|
|
502
|
+
try {
|
|
503
|
+
ws.close(code, reason);
|
|
504
|
+
} finally {
|
|
505
|
+
exit();
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
setTimeout(tick, 10).unref();
|
|
511
|
+
};
|
|
512
|
+
tick();
|
|
513
|
+
};
|
|
514
|
+
wss.on("listening", () => {
|
|
515
|
+
const addr = wss.address();
|
|
516
|
+
currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
|
|
517
|
+
currentTurnState = "waiting";
|
|
518
|
+
writeBridgeMeta("waiting");
|
|
519
|
+
stdout.write(JSON.stringify({
|
|
520
|
+
type: "bridge-ready",
|
|
521
|
+
port: currentBoundPort
|
|
522
|
+
}) + "\n");
|
|
523
|
+
options.onListening?.(currentBoundPort);
|
|
524
|
+
});
|
|
525
|
+
wss.on("connection", (ws, req) => {
|
|
526
|
+
if (new URL(req.url ?? "/", "http://localhost").searchParams.get("agent_bridge_token") !== expectedToken) {
|
|
527
|
+
ws.close(1008, "unauthorized");
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
sendControl(ws, {
|
|
531
|
+
type: "bridge-hello",
|
|
532
|
+
state: currentTurnState,
|
|
533
|
+
lastSeq: seqCounter,
|
|
534
|
+
capabilities: { experimental_userMessageResponses: true }
|
|
535
|
+
});
|
|
536
|
+
ws.on("message", (raw) => {
|
|
537
|
+
let parsed;
|
|
538
|
+
try {
|
|
539
|
+
const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
|
|
540
|
+
parsed = JSON.parse(text);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
sendControl(ws, {
|
|
543
|
+
type: "error",
|
|
544
|
+
error: `protocol parse error: ${err.message}`
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
handleInbound(parsed, ws);
|
|
549
|
+
});
|
|
550
|
+
ws.on("close", () => {
|
|
551
|
+
if (activeSocket === ws) activeSocket = void 0;
|
|
552
|
+
});
|
|
553
|
+
ws.on("error", () => {});
|
|
554
|
+
});
|
|
555
|
+
process.on("uncaughtException", (err) => {
|
|
556
|
+
emitError({
|
|
557
|
+
error: err,
|
|
558
|
+
message: "uncaught exception"
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
process.on("unhandledRejection", (err) => {
|
|
562
|
+
emitError({
|
|
563
|
+
error: err,
|
|
564
|
+
message: "unhandled rejection"
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
await new Promise((resolve, reject) => {
|
|
568
|
+
if (wss.address() != null) {
|
|
569
|
+
resolve();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
wss.once("listening", resolve);
|
|
573
|
+
wss.once("error", reject);
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
port: currentBoundPort,
|
|
577
|
+
close: () => new Promise((resolve) => {
|
|
578
|
+
wss.close(() => resolve());
|
|
579
|
+
})
|
|
580
|
+
};
|
|
650
581
|
}
|
|
651
582
|
function sendControl(socket, message) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
} catch {
|
|
656
|
-
}
|
|
657
|
-
}
|
|
583
|
+
if (socket?.readyState === WS_OPEN) try {
|
|
584
|
+
socket.send(JSON.stringify(message));
|
|
585
|
+
} catch {}
|
|
658
586
|
}
|
|
659
587
|
function serialiseError(err) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
588
|
+
if (err instanceof Error) return {
|
|
589
|
+
name: err.name,
|
|
590
|
+
message: err.message,
|
|
591
|
+
stack: err.stack
|
|
592
|
+
};
|
|
593
|
+
return err;
|
|
664
594
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
};
|
|
595
|
+
//#endregion
|
|
596
|
+
export { HarnessBridgeCapabilityUnsupportedError, runBridge };
|
|
597
|
+
|
|
669
598
|
//# sourceMappingURL=index.js.map
|