agents 0.14.5 → 0.16.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.
Files changed (53) hide show
  1. package/dist/{agent-tool-types-V25Z_HcX.d.ts → agent-tool-types-NofdbL9X.d.ts} +182 -112
  2. package/dist/agent-tool-types.d.ts +1 -1
  3. package/dist/{agent-tools-C-9s151X.d.ts → agent-tools-DLquv-dp.d.ts} +2 -2
  4. package/dist/agent-tools.d.ts +1 -1
  5. package/dist/browser/ai.d.ts +126 -7
  6. package/dist/browser/ai.js +73 -29
  7. package/dist/browser/ai.js.map +1 -1
  8. package/dist/browser/index.d.ts +81 -69
  9. package/dist/browser/index.js +3 -2
  10. package/dist/browser/tanstack-ai.d.ts +13 -7
  11. package/dist/browser/tanstack-ai.js +18 -19
  12. package/dist/browser/tanstack-ai.js.map +1 -1
  13. package/dist/chat/index.d.ts +111 -5
  14. package/dist/chat/index.js +207 -35
  15. package/dist/chat/index.js.map +1 -1
  16. package/dist/chat-sdk/index.d.ts +1 -1
  17. package/dist/{classPrivateFieldGet2-Beqsfu2Z.js → classPrivateFieldGet2-CZ7QjTXN.js} +5 -5
  18. package/dist/{classPrivateMethodInitSpec-B5ko1s2R.js → classPrivateMethodInitSpec-D-0__zd9.js} +2 -2
  19. package/dist/client.d.ts +19 -2
  20. package/dist/client.js +31 -11
  21. package/dist/client.js.map +1 -1
  22. package/dist/{compaction-helpers-BEUILPss.d.ts → compaction-helpers-DVcu5lPN.d.ts} +91 -12
  23. package/dist/connector-D6yYzYHg.js +1080 -0
  24. package/dist/connector-D6yYzYHg.js.map +1 -0
  25. package/dist/connector-DXursxV5.d.ts +340 -0
  26. package/dist/experimental/memory/session/index.d.ts +75 -12
  27. package/dist/experimental/memory/session/index.js +226 -21
  28. package/dist/experimental/memory/session/index.js.map +1 -1
  29. package/dist/experimental/memory/utils/index.d.ts +2 -2
  30. package/dist/{index-CPe1OtI0.d.ts → index-B7IbEeze.d.ts} +32 -1
  31. package/dist/index.d.ts +8 -2
  32. package/dist/index.js +116 -45
  33. package/dist/index.js.map +1 -1
  34. package/dist/mcp/client.d.ts +1 -1
  35. package/dist/mcp/index.d.ts +1 -1
  36. package/dist/mcp/index.js +262 -487
  37. package/dist/mcp/index.js.map +1 -1
  38. package/dist/observability/index.d.ts +1 -1
  39. package/dist/react.d.ts +12 -1
  40. package/dist/react.js +101 -30
  41. package/dist/react.js.map +1 -1
  42. package/dist/{retries-CF_HKSlJ.d.ts → retries-CwlpAGet.d.ts} +35 -5
  43. package/dist/retries.d.ts +9 -5
  44. package/dist/retries.js +87 -1
  45. package/dist/retries.js.map +1 -1
  46. package/dist/serializable.d.ts +1 -1
  47. package/dist/skills/index.js +2 -2
  48. package/dist/sub-routing.d.ts +1 -1
  49. package/dist/workflows.d.ts +1 -1
  50. package/package.json +10 -10
  51. package/dist/shared-4CAYLCTO.d.ts +0 -34
  52. package/dist/shared-wyII629d.js +0 -432
  53. package/dist/shared-wyII629d.js.map +0 -1
@@ -0,0 +1,1080 @@
1
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-CZ7QjTXN.js";
2
+ import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-D-0__zd9.js";
3
+ import { CodemodeConnector } from "@cloudflare/codemode";
4
+ //#region src/browser/cdp-session.ts
5
+ const DEFAULT_TIMEOUT_MS = 1e4;
6
+ const MAX_DEBUG_ENTRIES = 400;
7
+ var _socket$1 = /* @__PURE__ */ new WeakMap();
8
+ var _nextId = /* @__PURE__ */ new WeakMap();
9
+ var _pending = /* @__PURE__ */ new WeakMap();
10
+ var _debugLog = /* @__PURE__ */ new WeakMap();
11
+ var _defaultTimeoutMs = /* @__PURE__ */ new WeakMap();
12
+ var _dispose = /* @__PURE__ */ new WeakMap();
13
+ var _CdpSession_brand = /* @__PURE__ */ new WeakSet();
14
+ /**
15
+ * A CDP session over an open WebSocket. Manages command correlation,
16
+ * timeouts, target sessions, and a debug event ring buffer.
17
+ *
18
+ * Used host-side (not in the sandbox) — the sandbox calls into this
19
+ * via DynamicWorkerExecutor's ToolDispatcher RPC.
20
+ */
21
+ var CdpSession = class {
22
+ constructor(socket, defaultTimeoutMs = DEFAULT_TIMEOUT_MS, dispose, sessionId) {
23
+ _classPrivateMethodInitSpec(this, _CdpSession_brand);
24
+ _classPrivateFieldInitSpec(this, _socket$1, void 0);
25
+ _classPrivateFieldInitSpec(this, _nextId, 1);
26
+ _classPrivateFieldInitSpec(this, _pending, /* @__PURE__ */ new Map());
27
+ _classPrivateFieldInitSpec(this, _debugLog, []);
28
+ _classPrivateFieldInitSpec(this, _defaultTimeoutMs, void 0);
29
+ _classPrivateFieldInitSpec(this, _dispose, void 0);
30
+ _classPrivateFieldSet2(_socket$1, this, socket);
31
+ _classPrivateFieldSet2(_defaultTimeoutMs, this, defaultTimeoutMs);
32
+ _classPrivateFieldSet2(_dispose, this, dispose);
33
+ this.sessionId = sessionId;
34
+ socket.addEventListener("message", (event) => _assertClassBrand(_CdpSession_brand, this, _handleMessage).call(this, event));
35
+ socket.addEventListener("error", () => {
36
+ _assertClassBrand(_CdpSession_brand, this, _rejectAll).call(this, /* @__PURE__ */ new Error("CDP socket error"));
37
+ });
38
+ socket.addEventListener("close", () => {
39
+ _assertClassBrand(_CdpSession_brand, this, _rejectAll).call(this, /* @__PURE__ */ new Error("CDP connection closed"));
40
+ });
41
+ }
42
+ send(method, params, options = {}) {
43
+ var _this$nextId, _this$nextId2;
44
+ const id = (_classPrivateFieldSet2(_nextId, this, (_this$nextId = _classPrivateFieldGet2(_nextId, this), _this$nextId2 = _this$nextId++, _this$nextId)), _this$nextId2);
45
+ const timeoutMs = options.timeoutMs ?? _classPrivateFieldGet2(_defaultTimeoutMs, this);
46
+ const sessionId = typeof options.sessionId === "string" && options.sessionId.length > 0 ? options.sessionId : void 0;
47
+ const domain = typeof method === "string" ? method.split(".")[0] : "";
48
+ if (!sessionId && domain && !["Browser", "Target"].includes(domain)) _assertClassBrand(_CdpSession_brand, this, _recordDebug).call(this, "warning", {
49
+ id,
50
+ method,
51
+ reason: "target-scoped method sent without sessionId"
52
+ });
53
+ const result = new Promise((resolve, reject) => {
54
+ const startedAt = performance.now();
55
+ const timeoutId = setTimeout(() => {
56
+ _classPrivateFieldGet2(_pending, this).delete(id);
57
+ reject(/* @__PURE__ */ new Error(`CDP command timed out after ${timeoutMs}ms: ${method}`));
58
+ }, timeoutMs);
59
+ _classPrivateFieldGet2(_pending, this).set(id, {
60
+ resolve,
61
+ reject,
62
+ timeoutId,
63
+ method,
64
+ sessionId,
65
+ startedAt
66
+ });
67
+ });
68
+ _assertClassBrand(_CdpSession_brand, this, _recordDebug).call(this, "send", {
69
+ id,
70
+ method,
71
+ sessionId,
72
+ timeoutMs
73
+ });
74
+ _classPrivateFieldGet2(_socket$1, this).send(JSON.stringify({
75
+ id,
76
+ method,
77
+ params,
78
+ sessionId
79
+ }));
80
+ return result;
81
+ }
82
+ async attachToTarget(targetId, options = {}) {
83
+ if (typeof targetId !== "string" || !targetId) throw new Error("attachToTarget requires a targetId — list open tabs with send('Target.getTargets') or create one with send('Target.createTarget', { url })");
84
+ const sessionId = (await this.send("Target.attachToTarget", {
85
+ targetId,
86
+ flatten: true
87
+ }, { timeoutMs: options.timeoutMs }))?.sessionId ?? "";
88
+ if (!sessionId) throw new Error(`Target.attachToTarget did not return a sessionId for target ${targetId}`);
89
+ _assertClassBrand(_CdpSession_brand, this, _recordDebug).call(this, "attach", {
90
+ targetId,
91
+ sessionId
92
+ });
93
+ return sessionId;
94
+ }
95
+ getDebugLog(limit = 50) {
96
+ const normalized = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 50;
97
+ return _classPrivateFieldGet2(_debugLog, this).slice(-normalized);
98
+ }
99
+ clearDebugLog() {
100
+ _classPrivateFieldSet2(_debugLog, this, []);
101
+ }
102
+ disconnect() {
103
+ _assertClassBrand(_CdpSession_brand, this, _rejectAll).call(this, /* @__PURE__ */ new Error("CDP session disconnected"));
104
+ try {
105
+ _classPrivateFieldGet2(_socket$1, this).close(1e3, "Done");
106
+ } catch {}
107
+ }
108
+ close() {
109
+ this.disconnect();
110
+ _classPrivateFieldGet2(_dispose, this)?.call(this);
111
+ }
112
+ };
113
+ function _rejectAll(error) {
114
+ for (const [id, pending] of _classPrivateFieldGet2(_pending, this).entries()) {
115
+ clearTimeout(pending.timeoutId);
116
+ _classPrivateFieldGet2(_pending, this).delete(id);
117
+ pending.reject(error);
118
+ }
119
+ }
120
+ function _handleMessage(event) {
121
+ if (typeof event.data !== "string") return;
122
+ let payload;
123
+ try {
124
+ payload = JSON.parse(event.data);
125
+ } catch {
126
+ return;
127
+ }
128
+ _assertClassBrand(_CdpSession_brand, this, _recordDebug).call(this, "receive", {
129
+ id: payload.id,
130
+ method: payload.method,
131
+ sessionId: payload.sessionId,
132
+ hasError: !!payload.error
133
+ });
134
+ if (typeof payload.id !== "number") return;
135
+ const pending = _classPrivateFieldGet2(_pending, this).get(payload.id);
136
+ if (!pending) return;
137
+ clearTimeout(pending.timeoutId);
138
+ _classPrivateFieldGet2(_pending, this).delete(payload.id);
139
+ if (payload.error) {
140
+ const err = payload.error;
141
+ const code = err.code ?? "unknown";
142
+ const message = err.message ?? "CDP error";
143
+ pending.reject(/* @__PURE__ */ new Error(`CDP error ${code}: ${message} for ${pending.method}`));
144
+ return;
145
+ }
146
+ pending.resolve(payload.result);
147
+ }
148
+ function _recordDebug(type, data) {
149
+ _classPrivateFieldGet2(_debugLog, this).push({
150
+ at: (/* @__PURE__ */ new Date()).toISOString(),
151
+ type,
152
+ ...data
153
+ });
154
+ if (_classPrivateFieldGet2(_debugLog, this).length > MAX_DEBUG_ENTRIES) _classPrivateFieldGet2(_debugLog, this).splice(0, _classPrivateFieldGet2(_debugLog, this).length - MAX_DEBUG_ENTRIES);
155
+ }
156
+ const LOCALHOST_HOSTS = new Set([
157
+ "localhost",
158
+ "127.0.0.1",
159
+ "0.0.0.0",
160
+ "::1",
161
+ "[::1]"
162
+ ]);
163
+ /**
164
+ * Connect to a browser via a CDP base URL (e.g. http://localhost:9222).
165
+ * Discovers the WebSocket debugger URL via /json/version,
166
+ * rewrites localhost URLs to the base URL host, and opens the WebSocket.
167
+ *
168
+ * Useful for local development with `chrome --remote-debugging-port=9222`
169
+ * or when connecting through a tunnel.
170
+ */
171
+ async function connectUrl(baseUrl, options) {
172
+ const endpoint = new URL("/json/version", baseUrl).toString();
173
+ const response = await fetch(endpoint, { headers: options?.headers });
174
+ if (!response.ok) throw new Error(`Failed to discover CDP endpoint at ${endpoint}: ${response.status}`);
175
+ const payload = await response.json();
176
+ if (!payload.webSocketDebuggerUrl) throw new Error("CDP /json/version did not include webSocketDebuggerUrl");
177
+ let wsUrl = payload.webSocketDebuggerUrl;
178
+ const parsed = new URL(wsUrl);
179
+ if (LOCALHOST_HOSTS.has(parsed.hostname)) {
180
+ const base = new URL(baseUrl);
181
+ parsed.hostname = base.hostname;
182
+ parsed.port = base.port;
183
+ parsed.protocol = base.protocol;
184
+ } else parsed.protocol = parsed.protocol === "wss:" ? "https:" : "http:";
185
+ const fetchUrl = parsed.toString();
186
+ const wsResponse = await fetch(fetchUrl, { headers: {
187
+ ...options?.headers,
188
+ Upgrade: "websocket"
189
+ } });
190
+ const ws = wsResponse.webSocket;
191
+ if (!ws) throw new Error(`Failed to establish CDP WebSocket at ${fetchUrl} (status ${wsResponse.status})`);
192
+ ws.accept();
193
+ return new CdpSession(ws, options?.timeoutMs);
194
+ }
195
+ //#endregion
196
+ //#region src/browser/browser-run.ts
197
+ var BrowserRenderingError = class extends Error {
198
+ constructor(message, status) {
199
+ super(message);
200
+ this.status = status;
201
+ this.name = "BrowserRenderingError";
202
+ }
203
+ };
204
+ function browserSessionEndpoint(sessionId, options) {
205
+ const path = sessionId ? `/v1/devtools/browser/${sessionId}` : "/v1/devtools/browser";
206
+ const url = new URL(`https://localhost${path}`);
207
+ if (options?.keepAliveMs !== void 0) url.searchParams.set("keep_alive", String(options.keepAliveMs));
208
+ if (options?.includeTargets) url.searchParams.set("targets", "true");
209
+ return url.toString();
210
+ }
211
+ async function parseBrowserSessionInfo(response) {
212
+ const payload = await response.json();
213
+ const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : "";
214
+ if (!sessionId) throw new Error("Browser Rendering response did not include a sessionId");
215
+ return {
216
+ sessionId,
217
+ targets: Array.isArray(payload.targets) ? payload.targets : void 0,
218
+ webSocketDebuggerUrl: typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : void 0
219
+ };
220
+ }
221
+ async function createBrowserSession(browser, options) {
222
+ const response = await browser.fetch(browserSessionEndpoint(void 0, options), { method: "POST" });
223
+ if (!response.ok) throw new BrowserRenderingError(`Failed to create Browser Rendering session: ${response.status}`, response.status);
224
+ return parseBrowserSessionInfo(response);
225
+ }
226
+ async function listBrowserTargets(browser, sessionId) {
227
+ const response = await browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}/json/list`);
228
+ if (!response.ok) throw new BrowserRenderingError(`Failed to list Browser Rendering targets for ${sessionId}: ${response.status}`, response.status);
229
+ const payload = await response.json();
230
+ return Array.isArray(payload) ? payload : [];
231
+ }
232
+ async function deleteBrowserSession(browser, sessionId) {
233
+ const response = await browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}`, { method: "DELETE" });
234
+ if (!response.ok && response.status !== 404) throw new BrowserRenderingError(`Failed to delete Browser Rendering session ${sessionId}: ${response.status}`, response.status);
235
+ }
236
+ /**
237
+ * Connect to a Browser Rendering session and delete it when closed.
238
+ */
239
+ async function connectBrowser(browser, options) {
240
+ const normalizedOptions = typeof options === "number" ? { timeoutMs: options } : options ?? {};
241
+ const response = await browser.fetch(browserSessionEndpoint(void 0, {
242
+ keepAliveMs: normalizedOptions.keepAliveMs,
243
+ includeTargets: normalizedOptions.includeTargets
244
+ }), { headers: { Upgrade: "websocket" } });
245
+ const ws = response.webSocket;
246
+ if (!ws) throw new Error("Browser Rendering binding did not return a WebSocket. Ensure the 'browser' binding is configured in wrangler.jsonc.");
247
+ const sessionId = response.headers.get("cf-browser-session-id");
248
+ if (!sessionId) throw new Error("Browser Rendering binding did not include a session ID when opening the CDP WebSocket");
249
+ ws.accept();
250
+ return new CdpSession(ws, normalizedOptions.timeoutMs, () => {
251
+ deleteBrowserSession(browser, sessionId).catch((error) => {
252
+ console.warn(`[agents/browser] Failed to delete one-shot Browser Run session ${sessionId}`, error);
253
+ });
254
+ }, sessionId);
255
+ }
256
+ /**
257
+ * Connect to an existing Browser Rendering session without deleting it on close.
258
+ */
259
+ async function connectBrowserSession(browser, sessionId, timeoutMs) {
260
+ const ws = (await browser.fetch(browserSessionEndpoint(sessionId), { headers: { Upgrade: "websocket" } })).webSocket;
261
+ if (!ws) throw new Error(`Browser Rendering binding did not return a WebSocket for session ${sessionId}`);
262
+ ws.accept();
263
+ return new CdpSession(ws, timeoutMs, void 0, sessionId);
264
+ }
265
+ //#endregion
266
+ //#region src/browser/spec.ts
267
+ const MISSING_BROWSER_CONFIG = "Either 'browser' (Fetcher binding) or 'cdpUrl' must be provided";
268
+ const urlSpecCache = /* @__PURE__ */ new Map();
269
+ const bindingSpecCache = /* @__PURE__ */ new WeakMap();
270
+ const CACHE_TTL_MS = 300 * 1e3;
271
+ function normalizeCdpSpec(spec) {
272
+ return { domains: (spec.domains ?? []).map((domain) => ({
273
+ name: domain.domain,
274
+ description: domain.description,
275
+ commands: (domain.commands ?? []).map((command) => ({
276
+ name: command.name,
277
+ method: `${domain.domain}.${command.name}`,
278
+ description: command.description
279
+ })),
280
+ events: (domain.events ?? []).map((event) => ({
281
+ name: event.name,
282
+ event: `${domain.domain}.${event.name}`,
283
+ description: event.description
284
+ })),
285
+ types: (domain.types ?? []).map((type) => ({
286
+ id: type.id,
287
+ name: `${domain.domain}.${type.id}`,
288
+ description: type.description
289
+ }))
290
+ })) };
291
+ }
292
+ function getSpecCacheKey(source, headers) {
293
+ const headerEntries = Object.entries(headers ?? {}).sort(([a], [b]) => a.localeCompare(b));
294
+ return `${source}:${JSON.stringify(headerEntries)}`;
295
+ }
296
+ async function getCachedSpec(cache, key, load) {
297
+ const cached = cache.get(key);
298
+ if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) return cached.spec;
299
+ const spec = normalizeCdpSpec(await load());
300
+ cache.set(key, {
301
+ spec,
302
+ cachedAt: Date.now()
303
+ });
304
+ return spec;
305
+ }
306
+ async function fetchCdpSpecFromUrl(cdpBaseUrl, headers) {
307
+ const endpoint = new URL("/json/protocol", cdpBaseUrl).toString();
308
+ return getCachedSpec(urlSpecCache, getSpecCacheKey(endpoint, headers), async () => {
309
+ const response = await fetch(endpoint, { headers });
310
+ if (!response.ok) throw new Error(`Failed to fetch CDP spec from ${endpoint}: ${response.status}`);
311
+ return await response.json();
312
+ });
313
+ }
314
+ async function fetchCdpSpecFromBrowser(browser) {
315
+ return getCachedSpec(bindingSpecCache, browser, async () => {
316
+ const createResponse = await browser.fetch("https://localhost/v1/devtools/browser", { method: "POST" });
317
+ if (!createResponse.ok) throw new Error(`Failed to create Browser Rendering session for protocol fetch: ${createResponse.status}`);
318
+ const sessionId = (await createResponse.json()).sessionId;
319
+ if (!sessionId) throw new Error("Browser Rendering session response did not include a sessionId");
320
+ try {
321
+ const response = await browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}/json/protocol`);
322
+ if (!response.ok) throw new Error(`Failed to fetch CDP spec from Browser Rendering: ${response.status}`);
323
+ return await response.json();
324
+ } finally {
325
+ try {
326
+ await browser.fetch(`https://localhost/v1/devtools/browser/${sessionId}`, { method: "DELETE" });
327
+ } catch {}
328
+ }
329
+ });
330
+ }
331
+ /** Load the (cached) searchable CDP spec for a browser source. */
332
+ async function loadCdpSpec(source) {
333
+ if (source.cdpUrl) return fetchCdpSpecFromUrl(source.cdpUrl, source.cdpHeaders);
334
+ if (source.browser) return fetchCdpSpecFromBrowser(source.browser);
335
+ throw new Error(MISSING_BROWSER_CONFIG);
336
+ }
337
+ //#endregion
338
+ //#region src/browser/session-manager.ts
339
+ var _DurableBrowserSessionStore_brand = /* @__PURE__ */ new WeakSet();
340
+ var DurableBrowserSessionStore = class {
341
+ constructor(storage) {
342
+ this.storage = storage;
343
+ _classPrivateMethodInitSpec(this, _DurableBrowserSessionStore_brand);
344
+ }
345
+ async acquireLock(key) {
346
+ let queues = _queues._.get(this.storage);
347
+ if (!queues) {
348
+ queues = /* @__PURE__ */ new Map();
349
+ _queues._.set(this.storage, queues);
350
+ }
351
+ const previous = queues.get(key) ?? Promise.resolve();
352
+ let releaseQueue = () => void 0;
353
+ const current = previous.then(() => new Promise((resolve) => {
354
+ releaseQueue = resolve;
355
+ }));
356
+ queues.set(key, current);
357
+ await previous;
358
+ let released = false;
359
+ return { release: () => {
360
+ if (released) return;
361
+ released = true;
362
+ if (queues.get(key) === current) queues.delete(key);
363
+ releaseQueue();
364
+ } };
365
+ }
366
+ async get(key) {
367
+ return this.storage.get(_assertClassBrand(_DurableBrowserSessionStore_brand, this, _storageKey).call(this, key));
368
+ }
369
+ async set(key, session) {
370
+ await this.storage.put(_assertClassBrand(_DurableBrowserSessionStore_brand, this, _storageKey).call(this, key), session);
371
+ }
372
+ async delete(key) {
373
+ await this.storage.delete(_assertClassBrand(_DurableBrowserSessionStore_brand, this, _storageKey).call(this, key));
374
+ }
375
+ async list(prefix) {
376
+ const storagePrefix = _assertClassBrand(_DurableBrowserSessionStore_brand, this, _storageKey).call(this, prefix);
377
+ const entries = await this.storage.list({ prefix: storagePrefix });
378
+ const result = /* @__PURE__ */ new Map();
379
+ for (const [storageKey, value] of entries) result.set(storageKey.slice(16), value);
380
+ return result;
381
+ }
382
+ };
383
+ function _storageKey(key) {
384
+ return `browser-session:${key}`;
385
+ }
386
+ var _queues = { _: /* @__PURE__ */ new WeakMap() };
387
+ /**
388
+ * Default idle window used by {@link BrowserConnector.sweep} for the shared
389
+ * (reuse/promoted) session entry.
390
+ */
391
+ const DEFAULT_SWEEP_IDLE_MS = 600 * 1e3;
392
+ //#endregion
393
+ //#region src/browser/connector.ts
394
+ const EXEC_KEY_PREFIX = "cdp:exec:";
395
+ const REUSE_KEY_PREFIX = "cdp:reuse:";
396
+ /**
397
+ * Default idle window before {@link BrowserConnector.sweep} reclaims a
398
+ * per-execution session. Matches the codemode runtime's default paused TTL
399
+ * (24h): an execution paused for approval keeps its browser until the run
400
+ * itself is expired.
401
+ */
402
+ const DEFAULT_EXEC_SWEEP_IDLE_MS = 1440 * 60 * 1e3;
403
+ /**
404
+ * Minimum interval between `updatedAt` bumps on a per-execution store entry.
405
+ * Touching on every CDP call would write-amplify; once a minute is enough to
406
+ * keep an active or recently-resumed execution out of sweep range.
407
+ */
408
+ const EXEC_TOUCH_INTERVAL_MS = 60 * 1e3;
409
+ function isMissingBrowserSession(error) {
410
+ return error instanceof BrowserRenderingError && error.status === 404;
411
+ }
412
+ /**
413
+ * `cdp.attachToTarget` returns `{ sessionId: "target:<targetId>" }` — a stable
414
+ * handle instead of the raw CDP session id. (The object shape mirrors the real
415
+ * `Target.attachToTarget` response, which is what models reach for.) Raw ids
416
+ * are connection-scoped: a run that pauses
417
+ * for approval resumes on a fresh WebSocket where the old id is invalid, and
418
+ * the durable replay log would otherwise pin the stale value. The handle is a
419
+ * pure function of the target, so replayed code computes identical arguments,
420
+ * and `send` resolves it to a live session id on the current socket —
421
+ * re-attaching lazily after a reconnect.
422
+ */
423
+ const ATTACH_HANDLE_PREFIX = "target:";
424
+ var _options = /* @__PURE__ */ new WeakMap();
425
+ var _sockets = /* @__PURE__ */ new WeakMap();
426
+ var _connecting = /* @__PURE__ */ new WeakMap();
427
+ var _BrowserConnector_brand = /* @__PURE__ */ new WeakSet();
428
+ /**
429
+ * Codemode connector exposing a live browser over the Chrome DevTools
430
+ * Protocol as the `cdp` global.
431
+ *
432
+ * Per-execution resources are keyed by the codemode `executionId`:
433
+ *
434
+ * - The Browser Run session id is stored durably under `cdp:exec:<id>`, so a
435
+ * run that pauses for approval reconnects to the *same* browser when it
436
+ * resumes — even on a fresh instance.
437
+ * - The CDP WebSocket is per-pass: `onPassEnd` disconnects it (a paused run
438
+ * holds no socket), and the next pass reconnects from the stored id.
439
+ * - `disposeExecution` deletes the session unless it was promoted to the
440
+ * shared slot via `cdp.startSession()` (dynamic mode).
441
+ *
442
+ * Locks on the session store are held only around store reads/writes, never
443
+ * across network calls to Browser Run or while a socket is open.
444
+ */
445
+ var BrowserConnector = class extends CodemodeConnector {
446
+ constructor(ctx, options) {
447
+ super(ctx, {});
448
+ _classPrivateMethodInitSpec(this, _BrowserConnector_brand);
449
+ _classPrivateFieldInitSpec(this, _options, void 0);
450
+ _classPrivateFieldInitSpec(this, _sockets, /* @__PURE__ */ new Map());
451
+ _classPrivateFieldInitSpec(this, _connecting, /* @__PURE__ */ new Map());
452
+ if (!options.cdpUrl && !options.browser) throw new Error("BrowserConnector requires either 'browser' (Fetcher binding) or 'cdpUrl'");
453
+ if (options.browser && !options.store) throw new Error("BrowserConnector requires 'store' when using the Browser Rendering binding");
454
+ _classPrivateFieldSet2(_options, this, options);
455
+ }
456
+ name() {
457
+ return "cdp";
458
+ }
459
+ instructions() {
460
+ const mode = _assertClassBrand(_BrowserConnector_brand, this, _mode).call(this);
461
+ const lines = [
462
+ "Issue CDP calls sequentially — never in parallel (no Promise.all): call order is recorded for durable replay.",
463
+ "Browser-/Target-scoped commands (Target.createTarget, Target.getTargets) need no sessionId. Page-scoped commands (Page.navigate, Runtime.evaluate) require one: `const { sessionId } = await cdp.attachToTarget({ targetId });` then pass it to every page-scoped send.",
464
+ "Write large outputs (screenshots, page dumps) to a file or workspace immediately and pass around small references — large return values fail to record.",
465
+ "Use cdp.spec() to discover commands, events, and types when unsure.",
466
+ "If a command fails or times out, check cdp.getDebugLog() for recent protocol traffic."
467
+ ];
468
+ if (mode === "one-shot") lines.push("The browser session lasts for this execution only and is closed when it ends.");
469
+ else if (mode === "reuse") lines.push("The browser session is shared and persists across executions — tabs and state you leave behind will still be there next time.");
470
+ else lines.push("The browser session is one-shot by default. If browser state must persist after this execution (e.g. a logged-in page), call cdp.startSession() to keep it alive for later executions.");
471
+ return lines.join("\n");
472
+ }
473
+ tools() {
474
+ const tools = {
475
+ send: {
476
+ description: "Send a CDP command and return its result. Page-scoped commands require a sessionId — pass the handle returned by attachToTarget.",
477
+ inputSchema: {
478
+ type: "object",
479
+ properties: {
480
+ method: {
481
+ type: "string",
482
+ description: "CDP method, e.g. \"Target.createTarget\""
483
+ },
484
+ params: {
485
+ type: "object",
486
+ description: "CDP command parameters"
487
+ },
488
+ sessionId: {
489
+ type: "string",
490
+ description: "Session handle from attachToTarget, for page-scoped commands"
491
+ },
492
+ timeoutMs: {
493
+ type: "number",
494
+ description: "Per-command timeout override in milliseconds"
495
+ }
496
+ },
497
+ required: ["method"]
498
+ },
499
+ execute: async (args, ctx) => {
500
+ const { method, params, sessionId, timeoutMs } = args;
501
+ const executionId = _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx);
502
+ const socket = await _assertClassBrand(_BrowserConnector_brand, this, _socket).call(this, executionId);
503
+ const resolved = await _assertClassBrand(_BrowserConnector_brand, this, _resolveSessionHandle).call(this, executionId, sessionId);
504
+ try {
505
+ return await socket.send(method, params, {
506
+ sessionId: resolved,
507
+ timeoutMs
508
+ });
509
+ } catch (err) {
510
+ if (err instanceof Error && /-32601|wasn't found/.test(err.message)) {
511
+ if (await _assertClassBrand(_BrowserConnector_brand, this, _isSpecEvent).call(this, method)) throw new Error(`${err.message}. '${method}' is a CDP *event*, not a command — it cannot be called via cdp.send. To wait for page state, poll instead (e.g. Runtime.evaluate of document.readyState until "complete").`);
512
+ if (!sessionId) throw new Error(`${err.message}. Page-scoped commands need a sessionId: const { sessionId } = await cdp.attachToTarget({ targetId }); then pass sessionId to cdp.send.`);
513
+ }
514
+ throw err;
515
+ }
516
+ }
517
+ },
518
+ attachToTarget: {
519
+ description: "Attach to a target (tab) and return { sessionId } — a stable session handle to pass as sessionId in page-scoped send calls. The handle stays valid across pauses/resumes.",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ targetId: {
524
+ type: "string",
525
+ description: "Target id from Target.createTarget/getTargets"
526
+ },
527
+ timeoutMs: { type: "number" }
528
+ },
529
+ required: ["targetId"]
530
+ },
531
+ outputSchema: {
532
+ type: "object",
533
+ properties: { sessionId: {
534
+ type: "string",
535
+ description: "Stable session handle for page-scoped send calls (valid across pauses/resumes)"
536
+ } },
537
+ required: ["sessionId"]
538
+ },
539
+ execute: async (args, ctx) => {
540
+ const { targetId, timeoutMs } = args;
541
+ const executionId = _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx);
542
+ await _assertClassBrand(_BrowserConnector_brand, this, _attach).call(this, executionId, targetId, timeoutMs);
543
+ return { sessionId: `${ATTACH_HANDLE_PREFIX}${targetId}` };
544
+ }
545
+ },
546
+ spec: {
547
+ description: "Return the searchable Chrome DevTools Protocol spec: domains with their commands, events, and types. Use it to discover method names and capabilities.",
548
+ replay: "reexecute",
549
+ inputSchema: {
550
+ type: "object",
551
+ properties: {}
552
+ },
553
+ execute: async () => loadCdpSpec(_classPrivateFieldGet2(_options, this))
554
+ },
555
+ getDebugLog: {
556
+ description: "Return recent CDP protocol traffic (sends, receives, warnings) for this execution's connection — useful to diagnose failures and timeouts.",
557
+ replay: "reexecute",
558
+ inputSchema: {
559
+ type: "object",
560
+ properties: { limit: {
561
+ type: "number",
562
+ description: "Max entries to return (default 50)"
563
+ } }
564
+ },
565
+ execute: async (args, ctx) => {
566
+ const { limit } = args ?? {};
567
+ return (await _assertClassBrand(_BrowserConnector_brand, this, _socket).call(this, _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx))).getDebugLog(limit);
568
+ }
569
+ },
570
+ clearDebugLog: {
571
+ description: "Clear the CDP debug log for this execution's connection.",
572
+ inputSchema: {
573
+ type: "object",
574
+ properties: {}
575
+ },
576
+ execute: async (_args, ctx) => {
577
+ (await _assertClassBrand(_BrowserConnector_brand, this, _socket).call(this, _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx))).clearDebugLog();
578
+ return null;
579
+ }
580
+ }
581
+ };
582
+ const mode = _assertClassBrand(_BrowserConnector_brand, this, _mode).call(this);
583
+ if (mode === "reuse" || mode === "dynamic") {
584
+ tools.startSession = {
585
+ description: mode === "dynamic" ? "Promote the current browser session into the shared slot so it persists after this execution. Later executions reuse it. Returns the session info." : "Ensure the shared browser session exists and return its info.",
586
+ inputSchema: {
587
+ type: "object",
588
+ properties: {}
589
+ },
590
+ execute: async (_args, ctx) => _assertClassBrand(_BrowserConnector_brand, this, _startSession).call(this, _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx))
591
+ };
592
+ tools.sessionInfo = {
593
+ description: "Return info about the shared browser session (id and open targets), or null when none exists.",
594
+ replay: "reexecute",
595
+ inputSchema: {
596
+ type: "object",
597
+ properties: {}
598
+ },
599
+ execute: async () => await this.sessionInfo() ?? null
600
+ };
601
+ tools.closeSession = {
602
+ description: "Close the shared browser session, discarding its tabs and state.",
603
+ inputSchema: {
604
+ type: "object",
605
+ properties: {}
606
+ },
607
+ execute: async (_args, ctx) => {
608
+ await _assertClassBrand(_BrowserConnector_brand, this, _closeReusableFor).call(this, _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx));
609
+ return null;
610
+ }
611
+ };
612
+ tools.resetSession = {
613
+ description: "Close the shared browser session and start a fresh one. Returns the new session info.",
614
+ inputSchema: {
615
+ type: "object",
616
+ properties: {}
617
+ },
618
+ execute: async (_args, ctx) => _assertClassBrand(_BrowserConnector_brand, this, _resetSession).call(this, _assertClassBrand(_BrowserConnector_brand, this, _executionId).call(this, ctx))
619
+ };
620
+ }
621
+ return tools;
622
+ }
623
+ /**
624
+ * A pass is over (completed, errored, or paused awaiting approval) — drop
625
+ * the CDP socket. The Browser Run session itself stays alive; a resume
626
+ * reconnects from the durably stored session id.
627
+ */
628
+ async onPassEnd(executionId, _status) {
629
+ _assertClassBrand(_BrowserConnector_brand, this, _dropSocket).call(this, executionId);
630
+ }
631
+ /**
632
+ * The execution is terminal — delete its Browser Run session unless it was
633
+ * promoted to the shared slot via `cdp.startSession()`.
634
+ */
635
+ async disposeExecution(executionId, _status) {
636
+ _assertClassBrand(_BrowserConnector_brand, this, _dropSocket).call(this, executionId);
637
+ if (!_classPrivateFieldGet2(_options, this).browser) return;
638
+ const store = _classPrivateFieldGet2(_options, this).store;
639
+ const execKey = _assertClassBrand(_BrowserConnector_brand, this, _execKey).call(this, executionId);
640
+ const lock = await store.acquireLock(execKey);
641
+ let toClose;
642
+ try {
643
+ const stored = await store.get(execKey);
644
+ if (!stored) return;
645
+ let promoted = false;
646
+ if (_assertClassBrand(_BrowserConnector_brand, this, _mode).call(this) === "dynamic") promoted = (await store.get(_assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this)))?.sessionId === stored.sessionId;
647
+ if (!promoted && stored.closedAt === void 0) toClose = stored;
648
+ await store.delete(execKey);
649
+ } finally {
650
+ await lock.release();
651
+ }
652
+ if (toClose) try {
653
+ await deleteBrowserSession(_classPrivateFieldGet2(_options, this).browser, toClose.sessionId);
654
+ } catch (error) {
655
+ console.warn(`[agents/browser] Failed to delete Browser Run session ${toClose.sessionId} for execution ${executionId}`, error);
656
+ }
657
+ }
658
+ /** Info about the shared (reuse/promoted) session, if one exists. */
659
+ async sessionInfo() {
660
+ if (!_classPrivateFieldGet2(_options, this).browser) return void 0;
661
+ const store = _classPrivateFieldGet2(_options, this).store;
662
+ const key = _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this);
663
+ const lock = await store.acquireLock(key);
664
+ let stored;
665
+ try {
666
+ stored = await store.get(key);
667
+ } finally {
668
+ await lock.release();
669
+ }
670
+ if (!stored) return void 0;
671
+ try {
672
+ return {
673
+ sessionId: stored.sessionId,
674
+ targets: await listBrowserTargets(_classPrivateFieldGet2(_options, this).browser, stored.sessionId)
675
+ };
676
+ } catch (error) {
677
+ if (isMissingBrowserSession(error)) {
678
+ await _assertClassBrand(_BrowserConnector_brand, this, _deleteStoredEntry).call(this, key, stored.sessionId);
679
+ return;
680
+ }
681
+ throw error;
682
+ }
683
+ }
684
+ /** Close the shared (reuse/promoted) session, if one exists. */
685
+ async closeSession() {
686
+ if (!_classPrivateFieldGet2(_options, this).browser) return;
687
+ await _assertClassBrand(_BrowserConnector_brand, this, _closeStoredSession).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this));
688
+ }
689
+ /**
690
+ * Close stored sessions (shared and per-execution) idle past the threshold.
691
+ * Per-execution entries normally die with `disposeExecution` (or the
692
+ * codemode runtime's `expirePaused`); the sweep is the backstop for crashed
693
+ * hosts. Call it from a recurring alarm/scheduled task.
694
+ *
695
+ * Active executions bump their entry's `updatedAt` on use, so only runs
696
+ * idle past `maxExecIdleMs` (default 24h) are reclaimed. A swept
697
+ * per-execution entry is kept as a tombstone (`closedAt`) rather than
698
+ * deleted, so a later resume fails with a clear "session expired" error
699
+ * instead of silently continuing in a fresh browser; tombstones are
700
+ * deleted once they age past the threshold again.
701
+ */
702
+ async sweep(options) {
703
+ if (!_classPrivateFieldGet2(_options, this).browser) return { swept: [] };
704
+ const store = _classPrivateFieldGet2(_options, this).store;
705
+ const maxIdleMs = options?.maxIdleMs ?? _classPrivateFieldGet2(_options, this).session?.keepAliveMs ?? 6e5;
706
+ const maxExecIdleMs = options?.maxExecIdleMs ?? 864e5;
707
+ const keys = new Set([_assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this)]);
708
+ if (store.list) for (const key of (await store.list("cdp:")).keys()) keys.add(key);
709
+ const swept = [];
710
+ for (const key of keys) {
711
+ const isExec = key.startsWith(EXEC_KEY_PREFIX);
712
+ const idleMs = isExec ? maxExecIdleMs : maxIdleMs;
713
+ const lock = await store.acquireLock(key);
714
+ let toClose;
715
+ try {
716
+ const stored = await store.get(key);
717
+ if (!stored) continue;
718
+ const now = Date.now();
719
+ if (stored.closedAt !== void 0) {
720
+ if (now - stored.closedAt >= idleMs) await store.delete(key);
721
+ continue;
722
+ }
723
+ if (now - stored.updatedAt < idleMs) continue;
724
+ if (isExec) await store.set(key, {
725
+ ...stored,
726
+ closedAt: now
727
+ });
728
+ else await store.delete(key);
729
+ toClose = stored;
730
+ } finally {
731
+ await lock.release();
732
+ }
733
+ try {
734
+ await deleteBrowserSession(_classPrivateFieldGet2(_options, this).browser, toClose.sessionId);
735
+ } catch (error) {
736
+ console.warn(`[agents/browser] Sweep failed to delete Browser Run session ${toClose.sessionId}`, error);
737
+ }
738
+ swept.push({
739
+ key,
740
+ sessionId: toClose.sessionId
741
+ });
742
+ }
743
+ return { swept };
744
+ }
745
+ };
746
+ function _mode() {
747
+ return _classPrivateFieldGet2(_options, this).session?.mode ?? "one-shot";
748
+ }
749
+ function _execKey(executionId) {
750
+ return `${EXEC_KEY_PREFIX}${executionId}`;
751
+ }
752
+ function _reuseKey() {
753
+ return `${REUSE_KEY_PREFIX}${_classPrivateFieldGet2(_options, this).session?.key ?? "default"}`;
754
+ }
755
+ function _executionId(ctx) {
756
+ if (!ctx?.executionId) throw new Error("BrowserConnector requires an execution context — use it through createCodemodeRuntime");
757
+ return ctx.executionId;
758
+ }
759
+ function _dropSocket(executionId) {
760
+ const cached = _classPrivateFieldGet2(_sockets, this).get(executionId);
761
+ if (!cached) return;
762
+ _classPrivateFieldGet2(_sockets, this).delete(executionId);
763
+ cached.session.disconnect();
764
+ }
765
+ /** Attach the current socket to a target, caching the live CDP session id. */
766
+ async function _attach(executionId, targetId, timeoutMs) {
767
+ const socket = await _assertClassBrand(_BrowserConnector_brand, this, _socket).call(this, executionId);
768
+ const cached = _classPrivateFieldGet2(_sockets, this).get(executionId);
769
+ const handle = `${ATTACH_HANDLE_PREFIX}${targetId}`;
770
+ const existing = cached?.attached.get(handle);
771
+ if (existing) return existing;
772
+ const live = await socket.attachToTarget(targetId, { timeoutMs });
773
+ cached?.attached.set(handle, live);
774
+ return live;
775
+ }
776
+ /**
777
+ * Resolve a model-facing session handle to the live CDP session id on the
778
+ * current socket, re-attaching lazily after a reconnect. Raw CDP session
779
+ * ids (from manual Target.attachToTarget sends) pass through untouched.
780
+ */
781
+ /**
782
+ * True when `method` is a CDP *event* (e.g. `Page.loadEventFired`) rather
783
+ * than a command. Used only on the `send` failure path to produce a better
784
+ * error; any spec-loading failure just means no extra hint.
785
+ */
786
+ async function _isSpecEvent(method) {
787
+ try {
788
+ const spec = await loadCdpSpec(_classPrivateFieldGet2(_options, this));
789
+ const domain = method.split(".")[0];
790
+ return spec.domains.some((d) => d.name === domain && d.events.some((e) => e.event === method));
791
+ } catch {
792
+ return false;
793
+ }
794
+ }
795
+ async function _resolveSessionHandle(executionId, sessionId) {
796
+ if (!sessionId?.startsWith(ATTACH_HANDLE_PREFIX)) return sessionId;
797
+ const targetId = sessionId.slice(7);
798
+ return _assertClassBrand(_BrowserConnector_brand, this, _attach).call(this, executionId, targetId);
799
+ }
800
+ /**
801
+ * Get or open the CDP socket for an execution. Concurrent calls for the
802
+ * same execution (model code that ignores the "sequential calls" rule and
803
+ * uses Promise.all) share one in-flight connect instead of racing and
804
+ * leaking the loser's WebSocket.
805
+ */
806
+ function _socket(executionId) {
807
+ const inFlight = _classPrivateFieldGet2(_connecting, this).get(executionId);
808
+ if (inFlight) return inFlight;
809
+ const promise = _assertClassBrand(_BrowserConnector_brand, this, _socketInner).call(this, executionId).finally(() => {
810
+ if (_classPrivateFieldGet2(_connecting, this).get(executionId) === promise) _classPrivateFieldGet2(_connecting, this).delete(executionId);
811
+ });
812
+ _classPrivateFieldGet2(_connecting, this).set(executionId, promise);
813
+ return promise;
814
+ }
815
+ async function _socketInner(executionId) {
816
+ if (_classPrivateFieldGet2(_options, this).cdpUrl) {
817
+ const cached = _classPrivateFieldGet2(_sockets, this).get(executionId);
818
+ if (cached) return cached.session;
819
+ const session = await connectUrl(_classPrivateFieldGet2(_options, this).cdpUrl, {
820
+ timeoutMs: _classPrivateFieldGet2(_options, this).timeout,
821
+ headers: _classPrivateFieldGet2(_options, this).cdpHeaders
822
+ });
823
+ _classPrivateFieldGet2(_sockets, this).set(executionId, {
824
+ session,
825
+ attached: /* @__PURE__ */ new Map()
826
+ });
827
+ return session;
828
+ }
829
+ const browser = _classPrivateFieldGet2(_options, this).browser;
830
+ if (!browser) throw new Error("BrowserConnector has no browser binding");
831
+ const stored = await _assertClassBrand(_BrowserConnector_brand, this, _resolveSession).call(this, executionId);
832
+ const cached = _classPrivateFieldGet2(_sockets, this).get(executionId);
833
+ if (cached?.browserSessionId === stored.sessionId) return cached.session;
834
+ if (cached) _assertClassBrand(_BrowserConnector_brand, this, _dropSocket).call(this, executionId);
835
+ const session = await connectBrowserSession(browser, stored.sessionId, _classPrivateFieldGet2(_options, this).timeout);
836
+ _classPrivateFieldGet2(_sockets, this).set(executionId, {
837
+ session,
838
+ browserSessionId: stored.sessionId,
839
+ attached: /* @__PURE__ */ new Map()
840
+ });
841
+ return session;
842
+ }
843
+ /**
844
+ * Resolve the Browser Run session for an execution:
845
+ *
846
+ * - An existing `cdp:exec:<id>` entry wins. If its session is gone (e.g.
847
+ * expired while the run was paused), the run fails with a clear error
848
+ * rather than silently continuing in a fresh browser.
849
+ * - In `reuse` mode the shared session is used (created if missing).
850
+ * - In `dynamic` mode an alive shared session is used; otherwise a fresh
851
+ * per-execution session is created.
852
+ * - In `one-shot` mode a fresh per-execution session is created.
853
+ */
854
+ async function _resolveSession(executionId) {
855
+ if (!_classPrivateFieldGet2(_options, this).browser) throw new Error("BrowserConnector has no browser binding");
856
+ const mode = _assertClassBrand(_BrowserConnector_brand, this, _mode).call(this);
857
+ const execKey = _assertClassBrand(_BrowserConnector_brand, this, _execKey).call(this, executionId);
858
+ if (mode === "reuse") return _assertClassBrand(_BrowserConnector_brand, this, _ensureStoredSession).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this));
859
+ const existing = await _assertClassBrand(_BrowserConnector_brand, this, _readStored).call(this, execKey);
860
+ if (existing) {
861
+ if (existing.closedAt === void 0 && await _assertClassBrand(_BrowserConnector_brand, this, _isAlive).call(this, existing)) {
862
+ if (Date.now() - existing.updatedAt >= EXEC_TOUCH_INTERVAL_MS) await _assertClassBrand(_BrowserConnector_brand, this, _touchStored).call(this, execKey, existing);
863
+ return existing;
864
+ }
865
+ await _assertClassBrand(_BrowserConnector_brand, this, _deleteStoredEntry).call(this, execKey, existing.sessionId);
866
+ throw new Error(`Browser session ${existing.sessionId} expired or was swept while this execution was paused — the run cannot continue. Start a new execution.`);
867
+ }
868
+ if (mode === "dynamic") {
869
+ const shared = await _assertClassBrand(_BrowserConnector_brand, this, _readStored).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this));
870
+ if (shared) {
871
+ if (await _assertClassBrand(_BrowserConnector_brand, this, _isAlive).call(this, shared)) {
872
+ await _assertClassBrand(_BrowserConnector_brand, this, _touchStored).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this), shared);
873
+ return shared;
874
+ }
875
+ await _assertClassBrand(_BrowserConnector_brand, this, _deleteStoredEntry).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this), shared.sessionId);
876
+ }
877
+ }
878
+ return _assertClassBrand(_BrowserConnector_brand, this, _createAndCommit).call(this, execKey);
879
+ }
880
+ /**
881
+ * Get the stored session under `key`, validating and creating as needed.
882
+ *
883
+ * Network calls (the liveness probe, session creation) happen OUTSIDE the
884
+ * store lock — locks wrap storage only, so a hung Browser Rendering call
885
+ * can't serialize every other operation on this key. The lock is
886
+ * re-acquired to commit, with a sessionId re-check to detect a concurrent
887
+ * swap; on a swap the new entry is re-validated from the top.
888
+ */
889
+ async function _ensureStoredSession(key) {
890
+ if (!_classPrivateFieldGet2(_options, this).browser) throw new Error("BrowserConnector has no browser binding");
891
+ const store = _get_store.call(_assertClassBrand(_BrowserConnector_brand, this));
892
+ for (let attempt = 0; attempt < 3; attempt++) {
893
+ const existing = await store.get(key);
894
+ if (!existing) return _assertClassBrand(_BrowserConnector_brand, this, _createAndCommit).call(this, key);
895
+ const alive = await _assertClassBrand(_BrowserConnector_brand, this, _isAlive).call(this, existing);
896
+ const lock = await store.acquireLock(key);
897
+ try {
898
+ const current = await store.get(key);
899
+ if (current?.sessionId !== existing.sessionId) continue;
900
+ if (alive) {
901
+ const refreshed = {
902
+ ...current,
903
+ updatedAt: Date.now()
904
+ };
905
+ await store.set(key, refreshed);
906
+ return refreshed;
907
+ }
908
+ await store.delete(key);
909
+ } finally {
910
+ await lock.release();
911
+ }
912
+ }
913
+ throw new Error(`Browser session entry ${key} kept changing concurrently — retry`);
914
+ }
915
+ /**
916
+ * Create a Browser Run session (outside any lock) and commit it under
917
+ * `key`. If a concurrent caller committed first, their entry wins and the
918
+ * redundant session is deleted best-effort.
919
+ */
920
+ async function _createAndCommit(key) {
921
+ const browser = _classPrivateFieldGet2(_options, this).browser;
922
+ if (!browser) throw new Error("BrowserConnector has no browser binding");
923
+ const store = _get_store.call(_assertClassBrand(_BrowserConnector_brand, this));
924
+ const info = await createBrowserSession(browser, { keepAliveMs: _classPrivateFieldGet2(_options, this).session?.keepAliveMs });
925
+ const now = Date.now();
926
+ const stored = {
927
+ sessionId: info.sessionId,
928
+ createdAt: now,
929
+ updatedAt: now
930
+ };
931
+ const lock = await store.acquireLock(key);
932
+ let winner;
933
+ try {
934
+ const raced = await store.get(key);
935
+ if (raced) winner = raced;
936
+ else await store.set(key, stored);
937
+ } finally {
938
+ await lock.release();
939
+ }
940
+ if (winner) {
941
+ try {
942
+ await deleteBrowserSession(browser, stored.sessionId);
943
+ } catch (error) {
944
+ console.warn(`[agents/browser] Failed to delete redundant Browser Run session ${stored.sessionId}`, error);
945
+ }
946
+ return winner;
947
+ }
948
+ return stored;
949
+ }
950
+ async function _isAlive(stored) {
951
+ const browser = _classPrivateFieldGet2(_options, this).browser;
952
+ if (!browser) return false;
953
+ try {
954
+ await listBrowserTargets(browser, stored.sessionId);
955
+ return true;
956
+ } catch (error) {
957
+ if (isMissingBrowserSession(error)) return false;
958
+ throw error;
959
+ }
960
+ }
961
+ async function _startSession(executionId) {
962
+ const browser = _classPrivateFieldGet2(_options, this).browser;
963
+ if (!browser) throw new Error("startSession requires the Browser Rendering binding");
964
+ const store = _classPrivateFieldGet2(_options, this).store;
965
+ const reuseKey = _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this);
966
+ if (_assertClassBrand(_BrowserConnector_brand, this, _mode).call(this) === "dynamic") {
967
+ const exec = await _assertClassBrand(_BrowserConnector_brand, this, _readStored).call(this, _assertClassBrand(_BrowserConnector_brand, this, _execKey).call(this, executionId));
968
+ if (exec && exec.closedAt === void 0) {
969
+ const lock = await store.acquireLock(reuseKey);
970
+ let replaced;
971
+ try {
972
+ const shared = await store.get(reuseKey);
973
+ if (shared?.sessionId !== exec.sessionId) {
974
+ replaced = shared;
975
+ await store.set(reuseKey, {
976
+ ...exec,
977
+ updatedAt: Date.now()
978
+ });
979
+ }
980
+ } finally {
981
+ await lock.release();
982
+ }
983
+ if (replaced) try {
984
+ await deleteBrowserSession(browser, replaced.sessionId);
985
+ } catch (error) {
986
+ console.warn(`[agents/browser] Failed to delete replaced Browser Run session ${replaced.sessionId}`, error);
987
+ }
988
+ return {
989
+ sessionId: exec.sessionId,
990
+ targets: await listBrowserTargets(browser, exec.sessionId)
991
+ };
992
+ }
993
+ }
994
+ const stored = await _assertClassBrand(_BrowserConnector_brand, this, _ensureStoredSession).call(this, reuseKey);
995
+ return {
996
+ sessionId: stored.sessionId,
997
+ targets: await listBrowserTargets(browser, stored.sessionId)
998
+ };
999
+ }
1000
+ async function _resetSession(executionId) {
1001
+ const browser = _classPrivateFieldGet2(_options, this).browser;
1002
+ if (!browser) throw new Error("resetSession requires the Browser Rendering binding");
1003
+ await _assertClassBrand(_BrowserConnector_brand, this, _closeReusableFor).call(this, executionId);
1004
+ const stored = await _assertClassBrand(_BrowserConnector_brand, this, _ensureStoredSession).call(this, _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this));
1005
+ return {
1006
+ sessionId: stored.sessionId,
1007
+ targets: await listBrowserTargets(browser, stored.sessionId)
1008
+ };
1009
+ }
1010
+ /**
1011
+ * Close the shared session from inside an execution. If this execution's
1012
+ * socket is attached to that session, drop it first.
1013
+ */
1014
+ async function _closeReusableFor(executionId) {
1015
+ const reuseKey = _assertClassBrand(_BrowserConnector_brand, this, _reuseKey).call(this);
1016
+ const stored = await _assertClassBrand(_BrowserConnector_brand, this, _readStored).call(this, reuseKey);
1017
+ if (!stored) return;
1018
+ if (_classPrivateFieldGet2(_sockets, this).get(executionId)?.browserSessionId === stored.sessionId) _assertClassBrand(_BrowserConnector_brand, this, _dropSocket).call(this, executionId);
1019
+ await _assertClassBrand(_BrowserConnector_brand, this, _closeStoredSession).call(this, reuseKey);
1020
+ const exec = await _assertClassBrand(_BrowserConnector_brand, this, _readStored).call(this, _assertClassBrand(_BrowserConnector_brand, this, _execKey).call(this, executionId));
1021
+ if (exec?.sessionId === stored.sessionId) await _assertClassBrand(_BrowserConnector_brand, this, _deleteStoredEntry).call(this, _assertClassBrand(_BrowserConnector_brand, this, _execKey).call(this, executionId), exec.sessionId);
1022
+ }
1023
+ function _get_store() {
1024
+ const store = _classPrivateFieldGet2(_options, this).store;
1025
+ if (!store) throw new Error("BrowserConnector session storage requires the Browser Rendering binding");
1026
+ return store;
1027
+ }
1028
+ async function _readStored(key) {
1029
+ const store = _get_store.call(_assertClassBrand(_BrowserConnector_brand, this));
1030
+ const lock = await store.acquireLock(key);
1031
+ try {
1032
+ return await store.get(key);
1033
+ } finally {
1034
+ await lock.release();
1035
+ }
1036
+ }
1037
+ async function _writeStored(key, value) {
1038
+ const store = _get_store.call(_assertClassBrand(_BrowserConnector_brand, this));
1039
+ const lock = await store.acquireLock(key);
1040
+ try {
1041
+ await store.set(key, value);
1042
+ } finally {
1043
+ await lock.release();
1044
+ }
1045
+ }
1046
+ async function _touchStored(key, value) {
1047
+ await _assertClassBrand(_BrowserConnector_brand, this, _writeStored).call(this, key, {
1048
+ ...value,
1049
+ updatedAt: Date.now()
1050
+ });
1051
+ }
1052
+ /** Delete the store entry only if it still points at `sessionId`. */
1053
+ async function _deleteStoredEntry(key, sessionId) {
1054
+ const store = _get_store.call(_assertClassBrand(_BrowserConnector_brand, this));
1055
+ const lock = await store.acquireLock(key);
1056
+ try {
1057
+ if ((await store.get(key))?.sessionId === sessionId) await store.delete(key);
1058
+ } finally {
1059
+ await lock.release();
1060
+ }
1061
+ }
1062
+ /** Delete the stored entry under `key` and its Browser Run session. */
1063
+ async function _closeStoredSession(key) {
1064
+ const browser = _classPrivateFieldGet2(_options, this).browser;
1065
+ if (!browser) return;
1066
+ const store = _classPrivateFieldGet2(_options, this).store;
1067
+ const lock = await store.acquireLock(key);
1068
+ let stored;
1069
+ try {
1070
+ stored = await store.get(key);
1071
+ if (stored) await store.delete(key);
1072
+ } finally {
1073
+ await lock.release();
1074
+ }
1075
+ if (stored) await deleteBrowserSession(browser, stored.sessionId);
1076
+ }
1077
+ //#endregion
1078
+ export { loadCdpSpec as a, connectBrowserSession as c, listBrowserTargets as d, CdpSession as f, DurableBrowserSessionStore as i, createBrowserSession as l, DEFAULT_EXEC_SWEEP_IDLE_MS as n, BrowserRenderingError as o, connectUrl as p, DEFAULT_SWEEP_IDLE_MS as r, connectBrowser as s, BrowserConnector as t, deleteBrowserSession as u };
1079
+
1080
+ //# sourceMappingURL=connector-D6yYzYHg.js.map