agentic-browser 2.5.1 → 2.6.1

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 CHANGED
@@ -37,7 +37,7 @@ src/
37
37
  chrome-launcher.ts — Chrome executable discovery & launch
38
38
  transport/
39
39
  control-api.ts — ControlApi facade (delegates to SessionManager)
40
- ws-server.ts — Authenticated WebSocket server
40
+ auth-middleware.ts — verifyBearerAuth() token check helper
41
41
  memory/
42
42
  memory-service.ts — Task memory coordination
43
43
  memory-index.ts — Search/ranking (fuzzy match + freshness + domain)
@@ -1,2 +1,2 @@
1
1
 
2
- export { };
2
+ export {}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as createCliRuntime } from "../runtime-BeD8ZcjH.mjs";
2
+ import { r as createCliRuntime } from "../runtime-BzdbtFF4.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
@@ -323,6 +323,7 @@ async function main() {
323
323
  payload: JSON.parse(payloadJson)
324
324
  });
325
325
  console.log(JSON.stringify(result));
326
+ if (result.resultStatus === "failed" || result.resultStatus === "timed_out") process.exitCode = 1;
326
327
  });
327
328
  program.command("page:content").argument("<sessionId>").option("--mode <mode>", "title|text|html|a11y|summary", "summary").option("--selector <selector>", "optional CSS selector").option("--max-chars <n>", "max characters for text/html/a11y modes").action(async (sessionId, options) => {
328
329
  const result = await runPageContent(runtime, {
@@ -512,14 +513,14 @@ async function main() {
512
513
  await startMcpServer();
513
514
  });
514
515
  program.command("setup").description("Configure agentic-browser as MCP server for your AI tool").action(async () => {
515
- const { runSetup } = await import("../setup-BP0YyerC.mjs");
516
+ const { runSetup } = await import("../setup-DUAjdbQ3.mjs");
516
517
  await runSetup();
517
518
  });
518
519
  await program.parseAsync(process.argv);
519
520
  return keepAlive;
520
521
  }
521
522
  main().then((keepAlive) => {
522
- if (!keepAlive) process.exit(0);
523
+ if (!keepAlive) process.exit(process.exitCode ?? 0);
523
524
  }).catch((error) => {
524
525
  const message = error instanceof Error ? error.message : String(error);
525
526
  console.error(JSON.stringify({ error: message }));
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
- import WebSocket, { WebSocket as WebSocket$1 } from "ws";
2
+ import WebSocket from "ws";
3
3
  import { z } from "zod";
4
-
5
4
  //#region src/session/browser-controller.d.ts
6
5
  type InteractAction = "click" | "type" | "press" | "waitFor" | "evaluate" | "scroll" | "hover" | "select" | "toggle" | "goBack" | "goForward" | "refresh" | "dialog";
7
6
  interface InteractPayload {
@@ -306,11 +305,8 @@ type SessionEvent = z.infer<typeof SessionEventSchema>;
306
305
  //#endregion
307
306
  //#region src/lib/config.d.ts
308
307
  interface AppConfig {
309
- host: string;
310
- wsPort: number;
311
308
  dataDir: string;
312
309
  browserExecutablePath?: string;
313
- commandTimeoutMs: number;
314
310
  cdpUrl?: string;
315
311
  userProfileDir?: string;
316
312
  headless?: boolean;
@@ -337,8 +333,15 @@ declare class EventStore {
337
333
  private readonly filePath;
338
334
  private readonly bufferedLines;
339
335
  private flushTimer;
336
+ private pendingFlushDelayMs;
340
337
  private flushInFlight;
341
338
  constructor(baseDir: string);
339
+ /**
340
+ * Remove the process exit handlers and flush any buffered lines.
341
+ * Call when discarding a store (repeated context creation in tests/embedders
342
+ * would otherwise leak a listener pair per instance).
343
+ */
344
+ dispose(): void;
342
345
  append(event: SessionEvent): void;
343
346
  list(sessionId: string, limit?: number): SessionEvent[];
344
347
  clear(sessionId: string): void;
@@ -357,21 +360,6 @@ declare class SessionTokenService {
357
360
  seed(sessionId: string, token: string): void;
358
361
  }
359
362
  //#endregion
360
- //#region src/transport/ws-server.d.ts
361
- interface WsServerOptions {
362
- host: string;
363
- port: number;
364
- tokenService: SessionTokenService;
365
- }
366
- declare class AuthenticatedWsServer {
367
- private readonly options;
368
- private readonly logger;
369
- private server?;
370
- constructor(options: WsServerOptions);
371
- start(onMessage: (socket: WebSocket$1, data: unknown) => void): void;
372
- stop(): void;
373
- }
374
- //#endregion
375
363
  //#region src/memory/memory-service.d.ts
376
364
  interface MemorySearchInput {
377
365
  taskIntent: string;
@@ -445,7 +433,6 @@ interface AppContext {
445
433
  logger: Logger;
446
434
  eventStore: EventStore;
447
435
  tokenService: SessionTokenService;
448
- wsServer: AuthenticatedWsServer;
449
436
  memoryService: MemoryService;
450
437
  }
451
438
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-BeD8ZcjH.mjs";
2
+ import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-BzdbtFF4.mjs";
3
3
  export { AgenticBrowserCore, createAgenticBrowserCore, createMockAgenticBrowserCore };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createAgenticBrowserCore } from "../runtime-BeD8ZcjH.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,11 +147,10 @@ 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 {
@@ -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",