@agimon-ai/browse-tool 0.2.0 → 0.2.2
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 +69 -0
- package/dist/cli.cjs +24 -23
- package/dist/cli.mjs +23 -23
- package/dist/extension/background.js +1039 -339
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/content.js +45 -4
- package/dist/extension/content.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -2
- package/dist/playwright-test-BwI7HgW7.mjs +1 -2
- package/dist/{playwright-test-CnsuVfC9.cjs → playwright-test-DTw_9rvK.cjs} +1 -1
- package/dist/stdio-DKou0TqY.cjs +10 -0
- package/dist/stdio-ELROpCD_.mjs +9 -0
- package/dist/stubs/playwright-test.cjs +1 -1
- package/package.json +12 -2
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.mts +0 -2
- package/dist/cli.mjs.map +0 -1
- package/dist/index.d.cts +0 -309
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts +0 -310
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/playwright-test-BwI7HgW7.mjs.map +0 -1
- package/dist/stdio-BP3yiSxK.mjs +0 -9
- package/dist/stdio-BP3yiSxK.mjs.map +0 -1
- package/dist/stdio-ynNFGBY4.cjs +0 -9
- package/dist/stubs/playwright-test.d.cts +0 -111
- package/dist/stubs/playwright-test.d.cts.map +0 -1
- package/dist/stubs/playwright-test.d.mts +0 -111
- package/dist/stubs/playwright-test.d.mts.map +0 -1
|
@@ -1,3 +1,382 @@
|
|
|
1
|
+
async function screenshotHandler(args, _context, tabId) {
|
|
2
|
+
if (!tabId) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
5
|
+
isError: true
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const format = args.format || "png";
|
|
10
|
+
const quality = args.quality;
|
|
11
|
+
const tab = await globalThis.chrome.tabs.get(tabId);
|
|
12
|
+
if (!tab.windowId) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: "Tab has no window" }],
|
|
15
|
+
isError: true
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
await globalThis.chrome.tabs.update(tabId, { active: true });
|
|
19
|
+
const dataUrl = await globalThis.chrome.tabs.captureVisibleTab(tab.windowId, {
|
|
20
|
+
format,
|
|
21
|
+
quality: format === "jpeg" ? quality : void 0
|
|
22
|
+
});
|
|
23
|
+
const base64 = dataUrl.split(",")[1];
|
|
24
|
+
const mimeType = format === "png" ? "image/png" : "image/jpeg";
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "image",
|
|
29
|
+
data: base64,
|
|
30
|
+
mimeType
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : "Screenshot failed";
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: message }],
|
|
38
|
+
isError: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function snapshotHandler(args, _context, tabId) {
|
|
43
|
+
if (!tabId) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
46
|
+
isError: true
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const root = typeof args.root === "string" && args.root.trim() ? args.root.trim() : "body";
|
|
51
|
+
const [result] = await globalThis.chrome.scripting.executeScript({
|
|
52
|
+
target: { tabId },
|
|
53
|
+
world: "ISOLATED",
|
|
54
|
+
args: [root],
|
|
55
|
+
func: (rootSelector) => {
|
|
56
|
+
let uidCounter = 0;
|
|
57
|
+
const uidMap = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
const assignUid = (element) => {
|
|
59
|
+
const existing = uidMap.get(element);
|
|
60
|
+
if (existing) {
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
const uid = `ax-${++uidCounter}`;
|
|
64
|
+
uidMap.set(element, uid);
|
|
65
|
+
return uid;
|
|
66
|
+
};
|
|
67
|
+
const isVisible = (element) => {
|
|
68
|
+
const style = window.getComputedStyle(element);
|
|
69
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
70
|
+
};
|
|
71
|
+
const getInputRole = (input) => {
|
|
72
|
+
const type = (input.type || "text").toLowerCase();
|
|
73
|
+
const inputRoles = {
|
|
74
|
+
button: "button",
|
|
75
|
+
checkbox: "checkbox",
|
|
76
|
+
email: "textbox",
|
|
77
|
+
number: "spinbutton",
|
|
78
|
+
password: "textbox",
|
|
79
|
+
radio: "radio",
|
|
80
|
+
range: "slider",
|
|
81
|
+
search: "searchbox",
|
|
82
|
+
submit: "button",
|
|
83
|
+
tel: "textbox",
|
|
84
|
+
text: "textbox",
|
|
85
|
+
url: "textbox"
|
|
86
|
+
};
|
|
87
|
+
return inputRoles[type] || "textbox";
|
|
88
|
+
};
|
|
89
|
+
const getRole = (element) => {
|
|
90
|
+
const ariaRole = element.getAttribute("role");
|
|
91
|
+
if (ariaRole) {
|
|
92
|
+
return ariaRole;
|
|
93
|
+
}
|
|
94
|
+
const tagName = element.tagName.toLowerCase();
|
|
95
|
+
const roleMap = {
|
|
96
|
+
a: element.hasAttribute("href") ? "link" : "none",
|
|
97
|
+
article: "article",
|
|
98
|
+
aside: "complementary",
|
|
99
|
+
button: "button",
|
|
100
|
+
footer: "contentinfo",
|
|
101
|
+
form: "form",
|
|
102
|
+
h1: "heading",
|
|
103
|
+
h2: "heading",
|
|
104
|
+
h3: "heading",
|
|
105
|
+
h4: "heading",
|
|
106
|
+
h5: "heading",
|
|
107
|
+
h6: "heading",
|
|
108
|
+
header: "banner",
|
|
109
|
+
img: "img",
|
|
110
|
+
input: getInputRole(element),
|
|
111
|
+
li: "listitem",
|
|
112
|
+
main: "main",
|
|
113
|
+
nav: "navigation",
|
|
114
|
+
ol: "list",
|
|
115
|
+
option: "option",
|
|
116
|
+
progress: "progressbar",
|
|
117
|
+
section: element.hasAttribute("aria-label") ? "region" : "none",
|
|
118
|
+
select: "combobox",
|
|
119
|
+
table: "table",
|
|
120
|
+
tbody: "rowgroup",
|
|
121
|
+
td: "cell",
|
|
122
|
+
textarea: "textbox",
|
|
123
|
+
th: "columnheader",
|
|
124
|
+
tr: "row",
|
|
125
|
+
ul: "list"
|
|
126
|
+
};
|
|
127
|
+
return roleMap[tagName] || "none";
|
|
128
|
+
};
|
|
129
|
+
const getAccessibleName = (element) => {
|
|
130
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
131
|
+
if (ariaLabel) {
|
|
132
|
+
return ariaLabel;
|
|
133
|
+
}
|
|
134
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
135
|
+
if (labelledBy) {
|
|
136
|
+
const labels = labelledBy.split(" ").map((id) => document.getElementById(id)?.textContent).filter(Boolean).join(" ");
|
|
137
|
+
if (labels) {
|
|
138
|
+
return labels;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
142
|
+
if (element.id) {
|
|
143
|
+
const label = document.querySelector(`label[for="${element.id}"]`);
|
|
144
|
+
if (label?.textContent) {
|
|
145
|
+
return label.textContent.trim();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (element.placeholder) {
|
|
149
|
+
return element.placeholder;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const tagName = element.tagName.toLowerCase();
|
|
153
|
+
if (["button", "a", "h1", "h2", "h3", "h4", "h5", "h6", "label", "option"].includes(tagName)) {
|
|
154
|
+
const text = element.textContent?.trim();
|
|
155
|
+
if (text && text.length < 100) {
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (element instanceof HTMLImageElement && element.alt) {
|
|
160
|
+
return element.alt;
|
|
161
|
+
}
|
|
162
|
+
return void 0;
|
|
163
|
+
};
|
|
164
|
+
const addState = (element, node) => {
|
|
165
|
+
if (document.activeElement === element) {
|
|
166
|
+
node.focused = true;
|
|
167
|
+
}
|
|
168
|
+
if (element.disabled) {
|
|
169
|
+
node.disabled = true;
|
|
170
|
+
}
|
|
171
|
+
if (element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio")) {
|
|
172
|
+
node.checked = element.indeterminate ? "mixed" : element.checked;
|
|
173
|
+
}
|
|
174
|
+
if (element instanceof HTMLOptionElement) {
|
|
175
|
+
node.selected = element.selected;
|
|
176
|
+
}
|
|
177
|
+
const expanded = element.getAttribute("aria-expanded");
|
|
178
|
+
if (expanded) {
|
|
179
|
+
node.expanded = expanded === "true";
|
|
180
|
+
}
|
|
181
|
+
const headingMatch = element.tagName.match(/^H([1-6])$/);
|
|
182
|
+
if (headingMatch) {
|
|
183
|
+
node.level = Number.parseInt(headingMatch[1], 10);
|
|
184
|
+
}
|
|
185
|
+
const pressed = element.getAttribute("aria-pressed");
|
|
186
|
+
if (pressed) {
|
|
187
|
+
node.pressed = pressed === "mixed" ? "mixed" : pressed === "true";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const buildNode = (element) => {
|
|
191
|
+
if (!isVisible(element)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const children = [];
|
|
195
|
+
for (const child of element.children) {
|
|
196
|
+
const childNode = buildNode(child);
|
|
197
|
+
if (childNode) {
|
|
198
|
+
children.push(childNode);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const role = getRole(element);
|
|
202
|
+
const name = getAccessibleName(element);
|
|
203
|
+
if (role === "none" && !name && children.length === 1) {
|
|
204
|
+
return children[0];
|
|
205
|
+
}
|
|
206
|
+
const node = {
|
|
207
|
+
uid: assignUid(element),
|
|
208
|
+
role
|
|
209
|
+
};
|
|
210
|
+
if (name) {
|
|
211
|
+
node.name = name;
|
|
212
|
+
}
|
|
213
|
+
if (children.length > 0) {
|
|
214
|
+
node.children = children;
|
|
215
|
+
}
|
|
216
|
+
addState(element, node);
|
|
217
|
+
return node;
|
|
218
|
+
};
|
|
219
|
+
const rootElement = document.querySelector(rootSelector) ?? document.body;
|
|
220
|
+
if (!(rootElement instanceof Element)) {
|
|
221
|
+
throw new Error(`Root element not found: ${rootSelector}`);
|
|
222
|
+
}
|
|
223
|
+
return buildNode(rootElement) ?? { uid: "root", role: "none", name: "Empty page" };
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
if (!result) {
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: `Snapshot failed: executeScript returned no result (tabId=${tabId})` }],
|
|
229
|
+
isError: true
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const snapshot = JSON.stringify(result.result ?? { uid: "root", role: "none", name: "Empty page" }, null, 2);
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: snapshot || "No accessibility tree available"
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: "text", text: `Snapshot error (tabId=${tabId}): ${message}` }],
|
|
245
|
+
isError: true
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function resizePageHandler(args, _context, tabId) {
|
|
251
|
+
if (!tabId) {
|
|
252
|
+
return {
|
|
253
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
254
|
+
isError: true
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const width = args.width;
|
|
258
|
+
const height = args.height;
|
|
259
|
+
if (!width || !height) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: "text", text: "width and height are required" }],
|
|
262
|
+
isError: true
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const tab = await globalThis.chrome.tabs.get(tabId);
|
|
267
|
+
if (!tab.windowId) {
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: "text", text: "Cannot find window for tab" }],
|
|
270
|
+
isError: true
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
await globalThis.chrome.windows.update(tab.windowId, {
|
|
274
|
+
width,
|
|
275
|
+
height
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: `Page resized to ${width}x${height}` }]
|
|
279
|
+
};
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const message = error instanceof Error ? error.message : "Resize failed";
|
|
282
|
+
return {
|
|
283
|
+
content: [{ type: "text", text: message }],
|
|
284
|
+
isError: true
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function handleDialogHandler(args, _context, tabId) {
|
|
289
|
+
if (!tabId) {
|
|
290
|
+
return {
|
|
291
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
292
|
+
isError: true
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const accept = args.accept !== false;
|
|
296
|
+
const promptText = args.promptText;
|
|
297
|
+
try {
|
|
298
|
+
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
299
|
+
await globalThis.chrome.debugger.sendCommand({ tabId }, "Page.handleJavaScriptDialog", {
|
|
300
|
+
accept,
|
|
301
|
+
promptText
|
|
302
|
+
});
|
|
303
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: `Dialog ${accept ? "accepted" : "dismissed"}` }]
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
try {
|
|
309
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
const message = error instanceof Error ? error.message : "Handle dialog failed";
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: "text", text: message }],
|
|
315
|
+
isError: true
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function emulateHandler(args, _context, tabId) {
|
|
320
|
+
if (!tabId) {
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
323
|
+
isError: true
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
328
|
+
const commands = [];
|
|
329
|
+
if (args.geolocation) {
|
|
330
|
+
const geo = args.geolocation;
|
|
331
|
+
commands.push({
|
|
332
|
+
method: "Emulation.setGeolocationOverride",
|
|
333
|
+
params: {
|
|
334
|
+
latitude: geo.latitude,
|
|
335
|
+
longitude: geo.longitude,
|
|
336
|
+
accuracy: geo.accuracy || 100
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (args.timezone) {
|
|
341
|
+
commands.push({
|
|
342
|
+
method: "Emulation.setTimezoneOverride",
|
|
343
|
+
params: { timezoneId: args.timezone }
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (args.locale) {
|
|
347
|
+
commands.push({
|
|
348
|
+
method: "Emulation.setLocaleOverride",
|
|
349
|
+
params: { locale: args.locale }
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (args.colorScheme) {
|
|
353
|
+
commands.push({
|
|
354
|
+
method: "Emulation.setEmulatedMedia",
|
|
355
|
+
params: {
|
|
356
|
+
features: [{ name: "prefers-color-scheme", value: args.colorScheme }]
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
for (const cmd of commands) {
|
|
361
|
+
await globalThis.chrome.debugger.sendCommand({ tabId }, cmd.method, cmd.params);
|
|
362
|
+
}
|
|
363
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: "text", text: "Emulation settings applied" }]
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
try {
|
|
369
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
const message = error instanceof Error ? error.message : "Emulation failed";
|
|
373
|
+
return {
|
|
374
|
+
content: [{ type: "text", text: message }],
|
|
375
|
+
isError: true
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
1
380
|
/** A special constant with type `never` */
|
|
2
381
|
function $constructor(name, initializer, params) {
|
|
3
382
|
function init(inst, def) {
|
|
@@ -4786,17 +5165,60 @@ object({
|
|
|
4786
5165
|
function createContentMessage(action, params = {}) {
|
|
4787
5166
|
return { action, params };
|
|
4788
5167
|
}
|
|
5168
|
+
function attachTelemetryToParams(source, params) {
|
|
5169
|
+
const telemetry = source.__browseToolTelemetry;
|
|
5170
|
+
if (!telemetry || typeof telemetry !== "object") {
|
|
5171
|
+
return params;
|
|
5172
|
+
}
|
|
5173
|
+
return {
|
|
5174
|
+
...params,
|
|
5175
|
+
__browseToolTelemetry: telemetry
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
4789
5178
|
|
|
5179
|
+
const POST_INJECTION_RETRY_COUNT = 5;
|
|
5180
|
+
const POST_INJECTION_RETRY_DELAY_MS = 100;
|
|
5181
|
+
function isContentResponse(value) {
|
|
5182
|
+
if (!value || typeof value !== "object") {
|
|
5183
|
+
return false;
|
|
5184
|
+
}
|
|
5185
|
+
const candidate = value;
|
|
5186
|
+
return typeof candidate.success === "boolean";
|
|
5187
|
+
}
|
|
5188
|
+
async function delay(ms) {
|
|
5189
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
5190
|
+
}
|
|
5191
|
+
async function sendValidatedMessage(tabId, message) {
|
|
5192
|
+
const response = await globalThis.chrome.tabs.sendMessage(tabId, message);
|
|
5193
|
+
if (!isContentResponse(response)) {
|
|
5194
|
+
throw new Error(`Content script returned an invalid response (tabId=${tabId})`);
|
|
5195
|
+
}
|
|
5196
|
+
return response;
|
|
5197
|
+
}
|
|
4790
5198
|
async function sendMessageToContentScript(tabId, message) {
|
|
4791
5199
|
try {
|
|
4792
|
-
|
|
4793
|
-
return response;
|
|
5200
|
+
return await sendValidatedMessage(tabId, message);
|
|
4794
5201
|
} catch (error) {
|
|
4795
5202
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4796
|
-
if (errorMessage.includes("Could not establish connection") || errorMessage.includes("Receiving end does not exist")) {
|
|
5203
|
+
if (errorMessage.includes("Could not establish connection") || errorMessage.includes("Receiving end does not exist") || errorMessage.includes("invalid response")) {
|
|
4797
5204
|
await injectContentScript(tabId);
|
|
4798
|
-
|
|
4799
|
-
|
|
5205
|
+
let lastError;
|
|
5206
|
+
for (let attempt = 1; attempt <= POST_INJECTION_RETRY_COUNT; attempt += 1) {
|
|
5207
|
+
try {
|
|
5208
|
+
return await sendValidatedMessage(tabId, message);
|
|
5209
|
+
} catch (retryError) {
|
|
5210
|
+
lastError = retryError instanceof Error ? retryError : new Error(String(retryError));
|
|
5211
|
+
if (attempt < POST_INJECTION_RETRY_COUNT) {
|
|
5212
|
+
await delay(POST_INJECTION_RETRY_DELAY_MS);
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5216
|
+
throw new Error(
|
|
5217
|
+
`Content script returned an invalid response after injection (tabId=${tabId}): ${lastError?.message ?? "unknown error"}`,
|
|
5218
|
+
{
|
|
5219
|
+
cause: error
|
|
5220
|
+
}
|
|
5221
|
+
);
|
|
4800
5222
|
}
|
|
4801
5223
|
throw error;
|
|
4802
5224
|
}
|
|
@@ -4810,218 +5232,15 @@ async function injectContentScript(tabId) {
|
|
|
4810
5232
|
} catch (error) {
|
|
4811
5233
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4812
5234
|
if (errorMessage.includes("Cannot access")) {
|
|
4813
|
-
throw new Error(`Cannot inject content script into restricted page (tabId=${tabId}): ${errorMessage}
|
|
5235
|
+
throw new Error(`Cannot inject content script into restricted page (tabId=${tabId}): ${errorMessage}`, {
|
|
5236
|
+
cause: error
|
|
5237
|
+
});
|
|
4814
5238
|
}
|
|
4815
|
-
throw new Error(`Content script injection failed (tabId=${tabId}): ${errorMessage}
|
|
5239
|
+
throw new Error(`Content script injection failed (tabId=${tabId}): ${errorMessage}`, { cause: error });
|
|
4816
5240
|
}
|
|
4817
5241
|
}
|
|
4818
5242
|
|
|
4819
|
-
async function
|
|
4820
|
-
if (!tabId) {
|
|
4821
|
-
return {
|
|
4822
|
-
content: [{ type: "text", text: "No active tab" }],
|
|
4823
|
-
isError: true
|
|
4824
|
-
};
|
|
4825
|
-
}
|
|
4826
|
-
try {
|
|
4827
|
-
const format = args.format || "png";
|
|
4828
|
-
const quality = args.quality;
|
|
4829
|
-
const tab = await globalThis.chrome.tabs.get(tabId);
|
|
4830
|
-
if (!tab.windowId) {
|
|
4831
|
-
return {
|
|
4832
|
-
content: [{ type: "text", text: "Tab has no window" }],
|
|
4833
|
-
isError: true
|
|
4834
|
-
};
|
|
4835
|
-
}
|
|
4836
|
-
await globalThis.chrome.tabs.update(tabId, { active: true });
|
|
4837
|
-
const dataUrl = await globalThis.chrome.tabs.captureVisibleTab(tab.windowId, {
|
|
4838
|
-
format,
|
|
4839
|
-
quality: format === "jpeg" ? quality : void 0
|
|
4840
|
-
});
|
|
4841
|
-
const base64 = dataUrl.split(",")[1];
|
|
4842
|
-
const mimeType = format === "png" ? "image/png" : "image/jpeg";
|
|
4843
|
-
return {
|
|
4844
|
-
content: [
|
|
4845
|
-
{
|
|
4846
|
-
type: "image",
|
|
4847
|
-
data: base64,
|
|
4848
|
-
mimeType
|
|
4849
|
-
}
|
|
4850
|
-
]
|
|
4851
|
-
};
|
|
4852
|
-
} catch (error) {
|
|
4853
|
-
const message = error instanceof Error ? error.message : "Screenshot failed";
|
|
4854
|
-
return {
|
|
4855
|
-
content: [{ type: "text", text: message }],
|
|
4856
|
-
isError: true
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
}
|
|
4860
|
-
async function snapshotHandler(_args, _context, tabId) {
|
|
4861
|
-
if (!tabId) {
|
|
4862
|
-
return {
|
|
4863
|
-
content: [{ type: "text", text: "No active tab" }],
|
|
4864
|
-
isError: true
|
|
4865
|
-
};
|
|
4866
|
-
}
|
|
4867
|
-
try {
|
|
4868
|
-
const message = createContentMessage(ContentAction.GET_SNAPSHOT, {});
|
|
4869
|
-
const response = await sendMessageToContentScript(tabId, message);
|
|
4870
|
-
if (!response.success) {
|
|
4871
|
-
return {
|
|
4872
|
-
content: [{ type: "text", text: `Snapshot failed: ${response.error || "unknown error from content script"}` }],
|
|
4873
|
-
isError: true
|
|
4874
|
-
};
|
|
4875
|
-
}
|
|
4876
|
-
const snapshot = typeof response.result === "string" ? response.result : JSON.stringify(response.result, null, 2);
|
|
4877
|
-
return {
|
|
4878
|
-
content: [
|
|
4879
|
-
{
|
|
4880
|
-
type: "text",
|
|
4881
|
-
text: snapshot || "No accessibility tree available"
|
|
4882
|
-
}
|
|
4883
|
-
]
|
|
4884
|
-
};
|
|
4885
|
-
} catch (error) {
|
|
4886
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4887
|
-
return {
|
|
4888
|
-
content: [{ type: "text", text: `Snapshot error (tabId=${tabId}): ${message}` }],
|
|
4889
|
-
isError: true
|
|
4890
|
-
};
|
|
4891
|
-
}
|
|
4892
|
-
}
|
|
4893
|
-
|
|
4894
|
-
async function resizePageHandler(args, _context, tabId) {
|
|
4895
|
-
if (!tabId) {
|
|
4896
|
-
return {
|
|
4897
|
-
content: [{ type: "text", text: "No active tab" }],
|
|
4898
|
-
isError: true
|
|
4899
|
-
};
|
|
4900
|
-
}
|
|
4901
|
-
const width = args.width;
|
|
4902
|
-
const height = args.height;
|
|
4903
|
-
if (!width || !height) {
|
|
4904
|
-
return {
|
|
4905
|
-
content: [{ type: "text", text: "width and height are required" }],
|
|
4906
|
-
isError: true
|
|
4907
|
-
};
|
|
4908
|
-
}
|
|
4909
|
-
try {
|
|
4910
|
-
const tab = await globalThis.chrome.tabs.get(tabId);
|
|
4911
|
-
if (!tab.windowId) {
|
|
4912
|
-
return {
|
|
4913
|
-
content: [{ type: "text", text: "Cannot find window for tab" }],
|
|
4914
|
-
isError: true
|
|
4915
|
-
};
|
|
4916
|
-
}
|
|
4917
|
-
await globalThis.chrome.windows.update(tab.windowId, {
|
|
4918
|
-
width,
|
|
4919
|
-
height
|
|
4920
|
-
});
|
|
4921
|
-
return {
|
|
4922
|
-
content: [{ type: "text", text: `Page resized to ${width}x${height}` }]
|
|
4923
|
-
};
|
|
4924
|
-
} catch (error) {
|
|
4925
|
-
const message = error instanceof Error ? error.message : "Resize failed";
|
|
4926
|
-
return {
|
|
4927
|
-
content: [{ type: "text", text: message }],
|
|
4928
|
-
isError: true
|
|
4929
|
-
};
|
|
4930
|
-
}
|
|
4931
|
-
}
|
|
4932
|
-
async function handleDialogHandler(args, _context, tabId) {
|
|
4933
|
-
if (!tabId) {
|
|
4934
|
-
return {
|
|
4935
|
-
content: [{ type: "text", text: "No active tab" }],
|
|
4936
|
-
isError: true
|
|
4937
|
-
};
|
|
4938
|
-
}
|
|
4939
|
-
const accept = args.accept !== false;
|
|
4940
|
-
const promptText = args.promptText;
|
|
4941
|
-
try {
|
|
4942
|
-
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
4943
|
-
await globalThis.chrome.debugger.sendCommand({ tabId }, "Page.handleJavaScriptDialog", {
|
|
4944
|
-
accept,
|
|
4945
|
-
promptText
|
|
4946
|
-
});
|
|
4947
|
-
await globalThis.chrome.debugger.detach({ tabId });
|
|
4948
|
-
return {
|
|
4949
|
-
content: [{ type: "text", text: `Dialog ${accept ? "accepted" : "dismissed"}` }]
|
|
4950
|
-
};
|
|
4951
|
-
} catch (error) {
|
|
4952
|
-
try {
|
|
4953
|
-
await globalThis.chrome.debugger.detach({ tabId });
|
|
4954
|
-
} catch {
|
|
4955
|
-
}
|
|
4956
|
-
const message = error instanceof Error ? error.message : "Handle dialog failed";
|
|
4957
|
-
return {
|
|
4958
|
-
content: [{ type: "text", text: message }],
|
|
4959
|
-
isError: true
|
|
4960
|
-
};
|
|
4961
|
-
}
|
|
4962
|
-
}
|
|
4963
|
-
async function emulateHandler(args, _context, tabId) {
|
|
4964
|
-
if (!tabId) {
|
|
4965
|
-
return {
|
|
4966
|
-
content: [{ type: "text", text: "No active tab" }],
|
|
4967
|
-
isError: true
|
|
4968
|
-
};
|
|
4969
|
-
}
|
|
4970
|
-
try {
|
|
4971
|
-
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
4972
|
-
const commands = [];
|
|
4973
|
-
if (args.geolocation) {
|
|
4974
|
-
const geo = args.geolocation;
|
|
4975
|
-
commands.push({
|
|
4976
|
-
method: "Emulation.setGeolocationOverride",
|
|
4977
|
-
params: {
|
|
4978
|
-
latitude: geo.latitude,
|
|
4979
|
-
longitude: geo.longitude,
|
|
4980
|
-
accuracy: geo.accuracy || 100
|
|
4981
|
-
}
|
|
4982
|
-
});
|
|
4983
|
-
}
|
|
4984
|
-
if (args.timezone) {
|
|
4985
|
-
commands.push({
|
|
4986
|
-
method: "Emulation.setTimezoneOverride",
|
|
4987
|
-
params: { timezoneId: args.timezone }
|
|
4988
|
-
});
|
|
4989
|
-
}
|
|
4990
|
-
if (args.locale) {
|
|
4991
|
-
commands.push({
|
|
4992
|
-
method: "Emulation.setLocaleOverride",
|
|
4993
|
-
params: { locale: args.locale }
|
|
4994
|
-
});
|
|
4995
|
-
}
|
|
4996
|
-
if (args.colorScheme) {
|
|
4997
|
-
commands.push({
|
|
4998
|
-
method: "Emulation.setEmulatedMedia",
|
|
4999
|
-
params: {
|
|
5000
|
-
features: [{ name: "prefers-color-scheme", value: args.colorScheme }]
|
|
5001
|
-
}
|
|
5002
|
-
});
|
|
5003
|
-
}
|
|
5004
|
-
for (const cmd of commands) {
|
|
5005
|
-
await globalThis.chrome.debugger.sendCommand({ tabId }, cmd.method, cmd.params);
|
|
5006
|
-
}
|
|
5007
|
-
await globalThis.chrome.debugger.detach({ tabId });
|
|
5008
|
-
return {
|
|
5009
|
-
content: [{ type: "text", text: "Emulation settings applied" }]
|
|
5010
|
-
};
|
|
5011
|
-
} catch (error) {
|
|
5012
|
-
try {
|
|
5013
|
-
await globalThis.chrome.debugger.detach({ tabId });
|
|
5014
|
-
} catch {
|
|
5015
|
-
}
|
|
5016
|
-
const message = error instanceof Error ? error.message : "Emulation failed";
|
|
5017
|
-
return {
|
|
5018
|
-
content: [{ type: "text", text: message }],
|
|
5019
|
-
isError: true
|
|
5020
|
-
};
|
|
5021
|
-
}
|
|
5022
|
-
}
|
|
5023
|
-
|
|
5024
|
-
async function selectHandler(args, _context, tabId) {
|
|
5243
|
+
async function selectHandler(args, _context, tabId) {
|
|
5025
5244
|
if (!tabId) {
|
|
5026
5245
|
return {
|
|
5027
5246
|
content: [{ type: "text", text: "No active tab" }],
|
|
@@ -5030,10 +5249,12 @@ async function selectHandler(args, _context, tabId) {
|
|
|
5030
5249
|
}
|
|
5031
5250
|
try {
|
|
5032
5251
|
const message = createContentMessage(ContentAction.SELECT, {
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5252
|
+
...attachTelemetryToParams(args, {
|
|
5253
|
+
selector: args.selector,
|
|
5254
|
+
xpath: args.xpath,
|
|
5255
|
+
uid: args.uid,
|
|
5256
|
+
value: args.values || args.value
|
|
5257
|
+
})
|
|
5037
5258
|
});
|
|
5038
5259
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5039
5260
|
if (!response.success) {
|
|
@@ -5062,10 +5283,12 @@ async function hoverHandler(args, _context, tabId) {
|
|
|
5062
5283
|
}
|
|
5063
5284
|
try {
|
|
5064
5285
|
const message = createContentMessage(ContentAction.HOVER, {
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5286
|
+
...attachTelemetryToParams(args, {
|
|
5287
|
+
selector: args.selector,
|
|
5288
|
+
xpath: args.xpath,
|
|
5289
|
+
text: args.text,
|
|
5290
|
+
uid: args.uid
|
|
5291
|
+
})
|
|
5069
5292
|
});
|
|
5070
5293
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5071
5294
|
if (!response.success) {
|
|
@@ -5094,16 +5317,18 @@ async function dragHandler(args, _context, tabId) {
|
|
|
5094
5317
|
}
|
|
5095
5318
|
try {
|
|
5096
5319
|
const message = createContentMessage(ContentAction.DRAG, {
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5320
|
+
...attachTelemetryToParams(args, {
|
|
5321
|
+
source: {
|
|
5322
|
+
selector: args.selector,
|
|
5323
|
+
xpath: args.xpath,
|
|
5324
|
+
uid: args.uid
|
|
5325
|
+
},
|
|
5326
|
+
target: {
|
|
5327
|
+
selector: args.targetSelector,
|
|
5328
|
+
xpath: args.targetXpath,
|
|
5329
|
+
uid: args.targetUid
|
|
5330
|
+
}
|
|
5331
|
+
})
|
|
5107
5332
|
});
|
|
5108
5333
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5109
5334
|
if (!response.success) {
|
|
@@ -5139,8 +5364,10 @@ async function pressKeyHandler(args, _context, tabId) {
|
|
|
5139
5364
|
}
|
|
5140
5365
|
try {
|
|
5141
5366
|
const message = createContentMessage(ContentAction.PRESS_KEY, {
|
|
5142
|
-
|
|
5143
|
-
|
|
5367
|
+
...attachTelemetryToParams(args, {
|
|
5368
|
+
key,
|
|
5369
|
+
modifiers: args.modifiers
|
|
5370
|
+
})
|
|
5144
5371
|
});
|
|
5145
5372
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5146
5373
|
if (!response.success) {
|
|
@@ -5170,13 +5397,15 @@ async function clickHandler(args, _context, tabId) {
|
|
|
5170
5397
|
}
|
|
5171
5398
|
try {
|
|
5172
5399
|
const message = createContentMessage(ContentAction.CLICK, {
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5400
|
+
...attachTelemetryToParams(args, {
|
|
5401
|
+
selector: args.selector,
|
|
5402
|
+
xpath: args.xpath,
|
|
5403
|
+
text: args.text,
|
|
5404
|
+
uid: args.uid,
|
|
5405
|
+
button: args.button,
|
|
5406
|
+
clickCount: args.clickCount,
|
|
5407
|
+
modifiers: args.modifiers
|
|
5408
|
+
})
|
|
5180
5409
|
});
|
|
5181
5410
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5182
5411
|
if (!response.success) {
|
|
@@ -5212,12 +5441,14 @@ async function fillHandler(args, _context, tabId) {
|
|
|
5212
5441
|
}
|
|
5213
5442
|
try {
|
|
5214
5443
|
const message = createContentMessage(ContentAction.FILL, {
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5444
|
+
...attachTelemetryToParams(args, {
|
|
5445
|
+
selector: args.selector,
|
|
5446
|
+
xpath: args.xpath,
|
|
5447
|
+
text: args.text,
|
|
5448
|
+
uid: args.uid,
|
|
5449
|
+
value,
|
|
5450
|
+
force: args.force
|
|
5451
|
+
})
|
|
5221
5452
|
});
|
|
5222
5453
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5223
5454
|
if (!response.success) {
|
|
@@ -5253,11 +5484,13 @@ async function typeHandler(args, _context, tabId) {
|
|
|
5253
5484
|
}
|
|
5254
5485
|
try {
|
|
5255
5486
|
const message = createContentMessage(ContentAction.TYPE, {
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5487
|
+
...attachTelemetryToParams(args, {
|
|
5488
|
+
selector: args.selector,
|
|
5489
|
+
xpath: args.xpath,
|
|
5490
|
+
uid: args.uid,
|
|
5491
|
+
text,
|
|
5492
|
+
delay: args.delay
|
|
5493
|
+
})
|
|
5261
5494
|
});
|
|
5262
5495
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5263
5496
|
if (!response.success) {
|
|
@@ -5277,6 +5510,159 @@ async function typeHandler(args, _context, tabId) {
|
|
|
5277
5510
|
};
|
|
5278
5511
|
}
|
|
5279
5512
|
}
|
|
5513
|
+
async function uploadFileHandler(args, _context, tabId) {
|
|
5514
|
+
if (!tabId) {
|
|
5515
|
+
return {
|
|
5516
|
+
content: [{ type: "text", text: "No active tab" }],
|
|
5517
|
+
isError: true
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
const filesInput = args.files;
|
|
5521
|
+
const files = Array.isArray(filesInput) ? filesInput : typeof filesInput === "string" ? [filesInput] : [];
|
|
5522
|
+
if (files.length === 0 || files.some((file) => typeof file !== "string" || !file)) {
|
|
5523
|
+
return {
|
|
5524
|
+
content: [{ type: "text", text: "files is required and must be a string or string[]" }],
|
|
5525
|
+
isError: true
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
const markerAttr = "data-browse-tool-upload-id";
|
|
5529
|
+
const markerValue = `upload-${crypto.randomUUID()}`;
|
|
5530
|
+
try {
|
|
5531
|
+
const [markerResult] = await globalThis.chrome.scripting.executeScript({
|
|
5532
|
+
target: { tabId },
|
|
5533
|
+
world: "ISOLATED",
|
|
5534
|
+
args: [
|
|
5535
|
+
{
|
|
5536
|
+
selector: args.selector,
|
|
5537
|
+
xpath: args.xpath,
|
|
5538
|
+
text: args.text,
|
|
5539
|
+
uid: args.uid
|
|
5540
|
+
},
|
|
5541
|
+
markerAttr,
|
|
5542
|
+
markerValue
|
|
5543
|
+
],
|
|
5544
|
+
func: (locator, attrName, attrValue) => {
|
|
5545
|
+
const findByText = (text) => {
|
|
5546
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {
|
|
5547
|
+
acceptNode(node) {
|
|
5548
|
+
const element = node;
|
|
5549
|
+
return element.textContent?.includes(text) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
5550
|
+
}
|
|
5551
|
+
});
|
|
5552
|
+
const match = walker.nextNode();
|
|
5553
|
+
return match instanceof Element ? match : null;
|
|
5554
|
+
};
|
|
5555
|
+
const locateElement = () => {
|
|
5556
|
+
if (typeof locator.selector === "string" && locator.selector) {
|
|
5557
|
+
const element = document.querySelector(locator.selector);
|
|
5558
|
+
if (element) {
|
|
5559
|
+
return element;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
if (typeof locator.xpath === "string" && locator.xpath) {
|
|
5563
|
+
const result = document.evaluate(locator.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
5564
|
+
if (result.singleNodeValue instanceof Element) {
|
|
5565
|
+
return result.singleNodeValue;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
if (typeof locator.text === "string" && locator.text) {
|
|
5569
|
+
const element = findByText(locator.text);
|
|
5570
|
+
if (element) {
|
|
5571
|
+
return element;
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
if (typeof locator.uid === "string" && locator.uid) {
|
|
5575
|
+
const element = document.querySelector(`[data-browse-tool-uid="${locator.uid}"]`);
|
|
5576
|
+
if (element) {
|
|
5577
|
+
return element;
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
return document.querySelector('input[type="file"]');
|
|
5581
|
+
};
|
|
5582
|
+
const resolveFileInput = (element) => {
|
|
5583
|
+
if (!element) {
|
|
5584
|
+
return null;
|
|
5585
|
+
}
|
|
5586
|
+
if (element instanceof HTMLInputElement && element.type === "file") {
|
|
5587
|
+
return element;
|
|
5588
|
+
}
|
|
5589
|
+
if (element instanceof HTMLLabelElement && element.htmlFor) {
|
|
5590
|
+
const labelled = document.getElementById(element.htmlFor);
|
|
5591
|
+
if (labelled instanceof HTMLInputElement && labelled.type === "file") {
|
|
5592
|
+
return labelled;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
const nested = element.querySelector('input[type="file"]');
|
|
5596
|
+
return nested instanceof HTMLInputElement ? nested : null;
|
|
5597
|
+
};
|
|
5598
|
+
const input = resolveFileInput(locateElement());
|
|
5599
|
+
if (!input) {
|
|
5600
|
+
throw new Error("File input element not found");
|
|
5601
|
+
}
|
|
5602
|
+
input.setAttribute(attrName, attrValue);
|
|
5603
|
+
return {
|
|
5604
|
+
ok: true,
|
|
5605
|
+
multiple: input.multiple
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
5608
|
+
});
|
|
5609
|
+
if (!markerResult?.result || typeof markerResult.result !== "object") {
|
|
5610
|
+
return {
|
|
5611
|
+
content: [{ type: "text", text: "Failed to resolve file input element" }],
|
|
5612
|
+
isError: true
|
|
5613
|
+
};
|
|
5614
|
+
}
|
|
5615
|
+
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
5616
|
+
try {
|
|
5617
|
+
const documentRoot = await globalThis.chrome.debugger.sendCommand({ tabId }, "DOM.getDocument", {
|
|
5618
|
+
depth: -1
|
|
5619
|
+
});
|
|
5620
|
+
const rootNodeId = documentRoot.root?.nodeId;
|
|
5621
|
+
if (!rootNodeId) {
|
|
5622
|
+
throw new Error("Failed to resolve document root");
|
|
5623
|
+
}
|
|
5624
|
+
const queryResult = await globalThis.chrome.debugger.sendCommand({ tabId }, "DOM.querySelector", {
|
|
5625
|
+
nodeId: rootNodeId,
|
|
5626
|
+
selector: `input[${markerAttr}="${markerValue}"]`
|
|
5627
|
+
});
|
|
5628
|
+
if (!queryResult.nodeId) {
|
|
5629
|
+
throw new Error("Failed to resolve file input node");
|
|
5630
|
+
}
|
|
5631
|
+
await globalThis.chrome.debugger.sendCommand({ tabId }, "DOM.setFileInputFiles", {
|
|
5632
|
+
nodeId: queryResult.nodeId,
|
|
5633
|
+
files
|
|
5634
|
+
});
|
|
5635
|
+
} finally {
|
|
5636
|
+
try {
|
|
5637
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
5638
|
+
} catch {
|
|
5639
|
+
}
|
|
5640
|
+
try {
|
|
5641
|
+
await globalThis.chrome.scripting.executeScript({
|
|
5642
|
+
target: { tabId },
|
|
5643
|
+
world: "ISOLATED",
|
|
5644
|
+
args: [markerAttr, markerValue],
|
|
5645
|
+
func: (attrName, attrValue) => {
|
|
5646
|
+
const element = document.querySelector(`input[${attrName}="${attrValue}"]`);
|
|
5647
|
+
if (element instanceof HTMLInputElement) {
|
|
5648
|
+
element.removeAttribute(attrName);
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
});
|
|
5652
|
+
} catch {
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
return {
|
|
5656
|
+
content: [{ type: "text", text: `Uploaded ${files.length} file(s) successfully` }]
|
|
5657
|
+
};
|
|
5658
|
+
} catch (error) {
|
|
5659
|
+
const errorMessage = error instanceof Error ? error.message : "Upload failed";
|
|
5660
|
+
return {
|
|
5661
|
+
content: [{ type: "text", text: errorMessage }],
|
|
5662
|
+
isError: true
|
|
5663
|
+
};
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5280
5666
|
|
|
5281
5667
|
async function navigateHandler(args, _context, tabId) {
|
|
5282
5668
|
const url = args.url;
|
|
@@ -5325,7 +5711,9 @@ async function navigateHandler(args, _context, tabId) {
|
|
|
5325
5711
|
}
|
|
5326
5712
|
}
|
|
5327
5713
|
|
|
5328
|
-
|
|
5714
|
+
const DEFAULT_TOOL_TIMEOUT_MS = 18e4;
|
|
5715
|
+
|
|
5716
|
+
async function waitForNavigation(tabId, timeout = DEFAULT_TOOL_TIMEOUT_MS) {
|
|
5329
5717
|
return new Promise((resolve) => {
|
|
5330
5718
|
const listener = (updatedTabId, changeInfo) => {
|
|
5331
5719
|
if (updatedTabId === tabId && changeInfo.status === "complete") {
|
|
@@ -5450,7 +5838,7 @@ async function pdfHandler(args, _context, tabId) {
|
|
|
5450
5838
|
}
|
|
5451
5839
|
|
|
5452
5840
|
let activeRecordingTabId;
|
|
5453
|
-
async function startRecordingHandler(
|
|
5841
|
+
async function startRecordingHandler(args, context, tabId) {
|
|
5454
5842
|
if (!tabId) {
|
|
5455
5843
|
return {
|
|
5456
5844
|
content: [{ type: "text", text: "No active tab" }],
|
|
@@ -5493,7 +5881,7 @@ async function startRecordingHandler(_args, _context, tabId) {
|
|
|
5493
5881
|
};
|
|
5494
5882
|
}
|
|
5495
5883
|
}
|
|
5496
|
-
async function stopRecordingHandler(
|
|
5884
|
+
async function stopRecordingHandler(args, context, tabId) {
|
|
5497
5885
|
if (activeRecordingTabId === void 0) {
|
|
5498
5886
|
return {
|
|
5499
5887
|
content: [{ type: "text", text: "No recording in progress" }],
|
|
@@ -5544,47 +5932,38 @@ async function evaluateScriptHandler(args, _context, tabId) {
|
|
|
5544
5932
|
};
|
|
5545
5933
|
}
|
|
5546
5934
|
try {
|
|
5547
|
-
const
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
document.body.setAttribute('${resultId}', JSON.stringify({ success: true, result: __result }));
|
|
5557
|
-
} catch (e) {
|
|
5558
|
-
document.body.setAttribute('${resultId}', JSON.stringify({ success: false, error: e.message }));
|
|
5559
|
-
}
|
|
5560
|
-
`;
|
|
5561
|
-
document.body.appendChild(script2);
|
|
5562
|
-
script2.remove();
|
|
5563
|
-
const resultStr = document.body.getAttribute(resultId);
|
|
5564
|
-
document.body.removeAttribute(resultId);
|
|
5565
|
-
if (resultStr) {
|
|
5566
|
-
return JSON.parse(resultStr);
|
|
5567
|
-
}
|
|
5568
|
-
return { success: false, error: "No result returned" };
|
|
5569
|
-
},
|
|
5570
|
-
args: [script]
|
|
5935
|
+
const arg = args.arg;
|
|
5936
|
+
const functionLikePattern = /^\s*(?:async\s+)?(?:function\b|\(?\s*[\w$,\s]*\)?\s*=>)/;
|
|
5937
|
+
const expression = arg !== void 0 && functionLikePattern.test(script) ? `(${script})(${JSON.stringify(arg)})` : script;
|
|
5938
|
+
await globalThis.chrome.debugger.attach({ tabId }, "1.3");
|
|
5939
|
+
const response = await globalThis.chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
|
|
5940
|
+
expression,
|
|
5941
|
+
returnByValue: true,
|
|
5942
|
+
awaitPromise: true,
|
|
5943
|
+
allowUnsafeEvalBlockedByCSP: true
|
|
5571
5944
|
});
|
|
5572
|
-
|
|
5573
|
-
if (
|
|
5945
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
5946
|
+
if (response.exceptionDetails) {
|
|
5947
|
+
const errorMessage = response.exceptionDetails.exception?.description || response.exceptionDetails.exception?.value || response.exceptionDetails.text || "Script execution failed";
|
|
5574
5948
|
return {
|
|
5575
|
-
content: [{ type: "text", text:
|
|
5949
|
+
content: [{ type: "text", text: errorMessage }],
|
|
5576
5950
|
isError: true
|
|
5577
5951
|
};
|
|
5578
5952
|
}
|
|
5953
|
+
const value = response.result?.value ?? response.result?.unserializableValue ?? null;
|
|
5579
5954
|
return {
|
|
5580
5955
|
content: [
|
|
5581
5956
|
{
|
|
5582
5957
|
type: "text",
|
|
5583
|
-
text: JSON.stringify(
|
|
5958
|
+
text: JSON.stringify(value, null, 2)
|
|
5584
5959
|
}
|
|
5585
5960
|
]
|
|
5586
5961
|
};
|
|
5587
5962
|
} catch (error) {
|
|
5963
|
+
try {
|
|
5964
|
+
await globalThis.chrome.debugger.detach({ tabId });
|
|
5965
|
+
} catch {
|
|
5966
|
+
}
|
|
5588
5967
|
const message = error instanceof Error ? error.message : "Script execution failed";
|
|
5589
5968
|
return {
|
|
5590
5969
|
content: [{ type: "text", text: message }],
|
|
@@ -5599,13 +5978,23 @@ async function waitForHandler(args, _context, tabId) {
|
|
|
5599
5978
|
isError: true
|
|
5600
5979
|
};
|
|
5601
5980
|
}
|
|
5602
|
-
const timeout = args.timeout ||
|
|
5981
|
+
const timeout = args.timeout || DEFAULT_TOOL_TIMEOUT_MS;
|
|
5982
|
+
const selector = args.selector;
|
|
5983
|
+
const text = args.text;
|
|
5984
|
+
if (!selector && !text) {
|
|
5985
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
5986
|
+
return {
|
|
5987
|
+
content: [{ type: "text", text: `Waited for ${timeout}ms` }]
|
|
5988
|
+
};
|
|
5989
|
+
}
|
|
5603
5990
|
try {
|
|
5604
5991
|
const message = createContentMessage(ContentAction.WAIT_FOR, {
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5992
|
+
...attachTelemetryToParams(args, {
|
|
5993
|
+
selector,
|
|
5994
|
+
text,
|
|
5995
|
+
state: args.state || "visible",
|
|
5996
|
+
timeout
|
|
5997
|
+
})
|
|
5609
5998
|
});
|
|
5610
5999
|
const response = await sendMessageToContentScript(tabId, message);
|
|
5611
6000
|
if (!response.success) {
|
|
@@ -5641,7 +6030,7 @@ async function waitForResponseHandler(args, _context, tabId) {
|
|
|
5641
6030
|
isError: true
|
|
5642
6031
|
};
|
|
5643
6032
|
}
|
|
5644
|
-
const timeout = typeof args.timeout === "number" ? args.timeout :
|
|
6033
|
+
const timeout = typeof args.timeout === "number" ? args.timeout : DEFAULT_TOOL_TIMEOUT_MS;
|
|
5645
6034
|
const url = typeof args.url === "string" ? args.url : void 0;
|
|
5646
6035
|
const method = typeof args.method === "string" ? args.method : void 0;
|
|
5647
6036
|
const status = typeof args.status === "number" ? args.status : void 0;
|
|
@@ -5793,12 +6182,12 @@ async function clearStorageStateHandler(_args, _context, tabId) {
|
|
|
5793
6182
|
}
|
|
5794
6183
|
}
|
|
5795
6184
|
|
|
5796
|
-
async function listPagesHandler(
|
|
6185
|
+
async function listPagesHandler(args, context, tabId) {
|
|
5797
6186
|
try {
|
|
5798
6187
|
const chromeTabs = await globalThis.chrome.tabs.query({});
|
|
5799
6188
|
const tabList = chromeTabs.filter((tab) => tab.id !== void 0).map((tab) => {
|
|
5800
|
-
const
|
|
5801
|
-
const pageId = context.tabRegistry.getByTabId(
|
|
6189
|
+
const tabId2 = tab.id;
|
|
6190
|
+
const pageId = context.tabRegistry.getByTabId(tabId2)?.id || `tab-${tabId2}`;
|
|
5802
6191
|
return {
|
|
5803
6192
|
id: pageId,
|
|
5804
6193
|
url: tab.url || "",
|
|
@@ -5822,7 +6211,7 @@ async function listPagesHandler(_args, context, _tabId) {
|
|
|
5822
6211
|
};
|
|
5823
6212
|
}
|
|
5824
6213
|
}
|
|
5825
|
-
async function newPageHandler(args, context,
|
|
6214
|
+
async function newPageHandler(args, context, tabId) {
|
|
5826
6215
|
try {
|
|
5827
6216
|
const url = args.url || "about:blank";
|
|
5828
6217
|
const tab = await globalThis.chrome.tabs.create({ url });
|
|
@@ -5850,7 +6239,7 @@ async function newPageHandler(args, context, _tabId) {
|
|
|
5850
6239
|
};
|
|
5851
6240
|
}
|
|
5852
6241
|
}
|
|
5853
|
-
async function selectPageHandler(args, context,
|
|
6242
|
+
async function selectPageHandler(args, context, tabId) {
|
|
5854
6243
|
const pageId = args.pageId;
|
|
5855
6244
|
if (!pageId) {
|
|
5856
6245
|
return {
|
|
@@ -5884,7 +6273,7 @@ async function selectPageHandler(args, context, _tabId) {
|
|
|
5884
6273
|
};
|
|
5885
6274
|
}
|
|
5886
6275
|
}
|
|
5887
|
-
async function closePageHandler(args, context,
|
|
6276
|
+
async function closePageHandler(args, context, tabId) {
|
|
5888
6277
|
const pageId = args.pageId;
|
|
5889
6278
|
if (!pageId) {
|
|
5890
6279
|
return {
|
|
@@ -5934,6 +6323,7 @@ const toolHandlers = {
|
|
|
5934
6323
|
browser_click: clickHandler,
|
|
5935
6324
|
browser_fill: fillHandler,
|
|
5936
6325
|
browser_type: typeHandler,
|
|
6326
|
+
browser_upload_file: uploadFileHandler,
|
|
5937
6327
|
// Extended input
|
|
5938
6328
|
browser_select: selectHandler,
|
|
5939
6329
|
browser_hover: hoverHandler,
|
|
@@ -5987,6 +6377,226 @@ async function executeToolRequest(request, context) {
|
|
|
5987
6377
|
}
|
|
5988
6378
|
}
|
|
5989
6379
|
|
|
6380
|
+
const SCOPE_NAME = "browse-tool-extension";
|
|
6381
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
6382
|
+
function randomHex(byteLength) {
|
|
6383
|
+
const bytes = new Uint8Array(byteLength);
|
|
6384
|
+
crypto.getRandomValues(bytes);
|
|
6385
|
+
return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
6386
|
+
}
|
|
6387
|
+
function nowUnixNano() {
|
|
6388
|
+
return `${BigInt(Date.now()) * 1000000n}`;
|
|
6389
|
+
}
|
|
6390
|
+
function sanitizeAttributes(attributes) {
|
|
6391
|
+
if (!attributes) {
|
|
6392
|
+
return [];
|
|
6393
|
+
}
|
|
6394
|
+
return Object.entries(attributes).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => ({
|
|
6395
|
+
key,
|
|
6396
|
+
value: toOtlpAnyValue(value)
|
|
6397
|
+
}));
|
|
6398
|
+
}
|
|
6399
|
+
function toOtlpAnyValue(value) {
|
|
6400
|
+
if (typeof value === "boolean") {
|
|
6401
|
+
return { boolValue: value };
|
|
6402
|
+
}
|
|
6403
|
+
if (typeof value === "number") {
|
|
6404
|
+
return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };
|
|
6405
|
+
}
|
|
6406
|
+
if (Array.isArray(value)) {
|
|
6407
|
+
return {
|
|
6408
|
+
arrayValue: {
|
|
6409
|
+
values: value.map((item) => toOtlpAnyValue(item))
|
|
6410
|
+
}
|
|
6411
|
+
};
|
|
6412
|
+
}
|
|
6413
|
+
if (typeof value === "object" && value !== null) {
|
|
6414
|
+
return { stringValue: JSON.stringify(value) };
|
|
6415
|
+
}
|
|
6416
|
+
return { stringValue: String(value) };
|
|
6417
|
+
}
|
|
6418
|
+
function severityNumber(level) {
|
|
6419
|
+
switch (level) {
|
|
6420
|
+
case "trace":
|
|
6421
|
+
return 1;
|
|
6422
|
+
case "debug":
|
|
6423
|
+
return 5;
|
|
6424
|
+
case "info":
|
|
6425
|
+
return 9;
|
|
6426
|
+
case "warn":
|
|
6427
|
+
return 13;
|
|
6428
|
+
case "error":
|
|
6429
|
+
return 17;
|
|
6430
|
+
case "fatal":
|
|
6431
|
+
return 21;
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
function safeRequestOrigin(serverUrl) {
|
|
6435
|
+
try {
|
|
6436
|
+
return new URL(serverUrl).origin;
|
|
6437
|
+
} catch {
|
|
6438
|
+
return void 0;
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
function isLoopbackHost(hostname) {
|
|
6442
|
+
return LOOPBACK_HOSTS.has(hostname);
|
|
6443
|
+
}
|
|
6444
|
+
class ExtensionTelemetryClient {
|
|
6445
|
+
config = {
|
|
6446
|
+
enabled: false,
|
|
6447
|
+
serviceName: "browse-tool-extension"
|
|
6448
|
+
};
|
|
6449
|
+
configuredServerOrigin;
|
|
6450
|
+
async configure(serverUrl) {
|
|
6451
|
+
const requestOrigin = safeRequestOrigin(serverUrl);
|
|
6452
|
+
if (!requestOrigin || this.configuredServerOrigin === requestOrigin) {
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
try {
|
|
6456
|
+
const response = await fetch(`${requestOrigin}/extension/telemetry-config`, {
|
|
6457
|
+
method: "GET",
|
|
6458
|
+
headers: {
|
|
6459
|
+
Accept: "application/json"
|
|
6460
|
+
}
|
|
6461
|
+
});
|
|
6462
|
+
if (!response.ok) {
|
|
6463
|
+
throw new Error(`HTTP ${response.status}`);
|
|
6464
|
+
}
|
|
6465
|
+
const config = await response.json();
|
|
6466
|
+
this.config = config;
|
|
6467
|
+
this.configuredServerOrigin = requestOrigin;
|
|
6468
|
+
} catch {
|
|
6469
|
+
this.config = {
|
|
6470
|
+
enabled: false,
|
|
6471
|
+
serviceName: this.config.serviceName
|
|
6472
|
+
};
|
|
6473
|
+
this.configuredServerOrigin = requestOrigin;
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
getConfig() {
|
|
6477
|
+
return this.config;
|
|
6478
|
+
}
|
|
6479
|
+
startSpan(name, parentContext, options = {}) {
|
|
6480
|
+
const context = {
|
|
6481
|
+
traceId: parentContext?.traceId ?? randomHex(16),
|
|
6482
|
+
spanId: randomHex(8),
|
|
6483
|
+
...parentContext?.spanId ? { parentSpanId: parentContext.spanId } : {}
|
|
6484
|
+
};
|
|
6485
|
+
const startTimeUnixNano = nowUnixNano();
|
|
6486
|
+
return {
|
|
6487
|
+
context,
|
|
6488
|
+
end: async (status) => {
|
|
6489
|
+
if (!this.config.enabled || !this.config.tracesEndpoint) {
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
await this.postJson(this.config.tracesEndpoint, {
|
|
6493
|
+
resourceSpans: [
|
|
6494
|
+
{
|
|
6495
|
+
resource: {
|
|
6496
|
+
attributes: [
|
|
6497
|
+
{
|
|
6498
|
+
key: "service.name",
|
|
6499
|
+
value: { stringValue: this.config.serviceName }
|
|
6500
|
+
},
|
|
6501
|
+
{
|
|
6502
|
+
key: "telemetry.sdk.language",
|
|
6503
|
+
value: { stringValue: "webjs" }
|
|
6504
|
+
}
|
|
6505
|
+
]
|
|
6506
|
+
},
|
|
6507
|
+
scopeSpans: [
|
|
6508
|
+
{
|
|
6509
|
+
scope: { name: SCOPE_NAME },
|
|
6510
|
+
spans: [
|
|
6511
|
+
{
|
|
6512
|
+
traceId: context.traceId,
|
|
6513
|
+
spanId: context.spanId,
|
|
6514
|
+
parentSpanId: context.parentSpanId,
|
|
6515
|
+
name,
|
|
6516
|
+
kind: 1,
|
|
6517
|
+
startTimeUnixNano,
|
|
6518
|
+
endTimeUnixNano: nowUnixNano(),
|
|
6519
|
+
attributes: sanitizeAttributes(options.attributes),
|
|
6520
|
+
...status ? {
|
|
6521
|
+
status: {
|
|
6522
|
+
code: status.code ?? 1,
|
|
6523
|
+
...status.message ? { message: status.message } : {}
|
|
6524
|
+
}
|
|
6525
|
+
} : {}
|
|
6526
|
+
}
|
|
6527
|
+
]
|
|
6528
|
+
}
|
|
6529
|
+
]
|
|
6530
|
+
}
|
|
6531
|
+
]
|
|
6532
|
+
});
|
|
6533
|
+
}
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
async log(level, message, options = {}) {
|
|
6537
|
+
if (!this.config.enabled || !this.config.logsEndpoint) {
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
await this.postJson(this.config.logsEndpoint, {
|
|
6541
|
+
resourceLogs: [
|
|
6542
|
+
{
|
|
6543
|
+
resource: {
|
|
6544
|
+
attributes: [
|
|
6545
|
+
{
|
|
6546
|
+
key: "service.name",
|
|
6547
|
+
value: { stringValue: this.config.serviceName }
|
|
6548
|
+
},
|
|
6549
|
+
{
|
|
6550
|
+
key: "telemetry.sdk.language",
|
|
6551
|
+
value: { stringValue: "webjs" }
|
|
6552
|
+
}
|
|
6553
|
+
]
|
|
6554
|
+
},
|
|
6555
|
+
scopeLogs: [
|
|
6556
|
+
{
|
|
6557
|
+
scope: { name: SCOPE_NAME },
|
|
6558
|
+
logRecords: [
|
|
6559
|
+
{
|
|
6560
|
+
timeUnixNano: nowUnixNano(),
|
|
6561
|
+
observedTimeUnixNano: nowUnixNano(),
|
|
6562
|
+
severityText: level.toUpperCase(),
|
|
6563
|
+
severityNumber: severityNumber(level),
|
|
6564
|
+
body: { stringValue: message },
|
|
6565
|
+
attributes: sanitizeAttributes(options.attributes),
|
|
6566
|
+
...options.context ? {
|
|
6567
|
+
traceId: options.context.traceId,
|
|
6568
|
+
spanId: options.context.spanId
|
|
6569
|
+
} : {}
|
|
6570
|
+
}
|
|
6571
|
+
]
|
|
6572
|
+
}
|
|
6573
|
+
]
|
|
6574
|
+
}
|
|
6575
|
+
]
|
|
6576
|
+
});
|
|
6577
|
+
}
|
|
6578
|
+
async postJson(url, body) {
|
|
6579
|
+
try {
|
|
6580
|
+
const parsed = new URL(url);
|
|
6581
|
+
if (this.configuredServerOrigin) {
|
|
6582
|
+
const serverOrigin = new URL(this.configuredServerOrigin);
|
|
6583
|
+
if (isLoopbackHost(parsed.hostname) && !isLoopbackHost(serverOrigin.hostname)) {
|
|
6584
|
+
parsed.hostname = serverOrigin.hostname;
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
await fetch(parsed.toString(), {
|
|
6588
|
+
method: "POST",
|
|
6589
|
+
headers: {
|
|
6590
|
+
"Content-Type": "application/json"
|
|
6591
|
+
},
|
|
6592
|
+
body: JSON.stringify(body)
|
|
6593
|
+
});
|
|
6594
|
+
} catch {
|
|
6595
|
+
}
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
const extensionTelemetryClient = new ExtensionTelemetryClient();
|
|
6599
|
+
|
|
5990
6600
|
class ExtensionHttpClient {
|
|
5991
6601
|
serverUrl;
|
|
5992
6602
|
connected = false;
|
|
@@ -6365,6 +6975,10 @@ const ContentItemSchema = object({
|
|
|
6365
6975
|
data: string().optional(),
|
|
6366
6976
|
mimeType: string().optional()
|
|
6367
6977
|
});
|
|
6978
|
+
const TaskTelemetryContextSchema = object({
|
|
6979
|
+
traceId: string().regex(/^[0-9a-f]{32}$/i).optional(),
|
|
6980
|
+
parentSpanId: string().regex(/^[0-9a-f]{16}$/i).optional()
|
|
6981
|
+
});
|
|
6368
6982
|
const TaskResultContentSchema = object({
|
|
6369
6983
|
content: array(ContentItemSchema),
|
|
6370
6984
|
isError: boolean().optional()
|
|
@@ -6376,7 +6990,8 @@ const TaskPushSchema = object({
|
|
|
6376
6990
|
taskId: string(),
|
|
6377
6991
|
tool: string(),
|
|
6378
6992
|
arguments: record(string(), unknown()),
|
|
6379
|
-
pageId: string().optional()
|
|
6993
|
+
pageId: string().optional(),
|
|
6994
|
+
telemetry: TaskTelemetryContextSchema.optional()
|
|
6380
6995
|
})
|
|
6381
6996
|
});
|
|
6382
6997
|
const PageCreatedSchema = object({
|
|
@@ -6711,6 +7326,7 @@ class TaskPollerService {
|
|
|
6711
7326
|
if (serverUrl) {
|
|
6712
7327
|
this.serverUrl = serverUrl;
|
|
6713
7328
|
}
|
|
7329
|
+
await extensionTelemetryClient.configure(this.serverUrl);
|
|
6714
7330
|
this.browserId = browserId ?? `browser-${Date.now()}`;
|
|
6715
7331
|
this.useWebSocket = true;
|
|
6716
7332
|
this.status = "registering";
|
|
@@ -6751,10 +7367,23 @@ class TaskPollerService {
|
|
|
6751
7367
|
});
|
|
6752
7368
|
try {
|
|
6753
7369
|
await this.wsClient.connect();
|
|
7370
|
+
void extensionTelemetryClient.log("info", "extension websocket connected", {
|
|
7371
|
+
attributes: {
|
|
7372
|
+
"browse_tool.browser.id": this.browserId,
|
|
7373
|
+
"browse_tool.server.url": this.serverUrl
|
|
7374
|
+
}
|
|
7375
|
+
});
|
|
6754
7376
|
} catch (error) {
|
|
6755
7377
|
this.lastError = error instanceof Error ? error.message : String(error);
|
|
6756
7378
|
this.status = "error";
|
|
6757
7379
|
this.useWebSocket = false;
|
|
7380
|
+
void extensionTelemetryClient.log("error", "extension websocket connect failed", {
|
|
7381
|
+
attributes: {
|
|
7382
|
+
"browse_tool.browser.id": this.browserId,
|
|
7383
|
+
"browse_tool.server.url": this.serverUrl,
|
|
7384
|
+
"error.message": this.lastError
|
|
7385
|
+
}
|
|
7386
|
+
});
|
|
6758
7387
|
}
|
|
6759
7388
|
}
|
|
6760
7389
|
handleWebSocketTask(task) {
|
|
@@ -6767,7 +7396,15 @@ class TaskPollerService {
|
|
|
6767
7396
|
const serverTask = {
|
|
6768
7397
|
id: task.payload.taskId,
|
|
6769
7398
|
tool: task.payload.tool,
|
|
6770
|
-
arguments:
|
|
7399
|
+
arguments: {
|
|
7400
|
+
...task.payload.arguments,
|
|
7401
|
+
...task.payload.telemetry ? {
|
|
7402
|
+
__browseToolTelemetry: {
|
|
7403
|
+
traceId: task.payload.telemetry.traceId,
|
|
7404
|
+
parentSpanId: task.payload.telemetry.parentSpanId
|
|
7405
|
+
}
|
|
7406
|
+
} : {}
|
|
7407
|
+
}
|
|
6771
7408
|
};
|
|
6772
7409
|
this.executeTaskWithWebSocket(serverTask).then(() => {
|
|
6773
7410
|
this.taskCount++;
|
|
@@ -6780,30 +7417,7 @@ class TaskPollerService {
|
|
|
6780
7417
|
});
|
|
6781
7418
|
}
|
|
6782
7419
|
async executeTaskWithWebSocket(task) {
|
|
6783
|
-
|
|
6784
|
-
try {
|
|
6785
|
-
const { pageId, ...restArgs } = task.arguments;
|
|
6786
|
-
result = await executeToolRequest(
|
|
6787
|
-
{
|
|
6788
|
-
id: task.id,
|
|
6789
|
-
type: "request",
|
|
6790
|
-
tool: task.tool,
|
|
6791
|
-
arguments: restArgs,
|
|
6792
|
-
pageId
|
|
6793
|
-
},
|
|
6794
|
-
this.toolContext
|
|
6795
|
-
);
|
|
6796
|
-
} catch (error) {
|
|
6797
|
-
result = {
|
|
6798
|
-
content: [
|
|
6799
|
-
{
|
|
6800
|
-
type: "text",
|
|
6801
|
-
text: `Tool execution error: ${error instanceof Error ? error.message : String(error)}`
|
|
6802
|
-
}
|
|
6803
|
-
],
|
|
6804
|
-
isError: true
|
|
6805
|
-
};
|
|
6806
|
-
}
|
|
7420
|
+
const result = await this.executeTaskCore(task);
|
|
6807
7421
|
const resultContent = {
|
|
6808
7422
|
content: result.content.map((c) => ({
|
|
6809
7423
|
type: c.type,
|
|
@@ -6823,6 +7437,7 @@ class TaskPollerService {
|
|
|
6823
7437
|
this.serverUrl = serverUrl;
|
|
6824
7438
|
this.client.setServerUrl(serverUrl);
|
|
6825
7439
|
}
|
|
7440
|
+
await extensionTelemetryClient.configure(this.serverUrl);
|
|
6826
7441
|
this.browserId = browserId ?? `browser-${Date.now()}`;
|
|
6827
7442
|
this.useWebSocket = false;
|
|
6828
7443
|
this.status = "registering";
|
|
@@ -6912,6 +7527,15 @@ class TaskPollerService {
|
|
|
6912
7527
|
if (!task) {
|
|
6913
7528
|
return;
|
|
6914
7529
|
}
|
|
7530
|
+
if (task.telemetry) {
|
|
7531
|
+
task.arguments = {
|
|
7532
|
+
...task.arguments,
|
|
7533
|
+
__browseToolTelemetry: {
|
|
7534
|
+
traceId: task.telemetry.traceId,
|
|
7535
|
+
parentSpanId: task.telemetry.parentSpanId
|
|
7536
|
+
}
|
|
7537
|
+
};
|
|
7538
|
+
}
|
|
6915
7539
|
this.isExecuting = true;
|
|
6916
7540
|
this.status = "executing";
|
|
6917
7541
|
await this.executeTask(task);
|
|
@@ -6926,47 +7550,92 @@ class TaskPollerService {
|
|
|
6926
7550
|
}
|
|
6927
7551
|
}
|
|
6928
7552
|
async executeTask(task) {
|
|
7553
|
+
const result = await this.executeTaskCore(task);
|
|
7554
|
+
const taskResult = {
|
|
7555
|
+
taskId: task.id,
|
|
7556
|
+
success: !result.isError,
|
|
7557
|
+
result: {
|
|
7558
|
+
content: result.content.map((c) => ({
|
|
7559
|
+
type: c.type,
|
|
7560
|
+
text: "text" in c ? c.text : void 0,
|
|
7561
|
+
data: "data" in c ? c.data : void 0,
|
|
7562
|
+
mimeType: "mimeType" in c ? c.mimeType : void 0
|
|
7563
|
+
})),
|
|
7564
|
+
isError: result.isError
|
|
7565
|
+
}
|
|
7566
|
+
};
|
|
7567
|
+
try {
|
|
7568
|
+
await this.client.submitResult(taskResult);
|
|
7569
|
+
} catch {
|
|
7570
|
+
}
|
|
7571
|
+
}
|
|
7572
|
+
async executeTaskCore(task) {
|
|
7573
|
+
const parentContext = extractTelemetryContext(task.arguments);
|
|
7574
|
+
const taskSpan = extensionTelemetryClient.startSpan("browse_tool.extension.task.execute", parentContext, {
|
|
7575
|
+
attributes: {
|
|
7576
|
+
"browse_tool.task.id": task.id,
|
|
7577
|
+
"browse_tool.tool.name": task.tool
|
|
7578
|
+
}
|
|
7579
|
+
});
|
|
7580
|
+
void extensionTelemetryClient.log("info", "extension task execution started", {
|
|
7581
|
+
context: taskSpan.context,
|
|
7582
|
+
attributes: {
|
|
7583
|
+
"browse_tool.task.id": task.id,
|
|
7584
|
+
"browse_tool.tool.name": task.tool
|
|
7585
|
+
}
|
|
7586
|
+
});
|
|
6929
7587
|
let result;
|
|
6930
7588
|
try {
|
|
6931
|
-
const {
|
|
7589
|
+
const {
|
|
7590
|
+
pageId,
|
|
7591
|
+
__browseToolTelemetry: _unusedTelemetry,
|
|
7592
|
+
...restArgs
|
|
7593
|
+
} = task.arguments;
|
|
7594
|
+
void _unusedTelemetry;
|
|
6932
7595
|
result = await executeToolRequest(
|
|
6933
7596
|
{
|
|
6934
7597
|
id: task.id,
|
|
6935
7598
|
type: "request",
|
|
6936
7599
|
tool: task.tool,
|
|
6937
|
-
arguments:
|
|
7600
|
+
arguments: {
|
|
7601
|
+
...restArgs,
|
|
7602
|
+
__browseToolTelemetry: {
|
|
7603
|
+
traceId: taskSpan.context.traceId,
|
|
7604
|
+
spanId: taskSpan.context.spanId
|
|
7605
|
+
}
|
|
7606
|
+
},
|
|
6938
7607
|
pageId
|
|
6939
7608
|
},
|
|
6940
7609
|
this.toolContext
|
|
6941
7610
|
);
|
|
6942
7611
|
} catch (error) {
|
|
7612
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6943
7613
|
result = {
|
|
6944
7614
|
content: [
|
|
6945
7615
|
{
|
|
6946
7616
|
type: "text",
|
|
6947
|
-
text: `Tool execution error: ${
|
|
7617
|
+
text: `Tool execution error: ${message}`
|
|
6948
7618
|
}
|
|
6949
7619
|
],
|
|
6950
7620
|
isError: true
|
|
6951
7621
|
};
|
|
6952
7622
|
}
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
data: "data" in c ? c.data : void 0,
|
|
6961
|
-
mimeType: "mimeType" in c ? c.mimeType : void 0
|
|
6962
|
-
})),
|
|
6963
|
-
isError: result.isError
|
|
7623
|
+
void extensionTelemetryClient.log(result.isError ? "error" : "info", "extension task execution completed", {
|
|
7624
|
+
context: taskSpan.context,
|
|
7625
|
+
attributes: {
|
|
7626
|
+
"browse_tool.task.id": task.id,
|
|
7627
|
+
"browse_tool.tool.name": task.tool,
|
|
7628
|
+
"browse_tool.result.error": Boolean(result.isError),
|
|
7629
|
+
...result.isError ? { "error.message": extractResultError(result) ?? "Extension tool execution failed" } : {}
|
|
6964
7630
|
}
|
|
6965
|
-
};
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
7631
|
+
});
|
|
7632
|
+
await taskSpan.end(
|
|
7633
|
+
result.isError ? {
|
|
7634
|
+
code: 2,
|
|
7635
|
+
message: extractResultError(result) ?? "Extension tool execution failed"
|
|
7636
|
+
} : { code: 1 }
|
|
7637
|
+
);
|
|
7638
|
+
return result;
|
|
6970
7639
|
}
|
|
6971
7640
|
getState() {
|
|
6972
7641
|
const connectionStatus = this.client.getConnectionStatus();
|
|
@@ -6995,6 +7664,29 @@ class TaskPollerService {
|
|
|
6995
7664
|
}
|
|
6996
7665
|
}
|
|
6997
7666
|
const taskPollerService = new TaskPollerService();
|
|
7667
|
+
function extractTelemetryContext(args) {
|
|
7668
|
+
const telemetry = args.__browseToolTelemetry;
|
|
7669
|
+
if (!telemetry || typeof telemetry !== "object") {
|
|
7670
|
+
return void 0;
|
|
7671
|
+
}
|
|
7672
|
+
const traceId = typeof telemetry.traceId === "string" ? telemetry.traceId : void 0;
|
|
7673
|
+
const spanId = typeof telemetry.spanId === "string" ? telemetry.spanId : void 0;
|
|
7674
|
+
const parentSpanId = typeof telemetry.parentSpanId === "string" ? telemetry.parentSpanId : void 0;
|
|
7675
|
+
if (!traceId) {
|
|
7676
|
+
return void 0;
|
|
7677
|
+
}
|
|
7678
|
+
return {
|
|
7679
|
+
traceId,
|
|
7680
|
+
spanId: spanId ?? parentSpanId ?? "0000000000000000"
|
|
7681
|
+
};
|
|
7682
|
+
}
|
|
7683
|
+
function extractResultError(result) {
|
|
7684
|
+
if (!result.isError) {
|
|
7685
|
+
return void 0;
|
|
7686
|
+
}
|
|
7687
|
+
const firstContent = result.content[0];
|
|
7688
|
+
return firstContent?.type === "text" ? firstContent.text : void 0;
|
|
7689
|
+
}
|
|
6998
7690
|
|
|
6999
7691
|
const toolContext = {
|
|
7000
7692
|
tabRegistry
|
|
@@ -7211,6 +7903,14 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
|
|
7211
7903
|
}
|
|
7212
7904
|
});
|
|
7213
7905
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
7906
|
+
if (message.type === "BROWSER_TELEMETRY_EVENT") {
|
|
7907
|
+
void extensionTelemetryClient.log(message.level || "info", message.message || "browser telemetry event", {
|
|
7908
|
+
attributes: typeof message.attributes === "object" && message.attributes !== null ? message.attributes : void 0,
|
|
7909
|
+
context: typeof message.context === "object" && message.context !== null ? message.context : void 0
|
|
7910
|
+
});
|
|
7911
|
+
sendResponse({ success: true });
|
|
7912
|
+
return true;
|
|
7913
|
+
}
|
|
7214
7914
|
if (message.type === "CONNECT_TO_SERVER") {
|
|
7215
7915
|
const serverUrl = message.serverUrl || "http://localhost:3200";
|
|
7216
7916
|
const browserId = message.browserId;
|