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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createAgenticBrowserCore } from "../runtime-C3Y-UgC1.mjs";
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.selector === "string" && typeof element.text === "string" && element.text) primaryActions.push({
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()].sort((a, b) => b[1] - a[1])),
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(result, visibleOnly) {
174
- const elements = result.elements.map((el) => compactElement(el, visibleOnly));
177
+ function compactInteractiveElementsResult(elements, meta, visibleOnly) {
178
+ const compacted = elements.map((el) => compactElement(el, visibleOnly));
175
179
  return {
176
- elements,
177
- totalFound: result.totalFound,
178
- truncated: result.truncated,
179
- summary: summarizeElements(elements)
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\" (browser back), \"goForward\" (browser forward), \"refresh\" (reload page). Dialog action: \"dialog\" (handle JS alert/confirm/prompt — use text=\"dismiss\" to cancel, value=\"...\" for prompt input). Fallback selectors are tried automatically if the primary selector fails. A session is auto-started if needed.", {
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
- selector: z.string().optional().describe("CSS selector for the target element"),
315
- fallbackSelectors: z.array(z.string()).optional().describe("Backup CSS selectors tried if the primary selector fails (from browser_get_elements)"),
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) payload.selector = selector;
327
- if (fallbackSelectors) payload.fallbackSelectors = fallbackSelectors;
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 all interactive elements on the current page (buttons, links, inputs, etc.). Returns CSS selectors and fallbackSelectors you can use with browser_interact. Pass fallbackSelectors to browser_interact for automatic retry when the primary selector breaks. Actions are derived from role: link/button/custom→click, input/textarea/contenteditable→click+type+press, select→click+select, checkbox/radio→toggle. A session is auto-started if needed.", {
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 sid = await resolveSession(sessionId);
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(compactInteractiveElementsResult(result, visibleOnly))
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",