@browserbridge/bbx 1.11.1 → 1.11.3
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/package.json +7 -7
- package/packages/agent-client/src/client.js +113 -39
- package/packages/agent-client/src/npm-self-update.js +7 -3
- package/packages/agent-client/src/runtime.js +4 -3
- package/packages/mcp-server/src/handlers-capture.js +1 -1
- package/packages/mcp-server/src/handlers-navigation.js +14 -21
- package/packages/mcp-server/src/handlers-page.js +16 -20
- package/packages/mcp-server/src/handlers-utils.js +188 -44
- package/packages/mcp-server/src/server.js +2 -1
- package/packages/native-host/src/auth-token.js +90 -28
- package/packages/native-host/src/daemon-process.js +5 -2
- package/packages/native-host/src/daemon.js +123 -23
- package/packages/native-host/src/framing.js +107 -17
- package/packages/native-host/src/native-host.js +4 -0
- package/packages/protocol/src/protocol.js +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserbridge/bbx",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-tools",
|
|
@@ -76,19 +76,19 @@
|
|
|
76
76
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
77
77
|
"@mozilla/readability": "^0.6.0",
|
|
78
78
|
"linkedom": "^0.18.13",
|
|
79
|
-
"zod": "^4.
|
|
79
|
+
"zod": "^4.6.5"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
83
|
-
"@types/chrome": "^0.
|
|
84
|
-
"@types/node": "^26.
|
|
83
|
+
"@types/chrome": "^0.3.0",
|
|
84
|
+
"@types/node": "^26.6.1",
|
|
85
85
|
"ajv": "^8.20.0",
|
|
86
86
|
"ajv-formats": "^3.0.1",
|
|
87
87
|
"c8": "^12.0.0",
|
|
88
88
|
"har-schema": "^2.0.0",
|
|
89
|
-
"oxfmt": "^0.
|
|
90
|
-
"oxlint": "^1.
|
|
91
|
-
"tsx": "^4.23.
|
|
89
|
+
"oxfmt": "^0.68.0",
|
|
90
|
+
"oxlint": "^1.83.0",
|
|
91
|
+
"tsx": "^4.23.13",
|
|
92
92
|
"typescript": "^7.0.2"
|
|
93
93
|
},
|
|
94
94
|
"engines": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { isProtocolVersionSupported } from '../../protocol/src/index.js';
|
|
2
3
|
|
|
3
4
|
import { EventEmitter, once } from 'node:events';
|
|
4
5
|
import fs from 'node:fs';
|
|
@@ -104,16 +105,37 @@ function shouldAutoRestartDaemonOnVersionMismatch(env = process.env) {
|
|
|
104
105
|
/**
|
|
105
106
|
* @param {net.Socket} socket
|
|
106
107
|
* @param {string} line
|
|
108
|
+
* @param {AbortSignal} signal
|
|
107
109
|
* @returns {Promise<void>}
|
|
108
110
|
*/
|
|
109
|
-
async function writeSocketLine(socket, line) {
|
|
111
|
+
async function writeSocketLine(socket, line, signal) {
|
|
112
|
+
signal.throwIfAborted();
|
|
110
113
|
if (!socket.write(line)) {
|
|
111
|
-
await Promise
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
const cleanup = () => {
|
|
116
|
+
socket.off('drain', onDrain);
|
|
117
|
+
socket.off('error', onError);
|
|
118
|
+
socket.off('close', onClose);
|
|
119
|
+
signal.removeEventListener('abort', onAbort);
|
|
120
|
+
};
|
|
121
|
+
const onDrain = () => {
|
|
122
|
+
cleanup();
|
|
123
|
+
resolve(undefined);
|
|
124
|
+
};
|
|
125
|
+
/** @param {unknown} error */
|
|
126
|
+
const onError = (error) => {
|
|
127
|
+
cleanup();
|
|
128
|
+
reject(error);
|
|
129
|
+
};
|
|
130
|
+
const onClose = () => onError(new Error('Bridge socket closed while writing.'));
|
|
131
|
+
const onAbort = () => onError(signal.reason);
|
|
132
|
+
socket.once('drain', onDrain);
|
|
133
|
+
socket.once('error', onError);
|
|
134
|
+
socket.once('close', onClose);
|
|
135
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
136
|
+
if (signal.aborted) onAbort();
|
|
137
|
+
else if (socket.destroyed) onClose();
|
|
138
|
+
});
|
|
117
139
|
}
|
|
118
140
|
}
|
|
119
141
|
|
|
@@ -176,12 +198,31 @@ export class BridgeClient extends EventEmitter {
|
|
|
176
198
|
this.socket = socket;
|
|
177
199
|
try {
|
|
178
200
|
await new Promise((resolve, reject) => {
|
|
179
|
-
|
|
180
|
-
|
|
201
|
+
const cleanup = () => {
|
|
202
|
+
socket.off('connect', onConnect);
|
|
203
|
+
socket.off('error', onError);
|
|
204
|
+
socket.off('close', onClose);
|
|
205
|
+
};
|
|
206
|
+
const onConnect = () => {
|
|
207
|
+
cleanup();
|
|
208
|
+
resolve(undefined);
|
|
209
|
+
};
|
|
210
|
+
/** @param {Error} error */
|
|
211
|
+
const onError = (error) => {
|
|
212
|
+
cleanup();
|
|
213
|
+
reject(error);
|
|
214
|
+
};
|
|
215
|
+
const onClose = () => onError(new Error('Bridge socket closed while connecting.'));
|
|
216
|
+
socket.once('connect', onConnect);
|
|
217
|
+
socket.once('error', onError);
|
|
218
|
+
socket.once('close', onClose);
|
|
181
219
|
});
|
|
220
|
+
if (this.socket !== socket || socket.destroyed) {
|
|
221
|
+
throw new Error('Bridge socket closed while connecting.');
|
|
222
|
+
}
|
|
182
223
|
} catch (error) {
|
|
183
224
|
socket.destroy();
|
|
184
|
-
this.socket = null;
|
|
225
|
+
if (this.socket === socket) this.socket = null;
|
|
185
226
|
throw error;
|
|
186
227
|
}
|
|
187
228
|
|
|
@@ -198,6 +239,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
198
239
|
});
|
|
199
240
|
|
|
200
241
|
parseJsonLines(socket, (raw) => {
|
|
242
|
+
if (this.socket !== socket) return;
|
|
201
243
|
const message = /** @type {ClientMessage} */ (raw);
|
|
202
244
|
if (message.type === 'registered') {
|
|
203
245
|
const pending = this.waiting.get('registered');
|
|
@@ -231,6 +273,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
231
273
|
});
|
|
232
274
|
|
|
233
275
|
socket.on('close', () => {
|
|
276
|
+
if (this.socket !== socket) return;
|
|
234
277
|
this.connected = false;
|
|
235
278
|
this.socket = null;
|
|
236
279
|
this.rejectAllPending(new Error('Bridge socket closed.'));
|
|
@@ -240,35 +283,50 @@ export class BridgeClient extends EventEmitter {
|
|
|
240
283
|
});
|
|
241
284
|
|
|
242
285
|
socket.on('error', (error) => {
|
|
286
|
+
if (this.socket !== socket) return;
|
|
243
287
|
this.rejectAllPending(error);
|
|
244
288
|
// 'close' fires after 'error'; reconnect is triggered there.
|
|
245
289
|
});
|
|
246
290
|
|
|
247
|
-
const
|
|
248
|
-
this.authToken === undefined
|
|
249
|
-
? this.transport.type === 'tcp'
|
|
250
|
-
? await readBridgeAuthToken()
|
|
251
|
-
: null
|
|
252
|
-
: normalizeBridgeAuthToken(this.authToken);
|
|
291
|
+
const registrationWrite = new AbortController();
|
|
253
292
|
try {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
293
|
+
const writePromise = (async () => {
|
|
294
|
+
const authToken =
|
|
295
|
+
this.authToken === undefined
|
|
296
|
+
? this.transport.type === 'tcp'
|
|
297
|
+
? await readBridgeAuthToken()
|
|
298
|
+
: null
|
|
299
|
+
: normalizeBridgeAuthToken(this.authToken);
|
|
300
|
+
await writeSocketLine(
|
|
301
|
+
socket,
|
|
302
|
+
`${JSON.stringify({
|
|
303
|
+
type: 'register',
|
|
304
|
+
role: 'agent',
|
|
305
|
+
clientId: this.clientId,
|
|
306
|
+
...(authToken ? { authToken } : {}),
|
|
307
|
+
})}\n`,
|
|
308
|
+
registrationWrite.signal
|
|
309
|
+
);
|
|
310
|
+
})();
|
|
311
|
+
await Promise.race([registrationPromise, writePromise.then(() => registrationPromise)]);
|
|
312
|
+
if (this.socket !== socket || socket.destroyed) {
|
|
313
|
+
throw new Error('Bridge socket closed while registering.');
|
|
314
|
+
}
|
|
263
315
|
} catch (error) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
316
|
+
if (this.socket === socket) {
|
|
317
|
+
const pending = this.waiting.get('registered');
|
|
318
|
+
if (pending) {
|
|
319
|
+
clearTimeout(pending.timeoutId);
|
|
320
|
+
this.waiting.delete('registered');
|
|
321
|
+
}
|
|
322
|
+
this.connected = false;
|
|
323
|
+
this.socket = null;
|
|
268
324
|
}
|
|
325
|
+
socket.destroy();
|
|
269
326
|
throw error;
|
|
327
|
+
} finally {
|
|
328
|
+
registrationWrite.abort();
|
|
270
329
|
}
|
|
271
|
-
await registrationPromise;
|
|
272
330
|
|
|
273
331
|
this.protocolCompatibility = null;
|
|
274
332
|
this.protocolWarning = null;
|
|
@@ -553,18 +611,25 @@ export class BridgeClient extends EventEmitter {
|
|
|
553
611
|
});
|
|
554
612
|
});
|
|
555
613
|
|
|
614
|
+
const requestWrite = new AbortController();
|
|
556
615
|
try {
|
|
557
|
-
|
|
558
|
-
|
|
616
|
+
const writePromise = writeSocketLine(
|
|
617
|
+
this.socket,
|
|
618
|
+
`${JSON.stringify({ type: 'agent.request', request })}\n`,
|
|
619
|
+
requestWrite.signal
|
|
620
|
+
);
|
|
621
|
+
const response = /** @type {BridgeResponse} */ (
|
|
622
|
+
await Promise.race([responsePromise, writePromise.then(() => responsePromise)])
|
|
623
|
+
);
|
|
624
|
+
return this.attachProtocolWarning(response);
|
|
625
|
+
} finally {
|
|
626
|
+
requestWrite.abort();
|
|
559
627
|
const pending = this.waiting.get(request.id);
|
|
560
628
|
if (pending) {
|
|
561
629
|
clearTimeout(pending.timeoutId);
|
|
562
630
|
this.waiting.delete(request.id);
|
|
563
631
|
}
|
|
564
|
-
throw error;
|
|
565
632
|
}
|
|
566
|
-
const response = /** @type {BridgeResponse} */ (await responsePromise);
|
|
567
|
-
return this.attachProtocolWarning(response);
|
|
568
633
|
}
|
|
569
634
|
|
|
570
635
|
/**
|
|
@@ -611,11 +676,19 @@ export class BridgeClient extends EventEmitter {
|
|
|
611
676
|
*/
|
|
612
677
|
async close() {
|
|
613
678
|
this.autoReconnect = false; // prevent reconnect on intentional close
|
|
614
|
-
|
|
679
|
+
const socket = this.socket;
|
|
680
|
+
if (!socket) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (socket.connecting || !this.connected || socket.destroyed || socket.writableNeedDrain) {
|
|
684
|
+
this.socket = null;
|
|
685
|
+
this.connected = false;
|
|
686
|
+
this.rejectAllPending(new Error('Bridge socket closed.'));
|
|
687
|
+
socket.destroy();
|
|
615
688
|
return;
|
|
616
689
|
}
|
|
617
690
|
await new Promise((resolve) => {
|
|
618
|
-
|
|
691
|
+
socket.end(() => resolve(undefined));
|
|
619
692
|
});
|
|
620
693
|
}
|
|
621
694
|
|
|
@@ -672,7 +745,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
672
745
|
if (
|
|
673
746
|
healthResult?.extensionConnected === true &&
|
|
674
747
|
Array.isArray(extensionVersions) &&
|
|
675
|
-
!
|
|
748
|
+
!isProtocolVersionSupported(getProtocolVersion(), extensionVersions)
|
|
676
749
|
) {
|
|
677
750
|
return false;
|
|
678
751
|
}
|
|
@@ -697,6 +770,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
697
770
|
const previousAutoReconnect = this.autoReconnect;
|
|
698
771
|
this.autoReconnect = false;
|
|
699
772
|
this.connected = false;
|
|
773
|
+
this.rejectAllPending(new Error('Bridge socket closed.'));
|
|
700
774
|
this.socket = null;
|
|
701
775
|
|
|
702
776
|
if (!socket.destroyed) {
|
|
@@ -729,7 +803,7 @@ export class BridgeClient extends EventEmitter {
|
|
|
729
803
|
};
|
|
730
804
|
}
|
|
731
805
|
const localVersion = getProtocolVersion();
|
|
732
|
-
const compatible =
|
|
806
|
+
const compatible = isProtocolVersionSupported(localVersion, remoteVersions);
|
|
733
807
|
return {
|
|
734
808
|
compatible,
|
|
735
809
|
localVersion,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { isProtocolVersionSupported } from '../../protocol/src/index.js';
|
|
2
3
|
|
|
3
4
|
import { execFile } from 'node:child_process';
|
|
4
5
|
import { createHash } from 'node:crypto';
|
|
@@ -75,12 +76,12 @@ export function derivePackageProtocolVersion(version) {
|
|
|
75
76
|
*/
|
|
76
77
|
export function selectCompatibleNpmVersion(versions, supportedProtocols, currentVersion) {
|
|
77
78
|
if (!parseStableVersion(currentVersion)) return null;
|
|
78
|
-
const supported = new Set(supportedProtocols.filter((version) => /^\d+\.\d+$/u.test(version)));
|
|
79
79
|
let selected = null;
|
|
80
80
|
for (const value of versions) {
|
|
81
81
|
if (typeof value !== 'string' || !parseStableVersion(value)) continue;
|
|
82
82
|
const protocolVersion = derivePackageProtocolVersion(value);
|
|
83
|
-
if (!protocolVersion || !
|
|
83
|
+
if (!protocolVersion || !isProtocolVersionSupported(protocolVersion, supportedProtocols))
|
|
84
|
+
continue;
|
|
84
85
|
if (comparePackageVersions(value, currentVersion) <= 0) continue;
|
|
85
86
|
if (selected === null || comparePackageVersions(value, selected) > 0) selected = value;
|
|
86
87
|
}
|
|
@@ -252,7 +253,10 @@ export async function updateCompatibleNpmPackage(options) {
|
|
|
252
253
|
const packageRoot = options.packageRoot ?? PACKAGE_ROOT;
|
|
253
254
|
const runNpmFn = options.runNpmFn ?? runNpmCommand;
|
|
254
255
|
const extensionProtocol = derivePackageProtocolVersion(options.extensionVersion);
|
|
255
|
-
if (
|
|
256
|
+
if (
|
|
257
|
+
!extensionProtocol ||
|
|
258
|
+
!isProtocolVersionSupported(extensionProtocol, options.supportedVersions)
|
|
259
|
+
) {
|
|
256
260
|
return { updated: false, reason: 'invalid_extension_version' };
|
|
257
261
|
}
|
|
258
262
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { isProtocolVersionSupported } from '../../protocol/src/index.js';
|
|
2
3
|
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -204,8 +205,8 @@ function withRequestMeta(source, mcpEra, tokenBudget, automaticRetry) {
|
|
|
204
205
|
*/
|
|
205
206
|
export async function withBridgeClient(callback, options) {
|
|
206
207
|
const client = new BridgeClient(await applyConfiguredAutoUpdate(options));
|
|
207
|
-
await ensureClientConnected(client);
|
|
208
208
|
try {
|
|
209
|
+
await ensureClientConnected(client);
|
|
209
210
|
return await callback(client);
|
|
210
211
|
} finally {
|
|
211
212
|
await client.close();
|
|
@@ -839,11 +840,11 @@ function summarizeProtocol(health, extensionConnected) {
|
|
|
839
840
|
: safeVersions(health?.supported_versions)
|
|
840
841
|
: [];
|
|
841
842
|
const daemonCompatible = daemonSupportedVersions.length
|
|
842
|
-
?
|
|
843
|
+
? isProtocolVersionSupported(clientVersion, daemonSupportedVersions)
|
|
843
844
|
: null;
|
|
844
845
|
const extensionCompatible = extensionConnected
|
|
845
846
|
? extensionSupportedVersions.length
|
|
846
|
-
?
|
|
847
|
+
? isProtocolVersionSupported(clientVersion, extensionSupportedVersions)
|
|
847
848
|
: null
|
|
848
849
|
: null;
|
|
849
850
|
const knownCompatibility = [daemonCompatible, extensionCompatible].filter(
|
|
@@ -153,7 +153,7 @@ export function createScreenshotResult(response, method) {
|
|
|
153
153
|
return summarizeToolError(new Error(`${method} returned invalid artifact metadata.`));
|
|
154
154
|
}
|
|
155
155
|
return createToolResult(
|
|
156
|
-
`Captured screenshot artifact (${artifact.byteLength} bytes, expires ${artifact.expiresAt ?? 'soon'}). Read it in chunks with
|
|
156
|
+
`Captured screenshot artifact (${artifact.byteLength} bytes, expires ${artifact.expiresAt ?? 'soon'}). Read it in chunks with browser_call method "artifact.read", paging offset until nextOffset is null, then delete it with browser_call method "artifact.delete" when done.`,
|
|
157
157
|
{
|
|
158
158
|
ok: true,
|
|
159
159
|
method,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import { createBridgeClientForDestination } from '../../agent-client/src/remotes.js';
|
|
4
3
|
import {
|
|
5
4
|
callBridgeTool,
|
|
6
5
|
createToolResult,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
getToolTokenBudget,
|
|
9
8
|
requestBridgeWithRetry,
|
|
10
9
|
summarizeToolError,
|
|
10
|
+
withMcpRequestClient,
|
|
11
11
|
} from './handlers-utils.js';
|
|
12
12
|
|
|
13
13
|
/** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
|
|
@@ -28,23 +28,20 @@ export async function handleTabsTool(args) {
|
|
|
28
28
|
}
|
|
29
29
|
const results = await Promise.all(
|
|
30
30
|
destinations.map(async (destination) => {
|
|
31
|
-
/** @type {import('../../agent-client/src/client.js').BridgeClient | null} */
|
|
32
|
-
let client = null;
|
|
33
31
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
32
|
+
const response = await withMcpRequestClient(
|
|
33
|
+
(client) =>
|
|
34
|
+
requestBridgeWithRetry(
|
|
35
|
+
client,
|
|
36
|
+
'tabs.list',
|
|
37
|
+
{},
|
|
38
|
+
{
|
|
39
|
+
tabId: null,
|
|
40
|
+
source: 'mcp',
|
|
41
|
+
tokenBudget: null,
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
{ destinationId: destination.local ? null : destination.id }
|
|
48
45
|
);
|
|
49
46
|
if (!response.ok) {
|
|
50
47
|
return {
|
|
@@ -67,10 +64,6 @@ export async function handleTabsTool(args) {
|
|
|
67
64
|
error: { message: error instanceof Error ? error.message : String(error) },
|
|
68
65
|
tabs: [],
|
|
69
66
|
};
|
|
70
|
-
} finally {
|
|
71
|
-
if (client) {
|
|
72
|
-
await client.close().catch(() => {});
|
|
73
|
-
}
|
|
74
67
|
}
|
|
75
68
|
})
|
|
76
69
|
);
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
MAX_BATCH_CONCURRENCY,
|
|
14
14
|
METHOD_SET,
|
|
15
15
|
} from '../../protocol/src/index.js';
|
|
16
|
-
import { createBridgeClientForDestination } from '../../agent-client/src/remotes.js';
|
|
17
16
|
import {
|
|
18
17
|
annotateBridgeSummary,
|
|
19
18
|
applyLimitBudgetPreset,
|
|
@@ -31,6 +30,8 @@ import {
|
|
|
31
30
|
summarizeToolError,
|
|
32
31
|
summarizeToolResponse,
|
|
33
32
|
withToolClient,
|
|
33
|
+
withMcpRequestClient,
|
|
34
|
+
throwIfMcpRequestCancelled,
|
|
34
35
|
REQUEST_SOURCE,
|
|
35
36
|
} from './handlers-utils.js';
|
|
36
37
|
import { createScreenshotResult } from './handlers-capture.js';
|
|
@@ -383,33 +384,23 @@ export async function handleBatchTool(args) {
|
|
|
383
384
|
const tokenBudget = getToolTokenBudget(call);
|
|
384
385
|
const destinationId = typeof call.destinationId === 'string' ? call.destinationId : null;
|
|
385
386
|
const startTime = Date.now();
|
|
386
|
-
/** @type {BridgeClient | null} */
|
|
387
|
-
let callClient = null;
|
|
388
387
|
/** @type {import('../../protocol/src/types.js').BridgeResponse | null} */
|
|
389
388
|
let response = null;
|
|
390
389
|
/** @type {unknown} */
|
|
391
390
|
let callError = null;
|
|
392
391
|
try {
|
|
393
|
-
callClient = await createBridgeClientForDestination(destinationId, {
|
|
394
|
-
checkProtocolOnConnect: false,
|
|
395
|
-
});
|
|
396
|
-
await callClient.connect();
|
|
397
392
|
const params = applyMethodBudgetPreset(method, call.params || {}, call.budgetPreset);
|
|
398
|
-
response = await
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
393
|
+
response = await withMcpRequestClient(
|
|
394
|
+
(client) =>
|
|
395
|
+
requestBridgeWithRetry(client, method, params, {
|
|
396
|
+
tabId,
|
|
397
|
+
source: REQUEST_SOURCE,
|
|
398
|
+
tokenBudget,
|
|
399
|
+
}),
|
|
400
|
+
{ destinationId }
|
|
401
|
+
);
|
|
403
402
|
} catch (error) {
|
|
404
403
|
callError = error;
|
|
405
|
-
} finally {
|
|
406
|
-
if (callClient) {
|
|
407
|
-
try {
|
|
408
|
-
await callClient.close();
|
|
409
|
-
} catch (error) {
|
|
410
|
-
callError ??= error;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
404
|
}
|
|
414
405
|
if (callError || !response) {
|
|
415
406
|
return {
|
|
@@ -489,6 +480,7 @@ async function mapWithConcurrency(values, concurrency, callback) {
|
|
|
489
480
|
let nextIndex = 0;
|
|
490
481
|
const worker = async () => {
|
|
491
482
|
while (nextIndex < values.length) {
|
|
483
|
+
throwIfMcpRequestCancelled();
|
|
492
484
|
const index = nextIndex;
|
|
493
485
|
nextIndex += 1;
|
|
494
486
|
results[index] = await callback(values[index], index);
|
|
@@ -537,6 +529,9 @@ export async function handleRawCallTool(args) {
|
|
|
537
529
|
if (method === 'sensitive.read') {
|
|
538
530
|
return createSensitiveReadResult(response);
|
|
539
531
|
}
|
|
532
|
+
if (method === 'artifact.read') {
|
|
533
|
+
return createArtifactReadResult(response);
|
|
534
|
+
}
|
|
540
535
|
return summarizeToolResponse(response, method, params);
|
|
541
536
|
},
|
|
542
537
|
{ destinationId: args.destinationId ?? null }
|
|
@@ -729,6 +724,7 @@ export async function handleInvestigateTool(args) {
|
|
|
729
724
|
const stepResults = [];
|
|
730
725
|
|
|
731
726
|
for (const step of scope.steps) {
|
|
727
|
+
throwIfMcpRequestCancelled();
|
|
732
728
|
const startTime = Date.now();
|
|
733
729
|
try {
|
|
734
730
|
const response = await requestBridgeWithRetry(client, step.method, step.params(args), {
|