@vymalo/opencode-browser 0.12.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-client.d.ts +36 -36
- package/dist/agent-client.js +201 -189
- package/dist/agent-client.js.map +1 -1
- package/dist/broker.d.ts +85 -87
- package/dist/broker.js +614 -599
- package/dist/broker.js.map +1 -1
- package/dist/catalog.d.ts +76 -76
- package/dist/catalog.js +826 -596
- package/dist/catalog.js.map +1 -1
- package/dist/endpoint.d.ts +42 -42
- package/dist/endpoint.js +132 -131
- package/dist/endpoint.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/lib.js.map +1 -1
- package/dist/logging.d.ts +6 -6
- package/dist/logging.js +60 -63
- package/dist/logging.js.map +1 -1
- package/dist/node-transport.d.ts +6 -6
- package/dist/node-transport.js +48 -44
- package/dist/node-transport.js.map +1 -1
- package/dist/opencode.d.ts +10 -10
- package/dist/opencode.js +143 -146
- package/dist/opencode.js.map +1 -1
- package/dist/protocol.d.ts +127 -127
- package/dist/protocol.js +148 -128
- package/dist/protocol.js.map +1 -1
- package/dist/schema.d.ts +32 -32
- package/dist/schema.js +39 -44
- package/dist/schema.js.map +1 -1
- package/dist/token-file.d.ts +4 -4
- package/dist/token-file.js +68 -60
- package/dist/token-file.js.map +1 -1
- package/dist/tools.d.ts +11 -11
- package/dist/tools.js +128 -123
- package/dist/tools.js.map +1 -1
- package/dist/transport.d.ts +17 -17
- package/dist/transport.js +3 -9
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +61 -61
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
package/dist/broker.js
CHANGED
|
@@ -1,610 +1,625 @@
|
|
|
1
1
|
import { cancelFrame, decodeFrame, encodeFrame, errorFrame, nextId, PROTOCOL_VERSION, rejectedFrame, resultFrame, tokenFingerprint } from "./protocol.js";
|
|
2
2
|
/** Thrown when a command can't be routed or the executor reports failure. */
|
|
3
3
|
export class BrokerError extends Error {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
code;
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "BrokerError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export const DEFAULT_MAX_COMMAND_MS =
|
|
12
|
+
* Hard ceiling for any per-command timeout, so a tool requesting a long
|
|
13
|
+
* human-paced deadline can't pin a command open indefinitely. 10 minutes.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_MAX_COMMAND_MS = 6e5;
|
|
16
16
|
/** Actions that aren't group-scoped — routed to the primary/target executor. */
|
|
17
17
|
const GLOBAL_ACTIONS = new Set(["cookies"]);
|
|
18
18
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
* Hosts the WebSocket server and routes commands between **agents** (producers:
|
|
20
|
+
* the plugin, the MCP server, guest adapters) and **executors** (the browser
|
|
21
|
+
* extensions), keyed by named-group ownership. Generalizes the old single-client
|
|
22
|
+
* bridge. Runtime-agnostic via the injected transport.
|
|
23
|
+
*/
|
|
24
24
|
export class Broker {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
25
|
+
opts;
|
|
26
|
+
deps;
|
|
27
|
+
executors = new Map();
|
|
28
|
+
executorById = new Map();
|
|
29
|
+
agents = new Map();
|
|
30
|
+
groupOwner = new Map();
|
|
31
|
+
pending = new Map();
|
|
32
|
+
primaryExecutorId = null;
|
|
33
|
+
agentSeq = 0;
|
|
34
|
+
execSeq = 0;
|
|
35
|
+
started = false;
|
|
36
|
+
constructor(opts, deps) {
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
this.deps = deps;
|
|
39
|
+
}
|
|
40
|
+
get executorCount() {
|
|
41
|
+
return this.executorById.size;
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
if (this.started) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await this.deps.transport.listen({
|
|
48
|
+
host: this.opts.host,
|
|
49
|
+
port: this.opts.port
|
|
50
|
+
}, {
|
|
51
|
+
onOpen: () => {},
|
|
52
|
+
onMessage: (conn, data) => this.onMessage(conn, data),
|
|
53
|
+
onClose: (conn) => this.onClose(conn)
|
|
54
|
+
});
|
|
55
|
+
this.started = true;
|
|
56
|
+
this.deps.logger.info("browser_broker_listening", {
|
|
57
|
+
host: this.opts.host,
|
|
58
|
+
port: this.opts.port
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
stop() {
|
|
62
|
+
this.rejectAllPending(new BrokerError("broker stopped"));
|
|
63
|
+
this.executors.clear();
|
|
64
|
+
this.executorById.clear();
|
|
65
|
+
this.agents.clear();
|
|
66
|
+
this.groupOwner.clear();
|
|
67
|
+
this.primaryExecutorId = null;
|
|
68
|
+
this.started = false;
|
|
69
|
+
this.deps.transport.stop();
|
|
70
|
+
}
|
|
71
|
+
/** An in-process agent (the host adapter's own tools), bypassing a socket. */
|
|
72
|
+
createLocalAgent() {
|
|
73
|
+
const id = `local:${++this.agentSeq}`;
|
|
74
|
+
return {
|
|
75
|
+
send: (action, group, params, signal, target, timeoutMs) => this.route(id, {
|
|
76
|
+
action,
|
|
77
|
+
group,
|
|
78
|
+
params,
|
|
79
|
+
target,
|
|
80
|
+
timeoutMs
|
|
81
|
+
}, signal),
|
|
82
|
+
release: () => this.releaseForAgent(id)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// ─── connection handling ───────────────────────────────────────────────────
|
|
86
|
+
onMessage(conn, raw) {
|
|
87
|
+
const frame = decodeFrame(raw);
|
|
88
|
+
if (!frame) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (frame.type === "hello") {
|
|
92
|
+
this.handleHello(conn, frame);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const executor = this.executors.get(conn);
|
|
96
|
+
if (executor) {
|
|
97
|
+
if (frame.type === "result") {
|
|
98
|
+
this.handleResult(frame);
|
|
99
|
+
} else if (frame.type === "event") {
|
|
100
|
+
this.routeEvent(frame);
|
|
101
|
+
} else if (frame.type === "ping") {
|
|
102
|
+
this.safeSend(conn, {
|
|
103
|
+
v: PROTOCOL_VERSION,
|
|
104
|
+
type: "pong"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const agent = this.agents.get(conn);
|
|
110
|
+
if (agent) {
|
|
111
|
+
if (frame.type === "command") {
|
|
112
|
+
void this.handleAgentCommand(agent, conn, frame);
|
|
113
|
+
} else if (frame.type === "ping") {
|
|
114
|
+
this.safeSend(conn, {
|
|
115
|
+
v: PROTOCOL_VERSION,
|
|
116
|
+
type: "pong"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.deps.logger.warn("browser_frame_unauthenticated", { type: frame.type });
|
|
122
|
+
}
|
|
123
|
+
handleHello(conn, frame) {
|
|
124
|
+
const role = frame.role ?? "extension";
|
|
125
|
+
this.deps.logger.trace("browser_handshake_hello", {
|
|
126
|
+
role,
|
|
127
|
+
client: frame.client,
|
|
128
|
+
label: frame.label,
|
|
129
|
+
browser: frame.browser
|
|
130
|
+
});
|
|
131
|
+
if (frame.token !== this.opts.token) {
|
|
132
|
+
// The shared token may have rotated under this long-lived host (it resolved
|
|
133
|
+
// its token once at boot and otherwise never re-reads bridge.json). Re-read
|
|
134
|
+
// it; if the file now carries a different value, adopt it — so a rotation
|
|
135
|
+
// reaches a running host without a restart — then re-check the handshake.
|
|
136
|
+
this.adoptRotatedToken();
|
|
137
|
+
if (frame.token !== this.opts.token) {
|
|
138
|
+
// Fingerprints (never the raw tokens) make the mismatch diagnosable from
|
|
139
|
+
// the log alone — `expected` vs `got` of the same length but different
|
|
140
|
+
// value means a rotated/stale token, not a paste typo. role/client say
|
|
141
|
+
// which peer it was.
|
|
142
|
+
this.deps.logger.warn("browser_handshake_rejected", {
|
|
143
|
+
reason: "bad_token",
|
|
144
|
+
role: frame.role ?? "extension",
|
|
145
|
+
client: frame.client,
|
|
146
|
+
expected: tokenFingerprint(this.opts.token),
|
|
147
|
+
got: tokenFingerprint(frame.token)
|
|
148
|
+
});
|
|
149
|
+
// Tell the dialer *why* before dropping it, so it can show an actionable
|
|
150
|
+
// error and stop hammering instead of retrying a token that can't work.
|
|
151
|
+
this.safeSend(conn, rejectedFrame("bad_token"));
|
|
152
|
+
conn.close();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.deps.logger.info("browser_bridge_token_reloaded", { fingerprint: tokenFingerprint(this.opts.token) });
|
|
156
|
+
}
|
|
157
|
+
if (role === "agent") {
|
|
158
|
+
const id = `agent:${++this.agentSeq}`;
|
|
159
|
+
this.agents.set(conn, {
|
|
160
|
+
conn,
|
|
161
|
+
id
|
|
162
|
+
});
|
|
163
|
+
this.deps.logger.trace("browser_agent_accepted", {
|
|
164
|
+
id,
|
|
165
|
+
client: frame.client
|
|
166
|
+
});
|
|
167
|
+
this.deps.logger.info("browser_agent_connected", {
|
|
168
|
+
id,
|
|
169
|
+
client: frame.client
|
|
170
|
+
});
|
|
171
|
+
this.safeSend(conn, {
|
|
172
|
+
v: PROTOCOL_VERSION,
|
|
173
|
+
type: "ready",
|
|
174
|
+
server: "opencode-browser",
|
|
175
|
+
protocol: PROTOCOL_VERSION,
|
|
176
|
+
role: "agent",
|
|
177
|
+
clientId: id
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const id = frame.id || `exec:${++this.execSeq}`;
|
|
182
|
+
// Replace any stale connection carrying the same id (extension reconnect).
|
|
183
|
+
const prior = this.executorById.get(id);
|
|
184
|
+
if (prior && prior.conn !== conn) {
|
|
185
|
+
this.deps.logger.trace("browser_executor_replace_stale", { id });
|
|
186
|
+
this.executors.delete(prior.conn);
|
|
187
|
+
prior.conn.close();
|
|
188
|
+
}
|
|
189
|
+
const info = {
|
|
190
|
+
conn,
|
|
191
|
+
id,
|
|
192
|
+
label: frame.label || id,
|
|
193
|
+
browser: frame.browser || "unknown"
|
|
194
|
+
};
|
|
195
|
+
this.executors.set(conn, info);
|
|
196
|
+
this.executorById.set(id, info);
|
|
197
|
+
if (!this.primaryExecutorId || !this.executorById.has(this.primaryExecutorId)) {
|
|
198
|
+
this.primaryExecutorId = id;
|
|
199
|
+
this.deps.logger.trace("browser_primary_executor_set", { id });
|
|
200
|
+
}
|
|
201
|
+
this.deps.logger.info("browser_executor_connected", {
|
|
202
|
+
id,
|
|
203
|
+
label: info.label,
|
|
204
|
+
browser: info.browser
|
|
205
|
+
});
|
|
206
|
+
this.safeSend(conn, {
|
|
207
|
+
v: PROTOCOL_VERSION,
|
|
208
|
+
type: "ready",
|
|
209
|
+
server: "opencode-browser",
|
|
210
|
+
protocol: PROTOCOL_VERSION,
|
|
211
|
+
role: "extension",
|
|
212
|
+
clientId: id,
|
|
213
|
+
executor: this.opts.executor
|
|
214
|
+
});
|
|
215
|
+
// Rebuild group ownership from the executor's existing tabs (recovers after a
|
|
216
|
+
// broker re-election; groups come back orphaned and are adopted on first use).
|
|
217
|
+
void this.rebuildFromExecutor(info);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Re-read the shared token (via the injected `reloadToken`) and adopt it if the
|
|
221
|
+
* source of truth now holds a different, non-empty value. Lets a rotation in
|
|
222
|
+
* `bridge.json` reach this running host. No-op without a `reloadToken` dep, or
|
|
223
|
+
* when the file is unchanged/unavailable — so a genuinely wrong token still
|
|
224
|
+
* gets rejected. Reads only on a mismatch, so it's not on the hot path.
|
|
225
|
+
*/
|
|
226
|
+
adoptRotatedToken() {
|
|
227
|
+
const latest = this.deps.reloadToken?.();
|
|
228
|
+
if (latest && latest.length > 0 && latest !== this.opts.token) {
|
|
229
|
+
this.opts.token = latest;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async rebuildFromExecutor(executor) {
|
|
233
|
+
try {
|
|
234
|
+
const data = await this.sendToExecutor(executor, "broker", {
|
|
235
|
+
action: "tabs",
|
|
236
|
+
group: "",
|
|
237
|
+
params: {}
|
|
238
|
+
});
|
|
239
|
+
for (const g of data.groups ?? []) {
|
|
240
|
+
if (g.name && !this.groupOwner.has(g.name)) {
|
|
241
|
+
this.groupOwner.set(g.name, {
|
|
242
|
+
executorId: executor.id,
|
|
243
|
+
agentId: null
|
|
244
|
+
});
|
|
245
|
+
this.deps.logger.trace("browser_group_orphan_rebuilt", {
|
|
246
|
+
group: g.name,
|
|
247
|
+
executorId: executor.id
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
onClose(conn) {
|
|
254
|
+
const executor = this.executors.get(conn);
|
|
255
|
+
if (executor) {
|
|
256
|
+
this.executors.delete(conn);
|
|
257
|
+
// Only forget the id mapping if it still points at this connection.
|
|
258
|
+
if (this.executorById.get(executor.id)?.conn === conn) {
|
|
259
|
+
this.executorById.delete(executor.id);
|
|
260
|
+
for (const [group, owner] of this.groupOwner) {
|
|
261
|
+
if (owner.executorId === executor.id) {
|
|
262
|
+
this.deps.logger.trace("browser_group_dropped_executor_gone", {
|
|
263
|
+
group,
|
|
264
|
+
executorId: executor.id
|
|
265
|
+
});
|
|
266
|
+
this.groupOwner.delete(group);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const [reqId, p] of this.pending) {
|
|
270
|
+
if (p.executorId === executor.id) {
|
|
271
|
+
this.settleReject(reqId, new BrokerError("the browser disconnected", "executor_gone"));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (this.primaryExecutorId === executor.id) {
|
|
276
|
+
this.primaryExecutorId = this.executorById.keys().next().value ?? null;
|
|
277
|
+
}
|
|
278
|
+
this.deps.logger.info("browser_executor_disconnected", { id: executor.id });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const agent = this.agents.get(conn);
|
|
282
|
+
if (agent) {
|
|
283
|
+
this.agents.delete(conn);
|
|
284
|
+
// Orphan this agent's groups (adoptable by the next user) AND send a
|
|
285
|
+
// release frame to the executors it owned, so a guest agent dropping
|
|
286
|
+
// (MCP server / guest OpenCode exit) detaches the CDP debugger/banner
|
|
287
|
+
// instead of leaving it attached until a later manual release.
|
|
288
|
+
this.releaseForAgent(agent.id);
|
|
289
|
+
for (const [reqId, p] of this.pending) {
|
|
290
|
+
if (p.agentId === agent.id) {
|
|
291
|
+
// The executor may still be running this command — cancel it so any
|
|
292
|
+
// open UI (e.g. a feedback overlay) is torn down, not just orphaned.
|
|
293
|
+
this.abandon(reqId, new BrokerError("agent disconnected", "agent_gone"));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
this.deps.logger.info("browser_agent_disconnected", { id: agent.id });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ─── routing ───────────────────────────────────────────────────────────────
|
|
300
|
+
route(agentId, cmd, signal) {
|
|
301
|
+
if (cmd.action === "targets") {
|
|
302
|
+
return Promise.resolve(this.listTargets());
|
|
303
|
+
}
|
|
304
|
+
if (cmd.action === "release") {
|
|
305
|
+
this.releaseForAgent(agentId);
|
|
306
|
+
return Promise.resolve({ ok: true });
|
|
307
|
+
}
|
|
308
|
+
if (cmd.action === "tabs" && !cmd.group) {
|
|
309
|
+
return this.aggregateTabs();
|
|
310
|
+
}
|
|
311
|
+
let executor;
|
|
312
|
+
try {
|
|
313
|
+
executor = GLOBAL_ACTIONS.has(cmd.action) ? this.pickExecutor(cmd.target) : this.resolveExecutor(agentId, cmd);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
return Promise.reject(err);
|
|
316
|
+
}
|
|
317
|
+
return this.sendToExecutor(executor, agentId, cmd, signal);
|
|
318
|
+
}
|
|
319
|
+
resolveExecutor(agentId, cmd) {
|
|
320
|
+
const owner = this.groupOwner.get(cmd.group);
|
|
321
|
+
if (owner) {
|
|
322
|
+
if (owner.agentId && owner.agentId !== agentId) {
|
|
323
|
+
this.deps.logger.trace("browser_group_denied", {
|
|
324
|
+
group: cmd.group,
|
|
325
|
+
agentId,
|
|
326
|
+
ownerAgentId: owner.agentId
|
|
327
|
+
});
|
|
328
|
+
throw new BrokerError(`group "${cmd.group}" is owned by another client`, "group_owned");
|
|
329
|
+
}
|
|
330
|
+
if (owner.agentId === null) {
|
|
331
|
+
this.deps.logger.trace("browser_group_adopted", {
|
|
332
|
+
group: cmd.group,
|
|
333
|
+
agentId
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
owner.agentId = agentId;
|
|
337
|
+
const exec = this.executorById.get(owner.executorId);
|
|
338
|
+
if (!exec) {
|
|
339
|
+
throw new BrokerError(`the browser for group "${cmd.group}" is disconnected`, "executor_gone");
|
|
340
|
+
}
|
|
341
|
+
return exec;
|
|
342
|
+
}
|
|
343
|
+
if (cmd.action !== "open") {
|
|
344
|
+
throw new BrokerError(`group "${cmd.group}" has no open tabs — call browser_open first`, "no_group");
|
|
345
|
+
}
|
|
346
|
+
const exec = this.pickExecutor(cmd.target);
|
|
347
|
+
this.groupOwner.set(cmd.group, {
|
|
348
|
+
executorId: exec.id,
|
|
349
|
+
agentId
|
|
350
|
+
});
|
|
351
|
+
this.deps.logger.trace("browser_group_assigned", {
|
|
352
|
+
group: cmd.group,
|
|
353
|
+
agentId,
|
|
354
|
+
executorId: exec.id
|
|
355
|
+
});
|
|
356
|
+
return exec;
|
|
357
|
+
}
|
|
358
|
+
pickExecutor(target) {
|
|
359
|
+
if (this.executorById.size === 0) {
|
|
360
|
+
throw new BrokerError("no browser extension is connected to the bridge — open the extension and confirm it shows 'connected'", "not_connected");
|
|
361
|
+
}
|
|
362
|
+
if (target) {
|
|
363
|
+
const byId = this.executorById.get(target);
|
|
364
|
+
if (byId) {
|
|
365
|
+
return byId;
|
|
366
|
+
}
|
|
367
|
+
const byLabel = [...this.executorById.values()].filter((e) => e.label === target);
|
|
368
|
+
if (byLabel.length === 1) {
|
|
369
|
+
return byLabel[0];
|
|
370
|
+
}
|
|
371
|
+
if (byLabel.length > 1) {
|
|
372
|
+
throw new BrokerError(`target "${target}" is ambiguous — use the browser id`, "ambiguous_target");
|
|
373
|
+
}
|
|
374
|
+
throw new BrokerError(`no connected browser matches target "${target}"`, "unknown_target");
|
|
375
|
+
}
|
|
376
|
+
const primary = this.primaryExecutorId ? this.executorById.get(this.primaryExecutorId) : undefined;
|
|
377
|
+
if (primary) {
|
|
378
|
+
return primary;
|
|
379
|
+
}
|
|
380
|
+
for (const exec of this.executorById.values()) {
|
|
381
|
+
return exec;
|
|
382
|
+
}
|
|
383
|
+
throw new BrokerError("no browser extension is connected", "not_connected");
|
|
384
|
+
}
|
|
385
|
+
/** Effective per-command timeout: the override (or global), clamped to the ceiling. */
|
|
386
|
+
timeoutFor(override) {
|
|
387
|
+
const requested = override !== undefined ? override : this.opts.timeoutMs;
|
|
388
|
+
if (requested <= 0) {
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
const ceiling = this.opts.maxCommandMs ?? DEFAULT_MAX_COMMAND_MS;
|
|
392
|
+
return Math.min(requested, ceiling);
|
|
393
|
+
}
|
|
394
|
+
sendToExecutor(executor, agentId, cmd, signal) {
|
|
395
|
+
if (signal?.aborted) {
|
|
396
|
+
return Promise.reject(new BrokerError("aborted", "aborted"));
|
|
397
|
+
}
|
|
398
|
+
const reqId = nextId();
|
|
399
|
+
const frame = {
|
|
400
|
+
v: PROTOCOL_VERSION,
|
|
401
|
+
type: "command",
|
|
402
|
+
id: reqId,
|
|
403
|
+
action: cmd.action,
|
|
404
|
+
group: cmd.group,
|
|
405
|
+
params: cmd.params
|
|
406
|
+
};
|
|
407
|
+
const timeoutMs = this.timeoutFor(cmd.timeoutMs);
|
|
408
|
+
this.deps.logger.trace("browser_command_dispatched", {
|
|
409
|
+
id: reqId,
|
|
410
|
+
action: cmd.action,
|
|
411
|
+
group: cmd.group,
|
|
412
|
+
executorId: executor.id,
|
|
413
|
+
agentId,
|
|
414
|
+
timeoutMs
|
|
415
|
+
});
|
|
416
|
+
return new Promise((resolve, reject) => {
|
|
417
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
418
|
+
this.deps.logger.trace("browser_command_timeout_fired", {
|
|
419
|
+
id: reqId,
|
|
420
|
+
action: cmd.action,
|
|
421
|
+
timeoutMs
|
|
422
|
+
});
|
|
423
|
+
this.abandon(reqId, new BrokerError(`command '${cmd.action}' timed out after ${timeoutMs}ms`, "timeout"));
|
|
424
|
+
}, timeoutMs) : null;
|
|
425
|
+
let detachAbort = null;
|
|
426
|
+
if (signal) {
|
|
427
|
+
const onAbort = () => this.abandon(reqId, new BrokerError("aborted", "aborted"));
|
|
428
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
429
|
+
detachAbort = () => signal.removeEventListener("abort", onAbort);
|
|
430
|
+
}
|
|
431
|
+
this.pending.set(reqId, {
|
|
432
|
+
resolve,
|
|
433
|
+
reject,
|
|
434
|
+
timer,
|
|
435
|
+
detachAbort,
|
|
436
|
+
agentId,
|
|
437
|
+
executorId: executor.id
|
|
438
|
+
});
|
|
439
|
+
try {
|
|
440
|
+
executor.conn.send(encodeFrame(frame));
|
|
441
|
+
} catch (err) {
|
|
442
|
+
this.settleReject(reqId, new BrokerError(`failed to deliver '${cmd.action}': ${err instanceof Error ? err.message : String(err)}`, "send_failed"));
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
handleResult(frame) {
|
|
447
|
+
const p = this.pending.get(frame.id);
|
|
448
|
+
if (!p) {
|
|
449
|
+
this.deps.logger.trace("browser_result_unmatched", { id: frame.id });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.deps.logger.trace("browser_command_result_correlated", {
|
|
453
|
+
id: frame.id,
|
|
454
|
+
ok: frame.ok,
|
|
455
|
+
executorId: p.executorId,
|
|
456
|
+
agentId: p.agentId
|
|
457
|
+
});
|
|
458
|
+
this.clearPending(frame.id);
|
|
459
|
+
if (frame.ok) {
|
|
460
|
+
p.resolve(frame.data);
|
|
461
|
+
} else {
|
|
462
|
+
p.reject(new BrokerError(frame.error?.message ?? "executor error", frame.error?.code));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async handleAgentCommand(agent, conn, frame) {
|
|
466
|
+
this.deps.logger.trace("browser_agent_command_received", {
|
|
467
|
+
agentId: agent.id,
|
|
468
|
+
id: frame.id,
|
|
469
|
+
action: frame.action,
|
|
470
|
+
group: frame.group,
|
|
471
|
+
target: frame.target
|
|
472
|
+
});
|
|
473
|
+
try {
|
|
474
|
+
const data = await this.route(agent.id, {
|
|
475
|
+
action: frame.action,
|
|
476
|
+
group: frame.group,
|
|
477
|
+
params: frame.params,
|
|
478
|
+
target: frame.target,
|
|
479
|
+
timeoutMs: frame.timeoutMs
|
|
480
|
+
});
|
|
481
|
+
this.deps.logger.trace("browser_agent_command_result", {
|
|
482
|
+
agentId: agent.id,
|
|
483
|
+
id: frame.id,
|
|
484
|
+
ok: true
|
|
485
|
+
});
|
|
486
|
+
this.safeSend(conn, resultFrame(frame.id, data));
|
|
487
|
+
} catch (err) {
|
|
488
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
489
|
+
const code = err instanceof BrokerError ? err.code : undefined;
|
|
490
|
+
this.deps.logger.trace("browser_agent_command_result", {
|
|
491
|
+
agentId: agent.id,
|
|
492
|
+
id: frame.id,
|
|
493
|
+
ok: false,
|
|
494
|
+
code
|
|
495
|
+
});
|
|
496
|
+
this.safeSend(conn, errorFrame(frame.id, message, code));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
routeEvent(frame) {
|
|
500
|
+
// Forward to the owning agent if known, else broadcast to all remote agents.
|
|
501
|
+
const owner = frame.group ? this.groupOwner.get(frame.group) : undefined;
|
|
502
|
+
this.deps.logger.trace("browser_event_routed", {
|
|
503
|
+
name: frame.name,
|
|
504
|
+
group: frame.group,
|
|
505
|
+
ownerAgentId: owner?.agentId ?? null
|
|
506
|
+
});
|
|
507
|
+
for (const agent of this.agents.values()) {
|
|
508
|
+
if (!owner || owner.agentId === null || owner.agentId === agent.id) {
|
|
509
|
+
this.safeSend(agent.conn, frame);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
listTargets() {
|
|
514
|
+
return { targets: [...this.executorById.values()].map((e) => ({
|
|
515
|
+
id: e.id,
|
|
516
|
+
label: e.label,
|
|
517
|
+
browser: e.browser,
|
|
518
|
+
groups: [...this.groupOwner.entries()].filter(([, o]) => o.executorId === e.id).map(([g]) => g)
|
|
519
|
+
})) };
|
|
520
|
+
}
|
|
521
|
+
async aggregateTabs() {
|
|
522
|
+
const groups = [];
|
|
523
|
+
for (const executor of this.executorById.values()) {
|
|
524
|
+
try {
|
|
525
|
+
const data = await this.sendToExecutor(executor, "broker", {
|
|
526
|
+
action: "tabs",
|
|
527
|
+
group: "",
|
|
528
|
+
params: {}
|
|
529
|
+
});
|
|
530
|
+
for (const g of data.groups ?? []) {
|
|
531
|
+
groups.push({
|
|
532
|
+
...g,
|
|
533
|
+
executor: executor.id
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
} catch {}
|
|
537
|
+
}
|
|
538
|
+
return { groups };
|
|
539
|
+
}
|
|
540
|
+
releaseForAgent(agentId) {
|
|
541
|
+
const execIds = new Set();
|
|
542
|
+
for (const [group, owner] of this.groupOwner) {
|
|
543
|
+
if (owner.agentId === agentId) {
|
|
544
|
+
execIds.add(owner.executorId);
|
|
545
|
+
owner.agentId = null;
|
|
546
|
+
this.deps.logger.trace("browser_group_orphaned", {
|
|
547
|
+
group,
|
|
548
|
+
agentId
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// Only release the executors this agent actually owned. An agent that holds
|
|
553
|
+
// no groups must release nothing — broadcasting to every executor would
|
|
554
|
+
// detach CDP control from other clients' browsers in a shared bridge.
|
|
555
|
+
for (const id of execIds) {
|
|
556
|
+
const exec = this.executorById.get(id);
|
|
557
|
+
if (exec) {
|
|
558
|
+
this.safeSend(exec.conn, {
|
|
559
|
+
v: PROTOCOL_VERSION,
|
|
560
|
+
type: "release"
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
566
|
+
safeSend(conn, frame) {
|
|
567
|
+
try {
|
|
568
|
+
conn.send(encodeFrame(frame));
|
|
569
|
+
} catch {}
|
|
570
|
+
}
|
|
571
|
+
clearPending(reqId) {
|
|
572
|
+
const p = this.pending.get(reqId);
|
|
573
|
+
if (!p) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (p.timer) {
|
|
577
|
+
clearTimeout(p.timer);
|
|
578
|
+
}
|
|
579
|
+
p.detachAbort?.();
|
|
580
|
+
this.pending.delete(reqId);
|
|
581
|
+
}
|
|
582
|
+
settleReject(reqId, err) {
|
|
583
|
+
const p = this.pending.get(reqId);
|
|
584
|
+
if (!p) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
this.clearPending(reqId);
|
|
588
|
+
p.reject(err);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Give up on a command the executor may still be running (abort, timeout, or
|
|
592
|
+
* the requesting agent vanishing): tell the still-connected executor to tear
|
|
593
|
+
* down via a `cancel` frame, then reject the pending promise. For an executor
|
|
594
|
+
* that's already gone the cancel is skipped — there's nothing to tear down.
|
|
595
|
+
*/
|
|
596
|
+
abandon(reqId, err) {
|
|
597
|
+
const p = this.pending.get(reqId);
|
|
598
|
+
if (!p) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const exec = this.executorById.get(p.executorId);
|
|
602
|
+
this.deps.logger.trace("browser_command_abandoned", {
|
|
603
|
+
id: reqId,
|
|
604
|
+
executorId: p.executorId,
|
|
605
|
+
cancelSent: Boolean(exec),
|
|
606
|
+
reason: err.message
|
|
607
|
+
});
|
|
608
|
+
if (exec) {
|
|
609
|
+
this.safeSend(exec.conn, cancelFrame(reqId));
|
|
610
|
+
}
|
|
611
|
+
this.settleReject(reqId, err);
|
|
612
|
+
}
|
|
613
|
+
rejectAllPending(err) {
|
|
614
|
+
for (const [reqId, p] of this.pending) {
|
|
615
|
+
if (p.timer) {
|
|
616
|
+
clearTimeout(p.timer);
|
|
617
|
+
}
|
|
618
|
+
p.detachAbort?.();
|
|
619
|
+
this.pending.delete(reqId);
|
|
620
|
+
p.reject(err);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
609
623
|
}
|
|
624
|
+
|
|
610
625
|
//# sourceMappingURL=broker.js.map
|