agentic-browser 2.6.0 → 2.6.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/AGENTS.md CHANGED
@@ -3,14 +3,14 @@
3
3
  ## Quick Reference
4
4
 
5
5
  ```bash
6
- npm run build # tsdown (~20ms)
7
- npm run typecheck # tsc --noEmit
8
- npm run lint # oxlint
9
- npm run lint:fix # oxlint --fix
10
- npm run format # oxfmt --write
11
- npm test # vitest run
12
- npm run test:watch # vitest
13
- npm run docs:dev # vocs dev server
6
+ pnpm build # tsdown (~20ms)
7
+ pnpm typecheck # tsc --noEmit
8
+ pnpm lint # oxlint
9
+ pnpm lint:fix # oxlint --fix
10
+ pnpm format # oxfmt --write
11
+ pnpm test # vitest run
12
+ pnpm test:watch # vitest
13
+ pnpm docs:dev # vocs dev server
14
14
  ```
15
15
 
16
16
  ## Architecture
@@ -29,7 +29,7 @@ src/
29
29
  commands/agent.ts — Stateful agent commands (auto-restart, retry, session persistence)
30
30
  commands/*.ts — Low-level CLI command handlers
31
31
  mcp/
32
- index.ts — MCP server (stdio transport, 7 tools wrapping AgenticBrowserCore)
32
+ index.ts — MCP server (stdio transport, tools wrapping AgenticBrowserCore)
33
33
  session/
34
34
  browser-controller.ts — BrowserController interface + ChromeCdpBrowserController (CDP WebSocket)
35
35
  session-manager.ts — Orchestrates sessions, commands, memory recording
@@ -68,7 +68,7 @@ AgenticBrowserCore → ControlApi → SessionManager → BrowserController (CDP)
68
68
 
69
69
  - **ESM-only**: `"type": "module"`, use `.js` extensions in all TypeScript imports
70
70
  - **Zod v4**: `import { z } from "zod"` — `z.record()` requires key+value args
71
- - **Commander.js v14**: colon-namespaced commands (`session:start`, `memory:search`)
71
+ - **Commander.js v15**: colon-namespaced commands (`session:start`, `memory:search`)
72
72
  - **CLI output**: exactly one JSON line to `stdout`, errors to `stderr`
73
73
  - **Types**: interfaces for public contracts, type aliases for unions/inferred
74
74
  - **No console.log in MCP server**: use `process.stderr.write()` for debug output
@@ -111,21 +111,27 @@ AgenticBrowserCore → ControlApi → SessionManager → BrowserController (CDP)
111
111
  - `AGENTIC_BROWSER_DIR` — base dir for sessions/memory/events (default: `~/.agentic-browser`)
112
112
  - `AGENTIC_BROWSER_CHROME_EXECUTABLE_PATH` — explicit Chrome path (auto-discovered if not set)
113
113
  - `AGENTIC_BROWSER_CDP_URL` — connect to an already-running Chrome via CDP (e.g. `http://127.0.0.1:9222`)
114
+ - `AGENTIC_BROWSER_ALLOW_FILE_URLS` — allow `file:` URLs in navigate (`true` or `1`); default is http(s) and about:blank only
114
115
  - `AGENTIC_BROWSER_USER_PROFILE` — use real Chrome profile (`default`, `true`, or absolute path)
116
+ - `AGENTIC_BROWSER_CHROME_PATH` — alias of `AGENTIC_BROWSER_CHROME_EXECUTABLE_PATH`
117
+ - `AGENTIC_BROWSER_HEADLESS` — `true` or `1` to launch Chrome headless
118
+ - `AGENTIC_BROWSER_USER_AGENT` — override the browser user agent
115
119
 
116
120
  ## MCP Server
117
121
 
118
122
  Subcommand: `agentic-browser mcp` (stdio transport). Setup: `agentic-browser setup`. Tools:
119
123
 
120
- | Tool | Purpose |
121
- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
122
- | `browser_start_session` | Start Chrome, return sessionId |
123
- | `browser_navigate` | Navigate to URL |
124
- | `browser_interact` | click / type / press / waitFor / scroll / hover / select / toggle / goBack / goForward / refresh / dialog |
125
- | `browser_get_content` | Get page title / text / html |
126
- | `browser_get_elements` | Discover interactive elements |
127
- | `browser_search_memory` | Search task memory |
128
- | `browser_stop_session` | Stop Chrome session |
124
+ | Tool | Purpose |
125
+ | ------------------------- | --------------------------------------------------------------------------------------------------------- |
126
+ | `browser_start_session` | Start Chrome, return sessionId |
127
+ | `browser_navigate` | Navigate to URL |
128
+ | `browser_interact` | click / type / press / waitFor / scroll / hover / select / toggle / goBack / goForward / refresh / dialog |
129
+ | `browser_get_content` | Get page title / text / html |
130
+ | `browser_get_elements` | Discover interactive elements |
131
+ | `browser_search_memory` | Search task memory |
132
+ | `browser_stop_session` | Stop Chrome session |
133
+ | `browser_dismiss_cookies` | Dismiss cookie or consent banner |
134
+ | `browser_screenshot` | Screenshot as base64 (jpeg or png, optional full page) |
129
135
 
130
136
  ## For Browser Automation Tasks
131
137
 
package/README.md CHANGED
@@ -13,7 +13,7 @@ CLI and MCP server to control a local Chrome session for AI agents.
13
13
 
14
14
  ## Requirements
15
15
 
16
- - Node.js 20+
16
+ - Node.js 22+
17
17
  - Installed Chrome
18
18
 
19
19
  ## Install
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { r as createCliRuntime } from "../runtime-BzdbtFF4.mjs";
2
+ import { a as isConnectionError, o as ensurePrivateDir, r as createCliRuntime, s as writePrivateFileSync } from "../runtime-Bo7FmRYk.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import crypto from "node:crypto";
6
6
  import { Command } from "commander";
7
- //#region src/cli/commands/agent.ts
7
+ // src/cli/commands/agent.ts
8
8
  const STATE_FILE_NAME = "agent-state.json";
9
9
  function stateFilePath(runtime) {
10
10
  return path.join(runtime.context.config.dataDir, STATE_FILE_NAME);
@@ -20,8 +20,8 @@ function loadState(runtime) {
20
20
  }
21
21
  function saveState(runtime, state) {
22
22
  const dir = path.dirname(stateFilePath(runtime));
23
- fs.mkdirSync(dir, { recursive: true });
24
- fs.writeFileSync(stateFilePath(runtime), JSON.stringify(state, null, 2));
23
+ ensurePrivateDir(dir);
24
+ writePrivateFileSync(stateFilePath(runtime), JSON.stringify(state, null, 2));
25
25
  }
26
26
  function requireSessionId(runtime) {
27
27
  const state = loadState(runtime);
@@ -63,6 +63,12 @@ async function agentStop(runtime) {
63
63
  sessionId
64
64
  };
65
65
  }
66
+ /** Only connection-level failures justify a restart + re-run. */
67
+ function isRetryableFailure(message) {
68
+ if (isConnectionError(message)) return true;
69
+ const lower = message.toLowerCase();
70
+ return lower.includes("session") && (lower.includes("dead") || lower.includes("terminated"));
71
+ }
66
72
  async function executeAgentCommand(runtime, input) {
67
73
  const sessionId = requireSessionId(runtime);
68
74
  await ensureReady(runtime, sessionId);
@@ -71,16 +77,24 @@ async function executeAgentCommand(runtime, input) {
71
77
  type: input.type,
72
78
  payload: input.payload
73
79
  });
74
- try {
75
- const first = await attempt();
76
- if (first.resultStatus === "failed") throw new Error(first.resultMessage ?? "Command failed");
77
- return first;
78
- } catch {
80
+ const retryAfterRestart = async () => {
79
81
  await runtime.api.restartSession(sessionId);
80
82
  const second = await attempt();
81
83
  if (second.resultStatus === "failed") throw new Error(second.resultMessage ?? "Command failed after retry");
82
84
  return second;
85
+ };
86
+ const noRetry = input.type === "restart" || input.type === "terminate";
87
+ let first;
88
+ try {
89
+ first = await attempt();
90
+ } catch (error) {
91
+ if (noRetry || !isRetryableFailure(error.message)) throw error;
92
+ return await retryAfterRestart();
83
93
  }
94
+ if (first.resultStatus !== "failed") return first;
95
+ const message = first.resultMessage ?? "Command failed";
96
+ if (noRetry || !isRetryableFailure(message)) throw new Error(message);
97
+ return await retryAfterRestart();
84
98
  }
85
99
  async function agentNavigate(runtime, input) {
86
100
  const result = await executeAgentCommand(runtime, {
@@ -184,16 +198,14 @@ async function agentCleanup(runtime, input) {
184
198
  ...runtime.api.cleanupSessions(input)
185
199
  };
186
200
  }
187
- //#endregion
188
- //#region src/cli/output/result-formatter.ts
201
+ // src/cli/output/result-formatter.ts
189
202
  function formatResult(resultStatus, message) {
190
203
  return {
191
204
  resultStatus,
192
205
  message
193
206
  };
194
207
  }
195
- //#endregion
196
- //#region src/cli/commands/session-start.ts
208
+ // src/cli/commands/session-start.ts
197
209
  async function runSessionStart(runtime, input) {
198
210
  const session = await runtime.api.createSession(input);
199
211
  return {
@@ -201,29 +213,24 @@ async function runSessionStart(runtime, input) {
201
213
  sessionId: session.sessionId
202
214
  };
203
215
  }
204
- //#endregion
205
- //#region src/cli/commands/session-status.ts
216
+ // src/cli/commands/session-status.ts
206
217
  async function runSessionStatus(runtime, input) {
207
218
  return runtime.api.getSession(input.sessionId);
208
219
  }
209
- //#endregion
210
- //#region src/cli/commands/session-stop.ts
220
+ // src/cli/commands/session-stop.ts
211
221
  async function runSessionStop(runtime, input) {
212
222
  await runtime.api.terminateSession(input.sessionId);
213
223
  return { terminated: true };
214
224
  }
215
- //#endregion
216
- //#region src/cli/commands/session-cleanup.ts
225
+ // src/cli/commands/session-cleanup.ts
217
226
  async function runSessionCleanup(runtime, input) {
218
227
  return runtime.api.cleanupSessions(input);
219
228
  }
220
- //#endregion
221
- //#region src/cli/commands/session-restart.ts
229
+ // src/cli/commands/session-restart.ts
222
230
  async function runSessionRestart(runtime, input) {
223
231
  return await runtime.api.restartSession(input.sessionId);
224
232
  }
225
- //#endregion
226
- //#region src/cli/commands/session-auth.ts
233
+ // src/cli/commands/session-auth.ts
227
234
  async function runSessionAuth(runtime, input) {
228
235
  const token = runtime.api.rotateSessionToken(input.sessionId);
229
236
  return {
@@ -231,8 +238,7 @@ async function runSessionAuth(runtime, input) {
231
238
  valid: runtime.context.tokenService.validate(input.sessionId, token)
232
239
  };
233
240
  }
234
- //#endregion
235
- //#region src/cli/commands/command-run.ts
241
+ // src/cli/commands/command-run.ts
236
242
  async function runCommand(runtime, input) {
237
243
  if (!input.sessionId) throw new Error("No active session. Start or restart a session first.");
238
244
  try {
@@ -245,8 +251,7 @@ async function runCommand(runtime, input) {
245
251
  throw new Error(`Command rejected: ${error.message}`, { cause: error });
246
252
  }
247
253
  }
248
- //#endregion
249
- //#region src/cli/commands/page-content.ts
254
+ // src/cli/commands/page-content.ts
250
255
  async function runPageContent(runtime, input) {
251
256
  return await runtime.api.getContent(input.sessionId, {
252
257
  mode: input.mode,
@@ -254,31 +259,26 @@ async function runPageContent(runtime, input) {
254
259
  maxChars: input.maxChars
255
260
  });
256
261
  }
257
- //#endregion
258
- //#region src/cli/commands/memory-search.ts
262
+ // src/cli/commands/memory-search.ts
259
263
  async function runMemorySearch(runtime, input) {
260
264
  if (!input.taskIntent || !input.taskIntent.trim()) throw new Error("taskIntent is required");
261
265
  return runtime.api.searchMemory(input);
262
266
  }
263
- //#endregion
264
- //#region src/cli/commands/memory-inspect.ts
267
+ // src/cli/commands/memory-inspect.ts
265
268
  async function runMemoryInspect(runtime, input) {
266
269
  if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
267
270
  return runtime.api.inspectMemory(input.insightId);
268
271
  }
269
- //#endregion
270
- //#region src/cli/commands/memory-verify.ts
272
+ // src/cli/commands/memory-verify.ts
271
273
  async function runMemoryVerify(runtime, input) {
272
274
  if (!input.insightId || !input.insightId.trim()) throw new Error("insightId is required");
273
275
  return runtime.api.verifyMemory(input.insightId);
274
276
  }
275
- //#endregion
276
- //#region src/cli/commands/memory-stats.ts
277
+ // src/cli/commands/memory-stats.ts
277
278
  async function runMemoryStats(runtime) {
278
279
  return runtime.api.memoryStats();
279
280
  }
280
- //#endregion
281
- //#region src/cli/index.ts
281
+ // src/cli/index.ts
282
282
  const collect = (value, previous = []) => [...previous, value];
283
283
  async function main() {
284
284
  const runtime = createCliRuntime();
@@ -513,7 +513,7 @@ async function main() {
513
513
  await startMcpServer();
514
514
  });
515
515
  program.command("setup").description("Configure agentic-browser as MCP server for your AI tool").action(async () => {
516
- const { runSetup } = await import("../setup-DUAjdbQ3.mjs");
516
+ const { runSetup } = await import("../setup-DwFaZOPf.mjs");
517
517
  await runSetup();
518
518
  });
519
519
  await program.parseAsync(process.argv);
@@ -526,5 +526,4 @@ main().then((keepAlive) => {
526
526
  console.error(JSON.stringify({ error: message }));
527
527
  process.exit(1);
528
528
  });
529
- //#endregion
530
529
  export {};
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
- import WebSocket from "ws";
2
+ import "ws";
3
3
  import { z } from "zod";
4
- //#region src/session/browser-controller.d.ts
5
4
  type InteractAction = "click" | "type" | "press" | "waitFor" | "evaluate" | "scroll" | "hover" | "select" | "toggle" | "goBack" | "goForward" | "refresh" | "dialog";
6
5
  interface InteractPayload {
7
6
  action: InteractAction;
@@ -100,8 +99,6 @@ interface BrowserController {
100
99
  closeConnection?(targetWsUrl: string): void;
101
100
  dispose?(): void;
102
101
  }
103
- //#endregion
104
- //#region src/memory/memory-schemas.d.ts
105
102
  declare const InsightFreshnessSchema: z.ZodEnum<{
106
103
  fresh: "fresh";
107
104
  stale: "stale";
@@ -233,8 +230,6 @@ interface MemorySearchResult {
233
230
  score: number;
234
231
  siteProfile?: SiteProfileSummary;
235
232
  }
236
- //#endregion
237
- //#region src/lib/domain-schemas.d.ts
238
233
  declare const SessionStatusSchema: z.ZodEnum<{
239
234
  disconnected: "disconnected";
240
235
  failed: "failed";
@@ -302,8 +297,6 @@ type CommandType = z.infer<typeof CommandTypeSchema>;
302
297
  type Session = z.infer<typeof SessionSchema>;
303
298
  type Command = z.infer<typeof CommandSchema>;
304
299
  type SessionEvent = z.infer<typeof SessionEventSchema>;
305
- //#endregion
306
- //#region src/lib/config.d.ts
307
300
  interface AppConfig {
308
301
  dataDir: string;
309
302
  browserExecutablePath?: string;
@@ -311,10 +304,9 @@ interface AppConfig {
311
304
  userProfileDir?: string;
312
305
  headless?: boolean;
313
306
  userAgent?: string;
307
+ allowFileUrls: boolean;
314
308
  }
315
309
  declare function loadConfig(env?: NodeJS.ProcessEnv): AppConfig;
316
- //#endregion
317
- //#region src/observability/logger.d.ts
318
310
  interface LogContext {
319
311
  [key: string]: unknown;
320
312
  }
@@ -326,8 +318,6 @@ declare class Logger {
326
318
  warning(message: string, context?: LogContext): void;
327
319
  error(message: string, context?: LogContext): void;
328
320
  }
329
- //#endregion
330
- //#region src/observability/event-store.d.ts
331
321
  declare class EventStore {
332
322
  private readonly events;
333
323
  private readonly filePath;
@@ -349,8 +339,6 @@ declare class EventStore {
349
339
  private flushAsync;
350
340
  private readonly flushSync;
351
341
  }
352
- //#endregion
353
- //#region src/auth/session-token.d.ts
354
342
  declare class SessionTokenService {
355
343
  private readonly tokens;
356
344
  issue(sessionId: string): string;
@@ -359,8 +347,6 @@ declare class SessionTokenService {
359
347
  get(sessionId: string): string | undefined;
360
348
  seed(sessionId: string, token: string): void;
361
349
  }
362
- //#endregion
363
- //#region src/memory/memory-service.d.ts
364
350
  interface MemorySearchInput {
365
351
  taskIntent: string;
366
352
  siteDomain?: string;
@@ -385,6 +371,8 @@ declare class MemoryService {
385
371
  constructor(baseDir: string);
386
372
  /** Force an immediate synchronous flush of pending writes. */
387
373
  flushSync(): void;
374
+ /** Flush pending writes and release the stores' process exit hooks. */
375
+ dispose(): void;
388
376
  search(input: MemorySearchInput): MemorySearchResult[];
389
377
  /** Invalidate search cache when data changes. */
390
378
  private invalidateSearchCache;
@@ -400,8 +388,8 @@ declare class MemoryService {
400
388
  }>;
401
389
  };
402
390
  verify(insightId: string): TaskInsight;
403
- recordSuccess(input: RecordExecutionInput): TaskInsight;
404
- recordFailure(input: RecordExecutionInput, errorMessage: string): TaskInsight | undefined;
391
+ recordSuccess(rawInput: RecordExecutionInput): TaskInsight;
392
+ recordFailure(rawInput: RecordExecutionInput, errorMessage: string): TaskInsight | undefined;
405
393
  /** Update site profile from page content (call after getContent with mode="summary"). */
406
394
  updateProfileFromContent(domain: string, pathPattern: string, structuredContent: {
407
395
  headings?: string[];
@@ -421,13 +409,13 @@ declare class MemoryService {
421
409
  private findBestExactMatch;
422
410
  private scoreExactMatch;
423
411
  private sameFirstPathSegment;
412
+ /** Strip query strings and fragments from every URL-bearing field before it is persisted. */
413
+ private sanitizeInput;
424
414
  private createEvidence;
425
415
  private maybeGenerateAliases;
426
416
  private deriveAliasName;
427
417
  private mergeRecipe;
428
418
  }
429
- //#endregion
430
- //#region src/cli/app.d.ts
431
419
  interface AppContext {
432
420
  config: ReturnType<typeof loadConfig>;
433
421
  logger: Logger;
@@ -435,8 +423,6 @@ interface AppContext {
435
423
  tokenService: SessionTokenService;
436
424
  memoryService: MemoryService;
437
425
  }
438
- //#endregion
439
- //#region src/session/session-store.d.ts
440
426
  interface StoredSessionRecord {
441
427
  session: Session;
442
428
  cdpUrl: string;
@@ -444,8 +430,6 @@ interface StoredSessionRecord {
444
430
  pid: number;
445
431
  lastUrl?: string;
446
432
  }
447
- //#endregion
448
- //#region src/session/session-manager.d.ts
449
433
  interface CreateSessionInput {
450
434
  browser: "chrome";
451
435
  }
@@ -584,8 +568,6 @@ declare class SessionManager {
584
568
  private buildMemoryContext;
585
569
  private parseUrl;
586
570
  }
587
- //#endregion
588
- //#region src/transport/control-api.d.ts
589
571
  declare class ControlApi {
590
572
  private readonly sessions;
591
573
  constructor(sessions: SessionManager);
@@ -746,8 +728,6 @@ declare class ControlApi {
746
728
  dryRun?: boolean;
747
729
  }): CleanupResult;
748
730
  }
749
- //#endregion
750
- //#region src/cli/runtime.d.ts
751
731
  interface AgenticBrowserCoreOptions {
752
732
  env?: NodeJS.ProcessEnv;
753
733
  browserController?: BrowserController;
@@ -758,11 +738,19 @@ interface ExecuteCommandInput {
758
738
  type: CommandType;
759
739
  payload: Record<string, unknown>;
760
740
  }
761
- declare class AgenticBrowserCore {
741
+ export declare class AgenticBrowserCore {
762
742
  readonly context: AppContext;
763
743
  readonly sessions: SessionManager;
764
744
  readonly api: ControlApi;
745
+ private readonly browserController;
746
+ private disposed;
765
747
  constructor(context: AppContext, browserController: BrowserController);
748
+ /**
749
+ * Release process-level resources (event store and memory store exit handlers,
750
+ * controller timers). Flushes pending memory writes.
751
+ * Does not terminate browser sessions. Idempotent.
752
+ */
753
+ dispose(): Promise<void>;
766
754
  startSession(input?: {
767
755
  browser: "chrome";
768
756
  }): Promise<{
@@ -909,7 +897,6 @@ declare class AgenticBrowserCore {
909
897
  }>;
910
898
  };
911
899
  }
912
- declare function createAgenticBrowserCore(options?: AgenticBrowserCoreOptions): AgenticBrowserCore;
913
- declare function createMockAgenticBrowserCore(env: NodeJS.ProcessEnv): AgenticBrowserCore;
914
- //#endregion
915
- export { AgenticBrowserCore, type AgenticBrowserCoreOptions, type DismissCookieBannerResult, type ElementAction, type ErrorCode, type ExecuteCommandInput, type InteractiveElement, type InteractiveElementRole, type InteractiveElementsOptions, type InteractiveElementsResult, type LaunchOptions, type ScreenshotOptions, type ScreenshotResult, createAgenticBrowserCore, createMockAgenticBrowserCore };
900
+ export declare function createAgenticBrowserCore(options?: AgenticBrowserCoreOptions): AgenticBrowserCore;
901
+ export declare function createMockAgenticBrowserCore(env: NodeJS.ProcessEnv): AgenticBrowserCore;
902
+ export type { AgenticBrowserCoreOptions, DismissCookieBannerResult, ElementAction, ErrorCode, ExecuteCommandInput, InteractiveElement, InteractiveElementRole, InteractiveElementsOptions, InteractiveElementsResult, LaunchOptions, ScreenshotOptions, ScreenshotResult };
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { i as createMockAgenticBrowserCore, n as createAgenticBrowserCore, t as AgenticBrowserCore } from "./runtime-BzdbtFF4.mjs";
3
- export { AgenticBrowserCore, createAgenticBrowserCore, createMockAgenticBrowserCore };
2
+ export { t as AgenticBrowserCore, n as createAgenticBrowserCore, i as createMockAgenticBrowserCore } from "./runtime-Bo7FmRYk.mjs";
@@ -1,5 +1,2 @@
1
1
 
2
- //#region src/mcp/index.d.ts
3
- declare function main(): Promise<void>;
4
- //#endregion
5
- export { main };
2
+ export declare function main(): Promise<void>;
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { n as createAgenticBrowserCore } from "../runtime-BzdbtFF4.mjs";
2
+ import { n as createAgenticBrowserCore } from "../runtime-Bo7FmRYk.mjs";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { z } from "zod";
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- //#region src/mcp/response-helpers.ts
9
+ // src/mcp/response-helpers.ts
10
10
  function uniqueLines(content, maxLineLength = 160) {
11
11
  const seen = /* @__PURE__ */ new Set();
12
12
  const lines = [];
@@ -204,8 +204,7 @@ function compactMemoryResults(results) {
204
204
  return compact;
205
205
  }) };
206
206
  }
207
- //#endregion
208
- //#region src/mcp/element-refs.ts
207
+ // src/mcp/element-refs.ts
209
208
  var ElementRefRegistry = class {
210
209
  bySession = /* @__PURE__ */ new Map();
211
210
  /**
@@ -245,8 +244,41 @@ var ElementRefRegistry = class {
245
244
  this.bySession.delete(sessionId);
246
245
  }
247
246
  };
248
- //#endregion
249
- //#region src/mcp/index.ts
247
+ // src/mcp/session-resolver.ts
248
+ /**
249
+ * Tracks the active session and auto-starts one on demand. Concurrent callers
250
+ * share a single in-flight start so only one session is launched.
251
+ */
252
+ function createSessionResolver(start) {
253
+ let activeSessionId;
254
+ let pendingSession;
255
+ return {
256
+ async resolve(sessionId) {
257
+ if (sessionId) return sessionId;
258
+ if (activeSessionId) return activeSessionId;
259
+ if (pendingSession) return pendingSession;
260
+ const p = (async () => {
261
+ const session = await start();
262
+ activeSessionId = session.sessionId;
263
+ return session.sessionId;
264
+ })();
265
+ pendingSession = p;
266
+ try {
267
+ return await p;
268
+ } finally {
269
+ pendingSession = void 0;
270
+ }
271
+ },
272
+ current: () => activeSessionId,
273
+ set(sessionId) {
274
+ activeSessionId = sessionId;
275
+ },
276
+ clear() {
277
+ activeSessionId = void 0;
278
+ }
279
+ };
280
+ }
281
+ // src/mcp/index.ts
250
282
  function readPackageVersion() {
251
283
  try {
252
284
  const dir = dirname(fileURLToPath(import.meta.url));
@@ -271,7 +303,7 @@ function classifyError(message) {
271
303
  return "UNKNOWN";
272
304
  }
273
305
  let core;
274
- let activeSessionId;
306
+ const sessionResolver = createSessionResolver(() => getCore().startSession());
275
307
  const DEFAULT_MCP_MAX_CHARS = {
276
308
  summary: void 0,
277
309
  title: void 0,
@@ -313,10 +345,7 @@ async function snapshotElements(sessionId, opts = {}) {
313
345
  * This means the LLM never has to call browser_start_session explicitly.
314
346
  */
315
347
  async function resolveSession(sessionId) {
316
- if (sessionId) return sessionId;
317
- if (activeSessionId) return activeSessionId;
318
- activeSessionId = (await getCore().startSession()).sessionId;
319
- return activeSessionId;
348
+ return sessionResolver.resolve(sessionId);
320
349
  }
321
350
  const server = new McpServer({
322
351
  name: "agentic-browser",
@@ -324,7 +353,7 @@ const server = new McpServer({
324
353
  });
325
354
  server.tool("browser_start_session", "Start a new Chrome browser session (or return the existing one if healthy). Sessions auto-start when you call any other browser tool, so you rarely need to call this explicitly. Use this to force a fresh session after stopping the previous one.", {}, async () => {
326
355
  const session = await getCore().startSession();
327
- activeSessionId = session.sessionId;
356
+ sessionResolver.set(session.sessionId);
328
357
  const { authTokenRef: _, ...compactSession } = session;
329
358
  return { content: [{
330
359
  type: "text",
@@ -536,7 +565,7 @@ server.tool("browser_screenshot", "Capture a screenshot of the current page. Ret
536
565
  }] };
537
566
  });
538
567
  server.tool("browser_stop_session", "Stop the browser session and terminate Chrome. The next browser tool call will auto-start a fresh session.", { sessionId: z.string().optional().describe("Session ID (uses active session if omitted)") }, async ({ sessionId }) => {
539
- const sid = sessionId ?? activeSessionId;
568
+ const sid = sessionId ?? sessionResolver.current();
540
569
  if (!sid) return { content: [{
541
570
  type: "text",
542
571
  text: JSON.stringify({
@@ -546,7 +575,7 @@ server.tool("browser_stop_session", "Stop the browser session and terminate Chro
546
575
  }] };
547
576
  await getCore().stopSession(sid);
548
577
  refs.clear(sid);
549
- if (activeSessionId === sid) activeSessionId = void 0;
578
+ if (sessionResolver.current() === sid) sessionResolver.clear();
550
579
  return { content: [{
551
580
  type: "text",
552
581
  text: JSON.stringify({
@@ -558,11 +587,12 @@ server.tool("browser_stop_session", "Stop the browser session and terminate Chro
558
587
  async function main() {
559
588
  const transport = new StdioServerTransport();
560
589
  transport.onclose = async () => {
561
- if (activeSessionId) {
590
+ const activeId = sessionResolver.current();
591
+ if (activeId) {
562
592
  try {
563
- await getCore().stopSession(activeSessionId);
593
+ await getCore().stopSession(activeId);
564
594
  } catch {}
565
- activeSessionId = void 0;
595
+ sessionResolver.clear();
566
596
  }
567
597
  try {
568
598
  getCore().context.memoryService.flushSync();
@@ -573,5 +603,4 @@ async function main() {
573
603
  };
574
604
  await server.connect(transport);
575
605
  }
576
- //#endregion
577
606
  export { main };