@browserbridge/bbx 1.0.1 → 1.2.0
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/README.md +4 -4
- package/package.json +11 -13
- package/packages/agent-client/src/cli-helpers.js +33 -0
- package/packages/agent-client/src/cli.js +122 -45
- package/packages/agent-client/src/client.js +134 -8
- package/packages/agent-client/src/command-registry.js +4 -1
- package/packages/agent-client/src/detect.js +159 -48
- package/packages/agent-client/src/install.js +24 -1
- package/packages/agent-client/src/mcp-config.js +29 -10
- package/packages/agent-client/src/setup-status.js +12 -4
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers-capture.js +279 -0
- package/packages/mcp-server/src/handlers-dom.js +196 -0
- package/packages/mcp-server/src/handlers-navigation.js +79 -0
- package/packages/mcp-server/src/handlers-page.js +365 -0
- package/packages/mcp-server/src/handlers-utils.js +296 -0
- package/packages/mcp-server/src/handlers.js +63 -1159
- package/packages/mcp-server/src/server.js +13 -3
- package/packages/native-host/bin/bridge-daemon.js +34 -4
- package/packages/native-host/bin/install-manifest.js +32 -2
- package/packages/native-host/bin/postinstall.js +16 -0
- package/packages/native-host/src/config.js +131 -6
- package/packages/native-host/src/daemon-logger.js +157 -0
- package/packages/native-host/src/daemon-process.js +422 -0
- package/packages/native-host/src/daemon.js +322 -77
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +121 -7
- package/packages/native-host/src/native-host.js +110 -73
- package/packages/protocol/src/capabilities.js +4 -0
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +4 -0
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +143 -7
- package/packages/protocol/src/registry.js +13 -0
- package/packages/protocol/src/summary.js +18 -10
- package/packages/protocol/src/types.js +28 -3
- package/skills/browser-bridge/SKILL.md +2 -1
- package/skills/browser-bridge/references/interaction.md +1 -0
- package/skills/browser-bridge/references/protocol.md +2 -1
- package/CHANGELOG.md +0 -55
- package/assets/banner.jpg +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +0 -65
- package/docs/api-reference.md +0 -157
- package/docs/cli-guide.md +0 -128
- package/docs/index.md +0 -25
- package/docs/manual-setup.md +0 -140
- package/docs/mcp-vs-cli.md +0 -258
- package/docs/publishing.md +0 -112
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/unpacked-extension.md +0 -72
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -38
- package/packages/extension/assets/icon-128.png +0 -0
- package/packages/extension/assets/icon-16.png +0 -0
- package/packages/extension/assets/icon-32.png +0 -0
- package/packages/extension/assets/icon-48.png +0 -0
- package/packages/extension/src/background-helpers.js +0 -474
- package/packages/extension/src/background-routing.js +0 -89
- package/packages/extension/src/background.js +0 -3490
- package/packages/extension/src/content-script-helpers.js +0 -282
- package/packages/extension/src/content-script.js +0 -2043
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -104
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -298
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1771
- package/packages/extension/ui/ui.css +0 -1160
|
@@ -1,304 +1,69 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { DEFAULT_CONSOLE_LIMIT, createRuntimeContext } from '../../protocol/src/index.js';
|
|
5
|
+
import { collectSetupStatus } from '../../agent-client/src/setup-status.js';
|
|
3
6
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
DEFAULT_MAX_HTML_LENGTH,
|
|
7
|
-
DEFAULT_NETWORK_LIMIT,
|
|
8
|
-
estimateJsonPayloadCost,
|
|
9
|
-
getBudgetPreset,
|
|
10
|
-
getErrorRecovery,
|
|
11
|
-
isBudgetPresetName,
|
|
12
|
-
METHODS,
|
|
13
|
-
summarizeBatchErrorItem,
|
|
14
|
-
summarizeBatchResponseItem,
|
|
15
|
-
} from '../../protocol/src/index.js';
|
|
16
|
-
import {
|
|
7
|
+
callBridgeTool,
|
|
8
|
+
createToolResult,
|
|
17
9
|
getDoctorReport,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
function summarizeToolError(error) {
|
|
76
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
-
return createToolResult(
|
|
78
|
-
`ERROR: ${message}`,
|
|
79
|
-
{
|
|
80
|
-
ok: false,
|
|
81
|
-
evidence: null,
|
|
82
|
-
},
|
|
83
|
-
true
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* @param {(client: import('../../agent-client/src/client.js').BridgeClient) => Promise<ToolResult>} callback
|
|
89
|
-
* @returns {Promise<ToolResult>}
|
|
90
|
-
*/
|
|
91
|
-
async function withToolClient(callback) {
|
|
92
|
-
try {
|
|
93
|
-
return await withBridgeClient(callback);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
return summarizeToolError(error);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @param {import('../../agent-client/src/client.js').BridgeClient} client
|
|
101
|
-
* @param {{ elementRef?: string | undefined, selector?: string | undefined }} input
|
|
102
|
-
* @param {number | null | undefined} [tabId]
|
|
103
|
-
* @returns {Promise<string>}
|
|
104
|
-
*/
|
|
105
|
-
async function resolveToolRef(client, input, tabId = null) {
|
|
106
|
-
if (typeof input.elementRef === 'string' && input.elementRef) {
|
|
107
|
-
return input.elementRef;
|
|
108
|
-
}
|
|
109
|
-
if (typeof input.selector === 'string' && input.selector) {
|
|
110
|
-
return resolveRef(client, input.selector, tabId, REQUEST_SOURCE);
|
|
111
|
-
}
|
|
112
|
-
throw new Error('Provide either elementRef or selector.');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @param {unknown} value
|
|
117
|
-
* @returns {'quick' | 'normal' | 'deep' | null}
|
|
118
|
-
*/
|
|
119
|
-
function getBudgetPresetName(value) {
|
|
120
|
-
return isBudgetPresetName(value) ? value : null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Infer a budget preset from selector specificity when the caller hasn't
|
|
125
|
-
* provided an explicit budgetPreset.
|
|
126
|
-
*
|
|
127
|
-
* - ID selectors (#foo) or single element refs -> quick
|
|
128
|
-
* - Class / tag / attribute selectors -> normal
|
|
129
|
-
* - Universal (*), deeply nested, or missing selector -> deep
|
|
130
|
-
*
|
|
131
|
-
* @param {{ budgetPreset?: unknown, selector?: unknown, elementRef?: unknown }} args
|
|
132
|
-
* @returns {'quick' | 'normal' | 'deep' | null}
|
|
133
|
-
*/
|
|
134
|
-
function inferBudgetFromSelector(args) {
|
|
135
|
-
if (getBudgetPresetName(args.budgetPreset)) return null; // explicit preset wins
|
|
136
|
-
if (typeof args.elementRef === 'string' && args.elementRef) return 'quick';
|
|
137
|
-
const sel = typeof args.selector === 'string' ? args.selector.trim() : '';
|
|
138
|
-
if (!sel || sel === '*' || sel === 'body') return null; // keep default
|
|
139
|
-
if (/^#[\w-]+$/.test(sel)) return 'quick';
|
|
140
|
-
if ((sel.match(/\s/g) || []).length >= 3) return 'deep';
|
|
141
|
-
return 'normal';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* @param {{ budgetPreset?: unknown }} args
|
|
146
|
-
* @returns {number | null}
|
|
147
|
-
*/
|
|
148
|
-
function getToolTokenBudget(args) {
|
|
149
|
-
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
150
|
-
return presetName ? getBudgetPreset(presetName).tokenBudget : null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @template {{ budgetPreset?: unknown, maxNodes?: unknown, maxDepth?: unknown, textBudget?: unknown }} T
|
|
155
|
-
* @param {T} args
|
|
156
|
-
* @returns {T}
|
|
157
|
-
*/
|
|
158
|
-
function applyTreeBudgetPreset(args) {
|
|
159
|
-
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
160
|
-
if (!presetName) {
|
|
161
|
-
return args;
|
|
162
|
-
}
|
|
163
|
-
const preset = getBudgetPreset(presetName);
|
|
164
|
-
return /** @type {T} */ ({
|
|
165
|
-
...args,
|
|
166
|
-
maxNodes: args.maxNodes ?? preset.maxNodes,
|
|
167
|
-
maxDepth: args.maxDepth ?? preset.maxDepth,
|
|
168
|
-
textBudget: args.textBudget ?? preset.textBudget,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* @template {{ budgetPreset?: unknown, textBudget?: unknown }} T
|
|
174
|
-
* @param {T} args
|
|
175
|
-
* @returns {T}
|
|
176
|
-
*/
|
|
177
|
-
function applyTextBudgetPreset(args) {
|
|
178
|
-
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
179
|
-
if (!presetName) {
|
|
180
|
-
return args;
|
|
181
|
-
}
|
|
182
|
-
const preset = getBudgetPreset(presetName);
|
|
183
|
-
return /** @type {T} */ ({
|
|
184
|
-
...args,
|
|
185
|
-
textBudget: args.textBudget ?? preset.textBudget,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* @template {{ budgetPreset?: unknown, limit?: unknown }} T
|
|
191
|
-
* @param {T} args
|
|
192
|
-
* @param {{ quick: number, normal: number, deep: number }} defaults
|
|
193
|
-
* @returns {T}
|
|
194
|
-
*/
|
|
195
|
-
function applyLimitBudgetPreset(args, defaults) {
|
|
196
|
-
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
197
|
-
if (!presetName) {
|
|
198
|
-
return args;
|
|
199
|
-
}
|
|
200
|
-
return /** @type {T} */ ({
|
|
201
|
-
...args,
|
|
202
|
-
limit: args.limit ?? defaults[presetName],
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* @template {{ budgetPreset?: unknown, maxLength?: unknown }} T
|
|
208
|
-
* @param {T} args
|
|
209
|
-
* @returns {T}
|
|
210
|
-
*/
|
|
211
|
-
function applyHtmlBudgetPreset(args) {
|
|
212
|
-
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
213
|
-
if (!presetName) {
|
|
214
|
-
return args;
|
|
215
|
-
}
|
|
216
|
-
const maxLengthByPreset = {
|
|
217
|
-
quick: 600,
|
|
218
|
-
normal: DEFAULT_MAX_HTML_LENGTH,
|
|
219
|
-
deep: 6000,
|
|
220
|
-
};
|
|
221
|
-
return /** @type {T} */ ({
|
|
222
|
-
...args,
|
|
223
|
-
maxLength: args.maxLength ?? maxLengthByPreset[presetName],
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Call requestBridge and, on a retriable error, wait and retry once.
|
|
229
|
-
* Logs retries to stderr so they are visible in MCP server output.
|
|
230
|
-
*
|
|
231
|
-
* @param {import('../../agent-client/src/client.js').BridgeClient} client
|
|
232
|
-
* @param {BridgeMethod} method
|
|
233
|
-
* @param {Record<string, unknown>} params
|
|
234
|
-
* @param {{ tabId?: number | null, source?: import('../../protocol/src/types.js').BridgeRequestSource, tokenBudget?: number | null }} options
|
|
235
|
-
* @returns {Promise<BridgeResponse>}
|
|
236
|
-
*/
|
|
237
|
-
async function requestBridgeWithRetry(client, method, params, options) {
|
|
238
|
-
const response = await requestBridge(client, method, params, options);
|
|
239
|
-
const recovery = !response.ok && response.error ? getErrorRecovery(response.error.code) : null;
|
|
240
|
-
if (!response.ok && recovery?.retry) {
|
|
241
|
-
const delay = recovery.retryAfterMs ?? 1000;
|
|
242
|
-
process.stderr.write(
|
|
243
|
-
`[bbx-mcp] Retrying ${method} after ${delay}ms (${response.error.code})\n`
|
|
244
|
-
);
|
|
245
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
246
|
-
return requestBridge(client, method, params, options);
|
|
247
|
-
}
|
|
248
|
-
return response;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* @param {BridgeMethod} method
|
|
253
|
-
* @param {Record<string, unknown>} [params={}]
|
|
254
|
-
* @param {{ tabId?: number | null, summaryMethod?: string, tokenBudget?: number | null }} [options]
|
|
255
|
-
* @returns {Promise<ToolResult>}
|
|
256
|
-
*/
|
|
257
|
-
async function callBridgeTool(method, params = {}, options = {}) {
|
|
258
|
-
return withToolClient(async (client) => {
|
|
259
|
-
const response = await requestBridgeWithRetry(client, method, params, {
|
|
260
|
-
tabId: options.tabId ?? null,
|
|
261
|
-
source: REQUEST_SOURCE,
|
|
262
|
-
tokenBudget: options.tokenBudget ?? null,
|
|
263
|
-
});
|
|
264
|
-
return summarizeToolResponse(response, options.summaryMethod || method);
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* @typedef {{ ref: boolean, method: BridgeMethod, params: (args: Record<string, unknown>, ref?: string) => Record<string, unknown> }} ToolAction
|
|
270
|
-
*/
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Generic dispatcher for table-driven tool handlers. Each action entry
|
|
274
|
-
* declares a bridge method, whether it needs an element ref, and a function
|
|
275
|
-
* mapping args (and optional ref) to bridge params.
|
|
276
|
-
*
|
|
277
|
-
* @param {Record<string, ToolAction>} actions
|
|
278
|
-
* @param {Record<string, unknown> & { action: string }} args
|
|
279
|
-
* @param {string} toolName
|
|
280
|
-
* @returns {Promise<ToolResult>}
|
|
281
|
-
*/
|
|
282
|
-
async function dispatchToolAction(actions, args, toolName) {
|
|
283
|
-
const entry = actions[args.action];
|
|
284
|
-
if (!entry) return summarizeToolError(`Unsupported ${toolName} action "${args.action}".`);
|
|
285
|
-
return withToolClient(async (client) => {
|
|
286
|
-
const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
|
|
287
|
-
const ref = entry.ref
|
|
288
|
-
? await resolveToolRef(
|
|
289
|
-
client,
|
|
290
|
-
/** @type {{ elementRef?: string, selector?: string }} */ (args),
|
|
291
|
-
requestedTabId
|
|
292
|
-
)
|
|
293
|
-
: undefined;
|
|
294
|
-
const response = await requestBridgeWithRetry(client, entry.method, entry.params(args, ref), {
|
|
295
|
-
tabId: requestedTabId,
|
|
296
|
-
source: REQUEST_SOURCE,
|
|
297
|
-
tokenBudget: getToolTokenBudget(/** @type {{ budgetPreset?: unknown }} */ (args)),
|
|
298
|
-
});
|
|
299
|
-
return summarizeToolResponse(response, entry.method);
|
|
300
|
-
});
|
|
301
|
-
}
|
|
10
|
+
getToolTokenBudget,
|
|
11
|
+
applyLimitBudgetPreset,
|
|
12
|
+
summarizeToolError,
|
|
13
|
+
summarizeToolResponse,
|
|
14
|
+
withToolClient,
|
|
15
|
+
REQUEST_SOURCE,
|
|
16
|
+
} from './handlers-utils.js';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
createToolResult,
|
|
20
|
+
summarizeToolError,
|
|
21
|
+
summarizeToolResponse,
|
|
22
|
+
withToolClient,
|
|
23
|
+
resolveToolRef,
|
|
24
|
+
getBudgetPresetName,
|
|
25
|
+
inferBudgetFromSelector,
|
|
26
|
+
getToolTokenBudget,
|
|
27
|
+
applyTreeBudgetPreset,
|
|
28
|
+
applyTextBudgetPreset,
|
|
29
|
+
applyLimitBudgetPreset,
|
|
30
|
+
applyHtmlBudgetPreset,
|
|
31
|
+
requestBridgeWithRetry,
|
|
32
|
+
callBridgeTool,
|
|
33
|
+
dispatchToolAction,
|
|
34
|
+
REQUEST_SOURCE,
|
|
35
|
+
} from './handlers-utils.js';
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
DOM_ACTIONS,
|
|
39
|
+
STYLES_LAYOUT_ACTIONS,
|
|
40
|
+
PATCH_ACTIONS,
|
|
41
|
+
handleDomTool,
|
|
42
|
+
handleStylesLayoutTool,
|
|
43
|
+
handlePatchTool,
|
|
44
|
+
} from './handlers-dom.js';
|
|
45
|
+
|
|
46
|
+
export { NAVIGATION_ACTIONS, handleTabsTool, handleNavigationTool } from './handlers-navigation.js';
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
CAPTURE_ACTIONS,
|
|
50
|
+
INPUT_ACTION_METHODS,
|
|
51
|
+
handleCaptureTool,
|
|
52
|
+
handleInputTool,
|
|
53
|
+
} from './handlers-capture.js';
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
PAGE_ACTIONS,
|
|
57
|
+
handlePageTool,
|
|
58
|
+
handleBatchTool,
|
|
59
|
+
handleRawCallTool,
|
|
60
|
+
handleInvestigateTool,
|
|
61
|
+
} from './handlers-page.js';
|
|
62
|
+
|
|
63
|
+
/** @typedef {import('./handlers-utils.js').ToolAction} ToolAction */
|
|
64
|
+
/** @typedef {import('./handlers-utils.js').ToolResult} ToolResult */
|
|
65
|
+
|
|
66
|
+
const HOME_DIR = os.homedir();
|
|
302
67
|
|
|
303
68
|
/**
|
|
304
69
|
* @returns {Promise<ToolResult>}
|
|
@@ -320,583 +85,10 @@ export async function handleStatusTool() {
|
|
|
320
85
|
}
|
|
321
86
|
|
|
322
87
|
/**
|
|
323
|
-
* @param {{ action: string, url?: string, active?: boolean, tabId?: number }} args
|
|
324
|
-
* @returns {Promise<ToolResult>}
|
|
325
|
-
*/
|
|
326
|
-
export async function handleTabsTool(args) {
|
|
327
|
-
if (args.action === 'list') {
|
|
328
|
-
return callBridgeTool('tabs.list');
|
|
329
|
-
}
|
|
330
|
-
if (args.action === 'create') {
|
|
331
|
-
return callBridgeTool('tabs.create', {
|
|
332
|
-
url: args.url,
|
|
333
|
-
active: args.active,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
if (args.action === 'close') {
|
|
337
|
-
if (typeof args.tabId !== 'number') {
|
|
338
|
-
return summarizeToolError('tabId is required for tabs.close.');
|
|
339
|
-
}
|
|
340
|
-
return callBridgeTool('tabs.close', { tabId: args.tabId });
|
|
341
|
-
}
|
|
342
|
-
return summarizeToolError(`Unsupported tabs action "${args.action}".`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
/** @type {Record<string, ToolAction>} */
|
|
347
|
-
export const DOM_ACTIONS = {
|
|
348
|
-
query: {
|
|
349
|
-
ref: false,
|
|
350
|
-
method: 'dom.query',
|
|
351
|
-
params: (a) => ({
|
|
352
|
-
selector: a.selector || 'body',
|
|
353
|
-
withinRef: a.withinRef,
|
|
354
|
-
maxNodes: a.maxNodes,
|
|
355
|
-
maxDepth: a.maxDepth,
|
|
356
|
-
textBudget: a.textBudget,
|
|
357
|
-
includeBbox: a.includeBbox,
|
|
358
|
-
attributeAllowlist: a.attributeAllowlist,
|
|
359
|
-
}),
|
|
360
|
-
},
|
|
361
|
-
describe: {
|
|
362
|
-
ref: true,
|
|
363
|
-
method: 'dom.describe',
|
|
364
|
-
params: (_, r) => ({ elementRef: r }),
|
|
365
|
-
},
|
|
366
|
-
text: {
|
|
367
|
-
ref: true,
|
|
368
|
-
method: 'dom.get_text',
|
|
369
|
-
params: (a, r) => ({ elementRef: r, textBudget: a.textBudget }),
|
|
370
|
-
},
|
|
371
|
-
attributes: {
|
|
372
|
-
ref: true,
|
|
373
|
-
method: 'dom.get_attributes',
|
|
374
|
-
params: (a, r) => ({ elementRef: r, attributes: a.attributes || [] }),
|
|
375
|
-
},
|
|
376
|
-
wait: {
|
|
377
|
-
ref: false,
|
|
378
|
-
method: 'dom.wait_for',
|
|
379
|
-
params: (a) => ({
|
|
380
|
-
selector: a.selector,
|
|
381
|
-
text: a.text,
|
|
382
|
-
state: a.state,
|
|
383
|
-
timeoutMs: a.timeoutMs,
|
|
384
|
-
}),
|
|
385
|
-
},
|
|
386
|
-
find_text: {
|
|
387
|
-
ref: false,
|
|
388
|
-
method: 'dom.find_by_text',
|
|
389
|
-
params: (a) => ({
|
|
390
|
-
text: a.text,
|
|
391
|
-
exact: a.exact,
|
|
392
|
-
selector: a.selector,
|
|
393
|
-
maxResults: a.maxResults,
|
|
394
|
-
}),
|
|
395
|
-
},
|
|
396
|
-
find_role: {
|
|
397
|
-
ref: false,
|
|
398
|
-
method: 'dom.find_by_role',
|
|
399
|
-
params: (a) => ({
|
|
400
|
-
role: a.role,
|
|
401
|
-
name: a.name,
|
|
402
|
-
selector: a.selector,
|
|
403
|
-
maxResults: a.maxResults,
|
|
404
|
-
}),
|
|
405
|
-
},
|
|
406
|
-
html: {
|
|
407
|
-
ref: true,
|
|
408
|
-
method: 'dom.get_html',
|
|
409
|
-
params: (a, r) => ({
|
|
410
|
-
elementRef: r,
|
|
411
|
-
outer: a.outer,
|
|
412
|
-
maxLength: a.maxLength,
|
|
413
|
-
}),
|
|
414
|
-
},
|
|
415
|
-
accessibility_tree: {
|
|
416
|
-
ref: false,
|
|
417
|
-
method: 'dom.get_accessibility_tree',
|
|
418
|
-
params: (a) => ({ maxNodes: a.maxNodes, maxDepth: a.maxDepth }),
|
|
419
|
-
},
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* @param {{ action: string, selector?: string, elementRef?: string, withinRef?: string, maxNodes?: number, maxDepth?: number, textBudget?: number, includeBbox?: boolean, attributeAllowlist?: string[], attributes?: string[], text?: string, exact?: boolean, maxResults?: number, role?: string, name?: string, state?: string, timeoutMs?: number, outer?: boolean, maxLength?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
424
|
-
* @returns {Promise<ToolResult>}
|
|
425
|
-
*/
|
|
426
|
-
export async function handleDomTool(args) {
|
|
427
|
-
if (args.action === 'query' || args.action === 'accessibility_tree') {
|
|
428
|
-
const inferred = inferBudgetFromSelector(args);
|
|
429
|
-
const withBudget = inferred ? { ...args, budgetPreset: args.budgetPreset ?? inferred } : args;
|
|
430
|
-
return dispatchToolAction(DOM_ACTIONS, applyTreeBudgetPreset(withBudget), 'DOM');
|
|
431
|
-
}
|
|
432
|
-
if (args.action === 'text') {
|
|
433
|
-
return dispatchToolAction(DOM_ACTIONS, applyTextBudgetPreset(args), 'DOM');
|
|
434
|
-
}
|
|
435
|
-
if (args.action === 'html') {
|
|
436
|
-
return dispatchToolAction(DOM_ACTIONS, applyHtmlBudgetPreset(args), 'DOM');
|
|
437
|
-
}
|
|
438
|
-
return dispatchToolAction(DOM_ACTIONS, args, 'DOM');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** @type {Record<string, ToolAction>} */
|
|
442
|
-
export const STYLES_LAYOUT_ACTIONS = {
|
|
443
|
-
computed: {
|
|
444
|
-
ref: true,
|
|
445
|
-
method: 'styles.get_computed',
|
|
446
|
-
params: (a, r) => ({ elementRef: r, properties: a.properties }),
|
|
447
|
-
},
|
|
448
|
-
matched_rules: {
|
|
449
|
-
ref: true,
|
|
450
|
-
method: 'styles.get_matched_rules',
|
|
451
|
-
params: (_, r) => ({ elementRef: r }),
|
|
452
|
-
},
|
|
453
|
-
box_model: {
|
|
454
|
-
ref: true,
|
|
455
|
-
method: 'layout.get_box_model',
|
|
456
|
-
params: (_, r) => ({ elementRef: r }),
|
|
457
|
-
},
|
|
458
|
-
hit_test: {
|
|
459
|
-
ref: false,
|
|
460
|
-
method: 'layout.hit_test',
|
|
461
|
-
params: (a) => ({ x: a.x, y: a.y }),
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* @param {{ action: string, elementRef?: string, selector?: string, properties?: string[], x?: number, y?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
467
|
-
* @returns {Promise<ToolResult>}
|
|
468
|
-
*/
|
|
469
|
-
export async function handleStylesLayoutTool(args) {
|
|
470
|
-
return dispatchToolAction(STYLES_LAYOUT_ACTIONS, args, 'styles/layout');
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/** @type {Record<string, { method: BridgeMethod, params: (a: Record<string, unknown>) => Record<string, unknown> }>} */
|
|
474
|
-
export const PAGE_ACTIONS = {
|
|
475
|
-
state: { method: 'page.get_state', params: () => ({}) },
|
|
476
|
-
evaluate: {
|
|
477
|
-
method: 'page.evaluate',
|
|
478
|
-
params: (a) => ({
|
|
479
|
-
expression: a.expression,
|
|
480
|
-
awaitPromise: a.awaitPromise,
|
|
481
|
-
timeoutMs: a.timeoutMs,
|
|
482
|
-
returnByValue: a.returnByValue,
|
|
483
|
-
}),
|
|
484
|
-
},
|
|
485
|
-
console: {
|
|
486
|
-
method: 'page.get_console',
|
|
487
|
-
params: (a) => ({ level: a.level, clear: a.clear, limit: a.limit }),
|
|
488
|
-
},
|
|
489
|
-
wait_for_load: {
|
|
490
|
-
method: 'page.wait_for_load_state',
|
|
491
|
-
params: (a) => ({ timeoutMs: a.timeoutMs }),
|
|
492
|
-
},
|
|
493
|
-
storage: {
|
|
494
|
-
method: 'page.get_storage',
|
|
495
|
-
params: (a) => ({ type: a.type, keys: a.keys }),
|
|
496
|
-
},
|
|
497
|
-
text: {
|
|
498
|
-
method: 'page.get_text',
|
|
499
|
-
params: (a) => ({ textBudget: a.textBudget }),
|
|
500
|
-
},
|
|
501
|
-
network: {
|
|
502
|
-
method: 'page.get_network',
|
|
503
|
-
params: (a) => ({
|
|
504
|
-
clear: a.clear,
|
|
505
|
-
limit: a.limit,
|
|
506
|
-
urlPattern: a.urlPattern,
|
|
507
|
-
}),
|
|
508
|
-
},
|
|
509
|
-
performance: { method: 'performance.get_metrics', params: () => ({}) },
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* @param {{ action: string, expression?: string, awaitPromise?: boolean, timeoutMs?: number, returnByValue?: boolean, level?: string, clear?: boolean, limit?: number, type?: string, keys?: string[], textBudget?: number, urlPattern?: string, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
514
|
-
* @returns {Promise<ToolResult>}
|
|
515
|
-
*/
|
|
516
|
-
export async function handlePageTool(args) {
|
|
517
|
-
let normalizedArgs = args;
|
|
518
|
-
if (args.action === 'text') {
|
|
519
|
-
normalizedArgs = applyTextBudgetPreset(args);
|
|
520
|
-
} else if (args.action === 'console') {
|
|
521
|
-
normalizedArgs = applyLimitBudgetPreset(args, {
|
|
522
|
-
quick: 10,
|
|
523
|
-
normal: DEFAULT_CONSOLE_LIMIT,
|
|
524
|
-
deep: 100,
|
|
525
|
-
});
|
|
526
|
-
} else if (args.action === 'network') {
|
|
527
|
-
normalizedArgs = applyLimitBudgetPreset(args, {
|
|
528
|
-
quick: 10,
|
|
529
|
-
normal: DEFAULT_NETWORK_LIMIT,
|
|
530
|
-
deep: 100,
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
const entry = PAGE_ACTIONS[normalizedArgs.action];
|
|
534
|
-
if (!entry) return summarizeToolError(`Unsupported page action "${args.action}".`);
|
|
535
|
-
return callBridgeTool(entry.method, entry.params(normalizedArgs), {
|
|
536
|
-
tabId: typeof normalizedArgs.tabId === 'number' ? normalizedArgs.tabId : null,
|
|
537
|
-
tokenBudget: getToolTokenBudget(normalizedArgs),
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/** @type {Record<string, { method: BridgeMethod, params: (a: Record<string, unknown>) => Record<string, unknown> }>} */
|
|
542
|
-
export const NAVIGATION_ACTIONS = {
|
|
543
|
-
navigate: {
|
|
544
|
-
method: 'navigation.navigate',
|
|
545
|
-
params: (a) => ({
|
|
546
|
-
url: a.url,
|
|
547
|
-
waitForLoad: a.waitForLoad,
|
|
548
|
-
timeoutMs: a.timeoutMs,
|
|
549
|
-
}),
|
|
550
|
-
},
|
|
551
|
-
reload: {
|
|
552
|
-
method: 'navigation.reload',
|
|
553
|
-
params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
|
|
554
|
-
},
|
|
555
|
-
go_back: {
|
|
556
|
-
method: 'navigation.go_back',
|
|
557
|
-
params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
|
|
558
|
-
},
|
|
559
|
-
go_forward: {
|
|
560
|
-
method: 'navigation.go_forward',
|
|
561
|
-
params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
|
|
562
|
-
},
|
|
563
|
-
scroll: {
|
|
564
|
-
method: 'viewport.scroll',
|
|
565
|
-
params: (a) => ({
|
|
566
|
-
top: a.top,
|
|
567
|
-
left: a.left,
|
|
568
|
-
behavior: a.behavior,
|
|
569
|
-
relative: a.relative,
|
|
570
|
-
}),
|
|
571
|
-
},
|
|
572
|
-
resize: {
|
|
573
|
-
method: 'viewport.resize',
|
|
574
|
-
params: (a) => ({ width: a.width, height: a.height, reset: a.reset }),
|
|
575
|
-
},
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* @param {{ action: string, url?: string, waitForLoad?: boolean, timeoutMs?: number, top?: number, left?: number, behavior?: string, relative?: boolean, width?: number, height?: number, reset?: boolean, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
580
|
-
* @returns {Promise<ToolResult>}
|
|
581
|
-
*/
|
|
582
|
-
export async function handleNavigationTool(args) {
|
|
583
|
-
const entry = NAVIGATION_ACTIONS[args.action];
|
|
584
|
-
if (!entry) return summarizeToolError(`Unsupported navigation action "${args.action}".`);
|
|
585
|
-
return callBridgeTool(entry.method, entry.params(args), {
|
|
586
|
-
tabId: typeof args.tabId === 'number' ? args.tabId : null,
|
|
587
|
-
tokenBudget: getToolTokenBudget(args),
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/** @type {Record<string, BridgeMethod>} */
|
|
592
|
-
export const INPUT_ACTION_METHODS = {
|
|
593
|
-
click: 'input.click',
|
|
594
|
-
focus: 'input.focus',
|
|
595
|
-
type: 'input.type',
|
|
596
|
-
press_key: 'input.press_key',
|
|
597
|
-
set_checked: 'input.set_checked',
|
|
598
|
-
select_option: 'input.select_option',
|
|
599
|
-
hover: 'input.hover',
|
|
600
|
-
drag: 'input.drag',
|
|
601
|
-
scroll_into_view: 'input.scroll_into_view',
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* @param {{ action: string, elementRef?: string, selector?: string, button?: string, clickCount?: number, text?: string, clear?: boolean, submit?: boolean, key?: string, modifiers?: string[], checked?: boolean, values?: string[], labels?: string[], indexes?: number[], duration?: number, sourceElementRef?: string, sourceSelector?: string, destinationElementRef?: string, destinationSelector?: string, offsetX?: number, offsetY?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
606
|
-
* @returns {Promise<ToolResult>}
|
|
607
|
-
*/
|
|
608
|
-
export async function handleInputTool(args) {
|
|
609
|
-
return withToolClient(async (client) => {
|
|
610
|
-
const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
|
|
611
|
-
const elementTarget = async () => ({
|
|
612
|
-
elementRef: await resolveToolRef(client, args, requestedTabId),
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
switch (args.action) {
|
|
616
|
-
case 'click': {
|
|
617
|
-
const response = await requestBridge(
|
|
618
|
-
client,
|
|
619
|
-
'input.click',
|
|
620
|
-
{
|
|
621
|
-
target: await elementTarget(),
|
|
622
|
-
button: args.button,
|
|
623
|
-
clickCount: args.clickCount,
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
tabId: requestedTabId,
|
|
627
|
-
source: REQUEST_SOURCE,
|
|
628
|
-
tokenBudget: getToolTokenBudget(args),
|
|
629
|
-
}
|
|
630
|
-
);
|
|
631
|
-
return summarizeToolResponse(response, 'input.click');
|
|
632
|
-
}
|
|
633
|
-
case 'focus': {
|
|
634
|
-
const response = await requestBridge(
|
|
635
|
-
client,
|
|
636
|
-
'input.focus',
|
|
637
|
-
{
|
|
638
|
-
target: await elementTarget(),
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
tabId: requestedTabId,
|
|
642
|
-
source: REQUEST_SOURCE,
|
|
643
|
-
tokenBudget: getToolTokenBudget(args),
|
|
644
|
-
}
|
|
645
|
-
);
|
|
646
|
-
return summarizeToolResponse(response, 'input.focus');
|
|
647
|
-
}
|
|
648
|
-
case 'type': {
|
|
649
|
-
const response = await requestBridge(
|
|
650
|
-
client,
|
|
651
|
-
'input.type',
|
|
652
|
-
{
|
|
653
|
-
target: await elementTarget(),
|
|
654
|
-
text: args.text,
|
|
655
|
-
clear: args.clear,
|
|
656
|
-
submit: args.submit,
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
tabId: requestedTabId,
|
|
660
|
-
source: REQUEST_SOURCE,
|
|
661
|
-
tokenBudget: getToolTokenBudget(args),
|
|
662
|
-
}
|
|
663
|
-
);
|
|
664
|
-
return summarizeToolResponse(response, 'input.type');
|
|
665
|
-
}
|
|
666
|
-
case 'press_key': {
|
|
667
|
-
const target = args.elementRef || args.selector ? await elementTarget() : undefined;
|
|
668
|
-
const response = await requestBridge(
|
|
669
|
-
client,
|
|
670
|
-
'input.press_key',
|
|
671
|
-
{
|
|
672
|
-
target,
|
|
673
|
-
key: args.key,
|
|
674
|
-
modifiers: args.modifiers,
|
|
675
|
-
},
|
|
676
|
-
{
|
|
677
|
-
tabId: requestedTabId,
|
|
678
|
-
source: REQUEST_SOURCE,
|
|
679
|
-
tokenBudget: getToolTokenBudget(args),
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
return summarizeToolResponse(response, 'input.press_key');
|
|
683
|
-
}
|
|
684
|
-
case 'set_checked': {
|
|
685
|
-
const response = await requestBridge(
|
|
686
|
-
client,
|
|
687
|
-
'input.set_checked',
|
|
688
|
-
{
|
|
689
|
-
target: await elementTarget(),
|
|
690
|
-
checked: args.checked,
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
tabId: requestedTabId,
|
|
694
|
-
source: REQUEST_SOURCE,
|
|
695
|
-
tokenBudget: getToolTokenBudget(args),
|
|
696
|
-
}
|
|
697
|
-
);
|
|
698
|
-
return summarizeToolResponse(response, 'input.set_checked');
|
|
699
|
-
}
|
|
700
|
-
case 'select_option': {
|
|
701
|
-
const response = await requestBridge(
|
|
702
|
-
client,
|
|
703
|
-
'input.select_option',
|
|
704
|
-
{
|
|
705
|
-
target: await elementTarget(),
|
|
706
|
-
values: args.values,
|
|
707
|
-
labels: args.labels,
|
|
708
|
-
indexes: args.indexes,
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
tabId: requestedTabId,
|
|
712
|
-
source: REQUEST_SOURCE,
|
|
713
|
-
tokenBudget: getToolTokenBudget(args),
|
|
714
|
-
}
|
|
715
|
-
);
|
|
716
|
-
return summarizeToolResponse(response, 'input.select_option');
|
|
717
|
-
}
|
|
718
|
-
case 'hover': {
|
|
719
|
-
const response = await requestBridge(
|
|
720
|
-
client,
|
|
721
|
-
'input.hover',
|
|
722
|
-
{
|
|
723
|
-
target: await elementTarget(),
|
|
724
|
-
duration: args.duration,
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
tabId: requestedTabId,
|
|
728
|
-
source: REQUEST_SOURCE,
|
|
729
|
-
tokenBudget: getToolTokenBudget(args),
|
|
730
|
-
}
|
|
731
|
-
);
|
|
732
|
-
return summarizeToolResponse(response, 'input.hover');
|
|
733
|
-
}
|
|
734
|
-
case 'drag': {
|
|
735
|
-
const source = {
|
|
736
|
-
elementRef:
|
|
737
|
-
args.sourceElementRef ||
|
|
738
|
-
(args.sourceSelector
|
|
739
|
-
? await resolveRef(client, args.sourceSelector, requestedTabId, REQUEST_SOURCE)
|
|
740
|
-
: ''),
|
|
741
|
-
};
|
|
742
|
-
const destination = {
|
|
743
|
-
elementRef:
|
|
744
|
-
args.destinationElementRef ||
|
|
745
|
-
(args.destinationSelector
|
|
746
|
-
? await resolveRef(client, args.destinationSelector, requestedTabId, REQUEST_SOURCE)
|
|
747
|
-
: ''),
|
|
748
|
-
};
|
|
749
|
-
if (!source.elementRef || !destination.elementRef) {
|
|
750
|
-
return summarizeToolError(
|
|
751
|
-
'sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.'
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
const response = await requestBridge(
|
|
755
|
-
client,
|
|
756
|
-
'input.drag',
|
|
757
|
-
{
|
|
758
|
-
source,
|
|
759
|
-
destination,
|
|
760
|
-
offsetX: args.offsetX,
|
|
761
|
-
offsetY: args.offsetY,
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
tabId: requestedTabId,
|
|
765
|
-
source: REQUEST_SOURCE,
|
|
766
|
-
tokenBudget: getToolTokenBudget(args),
|
|
767
|
-
}
|
|
768
|
-
);
|
|
769
|
-
return summarizeToolResponse(response, 'input.drag');
|
|
770
|
-
}
|
|
771
|
-
case 'scroll_into_view': {
|
|
772
|
-
const response = await requestBridge(
|
|
773
|
-
client,
|
|
774
|
-
'input.scroll_into_view',
|
|
775
|
-
{
|
|
776
|
-
target: await elementTarget(),
|
|
777
|
-
},
|
|
778
|
-
{
|
|
779
|
-
tabId: requestedTabId,
|
|
780
|
-
source: REQUEST_SOURCE,
|
|
781
|
-
tokenBudget: getToolTokenBudget(args),
|
|
782
|
-
}
|
|
783
|
-
);
|
|
784
|
-
return summarizeToolResponse(response, 'input.scroll_into_view');
|
|
785
|
-
}
|
|
786
|
-
default:
|
|
787
|
-
return summarizeToolError(`Unsupported input action "${args.action}".`);
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/** @type {Record<string, ToolAction>} */
|
|
793
|
-
export const PATCH_ACTIONS = {
|
|
794
|
-
apply_styles: {
|
|
795
|
-
ref: true,
|
|
796
|
-
method: 'patch.apply_styles',
|
|
797
|
-
params: (a, r) => ({
|
|
798
|
-
target: { elementRef: r },
|
|
799
|
-
declarations: a.declarations,
|
|
800
|
-
important: a.important,
|
|
801
|
-
verify: a.verify,
|
|
802
|
-
}),
|
|
803
|
-
},
|
|
804
|
-
apply_dom: {
|
|
805
|
-
ref: true,
|
|
806
|
-
method: 'patch.apply_dom',
|
|
807
|
-
params: (a, r) => {
|
|
808
|
-
const operation = typeof a.operation === 'string' ? a.operation : '';
|
|
809
|
-
/** @type {Record<string, string>} */
|
|
810
|
-
const opMap = {
|
|
811
|
-
setAttribute: 'set_attribute',
|
|
812
|
-
removeAttribute: 'remove_attribute',
|
|
813
|
-
addClass: 'toggle_class',
|
|
814
|
-
removeClass: 'toggle_class',
|
|
815
|
-
setTextContent: 'set_text',
|
|
816
|
-
setProperty: 'set_attribute',
|
|
817
|
-
};
|
|
818
|
-
return {
|
|
819
|
-
target: { elementRef: r },
|
|
820
|
-
operation: opMap[operation] || operation,
|
|
821
|
-
value: a.value,
|
|
822
|
-
name: a.name,
|
|
823
|
-
verify: a.verify,
|
|
824
|
-
};
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
list: { ref: false, method: 'patch.list', params: () => ({}) },
|
|
828
|
-
rollback: {
|
|
829
|
-
ref: false,
|
|
830
|
-
method: 'patch.rollback',
|
|
831
|
-
params: (a) => ({ patchId: a.patchId }),
|
|
832
|
-
},
|
|
833
|
-
commit_baseline: {
|
|
834
|
-
ref: false,
|
|
835
|
-
method: 'patch.commit_session_baseline',
|
|
836
|
-
params: () => ({}),
|
|
837
|
-
},
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* @param {{ action: string, elementRef?: string, selector?: string, declarations?: Record<string, string>, important?: boolean, operation?: string, value?: unknown, name?: string, patchId?: string, verify?: boolean, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
842
|
-
* @returns {Promise<ToolResult>}
|
|
843
|
-
*/
|
|
844
|
-
export async function handlePatchTool(args) {
|
|
845
|
-
return dispatchToolAction(PATCH_ACTIONS, args, 'patch');
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
/** @type {Record<string, ToolAction>} */
|
|
849
|
-
export const CAPTURE_ACTIONS = {
|
|
850
|
-
element: {
|
|
851
|
-
ref: true,
|
|
852
|
-
method: 'screenshot.capture_element',
|
|
853
|
-
params: (_, r) => ({ elementRef: r }),
|
|
854
|
-
},
|
|
855
|
-
region: {
|
|
856
|
-
ref: false,
|
|
857
|
-
method: 'screenshot.capture_region',
|
|
858
|
-
params: (a) => /** @type {Record<string, unknown>} */ (a.rect || {}),
|
|
859
|
-
},
|
|
860
|
-
full_page: {
|
|
861
|
-
ref: false,
|
|
862
|
-
method: 'screenshot.capture_full_page',
|
|
863
|
-
params: () => ({}),
|
|
864
|
-
},
|
|
865
|
-
cdp_document: { ref: false, method: 'cdp.get_document', params: () => ({}) },
|
|
866
|
-
cdp_dom_snapshot: {
|
|
867
|
-
ref: false,
|
|
868
|
-
method: 'cdp.get_dom_snapshot',
|
|
869
|
-
params: () => ({}),
|
|
870
|
-
},
|
|
871
|
-
cdp_box_model: {
|
|
872
|
-
ref: true,
|
|
873
|
-
method: 'cdp.get_box_model',
|
|
874
|
-
params: (_, r) => ({ elementRef: r }),
|
|
875
|
-
},
|
|
876
|
-
cdp_computed_styles: {
|
|
877
|
-
ref: true,
|
|
878
|
-
method: 'cdp.get_computed_styles_for_node',
|
|
879
|
-
params: (_, r) => ({ elementRef: r }),
|
|
880
|
-
},
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
885
|
-
* @returns {Promise<ToolResult>}
|
|
886
|
-
*/
|
|
887
|
-
export async function handleCaptureTool(args) {
|
|
888
|
-
return dispatchToolAction(CAPTURE_ACTIONS, args, 'capture');
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Returns the live runtime context: budget presets, method groups, and active limits.
|
|
893
|
-
* Equivalent to `bbx skill`. Use this first to discover safe defaults before inspecting.
|
|
894
|
-
*
|
|
895
88
|
* @returns {Promise<ToolResult>}
|
|
896
89
|
*/
|
|
897
90
|
export async function handleSkillTool() {
|
|
898
91
|
try {
|
|
899
|
-
const { createRuntimeContext } = await import('../../protocol/src/index.js');
|
|
900
92
|
const ctx = createRuntimeContext();
|
|
901
93
|
return createToolResult('Runtime context retrieved.', {
|
|
902
94
|
ok: true,
|
|
@@ -908,14 +100,11 @@ export async function handleSkillTool() {
|
|
|
908
100
|
}
|
|
909
101
|
|
|
910
102
|
/**
|
|
911
|
-
* Check MCP config and CLI skill installation status.
|
|
912
|
-
*
|
|
913
103
|
* @param {{ global?: boolean }} args
|
|
914
104
|
* @returns {Promise<ToolResult>}
|
|
915
105
|
*/
|
|
916
106
|
export async function handleSetupTool(args) {
|
|
917
|
-
const
|
|
918
|
-
const projectPath = args.global !== false ? (await import('node:os')).homedir() : process.cwd();
|
|
107
|
+
const projectPath = args.global !== false ? HOME_DIR : process.cwd();
|
|
919
108
|
const status = await collectSetupStatus({
|
|
920
109
|
global: args.global !== false,
|
|
921
110
|
cwd: process.cwd(),
|
|
@@ -928,8 +117,6 @@ export async function handleSetupTool(args) {
|
|
|
928
117
|
}
|
|
929
118
|
|
|
930
119
|
/**
|
|
931
|
-
* Tail recent bridge logs for debugging.
|
|
932
|
-
*
|
|
933
120
|
* @param {{ limit?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
934
121
|
* @returns {Promise<ToolResult>}
|
|
935
122
|
*/
|
|
@@ -951,8 +138,6 @@ export async function handleLogTool(args) {
|
|
|
951
138
|
}
|
|
952
139
|
|
|
953
140
|
/**
|
|
954
|
-
* Ping the bridge to check connectivity.
|
|
955
|
-
*
|
|
956
141
|
* @returns {Promise<ToolResult>}
|
|
957
142
|
*/
|
|
958
143
|
export async function handleHealthTool() {
|
|
@@ -966,289 +151,8 @@ export async function handleHealthTool() {
|
|
|
966
151
|
}
|
|
967
152
|
|
|
968
153
|
/**
|
|
969
|
-
* @param {{ calls?: Array<{ method?: string, params?: Record<string, unknown>, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }> }} args
|
|
970
|
-
* @returns {Promise<ToolResult>}
|
|
971
|
-
*/
|
|
972
|
-
export async function handleBatchTool(args) {
|
|
973
|
-
if (!Array.isArray(args.calls) || args.calls.length === 0) {
|
|
974
|
-
return summarizeToolError('calls must be a non-empty array.');
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const calls = args.calls;
|
|
978
|
-
return withToolClient(async (client) => {
|
|
979
|
-
const results = await Promise.all(
|
|
980
|
-
calls.map(async (call) => {
|
|
981
|
-
if (!call || typeof call !== 'object' || typeof call.method !== 'string') {
|
|
982
|
-
return {
|
|
983
|
-
method: '',
|
|
984
|
-
tabId: null,
|
|
985
|
-
ok: false,
|
|
986
|
-
summary: 'INVALID_REQUEST: Each batch call needs a method.',
|
|
987
|
-
evidence: null,
|
|
988
|
-
error: {
|
|
989
|
-
code: 'INVALID_REQUEST',
|
|
990
|
-
message: 'Each batch call needs a method.',
|
|
991
|
-
},
|
|
992
|
-
response: null,
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
if (!METHODS.includes(/** @type {BridgeMethod} */ (call.method))) {
|
|
997
|
-
return {
|
|
998
|
-
method: call.method,
|
|
999
|
-
tabId: null,
|
|
1000
|
-
ok: false,
|
|
1001
|
-
summary: `INVALID_REQUEST: Unknown bridge method "${call.method}".`,
|
|
1002
|
-
evidence: null,
|
|
1003
|
-
error: {
|
|
1004
|
-
code: 'INVALID_REQUEST',
|
|
1005
|
-
message: `Unknown bridge method "${call.method}".`,
|
|
1006
|
-
},
|
|
1007
|
-
response: null,
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const method = /** @type {BridgeMethod} */ (call.method);
|
|
1012
|
-
const tabId = bridgeMethodNeedsTab(method)
|
|
1013
|
-
? typeof call.tabId === 'number'
|
|
1014
|
-
? call.tabId
|
|
1015
|
-
: null
|
|
1016
|
-
: null;
|
|
1017
|
-
const tokenBudget = getToolTokenBudget(call);
|
|
1018
|
-
|
|
1019
|
-
const startTime = Date.now();
|
|
1020
|
-
try {
|
|
1021
|
-
const response = await client.request({
|
|
1022
|
-
method,
|
|
1023
|
-
params: call.params || {},
|
|
1024
|
-
tabId,
|
|
1025
|
-
meta: {
|
|
1026
|
-
source: REQUEST_SOURCE,
|
|
1027
|
-
...(tokenBudget != null ? { token_budget: tokenBudget } : {}),
|
|
1028
|
-
},
|
|
1029
|
-
});
|
|
1030
|
-
return summarizeBatchResponseItem({
|
|
1031
|
-
method,
|
|
1032
|
-
tabId,
|
|
1033
|
-
response,
|
|
1034
|
-
durationMs: Date.now() - startTime,
|
|
1035
|
-
});
|
|
1036
|
-
} catch (error) {
|
|
1037
|
-
return summarizeBatchErrorItem({
|
|
1038
|
-
method,
|
|
1039
|
-
tabId,
|
|
1040
|
-
error,
|
|
1041
|
-
durationMs: Date.now() - startTime,
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
})
|
|
1045
|
-
);
|
|
1046
|
-
|
|
1047
|
-
const failureCount = results.filter((result) => !result.ok).length;
|
|
1048
|
-
const summary =
|
|
1049
|
-
failureCount === 0
|
|
1050
|
-
? `Batch executed ${results.length} call(s).`
|
|
1051
|
-
: `Batch executed ${results.length} call(s) with ${failureCount} error(s).`;
|
|
1052
|
-
return createToolResult(
|
|
1053
|
-
summary,
|
|
1054
|
-
{
|
|
1055
|
-
ok: failureCount === 0,
|
|
1056
|
-
results,
|
|
1057
|
-
},
|
|
1058
|
-
failureCount > 0
|
|
1059
|
-
);
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
/**
|
|
1064
|
-
* @param {{ method: string, params?: Record<string, unknown>, tabId?: number }} args
|
|
1065
|
-
* @returns {Promise<ToolResult>}
|
|
1066
|
-
*/
|
|
1067
|
-
export async function handleRawCallTool(args) {
|
|
1068
|
-
if (!METHODS.includes(/** @type {BridgeMethod} */ (args.method))) {
|
|
1069
|
-
return summarizeToolError(`Unknown bridge method "${args.method}".`);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
return withToolClient(async (client) => {
|
|
1073
|
-
const response = await requestBridge(
|
|
1074
|
-
client,
|
|
1075
|
-
/** @type {BridgeMethod} */ (args.method),
|
|
1076
|
-
args.params || {},
|
|
1077
|
-
{
|
|
1078
|
-
tabId: typeof args.tabId === 'number' ? args.tabId : null,
|
|
1079
|
-
source: REQUEST_SOURCE,
|
|
1080
|
-
}
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
if (!response.ok) {
|
|
1084
|
-
return createToolResult(
|
|
1085
|
-
response.error.message,
|
|
1086
|
-
{
|
|
1087
|
-
ok: false,
|
|
1088
|
-
error: response.error,
|
|
1089
|
-
response,
|
|
1090
|
-
},
|
|
1091
|
-
true
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
return createToolResult(`Called ${args.method}.`, {
|
|
1096
|
-
ok: true,
|
|
1097
|
-
response: response.result,
|
|
1098
|
-
});
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
/**
|
|
1103
|
-
* Explicitly request Browser Bridge access for the current browser window.
|
|
1104
|
-
* Surfaces an Enable cue in the extension popup or side panel.
|
|
1105
|
-
*
|
|
1106
154
|
* @returns {Promise<ToolResult>}
|
|
1107
155
|
*/
|
|
1108
156
|
export async function handleAccessTool() {
|
|
1109
157
|
return callBridgeTool('access.request');
|
|
1110
158
|
}
|
|
1111
|
-
|
|
1112
|
-
// ---------------------------------------------------------------------------
|
|
1113
|
-
// browser_investigate — heuristic fallback for subagent-delegatable inspection
|
|
1114
|
-
// ---------------------------------------------------------------------------
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* @typedef {{ method: BridgeMethod, params: (args: Record<string, unknown>) => Record<string, unknown> }} InvestigateStep
|
|
1118
|
-
*/
|
|
1119
|
-
|
|
1120
|
-
/** @type {Record<string, { label: string, steps: InvestigateStep[] }>} */
|
|
1121
|
-
const INVESTIGATE_SCOPES = {
|
|
1122
|
-
quick: {
|
|
1123
|
-
label: 'quick',
|
|
1124
|
-
steps: [
|
|
1125
|
-
{ method: 'page.get_state', params: () => ({}) },
|
|
1126
|
-
{
|
|
1127
|
-
method: 'dom.query',
|
|
1128
|
-
params: (a) => ({
|
|
1129
|
-
selector: a.selector || 'body',
|
|
1130
|
-
maxNodes: 10,
|
|
1131
|
-
maxDepth: 2,
|
|
1132
|
-
textBudget: 300,
|
|
1133
|
-
}),
|
|
1134
|
-
},
|
|
1135
|
-
],
|
|
1136
|
-
},
|
|
1137
|
-
normal: {
|
|
1138
|
-
label: 'normal',
|
|
1139
|
-
steps: [
|
|
1140
|
-
{ method: 'page.get_state', params: () => ({}) },
|
|
1141
|
-
{
|
|
1142
|
-
method: 'dom.query',
|
|
1143
|
-
params: (a) => ({
|
|
1144
|
-
selector: a.selector || 'body',
|
|
1145
|
-
maxNodes: 25,
|
|
1146
|
-
maxDepth: 4,
|
|
1147
|
-
textBudget: 600,
|
|
1148
|
-
}),
|
|
1149
|
-
},
|
|
1150
|
-
{ method: 'page.get_text', params: () => ({ textBudget: 4000 }) },
|
|
1151
|
-
],
|
|
1152
|
-
},
|
|
1153
|
-
deep: {
|
|
1154
|
-
label: 'deep',
|
|
1155
|
-
steps: [
|
|
1156
|
-
{ method: 'page.get_state', params: () => ({}) },
|
|
1157
|
-
{
|
|
1158
|
-
method: 'dom.query',
|
|
1159
|
-
params: (a) => ({
|
|
1160
|
-
selector: a.selector || 'body',
|
|
1161
|
-
maxNodes: 50,
|
|
1162
|
-
maxDepth: 6,
|
|
1163
|
-
textBudget: 1000,
|
|
1164
|
-
}),
|
|
1165
|
-
},
|
|
1166
|
-
{ method: 'page.get_text', params: () => ({ textBudget: 8000 }) },
|
|
1167
|
-
{
|
|
1168
|
-
method: 'page.get_console',
|
|
1169
|
-
params: () => ({ level: 'warn', limit: 20 }),
|
|
1170
|
-
},
|
|
1171
|
-
{ method: 'page.get_network', params: () => ({ limit: 20 }) },
|
|
1172
|
-
],
|
|
1173
|
-
},
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* Investigate a page to answer a question or verify a condition.
|
|
1178
|
-
* Runs a deterministic heuristic inspection sequence and returns a
|
|
1179
|
-
* structured summary. Designed so smart orchestrators can delegate
|
|
1180
|
-
* this to a cheaper subagent instead.
|
|
1181
|
-
*
|
|
1182
|
-
* @param {{ objective: string, scope?: 'quick' | 'normal' | 'deep', tabId?: number, selector?: string }} args
|
|
1183
|
-
* @returns {Promise<ToolResult>}
|
|
1184
|
-
*/
|
|
1185
|
-
export async function handleInvestigateTool(args) {
|
|
1186
|
-
const objective = typeof args.objective === 'string' ? args.objective.trim() : '';
|
|
1187
|
-
if (!objective) {
|
|
1188
|
-
return summarizeToolError('objective is required for browser_investigate.');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
const scopeName = args.scope || 'normal';
|
|
1192
|
-
const scope = INVESTIGATE_SCOPES[scopeName];
|
|
1193
|
-
if (!scope) {
|
|
1194
|
-
return summarizeToolError(`Unsupported investigation scope "${scopeName}".`);
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
return withToolClient(async (client) => {
|
|
1198
|
-
const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
|
|
1199
|
-
|
|
1200
|
-
/** @type {Array<{ method: string, ok: boolean, summary: string, evidence: unknown, durationMs: number }>} */
|
|
1201
|
-
const stepResults = [];
|
|
1202
|
-
|
|
1203
|
-
for (const step of scope.steps) {
|
|
1204
|
-
const startTime = Date.now();
|
|
1205
|
-
try {
|
|
1206
|
-
const response = await requestBridgeWithRetry(client, step.method, step.params(args), {
|
|
1207
|
-
tabId: requestedTabId,
|
|
1208
|
-
source: REQUEST_SOURCE,
|
|
1209
|
-
tokenBudget: null,
|
|
1210
|
-
});
|
|
1211
|
-
const bridgeSummary = annotateBridgeSummary(
|
|
1212
|
-
summarizeBridgeResponse(response, step.method),
|
|
1213
|
-
response
|
|
1214
|
-
);
|
|
1215
|
-
stepResults.push({
|
|
1216
|
-
method: step.method,
|
|
1217
|
-
ok: bridgeSummary.ok,
|
|
1218
|
-
summary: bridgeSummary.summary,
|
|
1219
|
-
evidence: bridgeSummary.evidence,
|
|
1220
|
-
durationMs: Date.now() - startTime,
|
|
1221
|
-
});
|
|
1222
|
-
} catch (error) {
|
|
1223
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1224
|
-
stepResults.push({
|
|
1225
|
-
method: step.method,
|
|
1226
|
-
ok: false,
|
|
1227
|
-
summary: `ERROR: ${message}`,
|
|
1228
|
-
evidence: null,
|
|
1229
|
-
durationMs: Date.now() - startTime,
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const failedSteps = stepResults.filter((s) => !s.ok);
|
|
1235
|
-
const allOk = failedSteps.length === 0;
|
|
1236
|
-
const totalDuration = stepResults.reduce((sum, s) => sum + s.durationMs, 0);
|
|
1237
|
-
|
|
1238
|
-
const summaryText = allOk
|
|
1239
|
-
? `Investigation complete (${scope.label}, ${stepResults.length} steps, ${totalDuration}ms). Objective: ${objective}`
|
|
1240
|
-
: `Investigation partial (${scope.label}, ${stepResults.length} steps, ${failedSteps.length} failed, ${totalDuration}ms). Objective: ${objective}`;
|
|
1241
|
-
|
|
1242
|
-
return createToolResult(
|
|
1243
|
-
summaryText,
|
|
1244
|
-
{
|
|
1245
|
-
ok: allOk,
|
|
1246
|
-
objective,
|
|
1247
|
-
scope: scopeName,
|
|
1248
|
-
heuristicFallback: true,
|
|
1249
|
-
steps: stepResults,
|
|
1250
|
-
},
|
|
1251
|
-
!allOk
|
|
1252
|
-
);
|
|
1253
|
-
});
|
|
1254
|
-
}
|