agentic-browser 2.5.0 → 2.6.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/AGENTS.md +1 -1
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.mjs +6 -5
- package/dist/index.d.mts +75 -88
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.mjs +112 -24
- package/dist/{runtime-C3Y-UgC1.mjs → runtime-BzdbtFF4.mjs} +461 -171
- package/package.json +19 -19
- /package/dist/{setup-BP0YyerC.mjs → setup-DUAjdbQ3.mjs} +0 -0
package/dist/mcp/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as createAgenticBrowserCore } from "../runtime-
|
|
2
|
+
import { n as createAgenticBrowserCore } from "../runtime-BzdbtFF4.mjs";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -147,15 +147,14 @@ function summarizeElements(elements) {
|
|
|
147
147
|
for (const element of elements) {
|
|
148
148
|
const role = String(element.role ?? "unknown");
|
|
149
149
|
byRole.set(role, (byRole.get(role) ?? 0) + 1);
|
|
150
|
-
if (primaryActions.length < 12 && typeof element.
|
|
150
|
+
if (primaryActions.length < 12 && typeof element.ref === "string" && element.ref && typeof element.text === "string" && element.text) primaryActions.push({
|
|
151
|
+
ref: element.ref,
|
|
151
152
|
role,
|
|
152
|
-
text: element.text
|
|
153
|
-
selector: element.selector,
|
|
154
|
-
fallbackSelectors: element.fallbackSelectors
|
|
153
|
+
text: element.text
|
|
155
154
|
});
|
|
156
155
|
}
|
|
157
156
|
return {
|
|
158
|
-
countsByRole: Object.fromEntries([...byRole.entries()].
|
|
157
|
+
countsByRole: Object.fromEntries([...byRole.entries()].toSorted((a, b) => b[1] - a[1])),
|
|
159
158
|
primaryActions
|
|
160
159
|
};
|
|
161
160
|
}
|
|
@@ -164,19 +163,24 @@ function compactElement(element, visibleOnly) {
|
|
|
164
163
|
if (visibleOnly) delete compact.visible;
|
|
165
164
|
delete compact.actions;
|
|
166
165
|
delete compact.tagName;
|
|
166
|
+
if (compact.ref) {
|
|
167
|
+
delete compact.selector;
|
|
168
|
+
delete compact.fallbackSelectors;
|
|
169
|
+
}
|
|
170
|
+
if (!compact.ref) delete compact.ref;
|
|
167
171
|
if (compact.enabled === true) delete compact.enabled;
|
|
168
172
|
if (!compact.text) delete compact.text;
|
|
169
173
|
if (compact.ariaLabel && compact.ariaLabel === compact.text) delete compact.ariaLabel;
|
|
170
174
|
if (compact.placeholder && compact.placeholder === compact.text) delete compact.placeholder;
|
|
171
175
|
return compact;
|
|
172
176
|
}
|
|
173
|
-
function compactInteractiveElementsResult(
|
|
174
|
-
const
|
|
177
|
+
function compactInteractiveElementsResult(elements, meta, visibleOnly) {
|
|
178
|
+
const compacted = elements.map((el) => compactElement(el, visibleOnly));
|
|
175
179
|
return {
|
|
176
|
-
elements,
|
|
177
|
-
totalFound:
|
|
178
|
-
truncated:
|
|
179
|
-
summary: summarizeElements(
|
|
180
|
+
elements: compacted,
|
|
181
|
+
totalFound: meta.totalFound,
|
|
182
|
+
truncated: meta.truncated,
|
|
183
|
+
summary: summarizeElements(compacted)
|
|
180
184
|
};
|
|
181
185
|
}
|
|
182
186
|
function compactMemoryResults(results) {
|
|
@@ -201,6 +205,47 @@ function compactMemoryResults(results) {
|
|
|
201
205
|
}) };
|
|
202
206
|
}
|
|
203
207
|
//#endregion
|
|
208
|
+
//#region src/mcp/element-refs.ts
|
|
209
|
+
var ElementRefRegistry = class {
|
|
210
|
+
bySession = /* @__PURE__ */ new Map();
|
|
211
|
+
/**
|
|
212
|
+
* Assign fresh refs to a freshly-discovered element list, replacing any prior
|
|
213
|
+
* refs for the session. Elements without a selector (nothing to act on) are
|
|
214
|
+
* still returned but get no ref.
|
|
215
|
+
*/
|
|
216
|
+
assign(sessionId, elements) {
|
|
217
|
+
const map = /* @__PURE__ */ new Map();
|
|
218
|
+
let n = 0;
|
|
219
|
+
const annotated = elements.map((el) => {
|
|
220
|
+
if (!el.selector) return {
|
|
221
|
+
...el,
|
|
222
|
+
ref: ""
|
|
223
|
+
};
|
|
224
|
+
const ref = `e${++n}`;
|
|
225
|
+
map.set(ref, {
|
|
226
|
+
selector: el.selector,
|
|
227
|
+
fallbackSelectors: el.fallbackSelectors,
|
|
228
|
+
text: el.text,
|
|
229
|
+
role: el.role
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
...el,
|
|
233
|
+
ref
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
this.bySession.set(sessionId, map);
|
|
237
|
+
return { elements: annotated };
|
|
238
|
+
}
|
|
239
|
+
/** Resolve a ref to its selector target, or undefined if unknown/stale. */
|
|
240
|
+
resolve(sessionId, ref) {
|
|
241
|
+
return this.bySession.get(sessionId)?.get(ref);
|
|
242
|
+
}
|
|
243
|
+
/** Drop a session's refs (on stop/restart) so stale handles can't resolve. */
|
|
244
|
+
clear(sessionId) {
|
|
245
|
+
this.bySession.delete(sessionId);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
//#endregion
|
|
204
249
|
//#region src/mcp/index.ts
|
|
205
250
|
function readPackageVersion() {
|
|
206
251
|
try {
|
|
@@ -241,6 +286,28 @@ function getCore() {
|
|
|
241
286
|
function genId(prefix) {
|
|
242
287
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
243
288
|
}
|
|
289
|
+
const refs = new ElementRefRegistry();
|
|
290
|
+
/**
|
|
291
|
+
* Discover interactive elements, assign fresh refs for the session, and return
|
|
292
|
+
* the compact agent-facing view (refs + role/text, no raw selectors). This is
|
|
293
|
+
* the single "what can I see and do now" snapshot both browser_get_elements and
|
|
294
|
+
* the post-interact page state share.
|
|
295
|
+
*/
|
|
296
|
+
async function snapshotElements(sessionId, opts = {}) {
|
|
297
|
+
const visibleOnly = opts.visibleOnly ?? true;
|
|
298
|
+
const result = await getCore().getInteractiveElements({
|
|
299
|
+
sessionId,
|
|
300
|
+
visibleOnly,
|
|
301
|
+
limit: opts.limit ?? 50,
|
|
302
|
+
selector: opts.selector,
|
|
303
|
+
roles: opts.roles
|
|
304
|
+
});
|
|
305
|
+
const { elements } = refs.assign(sessionId, result.elements);
|
|
306
|
+
return compactInteractiveElementsResult(elements, {
|
|
307
|
+
totalFound: result.totalFound,
|
|
308
|
+
truncated: result.truncated
|
|
309
|
+
}, visibleOnly);
|
|
310
|
+
}
|
|
244
311
|
/**
|
|
245
312
|
* Resolve a session ID — auto-starts a session if none exists.
|
|
246
313
|
* This means the LLM never has to call browser_start_session explicitly.
|
|
@@ -296,7 +363,7 @@ server.tool("browser_navigate", "Navigate the browser to a URL. A session is aut
|
|
|
296
363
|
text: JSON.stringify(compact)
|
|
297
364
|
}] };
|
|
298
365
|
});
|
|
299
|
-
server.tool("browser_interact", "Interact with a page element or perform browser actions. Element actions: \"click\", \"type\", \"press\", \"waitFor\", \"scroll\", \"hover\", \"select\", \"toggle\". Navigation actions: \"goBack\"
|
|
366
|
+
server.tool("browser_interact", "Interact with a page element or perform browser actions. Point at a target with `ref` (from browser_get_elements, e.g. \"e3\") — like clicking what you see. `selector` (CSS) still works as an escape hatch. Element actions: \"click\", \"type\", \"press\", \"waitFor\", \"scroll\", \"hover\", \"select\", \"toggle\". Navigation actions: \"goBack\", \"goForward\", \"refresh\". Dialog action: \"dialog\" (text=\"dismiss\" to cancel, value=\"...\" for prompt input). After an action that can change the page, the response includes a fresh `pageState` (new refs + summary) so you can see the result and pick your next action without a separate browser_get_elements call. A session is auto-started if needed.", {
|
|
300
367
|
action: z.enum([
|
|
301
368
|
"click",
|
|
302
369
|
"type",
|
|
@@ -311,8 +378,9 @@ server.tool("browser_interact", "Interact with a page element or perform browser
|
|
|
311
378
|
"refresh",
|
|
312
379
|
"dialog"
|
|
313
380
|
]).describe("The interaction type"),
|
|
314
|
-
|
|
315
|
-
|
|
381
|
+
ref: z.string().optional().describe("Element ref from browser_get_elements (e.g. \"e3\") — preferred over selector"),
|
|
382
|
+
selector: z.string().optional().describe("CSS selector for the target element (escape hatch)"),
|
|
383
|
+
fallbackSelectors: z.array(z.string()).optional().describe("Backup CSS selectors tried if the primary selector fails"),
|
|
316
384
|
text: z.string().optional().describe("Text to type (for \"type\"), or \"dismiss\" to dismiss a dialog (for \"dialog\")"),
|
|
317
385
|
key: z.string().optional().describe("Key to press (required for \"press\" action, e.g. \"Enter\", \"Tab\")"),
|
|
318
386
|
value: z.string().optional().describe("Option value to select (for \"select\"), or prompt text (for \"dialog\")"),
|
|
@@ -320,11 +388,25 @@ server.tool("browser_interact", "Interact with a page element or perform browser
|
|
|
320
388
|
scrollY: z.number().optional().describe("Vertical scroll delta in pixels (for \"scroll\" action, positive = down)"),
|
|
321
389
|
timeoutMs: z.number().optional().describe("Timeout in milliseconds (for \"waitFor\" action, default 4000)"),
|
|
322
390
|
sessionId: z.string().optional().describe("Session ID (auto-resolved if omitted)")
|
|
323
|
-
}, async ({ action, selector, fallbackSelectors, text, key, value, scrollX, scrollY, timeoutMs, sessionId }) => {
|
|
391
|
+
}, async ({ action, ref, selector, fallbackSelectors, text, key, value, scrollX, scrollY, timeoutMs, sessionId }) => {
|
|
324
392
|
const sid = await resolveSession(sessionId);
|
|
325
393
|
const payload = { action };
|
|
326
|
-
if (selector
|
|
327
|
-
|
|
394
|
+
if (!selector && ref) {
|
|
395
|
+
const resolved = refs.resolve(sid, ref);
|
|
396
|
+
if (!resolved) return { content: [{
|
|
397
|
+
type: "text",
|
|
398
|
+
text: JSON.stringify({
|
|
399
|
+
resultStatus: "failed",
|
|
400
|
+
resultMessage: `Unknown or stale ref "${ref}". Call browser_get_elements to refresh element refs.`,
|
|
401
|
+
errorCode: "SELECTOR_NOT_FOUND"
|
|
402
|
+
})
|
|
403
|
+
}] };
|
|
404
|
+
payload.selector = resolved.selector;
|
|
405
|
+
if (resolved.fallbackSelectors) payload.fallbackSelectors = resolved.fallbackSelectors;
|
|
406
|
+
} else {
|
|
407
|
+
if (selector) payload.selector = selector;
|
|
408
|
+
if (fallbackSelectors) payload.fallbackSelectors = fallbackSelectors;
|
|
409
|
+
}
|
|
328
410
|
if (text) payload.text = text;
|
|
329
411
|
if (key) payload.key = key;
|
|
330
412
|
if (value) payload.value = value;
|
|
@@ -347,6 +429,13 @@ server.tool("browser_interact", "Interact with a page element or perform browser
|
|
|
347
429
|
currentUrl
|
|
348
430
|
};
|
|
349
431
|
if (result.resultStatus === "failed") compact.errorCode = classifyError(result.resultMessage ?? "");
|
|
432
|
+
const pageChanging = action !== "hover" && action !== "waitFor";
|
|
433
|
+
if (result.resultStatus === "success" && pageChanging) try {
|
|
434
|
+
compact.pageState = await snapshotElements(sid, {
|
|
435
|
+
visibleOnly: true,
|
|
436
|
+
limit: 50
|
|
437
|
+
});
|
|
438
|
+
} catch {}
|
|
350
439
|
return { content: [{
|
|
351
440
|
type: "text",
|
|
352
441
|
text: JSON.stringify(compact)
|
|
@@ -374,10 +463,10 @@ server.tool("browser_get_content", "Get the current page content. Modes: \"summa
|
|
|
374
463
|
});
|
|
375
464
|
return { content: [{
|
|
376
465
|
type: "text",
|
|
377
|
-
text: JSON.stringify(compactPageContent(result))
|
|
466
|
+
text: JSON.stringify(compactPageContent(result, effectiveMaxChars))
|
|
378
467
|
}] };
|
|
379
468
|
});
|
|
380
|
-
server.tool("browser_get_elements", "Discover
|
|
469
|
+
server.tool("browser_get_elements", "Discover interactive elements on the current page (buttons, links, inputs, etc.). Each element gets a short ref like \"e1\" — pass that ref to browser_interact to act on it, the way you would point at something you see rather than describing its markup. Refs stay valid until the next browser_get_elements call or the next page-changing interaction. Actions per role: link/button/custom→click, input/textarea/contenteditable→click+type+press, select→click+select, checkbox/radio→toggle. A session is auto-started if needed.", {
|
|
381
470
|
roles: z.array(z.enum([
|
|
382
471
|
"link",
|
|
383
472
|
"button",
|
|
@@ -394,9 +483,7 @@ server.tool("browser_get_elements", "Discover all interactive elements on the cu
|
|
|
394
483
|
selector: z.string().optional().describe("CSS selector to scope element discovery to a subtree"),
|
|
395
484
|
sessionId: z.string().optional().describe("Session ID (auto-resolved if omitted)")
|
|
396
485
|
}, async ({ roles, visibleOnly, limit, selector, sessionId }) => {
|
|
397
|
-
const
|
|
398
|
-
const result = await getCore().getInteractiveElements({
|
|
399
|
-
sessionId: sid,
|
|
486
|
+
const snapshot = await snapshotElements(await resolveSession(sessionId), {
|
|
400
487
|
roles,
|
|
401
488
|
visibleOnly,
|
|
402
489
|
limit,
|
|
@@ -404,7 +491,7 @@ server.tool("browser_get_elements", "Discover all interactive elements on the cu
|
|
|
404
491
|
});
|
|
405
492
|
return { content: [{
|
|
406
493
|
type: "text",
|
|
407
|
-
text: JSON.stringify(
|
|
494
|
+
text: JSON.stringify(snapshot)
|
|
408
495
|
}] };
|
|
409
496
|
});
|
|
410
497
|
server.tool("browser_search_memory", "Search task memory for previously learned selectors, selector aliases, and interaction patterns. Results include selectorHints (proven selectors) and selectorAliases (human-readable names mapped to selectors with fallbacks). Use this before interacting with a known site to reuse proven selectors instead of rediscovering them.", {
|
|
@@ -458,6 +545,7 @@ server.tool("browser_stop_session", "Stop the browser session and terminate Chro
|
|
|
458
545
|
})
|
|
459
546
|
}] };
|
|
460
547
|
await getCore().stopSession(sid);
|
|
548
|
+
refs.clear(sid);
|
|
461
549
|
if (activeSessionId === sid) activeSessionId = void 0;
|
|
462
550
|
return { content: [{
|
|
463
551
|
type: "text",
|