@btraut/browser-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js ADDED
@@ -0,0 +1,4646 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // packages/cli/src/api.ts
31
+ var api_exports = {};
32
+ __export(api_exports, {
33
+ startCoreServer: () => startCoreServer,
34
+ startMcpServer: () => startMcpServer
35
+ });
36
+ module.exports = __toCommonJS(api_exports);
37
+
38
+ // packages/core/src/server.ts
39
+ var import_http = require("http");
40
+ var import_express2 = __toESM(require("express"));
41
+
42
+ // packages/core/src/routes/session.ts
43
+ var import_express = require("express");
44
+
45
+ // packages/core/src/state.ts
46
+ var InvalidSessionTransition = class extends Error {
47
+ constructor(from, event) {
48
+ super(`Invalid session transition from ${from} via ${event}.`);
49
+ this.name = "InvalidSessionTransition";
50
+ this.from = from;
51
+ this.event = event;
52
+ }
53
+ };
54
+ var transitionSession = (state, event) => {
55
+ if (event === "CLOSE") {
56
+ return "CLOSED" /* CLOSED */;
57
+ }
58
+ if (event === "RECOVER_FAILED") {
59
+ if (state === "CLOSED" /* CLOSED */) {
60
+ throw new InvalidSessionTransition(state, event);
61
+ }
62
+ return "BROKEN" /* BROKEN */;
63
+ }
64
+ switch (state) {
65
+ case "INIT" /* INIT */: {
66
+ if (event === "DRIVE_CONNECTED") {
67
+ return "DRIVE_READY" /* DRIVE_READY */;
68
+ }
69
+ if (event === "INSPECT_CONNECTED") {
70
+ return "INSPECT_READY" /* INSPECT_READY */;
71
+ }
72
+ break;
73
+ }
74
+ case "DRIVE_READY" /* DRIVE_READY */: {
75
+ if (event === "INSPECT_CONNECTED") {
76
+ return "READY" /* READY */;
77
+ }
78
+ if (event === "DRIVE_DISCONNECTED") {
79
+ return "INIT" /* INIT */;
80
+ }
81
+ break;
82
+ }
83
+ case "INSPECT_READY" /* INSPECT_READY */: {
84
+ if (event === "DRIVE_CONNECTED") {
85
+ return "READY" /* READY */;
86
+ }
87
+ break;
88
+ }
89
+ case "READY" /* READY */: {
90
+ if (event === "DRIVE_DISCONNECTED") {
91
+ return "DEGRADED_DRIVE" /* DEGRADED_DRIVE */;
92
+ }
93
+ if (event === "INSPECT_DISCONNECTED") {
94
+ return "DEGRADED_INSPECT" /* DEGRADED_INSPECT */;
95
+ }
96
+ break;
97
+ }
98
+ case "DEGRADED_DRIVE" /* DEGRADED_DRIVE */: {
99
+ if (event === "RECOVER_SUCCEEDED") {
100
+ return "READY" /* READY */;
101
+ }
102
+ break;
103
+ }
104
+ case "DEGRADED_INSPECT" /* DEGRADED_INSPECT */: {
105
+ if (event === "RECOVER_SUCCEEDED") {
106
+ return "READY" /* READY */;
107
+ }
108
+ break;
109
+ }
110
+ case "BROKEN" /* BROKEN */:
111
+ case "CLOSED" /* CLOSED */:
112
+ default:
113
+ break;
114
+ }
115
+ throw new InvalidSessionTransition(state, event);
116
+ };
117
+ var shouldRetryDriveOp = (options) => {
118
+ return options.retryable && options.attempt === 0;
119
+ };
120
+
121
+ // packages/core/src/session.ts
122
+ var import_crypto = require("crypto");
123
+ var SessionError = class extends Error {
124
+ constructor(code, message) {
125
+ super(message);
126
+ this.name = "SessionError";
127
+ this.code = code;
128
+ }
129
+ };
130
+ var SessionRegistry = class {
131
+ constructor() {
132
+ this.sessions = /* @__PURE__ */ new Map();
133
+ }
134
+ create() {
135
+ const now = /* @__PURE__ */ new Date();
136
+ const id = `session-${(0, import_crypto.randomUUID)()}`;
137
+ const session = {
138
+ id,
139
+ state: "INIT" /* INIT */,
140
+ createdAt: now,
141
+ updatedAt: now
142
+ };
143
+ this.sessions.set(id, session);
144
+ return session;
145
+ }
146
+ get(sessionId) {
147
+ return this.sessions.get(sessionId);
148
+ }
149
+ list() {
150
+ return Array.from(this.sessions.values());
151
+ }
152
+ require(sessionId) {
153
+ const session = this.sessions.get(sessionId);
154
+ if (!session) {
155
+ throw new SessionError(
156
+ "SESSION_NOT_FOUND",
157
+ `Session ${sessionId} does not exist.`
158
+ );
159
+ }
160
+ return session;
161
+ }
162
+ apply(sessionId, event) {
163
+ const session = this.require(sessionId);
164
+ if (session.state === "CLOSED" /* CLOSED */ && event !== "CLOSE") {
165
+ throw new SessionError(
166
+ "SESSION_CLOSED",
167
+ `Session ${sessionId} is closed.`
168
+ );
169
+ }
170
+ try {
171
+ session.state = transitionSession(session.state, event);
172
+ } catch (error) {
173
+ if (error instanceof InvalidSessionTransition) {
174
+ throw error;
175
+ }
176
+ throw error;
177
+ }
178
+ session.updatedAt = /* @__PURE__ */ new Date();
179
+ return session;
180
+ }
181
+ recover(sessionId, outcome) {
182
+ const session = this.require(sessionId);
183
+ if (session.state === "CLOSED" /* CLOSED */) {
184
+ throw new SessionError(
185
+ "SESSION_CLOSED",
186
+ `Session ${sessionId} is closed.`
187
+ );
188
+ }
189
+ let recovered = false;
190
+ let message;
191
+ if (session.state === "DEGRADED_DRIVE" /* DEGRADED_DRIVE */ || session.state === "DEGRADED_INSPECT" /* DEGRADED_INSPECT */) {
192
+ if (outcome?.recovered) {
193
+ session.state = transitionSession(session.state, "RECOVER_SUCCEEDED");
194
+ recovered = true;
195
+ message = outcome.message ?? "Recovery succeeded.";
196
+ } else if (outcome) {
197
+ session.state = transitionSession(session.state, "RECOVER_FAILED");
198
+ recovered = false;
199
+ message = outcome.message ?? "Recovery failed.";
200
+ } else {
201
+ recovered = false;
202
+ message = "Recovery not attempted.";
203
+ }
204
+ } else if (session.state === "BROKEN" /* BROKEN */) {
205
+ recovered = false;
206
+ message = "Session is broken.";
207
+ } else {
208
+ recovered = false;
209
+ message = "No recovery needed.";
210
+ }
211
+ session.updatedAt = /* @__PURE__ */ new Date();
212
+ return {
213
+ sessionId: session.id,
214
+ recovered,
215
+ state: session.state,
216
+ message
217
+ };
218
+ }
219
+ close(sessionId) {
220
+ const session = this.require(sessionId);
221
+ session.state = transitionSession(session.state, "CLOSE");
222
+ session.updatedAt = /* @__PURE__ */ new Date();
223
+ return session;
224
+ }
225
+ };
226
+
227
+ // packages/core/src/routes/shared.ts
228
+ var sendError = (res, status, error) => {
229
+ res.status(status).json({ ok: false, error });
230
+ };
231
+ var sendResult = (res, result) => {
232
+ res.status(200).json({ ok: true, result });
233
+ };
234
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
235
+ var errorStatus = (code) => {
236
+ switch (code) {
237
+ case "INVALID_ARGUMENT":
238
+ return 400;
239
+ case "SESSION_NOT_FOUND":
240
+ case "TAB_NOT_FOUND":
241
+ case "LOCATOR_NOT_FOUND":
242
+ return 404;
243
+ case "SESSION_CLOSED":
244
+ case "FAILED_PRECONDITION":
245
+ case "DEBUGGER_IN_USE":
246
+ return 409;
247
+ case "ATTACH_DENIED":
248
+ return 403;
249
+ case "NOT_SUPPORTED":
250
+ case "NOT_IMPLEMENTED":
251
+ return 501;
252
+ case "EXTENSION_DISCONNECTED":
253
+ case "INSPECT_UNAVAILABLE":
254
+ return 503;
255
+ case "TIMEOUT":
256
+ return 504;
257
+ default:
258
+ return 500;
259
+ }
260
+ };
261
+ var deriveHintFromTabs = (tabs) => {
262
+ if (!Array.isArray(tabs) || tabs.length === 0) {
263
+ return void 0;
264
+ }
265
+ let best;
266
+ let bestTime = -Infinity;
267
+ for (const tab of tabs) {
268
+ const raw = tab.last_active_at;
269
+ const time = raw ? Date.parse(raw) : NaN;
270
+ const score = Number.isFinite(time) ? time : -Infinity;
271
+ if (!best || score > bestTime) {
272
+ best = tab;
273
+ bestTime = score;
274
+ }
275
+ }
276
+ if (!best) {
277
+ return void 0;
278
+ }
279
+ if (!best.url && !best.title && !best.last_active_at) {
280
+ return void 0;
281
+ }
282
+ return {
283
+ url: best.url,
284
+ title: best.title,
285
+ lastActiveAt: best.last_active_at
286
+ };
287
+ };
288
+
289
+ // packages/core/src/routes/session.ts
290
+ var readSessionId = (body) => {
291
+ if (!isRecord(body)) {
292
+ return void 0;
293
+ }
294
+ const sessionId = body.session_id;
295
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
296
+ return void 0;
297
+ }
298
+ return sessionId;
299
+ };
300
+ var createSessionRouter = (registry, options = {}) => {
301
+ const router = (0, import_express.Router)();
302
+ router.post("/create", (req, res) => {
303
+ const body = req.body;
304
+ if (body !== void 0) {
305
+ if (!isRecord(body)) {
306
+ return sendError(res, 400, {
307
+ code: "INVALID_ARGUMENT",
308
+ message: "Request body must be an object.",
309
+ retryable: false
310
+ });
311
+ }
312
+ if (Object.keys(body).length > 0) {
313
+ return sendError(res, 400, {
314
+ code: "INVALID_ARGUMENT",
315
+ message: "session.create does not accept any input fields.",
316
+ retryable: false
317
+ });
318
+ }
319
+ }
320
+ const session = registry.create();
321
+ if (options.driveConnected?.()) {
322
+ try {
323
+ registry.apply(session.id, "DRIVE_CONNECTED");
324
+ } catch {
325
+ }
326
+ }
327
+ return sendResult(res, {
328
+ session_id: session.id,
329
+ state: session.state,
330
+ created_at: session.createdAt.toISOString()
331
+ });
332
+ });
333
+ router.post("/status", (req, res) => {
334
+ const sessionId = readSessionId(req.body);
335
+ if (!sessionId) {
336
+ return sendError(res, 400, {
337
+ code: "INVALID_ARGUMENT",
338
+ message: "session_id is required",
339
+ retryable: false
340
+ });
341
+ }
342
+ try {
343
+ const session = registry.require(sessionId);
344
+ return sendResult(res, {
345
+ session_id: session.id,
346
+ state: session.state,
347
+ updated_at: session.updatedAt.toISOString()
348
+ });
349
+ } catch (error) {
350
+ if (error instanceof SessionError) {
351
+ return sendError(res, 404, {
352
+ code: error.code,
353
+ message: error.message,
354
+ retryable: false
355
+ });
356
+ }
357
+ return sendError(res, 500, {
358
+ code: "INTERNAL",
359
+ message: "Unexpected error while fetching status.",
360
+ retryable: false
361
+ });
362
+ }
363
+ });
364
+ router.post("/recover", async (req, res) => {
365
+ const sessionId = readSessionId(req.body);
366
+ if (!sessionId) {
367
+ return sendError(res, 400, {
368
+ code: "INVALID_ARGUMENT",
369
+ message: "session_id is required",
370
+ retryable: false
371
+ });
372
+ }
373
+ try {
374
+ const session = registry.require(sessionId);
375
+ let outcome;
376
+ if (session.state === "DEGRADED_DRIVE" /* DEGRADED_DRIVE */) {
377
+ const connected = options.driveConnected?.() ?? false;
378
+ outcome = {
379
+ recovered: connected,
380
+ message: connected ? "Drive recovery succeeded." : "Drive recovery failed."
381
+ };
382
+ } else if (session.state === "DEGRADED_INSPECT" /* DEGRADED_INSPECT */) {
383
+ const recovered = options.inspectRecover ? await options.inspectRecover(sessionId) : false;
384
+ outcome = {
385
+ recovered,
386
+ message: recovered ? "Inspect recovery succeeded." : "Inspect recovery failed."
387
+ };
388
+ }
389
+ const result = registry.recover(sessionId, outcome);
390
+ if (outcome) {
391
+ const attempt = {
392
+ sessionId,
393
+ recovered: result.recovered,
394
+ state: result.state,
395
+ message: result.message,
396
+ at: (/* @__PURE__ */ new Date()).toISOString()
397
+ };
398
+ options.recordRecovery?.(attempt);
399
+ const status = result.recovered ? "succeeded" : "failed";
400
+ console.info(
401
+ `[recovery] session ${sessionId} ${status}: ${result.message ?? ""}`
402
+ );
403
+ }
404
+ return sendResult(res, {
405
+ session_id: result.sessionId,
406
+ recovered: result.recovered,
407
+ state: result.state,
408
+ message: result.message
409
+ });
410
+ } catch (error) {
411
+ if (error instanceof SessionError) {
412
+ const status = error.code === "SESSION_CLOSED" ? 409 : 404;
413
+ return sendError(res, status, {
414
+ code: error.code,
415
+ message: error.message,
416
+ retryable: false
417
+ });
418
+ }
419
+ if (error instanceof InvalidSessionTransition) {
420
+ return sendError(res, 409, {
421
+ code: "FAILED_PRECONDITION",
422
+ message: error.message,
423
+ retryable: false
424
+ });
425
+ }
426
+ return sendError(res, 500, {
427
+ code: "INTERNAL",
428
+ message: "Unexpected error while recovering session.",
429
+ retryable: false
430
+ });
431
+ }
432
+ });
433
+ router.post("/close", (req, res) => {
434
+ const sessionId = readSessionId(req.body);
435
+ if (!sessionId) {
436
+ return sendError(res, 400, {
437
+ code: "INVALID_ARGUMENT",
438
+ message: "session_id is required",
439
+ retryable: false
440
+ });
441
+ }
442
+ try {
443
+ registry.close(sessionId);
444
+ return sendResult(res, { ok: true });
445
+ } catch (error) {
446
+ if (error instanceof SessionError) {
447
+ return sendError(res, 404, {
448
+ code: error.code,
449
+ message: error.message,
450
+ retryable: false
451
+ });
452
+ }
453
+ if (error instanceof InvalidSessionTransition) {
454
+ return sendError(res, 409, {
455
+ code: "FAILED_PRECONDITION",
456
+ message: error.message,
457
+ retryable: false
458
+ });
459
+ }
460
+ return sendError(res, 500, {
461
+ code: "INTERNAL",
462
+ message: "Unexpected error while closing session.",
463
+ retryable: false
464
+ });
465
+ }
466
+ });
467
+ return router;
468
+ };
469
+
470
+ // packages/core/src/inspect.ts
471
+ var import_crypto3 = require("crypto");
472
+ var import_promises2 = require("node:fs/promises");
473
+ var import_node_path2 = __toESM(require("node:path"));
474
+ var import_readability = require("@mozilla/readability");
475
+ var import_jsdom = require("jsdom");
476
+ var import_turndown = __toESM(require("turndown"));
477
+
478
+ // packages/core/src/artifacts.ts
479
+ var import_promises = require("node:fs/promises");
480
+ var import_node_os = __toESM(require("node:os"));
481
+ var import_node_path = __toESM(require("node:path"));
482
+ var ARTIFACTS_DIR_NAME = "browser-agent";
483
+ var resolveTempRoot = () => process.env.TMPDIR || process.env.TEMP || process.env.TMP || import_node_os.default.tmpdir();
484
+ var getArtifactRootDir = (sessionId) => import_node_path.default.join(resolveTempRoot(), ARTIFACTS_DIR_NAME, sessionId);
485
+ var ensureArtifactRootDir = async (sessionId) => {
486
+ const rootDir = getArtifactRootDir(sessionId);
487
+ await (0, import_promises.mkdir)(rootDir, { recursive: true });
488
+ return rootDir;
489
+ };
490
+
491
+ // packages/core/src/extension-bridge.ts
492
+ var import_crypto2 = require("crypto");
493
+ var import_ws = require("ws");
494
+ var ExtensionBridgeError = class extends Error {
495
+ constructor(code, message, retryable = false, details) {
496
+ super(message);
497
+ this.name = "ExtensionBridgeError";
498
+ this.code = code;
499
+ this.retryable = retryable;
500
+ this.details = details;
501
+ }
502
+ };
503
+ var ExtensionBridge = class {
504
+ constructor(options = {}) {
505
+ this.socket = null;
506
+ this.pending = /* @__PURE__ */ new Map();
507
+ this.connected = false;
508
+ this.tabs = [];
509
+ this.heartbeatInterval = null;
510
+ this.awaitingHeartbeat = false;
511
+ this.debuggerListeners = /* @__PURE__ */ new Set();
512
+ this.wss = new import_ws.WebSocketServer({ noServer: true });
513
+ this.path = options.path ?? "/drive";
514
+ this.registry = options.registry;
515
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
516
+ this.heartbeatTimeoutMs = options.heartbeatTimeoutMs ?? 5e3;
517
+ this.wss.on("connection", (socket) => {
518
+ this.handleConnection(socket);
519
+ });
520
+ }
521
+ attach(server) {
522
+ server.on("upgrade", (request, socket, head) => {
523
+ const url = new URL(request.url ?? "", "ws://127.0.0.1");
524
+ if (url.pathname !== this.path) {
525
+ socket.destroy();
526
+ return;
527
+ }
528
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
529
+ this.wss.emit("connection", ws, request);
530
+ });
531
+ });
532
+ }
533
+ isConnected() {
534
+ return this.connected;
535
+ }
536
+ getStatus() {
537
+ return {
538
+ connected: this.connected,
539
+ lastSeenAt: this.lastSeenAt,
540
+ tabs: this.tabs
541
+ };
542
+ }
543
+ async request(action, params, timeoutMs = 3e4) {
544
+ const response = await this.requestInternal(action, params, timeoutMs);
545
+ return response;
546
+ }
547
+ async requestDebugger(action, params, timeoutMs = 3e4) {
548
+ const response = await this.requestInternal(action, params, timeoutMs);
549
+ return response;
550
+ }
551
+ onDebuggerEvent(listener) {
552
+ this.debuggerListeners.add(listener);
553
+ return () => {
554
+ this.debuggerListeners.delete(listener);
555
+ };
556
+ }
557
+ async requestInternal(action, params, timeoutMs = 3e4) {
558
+ if (!this.socket || this.socket.readyState !== import_ws.WebSocket.OPEN) {
559
+ throw new ExtensionBridgeError(
560
+ "EXTENSION_DISCONNECTED",
561
+ "Extension is not connected.",
562
+ true
563
+ );
564
+ }
565
+ const id = (0, import_crypto2.randomUUID)();
566
+ const request = typeof action === "string" && action.startsWith("debugger.") ? {
567
+ id,
568
+ action,
569
+ status: "request",
570
+ params
571
+ } : {
572
+ id,
573
+ action,
574
+ status: "request",
575
+ params
576
+ };
577
+ const response = await new Promise((resolve, reject) => {
578
+ const timeout = setTimeout(() => {
579
+ this.pending.delete(id);
580
+ reject(
581
+ new ExtensionBridgeError(
582
+ "TIMEOUT",
583
+ `Extension request timed out after ${timeoutMs}ms.`,
584
+ true,
585
+ { action }
586
+ )
587
+ );
588
+ }, timeoutMs);
589
+ this.pending.set(id, {
590
+ resolve,
591
+ reject,
592
+ timeout
593
+ });
594
+ this.socket?.send(JSON.stringify(request));
595
+ });
596
+ return response;
597
+ }
598
+ startHeartbeat() {
599
+ if (this.heartbeatInterval) {
600
+ return;
601
+ }
602
+ this.heartbeatInterval = setInterval(() => {
603
+ void this.sendHeartbeat();
604
+ }, this.heartbeatIntervalMs);
605
+ void this.sendHeartbeat();
606
+ }
607
+ stopHeartbeat() {
608
+ if (this.heartbeatInterval) {
609
+ clearInterval(this.heartbeatInterval);
610
+ this.heartbeatInterval = null;
611
+ }
612
+ this.awaitingHeartbeat = false;
613
+ }
614
+ async sendHeartbeat() {
615
+ if (!this.socket || this.socket.readyState !== import_ws.WebSocket.OPEN) {
616
+ return;
617
+ }
618
+ if (this.awaitingHeartbeat) {
619
+ return;
620
+ }
621
+ this.awaitingHeartbeat = true;
622
+ try {
623
+ await this.requestInternal(
624
+ "drive.ping",
625
+ void 0,
626
+ this.heartbeatTimeoutMs
627
+ );
628
+ } catch (error) {
629
+ console.warn("Extension heartbeat failed:", error);
630
+ this.forceDisconnect();
631
+ } finally {
632
+ this.awaitingHeartbeat = false;
633
+ }
634
+ }
635
+ forceDisconnect() {
636
+ if (this.socket && this.socket.readyState === import_ws.WebSocket.OPEN) {
637
+ try {
638
+ this.socket.terminate();
639
+ } catch {
640
+ this.socket.close();
641
+ }
642
+ }
643
+ this.handleDisconnect();
644
+ }
645
+ handleConnection(socket) {
646
+ if (this.socket && this.socket.readyState === import_ws.WebSocket.OPEN) {
647
+ this.socket.close();
648
+ }
649
+ this.socket = socket;
650
+ this.connected = true;
651
+ this.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
652
+ this.applyDriveConnected();
653
+ this.startHeartbeat();
654
+ socket.on("message", (data) => {
655
+ this.handleMessage(data);
656
+ });
657
+ socket.on("close", () => {
658
+ this.handleDisconnect();
659
+ });
660
+ socket.on("error", () => {
661
+ this.handleDisconnect();
662
+ });
663
+ }
664
+ handleDisconnect() {
665
+ if (!this.connected) {
666
+ return;
667
+ }
668
+ this.stopHeartbeat();
669
+ this.connected = false;
670
+ this.socket = null;
671
+ this.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
672
+ this.applyDriveDisconnected();
673
+ for (const [id, pending] of this.pending.entries()) {
674
+ clearTimeout(pending.timeout);
675
+ pending.reject(
676
+ new ExtensionBridgeError(
677
+ "EXTENSION_DISCONNECTED",
678
+ "Extension disconnected before responding.",
679
+ true,
680
+ { request_id: id }
681
+ )
682
+ );
683
+ this.pending.delete(id);
684
+ }
685
+ }
686
+ handleMessage(data) {
687
+ const text = typeof data === "string" ? data : data.toString();
688
+ let message = null;
689
+ try {
690
+ message = JSON.parse(text);
691
+ } catch {
692
+ return;
693
+ }
694
+ if (!message || typeof message !== "object") {
695
+ return;
696
+ }
697
+ this.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
698
+ if (message.status === "event") {
699
+ this.handleEvent(message);
700
+ return;
701
+ }
702
+ if (message.status === "ok" || message.status === "error" || message.status === "ack") {
703
+ const pending = this.pending.get(message.id);
704
+ if (!pending) {
705
+ return;
706
+ }
707
+ clearTimeout(pending.timeout);
708
+ this.pending.delete(message.id);
709
+ pending.resolve(message);
710
+ }
711
+ }
712
+ handleEvent(message) {
713
+ if (message.action === "drive.hello" || message.action === "drive.tab_report") {
714
+ const tabs = message.params?.tabs;
715
+ if (Array.isArray(tabs)) {
716
+ this.tabs = tabs;
717
+ }
718
+ }
719
+ if (typeof message.action === "string" && message.action.startsWith("debugger.")) {
720
+ this.emitDebuggerEvent(message);
721
+ }
722
+ }
723
+ emitDebuggerEvent(event) {
724
+ for (const listener of this.debuggerListeners) {
725
+ try {
726
+ listener(event);
727
+ } catch (error) {
728
+ console.debug("Debugger event listener failed.", error);
729
+ }
730
+ }
731
+ }
732
+ applyDriveConnected() {
733
+ if (!this.registry) {
734
+ return;
735
+ }
736
+ for (const session of this.registry.list()) {
737
+ try {
738
+ if (session.state === "INIT" /* INIT */) {
739
+ this.registry.apply(session.id, "DRIVE_CONNECTED");
740
+ } else if (session.state === "INSPECT_READY" /* INSPECT_READY */) {
741
+ this.registry.apply(session.id, "DRIVE_CONNECTED");
742
+ } else if (session.state === "DEGRADED_DRIVE" /* DEGRADED_DRIVE */) {
743
+ this.registry.apply(session.id, "RECOVER_SUCCEEDED");
744
+ }
745
+ } catch (error) {
746
+ console.debug(
747
+ `Drive connect transition ignored for session ${session.id} (${session.state}).`,
748
+ error
749
+ );
750
+ }
751
+ }
752
+ }
753
+ applyDriveDisconnected() {
754
+ if (!this.registry) {
755
+ return;
756
+ }
757
+ for (const session of this.registry.list()) {
758
+ try {
759
+ if (session.state === "READY" /* READY */) {
760
+ this.registry.apply(session.id, "DRIVE_DISCONNECTED");
761
+ }
762
+ } catch (error) {
763
+ console.debug(
764
+ `Drive disconnect transition ignored for session ${session.id} (${session.state}).`,
765
+ error
766
+ );
767
+ }
768
+ }
769
+ }
770
+ };
771
+ var toDriveError = (error) => ({
772
+ code: error.code,
773
+ message: error.message,
774
+ retryable: error.retryable,
775
+ ...error.details ? { details: error.details } : {}
776
+ });
777
+
778
+ // packages/core/src/drive.ts
779
+ var DriveMutex = class {
780
+ constructor() {
781
+ this.tail = Promise.resolve();
782
+ }
783
+ async runExclusive(work) {
784
+ const result = this.tail.then(work, work);
785
+ this.tail = result.then(
786
+ () => void 0,
787
+ () => void 0
788
+ );
789
+ return await result;
790
+ }
791
+ };
792
+ var driveMutex = new DriveMutex();
793
+ var DriveController = class {
794
+ constructor(bridge, registry) {
795
+ this.bridge = bridge;
796
+ this.registry = registry;
797
+ }
798
+ getLastError() {
799
+ if (!this.lastError || !this.lastErrorAt) {
800
+ return void 0;
801
+ }
802
+ return { error: this.lastError, at: this.lastErrorAt };
803
+ }
804
+ recordError(error) {
805
+ this.lastError = error;
806
+ this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
807
+ }
808
+ async execute(sessionId, action, params, timeoutMs) {
809
+ return await driveMutex.runExclusive(async () => {
810
+ try {
811
+ this.registry.require(sessionId);
812
+ } catch (error) {
813
+ if (error instanceof SessionError) {
814
+ const errorInfo2 = {
815
+ code: error.code,
816
+ message: error.message,
817
+ retryable: false
818
+ };
819
+ this.recordError(errorInfo2);
820
+ return {
821
+ ok: false,
822
+ error: errorInfo2
823
+ };
824
+ }
825
+ const errorInfo = {
826
+ code: "INTERNAL",
827
+ message: "Unexpected error while validating session.",
828
+ retryable: false
829
+ };
830
+ this.recordError(errorInfo);
831
+ return {
832
+ ok: false,
833
+ error: errorInfo
834
+ };
835
+ }
836
+ if (this.bridge.isConnected()) {
837
+ this.ensureDriveReady(sessionId);
838
+ }
839
+ let attempt = 0;
840
+ while (true) {
841
+ try {
842
+ const response = await this.bridge.request(
843
+ action,
844
+ params,
845
+ timeoutMs
846
+ );
847
+ if (response.status === "ok") {
848
+ return {
849
+ ok: true,
850
+ result: response.result
851
+ };
852
+ }
853
+ const errorInfo = response.error ?? {
854
+ code: "UNKNOWN",
855
+ message: "Drive operation failed.",
856
+ retryable: false
857
+ };
858
+ if (shouldRetryDriveOp({ attempt, retryable: errorInfo.retryable })) {
859
+ attempt += 1;
860
+ continue;
861
+ }
862
+ this.recordError(errorInfo);
863
+ return { ok: false, error: errorInfo };
864
+ } catch (error) {
865
+ if (error instanceof ExtensionBridgeError) {
866
+ if (error.code === "EXTENSION_DISCONNECTED") {
867
+ this.applyDriveDisconnected(sessionId);
868
+ }
869
+ const errorInfo2 = toDriveError(error);
870
+ if (shouldRetryDriveOp({ attempt, retryable: errorInfo2.retryable })) {
871
+ attempt += 1;
872
+ continue;
873
+ }
874
+ this.recordError(errorInfo2);
875
+ return { ok: false, error: errorInfo2 };
876
+ }
877
+ const errorInfo = {
878
+ code: "INTERNAL",
879
+ message: "Unexpected error while executing drive action.",
880
+ retryable: false
881
+ };
882
+ this.recordError(errorInfo);
883
+ return {
884
+ ok: false,
885
+ error: errorInfo
886
+ };
887
+ }
888
+ }
889
+ });
890
+ }
891
+ ensureDriveReady(sessionId) {
892
+ try {
893
+ const session = this.registry.require(sessionId);
894
+ if (session.state === "INIT" /* INIT */) {
895
+ this.registry.apply(sessionId, "DRIVE_CONNECTED");
896
+ } else if (session.state === "INSPECT_READY" /* INSPECT_READY */) {
897
+ this.registry.apply(sessionId, "DRIVE_CONNECTED");
898
+ } else if (session.state === "DEGRADED_DRIVE" /* DEGRADED_DRIVE */) {
899
+ this.registry.apply(sessionId, "RECOVER_SUCCEEDED");
900
+ }
901
+ } catch (error) {
902
+ console.debug(
903
+ `Drive ready transition ignored for session ${sessionId}.`,
904
+ error
905
+ );
906
+ }
907
+ }
908
+ applyDriveDisconnected(sessionId) {
909
+ try {
910
+ const session = this.registry.require(sessionId);
911
+ if (session.state === "READY" /* READY */) {
912
+ this.registry.apply(sessionId, "DRIVE_DISCONNECTED");
913
+ }
914
+ } catch (error) {
915
+ console.debug(
916
+ `Drive disconnect transition ignored for session ${sessionId}.`,
917
+ error
918
+ );
919
+ }
920
+ }
921
+ };
922
+
923
+ // packages/core/src/page-state-script.ts
924
+ var PAGE_STATE_SCRIPT = [
925
+ "(() => {",
926
+ " const escape = (value) => {",
927
+ " if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
928
+ " return CSS.escape(value);",
929
+ " }",
930
+ ` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
931
+ " };",
932
+ " const truncate = (value, max) => {",
933
+ " const text = String(value ?? '');",
934
+ " return text.length > max ? text.slice(0, max) : text;",
935
+ " };",
936
+ " const selectorFor = (element) => {",
937
+ " if (element.id) {",
938
+ " return `#${escape(element.id)}`;",
939
+ " }",
940
+ " const name = element.getAttribute('name');",
941
+ " if (name) {",
942
+ ' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
943
+ " }",
944
+ " const parts = [];",
945
+ " let node = element;",
946
+ " while (node && node.nodeType === 1 && parts.length < 4) {",
947
+ " let part = node.tagName.toLowerCase();",
948
+ " const parent = node.parentElement;",
949
+ " if (parent) {",
950
+ " const siblings = Array.from(parent.children).filter(",
951
+ " (child) => child.tagName === node.tagName",
952
+ " );",
953
+ " if (siblings.length > 1) {",
954
+ " part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
955
+ " }",
956
+ " }",
957
+ " parts.unshift(part);",
958
+ " node = parent;",
959
+ " }",
960
+ " return parts.join('>');",
961
+ " };",
962
+ " const readStorage = (storage, limit) => {",
963
+ " try {",
964
+ " return Object.keys(storage)",
965
+ " .slice(0, limit)",
966
+ " .map((key) => ({",
967
+ " key,",
968
+ " value: truncate(storage.getItem(key), 500),",
969
+ " }));",
970
+ " } catch {",
971
+ " return [];",
972
+ " }",
973
+ " };",
974
+ " const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
975
+ " const fields = Array.from(form.elements)",
976
+ " .filter((element) => element && element.tagName)",
977
+ " .map((element) => {",
978
+ " const tag = element.tagName.toLowerCase();",
979
+ " const type = 'type' in element && element.type ? element.type : tag;",
980
+ " const name = element.name || element.getAttribute('name') || element.id || '';",
981
+ " let value = '';",
982
+ " let options;",
983
+ " if (tag === 'select') {",
984
+ " const select = element;",
985
+ " value = select.value ?? '';",
986
+ " options = Array.from(select.options).map((option) => option.text);",
987
+ " } else if (tag === 'input' && element.type === 'password') {",
988
+ " value = '[redacted]';",
989
+ " } else if (tag === 'input' || tag === 'textarea') {",
990
+ " value = element.value ?? '';",
991
+ " } else if (element.isContentEditable) {",
992
+ " value = element.textContent ?? '';",
993
+ " } else if ('value' in element) {",
994
+ " value = element.value ?? '';",
995
+ " }",
996
+ " return {",
997
+ " name,",
998
+ " type,",
999
+ " value: truncate(value, 500),",
1000
+ " ...(options ? { options } : {}),",
1001
+ " };",
1002
+ " });",
1003
+ " return {",
1004
+ " selector: selectorFor(form),",
1005
+ " action: form.getAttribute('action') || undefined,",
1006
+ " method: form.getAttribute('method') || undefined,",
1007
+ " fields,",
1008
+ " };",
1009
+ " });",
1010
+ " const localStorage = readStorage(window.localStorage, 100);",
1011
+ " const sessionStorage = readStorage(window.sessionStorage, 100);",
1012
+ " const cookies = (document.cookie ? document.cookie.split(';') : [])",
1013
+ " .map((entry) => entry.trim())",
1014
+ " .filter((entry) => entry.length > 0)",
1015
+ " .slice(0, 50)",
1016
+ " .map((entry) => {",
1017
+ " const [key, ...rest] = entry.split('=');",
1018
+ " return { key, value: truncate(rest.join('='), 500) };",
1019
+ " });",
1020
+ " return { forms, localStorage, sessionStorage, cookies };",
1021
+ "})()"
1022
+ ].join("\n");
1023
+
1024
+ // packages/core/src/target-matching.ts
1025
+ var normalizeText = (value) => (value ?? "").trim().toLowerCase();
1026
+ var normalizeUrl = (value) => {
1027
+ const normalized = normalizeText(value);
1028
+ if (!normalized) {
1029
+ return "";
1030
+ }
1031
+ const hashIndex = normalized.indexOf("#");
1032
+ const withoutHash = hashIndex >= 0 ? normalized.slice(0, hashIndex) : normalized;
1033
+ return withoutHash.endsWith("/") && withoutHash.length > 1 ? withoutHash.slice(0, -1) : withoutHash;
1034
+ };
1035
+ var scoreUrlMatch = (candidateUrl, hintUrl) => {
1036
+ if (!candidateUrl || !hintUrl) {
1037
+ return 0;
1038
+ }
1039
+ if (candidateUrl === hintUrl) {
1040
+ return 100;
1041
+ }
1042
+ if (candidateUrl.includes(hintUrl) || hintUrl.includes(candidateUrl)) {
1043
+ return 60;
1044
+ }
1045
+ return 0;
1046
+ };
1047
+ var scoreTitleMatch = (candidateTitle, hintTitle) => {
1048
+ if (!candidateTitle || !hintTitle) {
1049
+ return 0;
1050
+ }
1051
+ if (candidateTitle === hintTitle) {
1052
+ return 80;
1053
+ }
1054
+ if (candidateTitle.includes(hintTitle) || hintTitle.includes(candidateTitle)) {
1055
+ return 40;
1056
+ }
1057
+ return 0;
1058
+ };
1059
+ var scoreRecency = (lastSeenAt, now) => {
1060
+ if (!lastSeenAt) {
1061
+ return 0;
1062
+ }
1063
+ const ageMinutes = Math.max(0, (now - lastSeenAt) / 6e4);
1064
+ return Math.max(0, 30 - ageMinutes);
1065
+ };
1066
+ var scoreHintRecency = (lastSeenAt, hintLastActiveAt) => {
1067
+ if (!lastSeenAt || !hintLastActiveAt) {
1068
+ return 0;
1069
+ }
1070
+ const deltaMinutes = Math.abs(lastSeenAt - hintLastActiveAt) / 6e4;
1071
+ return Math.max(0, 20 - deltaMinutes);
1072
+ };
1073
+ var isBlankUrl = (url) => url.length === 0 || url === "about:blank";
1074
+ var rankTargetCandidates = (candidates, hint, now = Date.now()) => {
1075
+ const normalizedHintUrl = normalizeUrl(hint?.url);
1076
+ const normalizedHintTitle = normalizeText(hint?.title);
1077
+ const hintLastActiveAt = hint?.lastActiveAt ? Date.parse(hint.lastActiveAt) : void 0;
1078
+ const ranked = candidates.map((candidate) => {
1079
+ const reasons = [];
1080
+ const normalizedUrl = normalizeUrl(candidate.url);
1081
+ const normalizedTitle = normalizeText(candidate.title);
1082
+ let score = 0;
1083
+ const urlScore = scoreUrlMatch(normalizedUrl, normalizedHintUrl);
1084
+ if (urlScore > 0) {
1085
+ score += urlScore;
1086
+ reasons.push(urlScore >= 100 ? "url:exact" : "url:partial");
1087
+ }
1088
+ const titleScore = scoreTitleMatch(normalizedTitle, normalizedHintTitle);
1089
+ if (titleScore > 0) {
1090
+ score += titleScore;
1091
+ reasons.push(titleScore >= 80 ? "title:exact" : "title:partial");
1092
+ }
1093
+ const recencyScore = scoreRecency(candidate.lastSeenAt, now);
1094
+ if (recencyScore > 0) {
1095
+ score += recencyScore;
1096
+ reasons.push("recency");
1097
+ }
1098
+ const hintRecencyScore = scoreHintRecency(
1099
+ candidate.lastSeenAt,
1100
+ Number.isFinite(hintLastActiveAt) ? hintLastActiveAt : void 0
1101
+ );
1102
+ if (hintRecencyScore > 0) {
1103
+ score += hintRecencyScore;
1104
+ reasons.push("hint-recency");
1105
+ }
1106
+ if (isBlankUrl(normalizedUrl)) {
1107
+ score -= 50;
1108
+ reasons.push("blank-url");
1109
+ }
1110
+ return { candidate, score, reasons };
1111
+ });
1112
+ return ranked.sort((a, b) => {
1113
+ if (a.score !== b.score) {
1114
+ return b.score - a.score;
1115
+ }
1116
+ const aSeen = a.candidate.lastSeenAt ?? 0;
1117
+ const bSeen = b.candidate.lastSeenAt ?? 0;
1118
+ if (aSeen !== bSeen) {
1119
+ return bSeen - aSeen;
1120
+ }
1121
+ return a.candidate.url.localeCompare(b.candidate.url);
1122
+ });
1123
+ };
1124
+ var pickBestTarget = (candidates, hint, now = Date.now()) => {
1125
+ const ranked = rankTargetCandidates(candidates, hint, now);
1126
+ return ranked.length > 0 ? ranked[0] : null;
1127
+ };
1128
+
1129
+ // packages/core/src/inspect.ts
1130
+ var InspectError = class extends Error {
1131
+ constructor(code, message, options = {}) {
1132
+ super(message);
1133
+ this.name = "InspectError";
1134
+ this.code = code;
1135
+ this.retryable = options.retryable ?? false;
1136
+ this.details = options.details;
1137
+ }
1138
+ };
1139
+ var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
1140
+ var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
1141
+ var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
1142
+ var MAX_REF_ASSIGNMENTS = 500;
1143
+ var MAX_REF_WARNINGS = 5;
1144
+ var INTERACTIVE_AX_ROLES = /* @__PURE__ */ new Set([
1145
+ "button",
1146
+ "link",
1147
+ "textbox",
1148
+ "combobox",
1149
+ "listbox",
1150
+ "checkbox",
1151
+ "radio",
1152
+ "switch",
1153
+ "searchbox",
1154
+ "spinbutton",
1155
+ "slider",
1156
+ "option"
1157
+ ]);
1158
+ var DECORATIVE_AX_ROLES = /* @__PURE__ */ new Set(["generic", "none", "presentation"]);
1159
+ var LABEL_AX_ROLES = /* @__PURE__ */ new Set([
1160
+ "textbox",
1161
+ "combobox",
1162
+ "listbox",
1163
+ "checkbox",
1164
+ "radio",
1165
+ "switch",
1166
+ "searchbox",
1167
+ "spinbutton",
1168
+ "slider"
1169
+ ]);
1170
+ var InspectService = class {
1171
+ constructor(options) {
1172
+ this.snapshotHistory = [];
1173
+ this.registry = options.registry;
1174
+ this.debugger = options.debuggerBridge;
1175
+ this.extensionBridge = options.extensionBridge;
1176
+ this.maxSnapshotsPerSession = options.maxSnapshotsPerSession ?? DEFAULT_MAX_SNAPSHOTS_PER_SESSION;
1177
+ this.maxSnapshotHistory = options.maxSnapshotHistory ?? DEFAULT_MAX_SNAPSHOT_HISTORY;
1178
+ }
1179
+ isConnected() {
1180
+ return this.debugger?.hasAttachments() ?? false;
1181
+ }
1182
+ getLastError() {
1183
+ if (!this.lastError || !this.lastErrorAt) {
1184
+ const debuggerError = this.debugger?.getLastError();
1185
+ if (!debuggerError) {
1186
+ return void 0;
1187
+ }
1188
+ return {
1189
+ error: new InspectError(
1190
+ "INSPECT_UNAVAILABLE",
1191
+ debuggerError.error.message,
1192
+ {
1193
+ retryable: debuggerError.error.retryable,
1194
+ details: {
1195
+ code: debuggerError.error.code,
1196
+ ...debuggerError.error.details ? debuggerError.error.details : {}
1197
+ }
1198
+ }
1199
+ ),
1200
+ at: debuggerError.at
1201
+ };
1202
+ }
1203
+ return { error: this.lastError, at: this.lastErrorAt };
1204
+ }
1205
+ async reconnect(sessionId) {
1206
+ try {
1207
+ this.requireSession(sessionId);
1208
+ const selection = await this.resolveTab();
1209
+ const debuggerBridge = this.ensureDebugger();
1210
+ const result = await debuggerBridge.attach(selection.tabId);
1211
+ if (result.ok) {
1212
+ this.markInspectConnected(sessionId);
1213
+ return true;
1214
+ }
1215
+ const error = this.mapDebuggerError(result.error);
1216
+ this.recordError(error);
1217
+ return false;
1218
+ } catch (error) {
1219
+ if (error instanceof InspectError) {
1220
+ this.recordError(error);
1221
+ }
1222
+ return false;
1223
+ }
1224
+ }
1225
+ async domSnapshot(input) {
1226
+ this.requireSession(input.sessionId);
1227
+ const selection = await this.resolveTab(input.targetHint);
1228
+ const work = async () => {
1229
+ if (input.format === "html") {
1230
+ const html = await this.captureHtml(selection.tabId, input.selector);
1231
+ const warnings = [...selection.warnings ?? []];
1232
+ if (input.interactive) {
1233
+ warnings.push(
1234
+ "Interactive filter is only supported for AX snapshots."
1235
+ );
1236
+ }
1237
+ if (input.compact) {
1238
+ warnings.push("Compact filter is only supported for AX snapshots.");
1239
+ }
1240
+ if (input.selector && html === "") {
1241
+ warnings.push(`Selector not found: ${input.selector}`);
1242
+ }
1243
+ return {
1244
+ format: "html",
1245
+ snapshot: html,
1246
+ ...warnings.length > 0 ? { warnings } : {}
1247
+ };
1248
+ }
1249
+ try {
1250
+ await this.enableAccessibility(selection.tabId);
1251
+ const selectorWarnings = [];
1252
+ let result2;
1253
+ if (input.selector) {
1254
+ const resolved = await this.resolveNodeIdForSelector(
1255
+ selection.tabId,
1256
+ input.selector
1257
+ );
1258
+ selectorWarnings.push(...resolved.warnings ?? []);
1259
+ if (!resolved.nodeId) {
1260
+ let refWarnings2 = [];
1261
+ try {
1262
+ refWarnings2 = await this.applySnapshotRefs(
1263
+ selection.tabId,
1264
+ /* @__PURE__ */ new Map()
1265
+ );
1266
+ } catch {
1267
+ refWarnings2 = ["Failed to clear prior snapshot refs."];
1268
+ }
1269
+ const warnings2 = [
1270
+ ...selection.warnings ?? [],
1271
+ ...selectorWarnings,
1272
+ ...refWarnings2
1273
+ ];
1274
+ return {
1275
+ format: "ax",
1276
+ snapshot: { nodes: [] },
1277
+ ...warnings2.length > 0 ? { warnings: warnings2 } : {}
1278
+ };
1279
+ }
1280
+ result2 = await this.debuggerCommand(
1281
+ selection.tabId,
1282
+ "Accessibility.getPartialAXTree",
1283
+ { nodeId: resolved.nodeId }
1284
+ );
1285
+ } else {
1286
+ result2 = await this.debuggerCommand(
1287
+ selection.tabId,
1288
+ "Accessibility.getFullAXTree",
1289
+ {}
1290
+ );
1291
+ }
1292
+ const snapshot = input.interactive || input.compact ? this.applyAxSnapshotFilters(result2, {
1293
+ interactiveOnly: input.interactive,
1294
+ compact: input.compact
1295
+ }) : result2;
1296
+ const refMap = this.assignRefsToAxSnapshot(snapshot);
1297
+ const refWarnings = await this.applySnapshotRefs(
1298
+ selection.tabId,
1299
+ refMap
1300
+ );
1301
+ const warnings = [
1302
+ ...selection.warnings ?? [],
1303
+ ...selectorWarnings,
1304
+ ...refWarnings ?? []
1305
+ ];
1306
+ return {
1307
+ format: "ax",
1308
+ snapshot,
1309
+ ...warnings.length > 0 ? { warnings } : {}
1310
+ };
1311
+ } catch (error) {
1312
+ if (error instanceof InspectError) {
1313
+ const fallbackCodes = [
1314
+ "NOT_SUPPORTED",
1315
+ "INSPECT_UNAVAILABLE",
1316
+ "EVALUATION_FAILED"
1317
+ ];
1318
+ if (!fallbackCodes.includes(error.code)) {
1319
+ throw error;
1320
+ }
1321
+ const html = await this.captureHtml(selection.tabId, input.selector);
1322
+ const warnings = [
1323
+ ...selection.warnings ?? [],
1324
+ "AX snapshot failed; returned HTML instead.",
1325
+ ...input.interactive ? ["Interactive filter is only supported for AX snapshots."] : [],
1326
+ ...input.compact ? ["Compact filter is only supported for AX snapshots."] : [],
1327
+ ...input.selector && html === "" ? [`Selector not found: ${input.selector}`] : []
1328
+ ];
1329
+ return {
1330
+ format: "html",
1331
+ snapshot: html,
1332
+ warnings
1333
+ };
1334
+ }
1335
+ throw error;
1336
+ }
1337
+ };
1338
+ if (input.consistency === "quiesce") {
1339
+ const result2 = await driveMutex.runExclusive(work);
1340
+ this.recordSnapshot(input.sessionId, result2);
1341
+ this.markInspectConnected(input.sessionId);
1342
+ return result2;
1343
+ }
1344
+ const result = await work();
1345
+ this.recordSnapshot(input.sessionId, result);
1346
+ this.markInspectConnected(input.sessionId);
1347
+ return result;
1348
+ }
1349
+ domDiff(input) {
1350
+ this.requireSession(input.sessionId);
1351
+ this.markInspectConnected(input.sessionId);
1352
+ const snapshots = this.snapshotHistory.filter(
1353
+ (record) => record.sessionId === input.sessionId
1354
+ );
1355
+ if (snapshots.length < 2) {
1356
+ return {
1357
+ added: [],
1358
+ removed: [],
1359
+ changed: [],
1360
+ summary: "Not enough snapshots to diff."
1361
+ };
1362
+ }
1363
+ const previous = snapshots[snapshots.length - 2];
1364
+ const current = snapshots[snapshots.length - 1];
1365
+ const added = [];
1366
+ const removed = [];
1367
+ const changed = [];
1368
+ for (const [key, value] of current.entries.entries()) {
1369
+ if (!previous.entries.has(key)) {
1370
+ added.push(key);
1371
+ } else if (previous.entries.get(key) !== value) {
1372
+ changed.push(key);
1373
+ }
1374
+ }
1375
+ for (const key of previous.entries.keys()) {
1376
+ if (!current.entries.has(key)) {
1377
+ removed.push(key);
1378
+ }
1379
+ }
1380
+ return {
1381
+ added,
1382
+ removed,
1383
+ changed,
1384
+ summary: `Added ${added.length}, removed ${removed.length}, changed ${changed.length}.`
1385
+ };
1386
+ }
1387
+ async find(input) {
1388
+ const snapshot = await this.domSnapshot({
1389
+ sessionId: input.sessionId,
1390
+ format: "ax",
1391
+ consistency: "best_effort",
1392
+ targetHint: input.targetHint
1393
+ });
1394
+ const warnings = [...snapshot.warnings ?? []];
1395
+ if (snapshot.format !== "ax") {
1396
+ warnings.push("AX snapshot unavailable; cannot resolve refs.");
1397
+ return {
1398
+ matches: [],
1399
+ ...warnings.length > 0 ? { warnings } : {}
1400
+ };
1401
+ }
1402
+ const nodes = this.getAxNodes(snapshot.snapshot);
1403
+ const matches = [];
1404
+ const nameQuery = typeof input.name === "string" ? this.normalizeQuery(input.name) : "";
1405
+ const textQuery = typeof input.text === "string" ? this.normalizeQuery(input.text) : "";
1406
+ const labelQuery = typeof input.label === "string" ? this.normalizeQuery(input.label) : "";
1407
+ const roleQuery = typeof input.role === "string" ? this.normalizeQuery(input.role) : "";
1408
+ for (const node of nodes) {
1409
+ if (!node || typeof node !== "object") {
1410
+ continue;
1411
+ }
1412
+ if (typeof node.ref !== "string" || node.ref.length === 0) {
1413
+ continue;
1414
+ }
1415
+ const role = this.getAxRole(node);
1416
+ const name = this.getAxName(node);
1417
+ if (input.kind === "role") {
1418
+ if (!role || role !== roleQuery) {
1419
+ continue;
1420
+ }
1421
+ if (nameQuery && !this.matchesTextValue(name, nameQuery)) {
1422
+ continue;
1423
+ }
1424
+ } else if (input.kind === "text") {
1425
+ if (!textQuery || !this.matchesAxText(node, textQuery)) {
1426
+ continue;
1427
+ }
1428
+ } else if (input.kind === "label") {
1429
+ if (!labelQuery || !LABEL_AX_ROLES.has(role)) {
1430
+ continue;
1431
+ }
1432
+ if (!this.matchesTextValue(name, labelQuery)) {
1433
+ continue;
1434
+ }
1435
+ }
1436
+ matches.push({
1437
+ ref: node.ref,
1438
+ ...role ? { role } : {},
1439
+ ...name ? { name } : {}
1440
+ });
1441
+ }
1442
+ return {
1443
+ matches,
1444
+ ...warnings.length > 0 ? { warnings } : {}
1445
+ };
1446
+ }
1447
+ async consoleList(input) {
1448
+ this.requireSession(input.sessionId);
1449
+ const selection = await this.resolveTab(input.targetHint);
1450
+ await this.enableConsole(selection.tabId);
1451
+ const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
1452
+ const entries = events.map((event) => this.toConsoleEntry(event)).filter((entry) => entry !== null);
1453
+ const result = {
1454
+ entries,
1455
+ warnings: selection.warnings
1456
+ };
1457
+ this.markInspectConnected(input.sessionId);
1458
+ return result;
1459
+ }
1460
+ async networkHar(input) {
1461
+ this.requireSession(input.sessionId);
1462
+ const selection = await this.resolveTab(input.targetHint);
1463
+ await this.enableNetwork(selection.tabId);
1464
+ const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
1465
+ const har = this.buildHar(events, selection.tab.title);
1466
+ try {
1467
+ const rootDir = await ensureArtifactRootDir(input.sessionId);
1468
+ const artifactId = (0, import_crypto3.randomUUID)();
1469
+ const filePath = import_node_path2.default.join(rootDir, `har-${artifactId}.json`);
1470
+ await (0, import_promises2.writeFile)(filePath, JSON.stringify(har, null, 2), "utf-8");
1471
+ const result = {
1472
+ artifact_id: artifactId,
1473
+ path: filePath,
1474
+ mime: "application/json"
1475
+ };
1476
+ this.markInspectConnected(input.sessionId);
1477
+ return result;
1478
+ } catch {
1479
+ const error = new InspectError(
1480
+ "ARTIFACT_IO_ERROR",
1481
+ "Failed to write HAR file."
1482
+ );
1483
+ this.recordError(error);
1484
+ throw error;
1485
+ }
1486
+ }
1487
+ async evaluate(input) {
1488
+ this.requireSession(input.sessionId);
1489
+ const selection = await this.resolveTab(input.targetHint);
1490
+ const expression = input.expression ?? "undefined";
1491
+ await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
1492
+ const result = await this.debuggerCommand(
1493
+ selection.tabId,
1494
+ "Runtime.evaluate",
1495
+ {
1496
+ expression,
1497
+ returnByValue: true,
1498
+ awaitPromise: true
1499
+ }
1500
+ );
1501
+ if (result && typeof result === "object" && "exceptionDetails" in result) {
1502
+ const output2 = {
1503
+ exception: result.exceptionDetails,
1504
+ warnings: selection.warnings
1505
+ };
1506
+ this.markInspectConnected(input.sessionId);
1507
+ return output2;
1508
+ }
1509
+ const output = {
1510
+ value: result?.result?.value,
1511
+ warnings: selection.warnings
1512
+ };
1513
+ this.markInspectConnected(input.sessionId);
1514
+ return output;
1515
+ }
1516
+ async extractContent(input) {
1517
+ this.requireSession(input.sessionId);
1518
+ const selection = await this.resolveTab(input.targetHint);
1519
+ const html = await this.captureHtml(selection.tabId);
1520
+ const url = selection.tab.url ?? "about:blank";
1521
+ let article = null;
1522
+ try {
1523
+ const dom = new import_jsdom.JSDOM(html, { url });
1524
+ const reader = new import_readability.Readability(dom.window.document);
1525
+ article = reader.parse();
1526
+ } catch {
1527
+ const err = new InspectError(
1528
+ "EVALUATION_FAILED",
1529
+ "Failed to parse page content.",
1530
+ { retryable: false }
1531
+ );
1532
+ this.recordError(err);
1533
+ throw err;
1534
+ }
1535
+ if (!article) {
1536
+ const err = new InspectError(
1537
+ "NOT_SUPPORTED",
1538
+ "Readability could not extract content.",
1539
+ { retryable: false }
1540
+ );
1541
+ this.recordError(err);
1542
+ throw err;
1543
+ }
1544
+ let content = "";
1545
+ if (input.format === "article_json") {
1546
+ content = JSON.stringify(article, null, 2);
1547
+ } else if (input.format === "text") {
1548
+ content = article.textContent ?? "";
1549
+ } else {
1550
+ const turndown = new import_turndown.default();
1551
+ content = turndown.turndown(article.content ?? "");
1552
+ }
1553
+ const warnings = selection.warnings ?? [];
1554
+ const includeMetadata = input.includeMetadata ?? true;
1555
+ const output = {
1556
+ content,
1557
+ ...includeMetadata ? {
1558
+ title: article.title ?? void 0,
1559
+ byline: article.byline ?? void 0,
1560
+ excerpt: article.excerpt ?? void 0,
1561
+ siteName: article.siteName ?? void 0
1562
+ } : {},
1563
+ ...warnings.length > 0 ? { warnings } : {}
1564
+ };
1565
+ this.markInspectConnected(input.sessionId);
1566
+ return output;
1567
+ }
1568
+ async pageState(input) {
1569
+ this.requireSession(input.sessionId);
1570
+ const selection = await this.resolveTab(input.targetHint);
1571
+ await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
1572
+ const expression = PAGE_STATE_SCRIPT;
1573
+ const result = await this.debuggerCommand(
1574
+ selection.tabId,
1575
+ "Runtime.evaluate",
1576
+ {
1577
+ expression,
1578
+ returnByValue: true,
1579
+ awaitPromise: true
1580
+ }
1581
+ );
1582
+ if (result && typeof result === "object" && "exceptionDetails" in result) {
1583
+ const error = new InspectError(
1584
+ "EVALUATION_FAILED",
1585
+ "Failed to capture page state.",
1586
+ { retryable: false }
1587
+ );
1588
+ this.recordError(error);
1589
+ throw error;
1590
+ }
1591
+ const value = result?.result?.value;
1592
+ const raw = value && typeof value === "object" ? value : {};
1593
+ const warnings = [
1594
+ ...Array.isArray(raw.warnings) ? raw.warnings : [],
1595
+ ...selection.warnings ?? []
1596
+ ];
1597
+ const output = {
1598
+ forms: Array.isArray(raw.forms) ? raw.forms : [],
1599
+ localStorage: Array.isArray(raw.localStorage) ? raw.localStorage : [],
1600
+ sessionStorage: Array.isArray(raw.sessionStorage) ? raw.sessionStorage : [],
1601
+ cookies: Array.isArray(raw.cookies) ? raw.cookies : [],
1602
+ ...warnings.length > 0 ? { warnings } : {}
1603
+ };
1604
+ this.markInspectConnected(input.sessionId);
1605
+ return output;
1606
+ }
1607
+ async performanceMetrics(input) {
1608
+ this.requireSession(input.sessionId);
1609
+ const selection = await this.resolveTab(input.targetHint);
1610
+ await this.debuggerCommand(selection.tabId, "Performance.enable", {});
1611
+ const result = await this.debuggerCommand(
1612
+ selection.tabId,
1613
+ "Performance.getMetrics",
1614
+ {}
1615
+ );
1616
+ const metrics = Array.isArray(result?.metrics) ? result.metrics.map((metric) => ({
1617
+ name: metric.name,
1618
+ value: metric.value
1619
+ })) : [];
1620
+ const output = { metrics, warnings: selection.warnings };
1621
+ this.markInspectConnected(input.sessionId);
1622
+ return output;
1623
+ }
1624
+ async screenshot(input) {
1625
+ this.requireSession(input.sessionId);
1626
+ const selection = await this.resolveTab(input.targetHint);
1627
+ await this.debuggerCommand(selection.tabId, "Page.enable", {});
1628
+ const format = input.format ?? "png";
1629
+ let captureParams = {
1630
+ format,
1631
+ fromSurface: true
1632
+ };
1633
+ if (format !== "png" && typeof input.quality === "number") {
1634
+ captureParams = { ...captureParams, quality: input.quality };
1635
+ }
1636
+ if (input.target === "full") {
1637
+ const layout = await this.debuggerCommand(
1638
+ selection.tabId,
1639
+ "Page.getLayoutMetrics",
1640
+ {}
1641
+ );
1642
+ const contentSize = layout?.contentSize;
1643
+ if (contentSize) {
1644
+ captureParams = {
1645
+ ...captureParams,
1646
+ clip: {
1647
+ x: 0,
1648
+ y: 0,
1649
+ width: contentSize.width,
1650
+ height: contentSize.height,
1651
+ scale: 1
1652
+ }
1653
+ };
1654
+ } else {
1655
+ captureParams = { ...captureParams, captureBeyondViewport: true };
1656
+ }
1657
+ }
1658
+ const result = await this.debuggerCommand(
1659
+ selection.tabId,
1660
+ "Page.captureScreenshot",
1661
+ captureParams
1662
+ );
1663
+ const data = result.data;
1664
+ if (!data) {
1665
+ const error = new InspectError(
1666
+ "INSPECT_UNAVAILABLE",
1667
+ "Failed to capture screenshot.",
1668
+ { retryable: false }
1669
+ );
1670
+ this.recordError(error);
1671
+ throw error;
1672
+ }
1673
+ try {
1674
+ const rootDir = await ensureArtifactRootDir(input.sessionId);
1675
+ const artifactId = (0, import_crypto3.randomUUID)();
1676
+ const extension = format === "jpeg" ? "jpg" : format;
1677
+ const filePath = import_node_path2.default.join(
1678
+ rootDir,
1679
+ `screenshot-${artifactId}.${extension}`
1680
+ );
1681
+ await (0, import_promises2.writeFile)(filePath, Buffer.from(data, "base64"));
1682
+ const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
1683
+ const output = {
1684
+ artifact_id: artifactId,
1685
+ path: filePath,
1686
+ mime
1687
+ };
1688
+ this.markInspectConnected(input.sessionId);
1689
+ return output;
1690
+ } catch {
1691
+ const error = new InspectError(
1692
+ "ARTIFACT_IO_ERROR",
1693
+ "Failed to write screenshot file."
1694
+ );
1695
+ this.recordError(error);
1696
+ throw error;
1697
+ }
1698
+ }
1699
+ ensureDebugger() {
1700
+ if (!this.debugger) {
1701
+ const error = this.buildUnavailableError();
1702
+ this.recordError(error);
1703
+ throw error;
1704
+ }
1705
+ return this.debugger;
1706
+ }
1707
+ async resolveTab(hint) {
1708
+ if (!this.extensionBridge || !this.extensionBridge.isConnected()) {
1709
+ const error = new InspectError(
1710
+ "EXTENSION_DISCONNECTED",
1711
+ "Extension is not connected.",
1712
+ { retryable: true }
1713
+ );
1714
+ this.recordError(error);
1715
+ throw error;
1716
+ }
1717
+ const tabs = this.extensionBridge.getStatus().tabs ?? [];
1718
+ if (!Array.isArray(tabs) || tabs.length === 0) {
1719
+ const error = new InspectError(
1720
+ "TAB_NOT_FOUND",
1721
+ "No tabs available to inspect."
1722
+ );
1723
+ this.recordError(error);
1724
+ throw error;
1725
+ }
1726
+ const candidates = tabs.map((tab2) => ({
1727
+ id: String(tab2.tab_id),
1728
+ url: tab2.url ?? "",
1729
+ title: tab2.title,
1730
+ lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
1731
+ }));
1732
+ const ranked = pickBestTarget(candidates, hint);
1733
+ if (!ranked) {
1734
+ const error = new InspectError("TAB_NOT_FOUND", "No matching tab found.");
1735
+ this.recordError(error);
1736
+ throw error;
1737
+ }
1738
+ const tabId = Number(ranked.candidate.id);
1739
+ if (!Number.isFinite(tabId)) {
1740
+ const error = new InspectError(
1741
+ "TAB_NOT_FOUND",
1742
+ "Resolved tab id is invalid."
1743
+ );
1744
+ this.recordError(error);
1745
+ throw error;
1746
+ }
1747
+ const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
1748
+ const warnings = [];
1749
+ if (!hint) {
1750
+ warnings.push("No target hint provided; using the most recent tab.");
1751
+ } else if (ranked.score < 20) {
1752
+ warnings.push("Weak target match; using best available tab.");
1753
+ }
1754
+ return {
1755
+ tabId,
1756
+ tab,
1757
+ warnings: warnings.length > 0 ? warnings : void 0
1758
+ };
1759
+ }
1760
+ async enableConsole(tabId) {
1761
+ await this.debuggerCommand(tabId, "Runtime.enable", {});
1762
+ await this.debuggerCommand(tabId, "Log.enable", {});
1763
+ }
1764
+ async enableNetwork(tabId) {
1765
+ await this.debuggerCommand(tabId, "Network.enable", {});
1766
+ }
1767
+ async enableAccessibility(tabId) {
1768
+ await this.debuggerCommand(tabId, "Accessibility.enable", {});
1769
+ }
1770
+ recordSnapshot(sessionId, snapshot) {
1771
+ const entries = this.collectSnapshotEntries(snapshot);
1772
+ if (!entries) {
1773
+ return;
1774
+ }
1775
+ this.snapshotHistory.push({
1776
+ sessionId,
1777
+ format: snapshot.format,
1778
+ entries,
1779
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
1780
+ });
1781
+ let count = 0;
1782
+ for (const record of this.snapshotHistory) {
1783
+ if (record.sessionId === sessionId) {
1784
+ count += 1;
1785
+ }
1786
+ }
1787
+ while (count > this.maxSnapshotsPerSession) {
1788
+ const index = this.snapshotHistory.findIndex(
1789
+ (record) => record.sessionId === sessionId
1790
+ );
1791
+ if (index === -1) {
1792
+ break;
1793
+ }
1794
+ this.snapshotHistory.splice(index, 1);
1795
+ count -= 1;
1796
+ }
1797
+ while (this.snapshotHistory.length > this.maxSnapshotHistory) {
1798
+ this.snapshotHistory.shift();
1799
+ }
1800
+ }
1801
+ collectSnapshotEntries(snapshot) {
1802
+ if (snapshot.format === "html" && typeof snapshot.snapshot === "string") {
1803
+ return this.collectHtmlEntries(snapshot.snapshot);
1804
+ }
1805
+ if (snapshot.format === "ax") {
1806
+ return this.collectAxEntries(snapshot.snapshot);
1807
+ }
1808
+ return null;
1809
+ }
1810
+ getAxNodes(snapshot) {
1811
+ const nodes = Array.isArray(snapshot) ? snapshot : snapshot?.nodes;
1812
+ return Array.isArray(nodes) ? nodes : [];
1813
+ }
1814
+ applyAxSnapshotFilters(snapshot, options) {
1815
+ let filtered = snapshot;
1816
+ if (options.compact) {
1817
+ filtered = this.compactAxSnapshot(filtered);
1818
+ }
1819
+ if (options.interactiveOnly) {
1820
+ filtered = this.filterAxSnapshot(
1821
+ filtered,
1822
+ (node) => this.isInteractiveAxNode(node)
1823
+ );
1824
+ }
1825
+ return filtered;
1826
+ }
1827
+ filterAxSnapshot(snapshot, predicate) {
1828
+ const nodes = this.getAxNodes(snapshot);
1829
+ if (nodes.length === 0) {
1830
+ return snapshot;
1831
+ }
1832
+ const keepIds = /* @__PURE__ */ new Set();
1833
+ const filtered = nodes.filter((node) => {
1834
+ if (!node || typeof node !== "object") {
1835
+ return false;
1836
+ }
1837
+ const keep = predicate(node);
1838
+ if (keep && typeof node.nodeId === "string") {
1839
+ keepIds.add(node.nodeId);
1840
+ }
1841
+ return keep;
1842
+ });
1843
+ for (const node of filtered) {
1844
+ if (Array.isArray(node.childIds)) {
1845
+ node.childIds = node.childIds.filter((id) => keepIds.has(id));
1846
+ }
1847
+ }
1848
+ return this.replaceAxNodes(snapshot, filtered);
1849
+ }
1850
+ replaceAxNodes(snapshot, nodes) {
1851
+ if (Array.isArray(snapshot)) {
1852
+ return nodes;
1853
+ }
1854
+ if (snapshot && typeof snapshot === "object") {
1855
+ snapshot.nodes = nodes;
1856
+ }
1857
+ return snapshot;
1858
+ }
1859
+ isInteractiveAxNode(node) {
1860
+ const role = this.getAxRole(node);
1861
+ return Boolean(role && INTERACTIVE_AX_ROLES.has(role));
1862
+ }
1863
+ compactAxSnapshot(snapshot) {
1864
+ const nodes = this.getAxNodes(snapshot);
1865
+ if (nodes.length === 0) {
1866
+ return snapshot;
1867
+ }
1868
+ const nodeById = /* @__PURE__ */ new Map();
1869
+ nodes.forEach((node) => {
1870
+ if (node && typeof node.nodeId === "string") {
1871
+ nodeById.set(node.nodeId, node);
1872
+ }
1873
+ });
1874
+ const keepIds = /* @__PURE__ */ new Set();
1875
+ for (const node of nodes) {
1876
+ if (!node || typeof node !== "object" || typeof node.nodeId !== "string") {
1877
+ continue;
1878
+ }
1879
+ if (this.shouldKeepCompactNode(node)) {
1880
+ keepIds.add(node.nodeId);
1881
+ }
1882
+ }
1883
+ const filtered = nodes.filter(
1884
+ (node) => node && typeof node.nodeId === "string" && keepIds.has(node.nodeId)
1885
+ );
1886
+ for (const node of filtered) {
1887
+ if (!Array.isArray(node.childIds) || typeof node.nodeId !== "string") {
1888
+ continue;
1889
+ }
1890
+ const nextChildIds = [];
1891
+ for (const childId of node.childIds) {
1892
+ nextChildIds.push(
1893
+ ...this.collectKeptDescendants(childId, nodeById, keepIds)
1894
+ );
1895
+ }
1896
+ node.childIds = Array.from(new Set(nextChildIds));
1897
+ }
1898
+ return this.replaceAxNodes(snapshot, filtered);
1899
+ }
1900
+ collectKeptDescendants(nodeId, nodeById, keepIds, visited = /* @__PURE__ */ new Set()) {
1901
+ if (visited.has(nodeId)) {
1902
+ return [];
1903
+ }
1904
+ visited.add(nodeId);
1905
+ if (keepIds.has(nodeId)) {
1906
+ return [nodeId];
1907
+ }
1908
+ const node = nodeById.get(nodeId);
1909
+ if (!node || !Array.isArray(node.childIds)) {
1910
+ return [];
1911
+ }
1912
+ const output = [];
1913
+ for (const childId of node.childIds) {
1914
+ output.push(
1915
+ ...this.collectKeptDescendants(childId, nodeById, keepIds, visited)
1916
+ );
1917
+ }
1918
+ return output;
1919
+ }
1920
+ shouldKeepCompactNode(node) {
1921
+ if (node.ignored) {
1922
+ return false;
1923
+ }
1924
+ const role = this.getAxRole(node);
1925
+ if (role && INTERACTIVE_AX_ROLES.has(role)) {
1926
+ return true;
1927
+ }
1928
+ const name = this.getAxName(node);
1929
+ const hasName = name.trim().length > 0;
1930
+ const hasValue = this.hasAxValue(node);
1931
+ if (hasName || hasValue) {
1932
+ return true;
1933
+ }
1934
+ const hasChildren = Array.isArray(node.childIds) && node.childIds.length > 0;
1935
+ if (!role || DECORATIVE_AX_ROLES.has(role)) {
1936
+ return false;
1937
+ }
1938
+ return hasChildren;
1939
+ }
1940
+ getAxRole(node) {
1941
+ const role = typeof node.role === "string" ? node.role : node.role?.value ?? "";
1942
+ return typeof role === "string" ? role.toLowerCase() : "";
1943
+ }
1944
+ getAxName(node) {
1945
+ const name = typeof node.name === "string" ? node.name : node.name?.value ?? "";
1946
+ return typeof name === "string" ? name : "";
1947
+ }
1948
+ hasAxValue(node) {
1949
+ if (!Array.isArray(node.properties)) {
1950
+ return false;
1951
+ }
1952
+ for (const prop of node.properties) {
1953
+ if (!prop || typeof prop !== "object") {
1954
+ continue;
1955
+ }
1956
+ const value = prop.value?.value;
1957
+ if (value === void 0 || value === null) {
1958
+ continue;
1959
+ }
1960
+ if (typeof value === "string" && value.trim().length === 0) {
1961
+ continue;
1962
+ }
1963
+ return true;
1964
+ }
1965
+ return false;
1966
+ }
1967
+ normalizeQuery(value) {
1968
+ return value.trim().toLowerCase();
1969
+ }
1970
+ matchesTextValue(value, query) {
1971
+ if (!query) {
1972
+ return false;
1973
+ }
1974
+ return value.toLowerCase().includes(query);
1975
+ }
1976
+ matchesAxText(node, query) {
1977
+ if (!query) {
1978
+ return false;
1979
+ }
1980
+ const candidates = [this.getAxName(node)];
1981
+ if (Array.isArray(node.properties)) {
1982
+ for (const prop of node.properties) {
1983
+ if (!prop || typeof prop !== "object") {
1984
+ continue;
1985
+ }
1986
+ const value = prop.value?.value;
1987
+ if (value === void 0 || value === null) {
1988
+ continue;
1989
+ }
1990
+ if (typeof value === "string") {
1991
+ candidates.push(value);
1992
+ } else if (typeof value === "number" || typeof value === "boolean") {
1993
+ candidates.push(String(value));
1994
+ }
1995
+ }
1996
+ }
1997
+ return candidates.some((text) => this.matchesTextValue(text, query));
1998
+ }
1999
+ collectHtmlEntries(html) {
2000
+ const entries = /* @__PURE__ */ new Map();
2001
+ const tagPattern = /<([a-zA-Z0-9-]+)([^>]*)>/g;
2002
+ let match;
2003
+ let index = 0;
2004
+ while ((match = tagPattern.exec(html)) && entries.size < 1e3) {
2005
+ const tag = match[1].toLowerCase();
2006
+ const attrs = match[2] ?? "";
2007
+ const idMatch = /\bid=["']([^"']+)["']/.exec(attrs);
2008
+ const classMatch = /\bclass=["']([^"']+)["']/.exec(attrs);
2009
+ const id = idMatch?.[1];
2010
+ const className = classMatch?.[1]?.split(/\s+/)[0];
2011
+ let key = tag;
2012
+ if (id) {
2013
+ key = `${tag}#${id}`;
2014
+ } else if (className) {
2015
+ key = `${tag}.${className}`;
2016
+ } else {
2017
+ key = `${tag}:nth-${index}`;
2018
+ }
2019
+ entries.set(key, attrs.trim());
2020
+ index += 1;
2021
+ }
2022
+ return entries;
2023
+ }
2024
+ collectAxEntries(snapshot) {
2025
+ const entries = /* @__PURE__ */ new Map();
2026
+ const nodes = this.getAxNodes(snapshot);
2027
+ if (nodes.length === 0) {
2028
+ return entries;
2029
+ }
2030
+ nodes.forEach((node, index) => {
2031
+ if (!node || typeof node !== "object") {
2032
+ return;
2033
+ }
2034
+ const record = node;
2035
+ const role = typeof record.role === "string" ? record.role : record.role?.value ?? "node";
2036
+ const name = typeof record.name === "string" ? record.name : record.name?.value ?? "";
2037
+ const nodeId = record.nodeId ?? (record.backendDOMNodeId !== void 0 ? String(record.backendDOMNodeId) : void 0);
2038
+ const key = nodeId ? `node-${nodeId}` : `${role}:${name}:${index}`;
2039
+ entries.set(key, `${role}:${name}`);
2040
+ });
2041
+ return entries;
2042
+ }
2043
+ assignRefsToAxSnapshot(snapshot) {
2044
+ const nodes = this.getAxNodes(snapshot);
2045
+ const refs = /* @__PURE__ */ new Map();
2046
+ let index = 1;
2047
+ for (const node of nodes) {
2048
+ if (!node || typeof node !== "object") {
2049
+ continue;
2050
+ }
2051
+ if (node.ignored) {
2052
+ continue;
2053
+ }
2054
+ const backendId = node.backendDOMNodeId;
2055
+ if (typeof backendId !== "number") {
2056
+ continue;
2057
+ }
2058
+ const ref = `@e${index}`;
2059
+ index += 1;
2060
+ node.ref = ref;
2061
+ refs.set(backendId, ref);
2062
+ }
2063
+ return refs;
2064
+ }
2065
+ async applySnapshotRefs(tabId, refs) {
2066
+ const warnings = [];
2067
+ await this.debuggerCommand(tabId, "DOM.enable", {});
2068
+ await this.debuggerCommand(tabId, "Runtime.enable", {});
2069
+ try {
2070
+ await this.clearSnapshotRefs(tabId);
2071
+ } catch {
2072
+ warnings.push("Failed to clear prior snapshot refs.");
2073
+ }
2074
+ if (refs.size === 0) {
2075
+ return warnings;
2076
+ }
2077
+ let applied = 0;
2078
+ for (const [backendNodeId, ref] of refs) {
2079
+ if (applied >= MAX_REF_ASSIGNMENTS) {
2080
+ warnings.push(
2081
+ `Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
2082
+ );
2083
+ break;
2084
+ }
2085
+ try {
2086
+ const described = await this.debuggerCommand(
2087
+ tabId,
2088
+ "DOM.describeNode",
2089
+ {
2090
+ backendNodeId
2091
+ }
2092
+ );
2093
+ const node = described.node;
2094
+ if (!node || node.nodeType !== 1 || typeof node.nodeId !== "number") {
2095
+ if (warnings.length < MAX_REF_WARNINGS) {
2096
+ warnings.push(`Ref ${ref} could not be applied to a DOM element.`);
2097
+ }
2098
+ continue;
2099
+ }
2100
+ await this.debuggerCommand(tabId, "DOM.setAttributeValue", {
2101
+ nodeId: node.nodeId,
2102
+ name: SNAPSHOT_REF_ATTRIBUTE,
2103
+ value: ref
2104
+ });
2105
+ applied += 1;
2106
+ } catch {
2107
+ if (warnings.length < MAX_REF_WARNINGS) {
2108
+ warnings.push(`Ref ${ref} could not be applied.`);
2109
+ }
2110
+ }
2111
+ }
2112
+ return warnings;
2113
+ }
2114
+ async clearSnapshotRefs(tabId) {
2115
+ await this.debuggerCommand(tabId, "Runtime.evaluate", {
2116
+ expression: `document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'))`,
2117
+ returnByValue: true,
2118
+ awaitPromise: true
2119
+ });
2120
+ }
2121
+ async resolveNodeIdForSelector(tabId, selector) {
2122
+ await this.debuggerCommand(tabId, "DOM.enable", {});
2123
+ const document = await this.debuggerCommand(tabId, "DOM.getDocument", {
2124
+ depth: 1
2125
+ });
2126
+ const rootNodeId = document.root?.nodeId;
2127
+ if (typeof rootNodeId !== "number") {
2128
+ return { warnings: ["Failed to resolve DOM root for selector."] };
2129
+ }
2130
+ try {
2131
+ const result = await this.debuggerCommand(tabId, "DOM.querySelector", {
2132
+ nodeId: rootNodeId,
2133
+ selector
2134
+ });
2135
+ const nodeId = result.nodeId;
2136
+ if (!nodeId) {
2137
+ return { warnings: [`Selector not found: ${selector}`] };
2138
+ }
2139
+ return { nodeId };
2140
+ } catch (error) {
2141
+ if (error instanceof InspectError) {
2142
+ return { warnings: [error.message] };
2143
+ }
2144
+ return { warnings: ["Selector query failed."] };
2145
+ }
2146
+ }
2147
+ async captureHtml(tabId, selector) {
2148
+ await this.debuggerCommand(tabId, "Runtime.enable", {});
2149
+ const expression = selector ? `(() => { try { const el = document.querySelector(${JSON.stringify(
2150
+ selector
2151
+ )}); return el ? el.outerHTML : ""; } catch { return ""; } })()` : "document.documentElement ? document.documentElement.outerHTML : ''";
2152
+ const result = await this.debuggerCommand(tabId, "Runtime.evaluate", {
2153
+ expression,
2154
+ returnByValue: true,
2155
+ awaitPromise: true
2156
+ });
2157
+ if (result && typeof result === "object" && "exceptionDetails" in result) {
2158
+ const error = new InspectError(
2159
+ "EVALUATION_FAILED",
2160
+ "Failed to evaluate HTML snapshot.",
2161
+ { retryable: false }
2162
+ );
2163
+ this.recordError(error);
2164
+ throw error;
2165
+ }
2166
+ return String(
2167
+ result?.result?.value ?? ""
2168
+ );
2169
+ }
2170
+ async debuggerCommand(tabId, method, params, timeoutMs) {
2171
+ const debuggerBridge = this.ensureDebugger();
2172
+ const result = await debuggerBridge.command(
2173
+ tabId,
2174
+ method,
2175
+ params,
2176
+ timeoutMs
2177
+ );
2178
+ if (!result.ok) {
2179
+ const error = this.mapDebuggerError(result.error);
2180
+ this.recordError(error);
2181
+ throw error;
2182
+ }
2183
+ return result.result;
2184
+ }
2185
+ mapDebuggerError(error) {
2186
+ const allowed = [
2187
+ "INSPECT_UNAVAILABLE",
2188
+ "EXTENSION_DISCONNECTED",
2189
+ "DEBUGGER_IN_USE",
2190
+ "ATTACH_DENIED",
2191
+ "TAB_NOT_FOUND",
2192
+ "NOT_SUPPORTED",
2193
+ "TIMEOUT",
2194
+ "EVALUATION_FAILED",
2195
+ "ARTIFACT_IO_ERROR",
2196
+ "INVALID_ARGUMENT",
2197
+ "INTERNAL"
2198
+ ];
2199
+ const code = allowed.includes(error.code) ? error.code : "INSPECT_UNAVAILABLE";
2200
+ return new InspectError(code, error.message, {
2201
+ retryable: error.retryable,
2202
+ details: error.details
2203
+ });
2204
+ }
2205
+ toConsoleEntry(event) {
2206
+ const params = event.params ?? {};
2207
+ switch (event.method) {
2208
+ case "Runtime.consoleAPICalled": {
2209
+ const rawArgs = Array.isArray(params.args) ? params.args : [];
2210
+ const text = rawArgs.map((arg) => this.stringifyRemoteObject(arg)).join(" ");
2211
+ const level = String(params.type ?? "log");
2212
+ const stack = this.toStackFrames(
2213
+ params.stackTrace
2214
+ );
2215
+ const args = rawArgs.map((arg) => this.toRemoteObjectSummary(arg)).filter(
2216
+ (entry) => Boolean(entry)
2217
+ );
2218
+ return {
2219
+ level,
2220
+ text,
2221
+ timestamp: event.timestamp,
2222
+ ...stack && stack.length > 0 ? { stack } : {},
2223
+ ...args.length > 0 ? { args } : {}
2224
+ };
2225
+ }
2226
+ case "Runtime.exceptionThrown": {
2227
+ const details = params.exceptionDetails;
2228
+ const exception = this.toRemoteObjectSummary(details?.exception);
2229
+ const stack = this.toStackFrames(details?.stackTrace);
2230
+ const source = this.toSourceLocation({
2231
+ url: details?.url,
2232
+ lineNumber: details?.lineNumber,
2233
+ columnNumber: details?.columnNumber
2234
+ }) ?? // If the top frame exists, treat it as the source.
2235
+ (stack && stack.length > 0 ? {
2236
+ url: stack[0].url,
2237
+ line: stack[0].line,
2238
+ column: stack[0].column
2239
+ } : void 0);
2240
+ const baseText = typeof details?.text === "string" && details.text.trim().length > 0 ? details.text : "Uncaught exception";
2241
+ const exceptionDesc = typeof exception?.description === "string" && exception.description.trim().length > 0 ? exception.description : void 0;
2242
+ const text = baseText === "Uncaught" && exceptionDesc ? `Uncaught: ${exceptionDesc}` : baseText;
2243
+ return {
2244
+ level: "error",
2245
+ text,
2246
+ timestamp: event.timestamp,
2247
+ ...source ? { source } : {},
2248
+ ...stack && stack.length > 0 ? { stack } : {},
2249
+ ...exception ? { exception } : {}
2250
+ };
2251
+ }
2252
+ case "Log.entryAdded": {
2253
+ const entry = params.entry;
2254
+ if (!entry) {
2255
+ return null;
2256
+ }
2257
+ const stack = this.toStackFrames(entry.stackTrace);
2258
+ const source = this.toSourceLocation({
2259
+ url: entry.url,
2260
+ lineNumber: entry.lineNumber,
2261
+ columnNumber: void 0
2262
+ });
2263
+ return {
2264
+ level: entry.level ?? "log",
2265
+ text: entry.text ?? "",
2266
+ timestamp: event.timestamp,
2267
+ ...source ? { source } : {},
2268
+ ...stack && stack.length > 0 ? { stack } : {}
2269
+ };
2270
+ }
2271
+ default:
2272
+ return null;
2273
+ }
2274
+ }
2275
+ toSourceLocation(input) {
2276
+ const url = typeof input.url === "string" && input.url.length > 0 ? input.url : void 0;
2277
+ const line = typeof input.lineNumber === "number" && Number.isFinite(input.lineNumber) ? Math.max(1, Math.floor(input.lineNumber) + 1) : void 0;
2278
+ const column = typeof input.columnNumber === "number" && Number.isFinite(input.columnNumber) ? Math.max(1, Math.floor(input.columnNumber) + 1) : void 0;
2279
+ if (!url && !line && !column) {
2280
+ return void 0;
2281
+ }
2282
+ return {
2283
+ ...url ? { url } : {},
2284
+ ...line ? { line } : {},
2285
+ ...column ? { column } : {}
2286
+ };
2287
+ }
2288
+ toStackFrames(stackTrace) {
2289
+ const frames = [];
2290
+ const collect = (trace) => {
2291
+ if (!trace || typeof trace !== "object") {
2292
+ return;
2293
+ }
2294
+ const callFrames = trace.callFrames;
2295
+ if (Array.isArray(callFrames)) {
2296
+ for (const frame of callFrames) {
2297
+ if (!frame || typeof frame !== "object") {
2298
+ continue;
2299
+ }
2300
+ const functionName = typeof frame.functionName === "string" ? String(frame.functionName) : void 0;
2301
+ const url = typeof frame.url === "string" ? String(frame.url) : void 0;
2302
+ const lineNumber = frame.lineNumber;
2303
+ const columnNumber = frame.columnNumber;
2304
+ const loc = this.toSourceLocation({ url, lineNumber, columnNumber });
2305
+ frames.push({
2306
+ ...functionName ? { functionName } : {},
2307
+ ...loc?.url ? { url: loc.url } : {},
2308
+ ...loc?.line ? { line: loc.line } : {},
2309
+ ...loc?.column ? { column: loc.column } : {}
2310
+ });
2311
+ if (frames.length >= 50) {
2312
+ return;
2313
+ }
2314
+ }
2315
+ }
2316
+ const parent = trace.parent;
2317
+ if (frames.length < 50 && parent) {
2318
+ collect(parent);
2319
+ }
2320
+ };
2321
+ collect(stackTrace);
2322
+ return frames.length > 0 ? frames : void 0;
2323
+ }
2324
+ toRemoteObjectSummary(obj) {
2325
+ if (!obj || typeof obj !== "object") {
2326
+ return void 0;
2327
+ }
2328
+ const raw = obj;
2329
+ const type = typeof raw.type === "string" ? raw.type : void 0;
2330
+ const subtype = typeof raw.subtype === "string" ? raw.subtype : void 0;
2331
+ const description = typeof raw.description === "string" ? raw.description : void 0;
2332
+ const unserializableValue = typeof raw.unserializableValue === "string" ? raw.unserializableValue : void 0;
2333
+ const out = {};
2334
+ if (type) out.type = type;
2335
+ if (subtype) out.subtype = subtype;
2336
+ if (description) out.description = description;
2337
+ if (raw.value !== void 0) out.value = raw.value;
2338
+ if (unserializableValue) out.unserializableValue = unserializableValue;
2339
+ return Object.keys(out).length > 0 ? out : void 0;
2340
+ }
2341
+ stringifyRemoteObject(value) {
2342
+ if (!value || typeof value !== "object") {
2343
+ return String(value ?? "");
2344
+ }
2345
+ const obj = value;
2346
+ if (obj.unserializableValue) {
2347
+ return obj.unserializableValue;
2348
+ }
2349
+ if (obj.value !== void 0) {
2350
+ try {
2351
+ return typeof obj.value === "string" ? obj.value : JSON.stringify(obj.value);
2352
+ } catch {
2353
+ return String(obj.value);
2354
+ }
2355
+ }
2356
+ if (obj.description) {
2357
+ return obj.description;
2358
+ }
2359
+ return obj.type ?? "";
2360
+ }
2361
+ buildHar(events, title) {
2362
+ const requests = /* @__PURE__ */ new Map();
2363
+ const toTimestamp = (event, fallback) => {
2364
+ const raw = event.params?.wallTime;
2365
+ if (typeof raw === "number") {
2366
+ return raw * 1e3;
2367
+ }
2368
+ const ts = event.params?.timestamp;
2369
+ if (typeof ts === "number") {
2370
+ return ts * 1e3;
2371
+ }
2372
+ const parsed = Date.parse(event.timestamp);
2373
+ if (Number.isFinite(parsed)) {
2374
+ return parsed;
2375
+ }
2376
+ return fallback ?? Date.now();
2377
+ };
2378
+ for (const event of events) {
2379
+ const params = event.params ?? {};
2380
+ switch (event.method) {
2381
+ case "Network.requestWillBeSent": {
2382
+ const requestId = String(
2383
+ params.requestId
2384
+ );
2385
+ if (!requestId) {
2386
+ break;
2387
+ }
2388
+ const request = params.request;
2389
+ const record = {
2390
+ id: requestId,
2391
+ url: request?.url,
2392
+ method: request?.method,
2393
+ requestHeaders: request?.headers ?? {},
2394
+ startTime: toTimestamp(event)
2395
+ };
2396
+ requests.set(requestId, record);
2397
+ break;
2398
+ }
2399
+ case "Network.responseReceived": {
2400
+ const requestId = String(
2401
+ params.requestId
2402
+ );
2403
+ if (!requestId) {
2404
+ break;
2405
+ }
2406
+ const response = params.response;
2407
+ const record = requests.get(requestId) ?? { id: requestId };
2408
+ record.status = response?.status;
2409
+ record.statusText = response?.statusText;
2410
+ record.mimeType = response?.mimeType;
2411
+ record.responseHeaders = response?.headers ?? {};
2412
+ record.protocol = response?.protocol;
2413
+ record.startTime = record.startTime ?? toTimestamp(event);
2414
+ requests.set(requestId, record);
2415
+ break;
2416
+ }
2417
+ case "Network.loadingFinished": {
2418
+ const requestId = String(
2419
+ params.requestId
2420
+ );
2421
+ if (!requestId) {
2422
+ break;
2423
+ }
2424
+ const record = requests.get(requestId) ?? { id: requestId };
2425
+ record.encodedDataLength = params.encodedDataLength;
2426
+ record.endTime = toTimestamp(event, record.startTime);
2427
+ requests.set(requestId, record);
2428
+ break;
2429
+ }
2430
+ case "Network.loadingFailed": {
2431
+ const requestId = String(
2432
+ params.requestId
2433
+ );
2434
+ if (!requestId) {
2435
+ break;
2436
+ }
2437
+ const record = requests.get(requestId) ?? { id: requestId };
2438
+ record.endTime = toTimestamp(event, record.startTime);
2439
+ requests.set(requestId, record);
2440
+ break;
2441
+ }
2442
+ default:
2443
+ break;
2444
+ }
2445
+ }
2446
+ const entries = Array.from(requests.values()).map((record) => {
2447
+ const started = record.startTime ?? Date.now();
2448
+ const ended = record.endTime ?? started;
2449
+ const time = Math.max(0, ended - started);
2450
+ const url = record.url ?? "";
2451
+ const queryString = [];
2452
+ try {
2453
+ const parsed = new URL(url);
2454
+ parsed.searchParams.forEach((value, name) => {
2455
+ queryString.push({ name, value });
2456
+ });
2457
+ } catch {
2458
+ }
2459
+ return {
2460
+ pageref: "page_0",
2461
+ startedDateTime: new Date(started).toISOString(),
2462
+ time,
2463
+ request: {
2464
+ method: record.method ?? "GET",
2465
+ url,
2466
+ httpVersion: record.protocol ?? "HTTP/1.1",
2467
+ cookies: [],
2468
+ headers: [],
2469
+ queryString,
2470
+ headersSize: -1,
2471
+ bodySize: -1
2472
+ },
2473
+ response: {
2474
+ status: record.status ?? 0,
2475
+ statusText: record.statusText ?? "",
2476
+ httpVersion: record.protocol ?? "HTTP/1.1",
2477
+ cookies: [],
2478
+ headers: [],
2479
+ redirectURL: "",
2480
+ headersSize: -1,
2481
+ bodySize: record.encodedDataLength ?? 0,
2482
+ content: {
2483
+ size: record.encodedDataLength ?? 0,
2484
+ mimeType: record.mimeType ?? ""
2485
+ }
2486
+ },
2487
+ cache: {},
2488
+ timings: {
2489
+ send: 0,
2490
+ wait: time,
2491
+ receive: 0
2492
+ }
2493
+ };
2494
+ });
2495
+ const startedDateTime = entries.length ? entries[0].startedDateTime : (/* @__PURE__ */ new Date()).toISOString();
2496
+ return {
2497
+ log: {
2498
+ version: "1.2",
2499
+ creator: {
2500
+ name: "browser-bridge",
2501
+ version: "0.0.0"
2502
+ },
2503
+ pages: [
2504
+ {
2505
+ id: "page_0",
2506
+ title: title ?? "page",
2507
+ startedDateTime,
2508
+ pageTimings: {
2509
+ onContentLoad: -1,
2510
+ onLoad: -1
2511
+ }
2512
+ }
2513
+ ],
2514
+ entries
2515
+ }
2516
+ };
2517
+ }
2518
+ markInspectConnected(sessionId) {
2519
+ try {
2520
+ const session = this.registry.require(sessionId);
2521
+ if (session.state === "INIT" /* INIT */ || session.state === "DRIVE_READY" /* DRIVE_READY */) {
2522
+ this.registry.apply(sessionId, "INSPECT_CONNECTED");
2523
+ } else if (session.state === "DEGRADED_INSPECT" /* DEGRADED_INSPECT */) {
2524
+ this.registry.apply(sessionId, "RECOVER_SUCCEEDED");
2525
+ }
2526
+ } catch {
2527
+ }
2528
+ }
2529
+ recordError(error) {
2530
+ this.lastError = error;
2531
+ this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
2532
+ }
2533
+ buildUnavailableError() {
2534
+ return new InspectError(
2535
+ "INSPECT_UNAVAILABLE",
2536
+ "Inspect is not available until the debugger bridge is configured.",
2537
+ { retryable: false }
2538
+ );
2539
+ }
2540
+ throwUnavailable() {
2541
+ const error = this.buildUnavailableError();
2542
+ this.recordError(error);
2543
+ throw error;
2544
+ }
2545
+ requireSession(sessionId) {
2546
+ try {
2547
+ return this.registry.require(sessionId);
2548
+ } catch (error) {
2549
+ if (error instanceof SessionError) {
2550
+ const code = error.code === "SESSION_CLOSED" ? "SESSION_CLOSED" : "SESSION_NOT_FOUND";
2551
+ const wrapped2 = new InspectError(code, error.message);
2552
+ this.recordError(wrapped2);
2553
+ throw wrapped2;
2554
+ }
2555
+ const wrapped = new InspectError("INTERNAL", "Failed to load session.");
2556
+ this.recordError(wrapped);
2557
+ throw wrapped;
2558
+ }
2559
+ }
2560
+ };
2561
+ var createInspectService = (options) => new InspectService(options);
2562
+
2563
+ // packages/shared/src/errors.ts
2564
+ var import_zod = require("zod");
2565
+ var ErrorCodeSchema = import_zod.z.enum([
2566
+ "UNKNOWN",
2567
+ "INVALID_ARGUMENT",
2568
+ "NOT_FOUND",
2569
+ "ALREADY_EXISTS",
2570
+ "FAILED_PRECONDITION",
2571
+ "UNAUTHORIZED",
2572
+ "FORBIDDEN",
2573
+ "CONFLICT",
2574
+ "TIMEOUT",
2575
+ "CANCELLED",
2576
+ "UNAVAILABLE",
2577
+ "RATE_LIMITED",
2578
+ "NOT_IMPLEMENTED",
2579
+ "INTERNAL",
2580
+ "SESSION_NOT_FOUND",
2581
+ "SESSION_CLOSED",
2582
+ "SESSION_BROKEN",
2583
+ "DRIVE_UNAVAILABLE",
2584
+ "INSPECT_UNAVAILABLE",
2585
+ "EXTENSION_DISCONNECTED",
2586
+ "DEBUGGER_IN_USE",
2587
+ "ATTACH_DENIED",
2588
+ "TAB_NOT_FOUND",
2589
+ "NOT_SUPPORTED",
2590
+ "LOCATOR_NOT_FOUND",
2591
+ "NAVIGATION_FAILED",
2592
+ "EVALUATION_FAILED",
2593
+ "ARTIFACT_IO_ERROR"
2594
+ ]);
2595
+ var ErrorInfoSchema = import_zod.z.object({
2596
+ code: ErrorCodeSchema,
2597
+ message: import_zod.z.string(),
2598
+ retryable: import_zod.z.boolean(),
2599
+ details: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
2600
+ });
2601
+ var ErrorEnvelopeSchema = import_zod.z.object({
2602
+ ok: import_zod.z.literal(false),
2603
+ error: ErrorInfoSchema
2604
+ });
2605
+ var successEnvelopeSchema = (result) => import_zod.z.object({
2606
+ ok: import_zod.z.literal(true),
2607
+ result
2608
+ });
2609
+ var apiEnvelopeSchema = (result) => import_zod.z.union([successEnvelopeSchema(result), ErrorEnvelopeSchema]);
2610
+
2611
+ // packages/shared/src/schemas.ts
2612
+ var import_zod2 = require("zod");
2613
+ var LocatorRoleSchema = import_zod2.z.object({
2614
+ name: import_zod2.z.string(),
2615
+ value: import_zod2.z.string().optional()
2616
+ });
2617
+ var LocatorSchema = import_zod2.z.object({
2618
+ ref: import_zod2.z.string().min(1).optional(),
2619
+ testid: import_zod2.z.string().min(1).optional(),
2620
+ css: import_zod2.z.string().min(1).optional(),
2621
+ text: import_zod2.z.string().min(1).optional(),
2622
+ role: LocatorRoleSchema.optional()
2623
+ }).refine(
2624
+ (value) => Boolean(
2625
+ value.ref || value.testid || value.css || value.text || value.role
2626
+ ),
2627
+ {
2628
+ message: "Locator must include at least one selector."
2629
+ }
2630
+ );
2631
+ var OpResultSchema = import_zod2.z.object({
2632
+ ok: import_zod2.z.literal(true),
2633
+ message: import_zod2.z.string().optional(),
2634
+ warnings: import_zod2.z.array(import_zod2.z.string()).optional()
2635
+ });
2636
+ var SessionStateSchema = import_zod2.z.enum([
2637
+ "INIT",
2638
+ "DRIVE_READY",
2639
+ "INSPECT_READY",
2640
+ "READY",
2641
+ "DEGRADED_DRIVE",
2642
+ "DEGRADED_INSPECT",
2643
+ "BROKEN",
2644
+ "CLOSED"
2645
+ ]);
2646
+ var SessionInfoSchema = import_zod2.z.object({
2647
+ session_id: import_zod2.z.string(),
2648
+ state: SessionStateSchema,
2649
+ created_at: import_zod2.z.string().datetime().optional()
2650
+ });
2651
+ var SessionPlaneStatusSchema = import_zod2.z.object({
2652
+ connected: import_zod2.z.boolean(),
2653
+ last_seen_at: import_zod2.z.string().datetime().optional(),
2654
+ error: ErrorInfoSchema.optional()
2655
+ });
2656
+ var SessionStatusSchema = import_zod2.z.object({
2657
+ session_id: import_zod2.z.string(),
2658
+ state: SessionStateSchema,
2659
+ drive: SessionPlaneStatusSchema.optional(),
2660
+ inspect: SessionPlaneStatusSchema.optional(),
2661
+ updated_at: import_zod2.z.string().datetime().optional()
2662
+ });
2663
+ var RecoverResultSchema = import_zod2.z.object({
2664
+ session_id: import_zod2.z.string(),
2665
+ recovered: import_zod2.z.boolean(),
2666
+ state: SessionStateSchema,
2667
+ message: import_zod2.z.string().optional()
2668
+ });
2669
+ var DiagnosticCheckSchema = import_zod2.z.object({
2670
+ name: import_zod2.z.string(),
2671
+ ok: import_zod2.z.boolean(),
2672
+ message: import_zod2.z.string().optional(),
2673
+ details: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
2674
+ });
2675
+ var DiagnosticReportSchema = import_zod2.z.object({
2676
+ ok: import_zod2.z.boolean(),
2677
+ session_id: import_zod2.z.string().optional(),
2678
+ checks: import_zod2.z.array(DiagnosticCheckSchema).optional(),
2679
+ extension: import_zod2.z.object({
2680
+ connected: import_zod2.z.boolean().optional(),
2681
+ version: import_zod2.z.string().optional(),
2682
+ last_seen_at: import_zod2.z.string().datetime().optional()
2683
+ }).optional(),
2684
+ debugger: import_zod2.z.object({
2685
+ attached: import_zod2.z.boolean().optional(),
2686
+ idle_timeout_ms: import_zod2.z.number().finite().optional(),
2687
+ console_buffer_size: import_zod2.z.number().finite().optional(),
2688
+ network_buffer_size: import_zod2.z.number().finite().optional(),
2689
+ last_error: ErrorInfoSchema.optional()
2690
+ }).optional(),
2691
+ artifacts: import_zod2.z.object({
2692
+ root_dir: import_zod2.z.string().optional()
2693
+ }).optional(),
2694
+ recovery: import_zod2.z.object({
2695
+ last_attempt: import_zod2.z.object({
2696
+ session_id: import_zod2.z.string(),
2697
+ recovered: import_zod2.z.boolean(),
2698
+ state: SessionStateSchema,
2699
+ message: import_zod2.z.string().optional(),
2700
+ at: import_zod2.z.string()
2701
+ }).optional(),
2702
+ attempts: import_zod2.z.array(
2703
+ import_zod2.z.object({
2704
+ session_id: import_zod2.z.string(),
2705
+ recovered: import_zod2.z.boolean(),
2706
+ state: SessionStateSchema,
2707
+ message: import_zod2.z.string().optional(),
2708
+ at: import_zod2.z.string()
2709
+ })
2710
+ ).optional(),
2711
+ success_count: import_zod2.z.number().finite().optional(),
2712
+ failure_count: import_zod2.z.number().finite().optional(),
2713
+ success_rate: import_zod2.z.number().finite().optional(),
2714
+ recent_failure_count: import_zod2.z.number().finite().optional(),
2715
+ loop_detected: import_zod2.z.boolean().optional()
2716
+ }).optional(),
2717
+ warnings: import_zod2.z.array(import_zod2.z.string()).optional(),
2718
+ notes: import_zod2.z.array(import_zod2.z.string()).optional()
2719
+ });
2720
+ var SessionIdSchema = import_zod2.z.object({
2721
+ session_id: import_zod2.z.string().min(1)
2722
+ });
2723
+ var SessionCreateInputSchema = import_zod2.z.object({}).strict().default({});
2724
+ var SessionCreateOutputSchema = SessionInfoSchema;
2725
+ var SessionStatusInputSchema = SessionIdSchema;
2726
+ var SessionStatusOutputSchema = SessionStatusSchema;
2727
+ var SessionRecoverInputSchema = SessionIdSchema;
2728
+ var SessionRecoverOutputSchema = RecoverResultSchema;
2729
+ var SessionCloseInputSchema = SessionIdSchema;
2730
+ var SessionCloseOutputSchema = import_zod2.z.object({
2731
+ ok: import_zod2.z.boolean()
2732
+ });
2733
+ var DriveWaitConditionSchema = import_zod2.z.object({
2734
+ kind: import_zod2.z.enum(["locator_visible", "text_present", "url_matches"]),
2735
+ value: import_zod2.z.string().min(1)
2736
+ });
2737
+ var DriveNavigateInputSchema = import_zod2.z.object({
2738
+ session_id: import_zod2.z.string().min(1),
2739
+ url: import_zod2.z.string().min(1),
2740
+ tab_id: import_zod2.z.number().finite().optional(),
2741
+ wait: import_zod2.z.enum(["none", "domcontentloaded"]).default("domcontentloaded")
2742
+ });
2743
+ var DriveNavigateOutputSchema = OpResultSchema;
2744
+ var DriveGoBackInputSchema = import_zod2.z.object({
2745
+ session_id: import_zod2.z.string().min(1),
2746
+ tab_id: import_zod2.z.number().finite().optional()
2747
+ });
2748
+ var DriveGoBackOutputSchema = OpResultSchema;
2749
+ var DriveGoForwardInputSchema = import_zod2.z.object({
2750
+ session_id: import_zod2.z.string().min(1),
2751
+ tab_id: import_zod2.z.number().finite().optional()
2752
+ });
2753
+ var DriveGoForwardOutputSchema = OpResultSchema;
2754
+ var DriveBackInputSchema = DriveGoBackInputSchema;
2755
+ var DriveBackOutputSchema = OpResultSchema;
2756
+ var DriveForwardInputSchema = DriveGoForwardInputSchema;
2757
+ var DriveForwardOutputSchema = OpResultSchema;
2758
+ var DriveClickInputSchema = import_zod2.z.object({
2759
+ session_id: import_zod2.z.string().min(1),
2760
+ locator: LocatorSchema,
2761
+ tab_id: import_zod2.z.number().finite().optional(),
2762
+ click_count: import_zod2.z.number().finite().optional()
2763
+ });
2764
+ var DriveClickOutputSchema = OpResultSchema;
2765
+ var DriveHoverInputSchema = import_zod2.z.object({
2766
+ session_id: import_zod2.z.string().min(1),
2767
+ locator: LocatorSchema,
2768
+ delay_ms: import_zod2.z.number().min(0).max(1e4).optional(),
2769
+ tab_id: import_zod2.z.number().finite().optional()
2770
+ });
2771
+ var DriveHoverOutputSchema = import_zod2.z.object({
2772
+ format: import_zod2.z.literal("html"),
2773
+ snapshot: import_zod2.z.string()
2774
+ });
2775
+ var DriveSelectInputSchema = import_zod2.z.object({
2776
+ session_id: import_zod2.z.string().min(1),
2777
+ locator: LocatorSchema,
2778
+ value: import_zod2.z.string().min(1).optional(),
2779
+ text: import_zod2.z.string().min(1).optional(),
2780
+ index: import_zod2.z.number().int().min(0).optional(),
2781
+ tab_id: import_zod2.z.number().finite().optional()
2782
+ }).refine((value) => value.value || value.text || value.index !== void 0, {
2783
+ message: "Either value, text, or index must be provided.",
2784
+ path: ["select"]
2785
+ });
2786
+ var DriveSelectOutputSchema = OpResultSchema;
2787
+ var DriveTypeInputSchema = import_zod2.z.object({
2788
+ session_id: import_zod2.z.string().min(1),
2789
+ locator: LocatorSchema.optional(),
2790
+ text: import_zod2.z.string().min(1),
2791
+ tab_id: import_zod2.z.number().finite().optional(),
2792
+ clear: import_zod2.z.boolean().default(false),
2793
+ submit: import_zod2.z.boolean().default(false)
2794
+ });
2795
+ var DriveTypeOutputSchema = OpResultSchema;
2796
+ var DriveFillFormFieldSchema = import_zod2.z.object({
2797
+ selector: import_zod2.z.string().min(1).optional(),
2798
+ locator: LocatorSchema.optional(),
2799
+ value: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.boolean()]),
2800
+ type: import_zod2.z.enum(["auto", "text", "select", "checkbox", "radio", "contentEditable"]).default("auto"),
2801
+ submit: import_zod2.z.boolean().default(false)
2802
+ }).refine((value) => Boolean(value.selector || value.locator), {
2803
+ message: "fill_form field requires selector or locator.",
2804
+ path: ["selector"]
2805
+ });
2806
+ var DriveFillFormInputSchema = import_zod2.z.object({
2807
+ session_id: import_zod2.z.string().min(1),
2808
+ fields: import_zod2.z.array(DriveFillFormFieldSchema).min(1),
2809
+ tab_id: import_zod2.z.number().finite().optional()
2810
+ });
2811
+ var DriveFillFormOutputSchema = import_zod2.z.object({
2812
+ filled: import_zod2.z.number().finite(),
2813
+ attempted: import_zod2.z.number().finite(),
2814
+ errors: import_zod2.z.array(import_zod2.z.string()).optional()
2815
+ });
2816
+ var DriveDragInputSchema = import_zod2.z.object({
2817
+ session_id: import_zod2.z.string().min(1),
2818
+ from: LocatorSchema,
2819
+ to: LocatorSchema,
2820
+ steps: import_zod2.z.number().min(1).max(50).default(12),
2821
+ tab_id: import_zod2.z.number().finite().optional()
2822
+ });
2823
+ var DriveDragOutputSchema = OpResultSchema;
2824
+ var DriveHandleDialogInputSchema = import_zod2.z.object({
2825
+ session_id: import_zod2.z.string().min(1),
2826
+ action: import_zod2.z.enum(["accept", "dismiss"]),
2827
+ promptText: import_zod2.z.string().optional(),
2828
+ tab_id: import_zod2.z.number().finite().optional()
2829
+ });
2830
+ var DriveHandleDialogOutputSchema = OpResultSchema;
2831
+ var DialogAcceptInputSchema = SessionIdSchema.extend({
2832
+ promptText: import_zod2.z.string().optional(),
2833
+ tab_id: import_zod2.z.number().finite().optional()
2834
+ });
2835
+ var DialogAcceptOutputSchema = OpResultSchema;
2836
+ var DialogDismissInputSchema = SessionIdSchema.extend({
2837
+ tab_id: import_zod2.z.number().finite().optional()
2838
+ });
2839
+ var DialogDismissOutputSchema = OpResultSchema;
2840
+ var DriveKeyModifiersSchema = import_zod2.z.object({
2841
+ ctrl: import_zod2.z.boolean().optional(),
2842
+ alt: import_zod2.z.boolean().optional(),
2843
+ shift: import_zod2.z.boolean().optional(),
2844
+ meta: import_zod2.z.boolean().optional()
2845
+ });
2846
+ var DriveKeyPressInputSchema = import_zod2.z.object({
2847
+ session_id: import_zod2.z.string().min(1),
2848
+ key: import_zod2.z.string().min(1),
2849
+ modifiers: DriveKeyModifiersSchema.optional(),
2850
+ tab_id: import_zod2.z.number().finite().optional()
2851
+ });
2852
+ var DriveKeyPressOutputSchema = OpResultSchema;
2853
+ var DriveKeyModifierSchema = import_zod2.z.enum(["ctrl", "alt", "shift", "meta"]);
2854
+ var DriveKeyInputSchema = import_zod2.z.object({
2855
+ session_id: import_zod2.z.string().min(1),
2856
+ key: import_zod2.z.string().min(1),
2857
+ modifiers: import_zod2.z.array(DriveKeyModifierSchema).optional(),
2858
+ repeat: import_zod2.z.number().int().min(1).max(50).optional(),
2859
+ tab_id: import_zod2.z.number().finite().optional()
2860
+ });
2861
+ var DriveKeyOutputSchema = OpResultSchema;
2862
+ var DriveScrollInputSchema = import_zod2.z.object({
2863
+ session_id: import_zod2.z.string().min(1),
2864
+ delta_x: import_zod2.z.number().finite().optional(),
2865
+ delta_y: import_zod2.z.number().finite().optional(),
2866
+ top: import_zod2.z.number().finite().optional(),
2867
+ left: import_zod2.z.number().finite().optional(),
2868
+ behavior: import_zod2.z.enum(["auto", "smooth"]).optional(),
2869
+ tab_id: import_zod2.z.number().finite().optional()
2870
+ }).refine(
2871
+ (value) => value.delta_x !== void 0 || value.delta_y !== void 0 || value.top !== void 0 || value.left !== void 0,
2872
+ {
2873
+ message: "scroll requires delta_x/delta_y or top/left.",
2874
+ path: ["scroll"]
2875
+ }
2876
+ );
2877
+ var DriveScrollOutputSchema = OpResultSchema;
2878
+ var DriveWaitForInputSchema = import_zod2.z.object({
2879
+ session_id: import_zod2.z.string().min(1),
2880
+ condition: DriveWaitConditionSchema,
2881
+ timeout_ms: import_zod2.z.number().finite().optional(),
2882
+ tab_id: import_zod2.z.number().finite().optional()
2883
+ });
2884
+ var DriveWaitForOutputSchema = OpResultSchema;
2885
+ var DriveTabInfoSchema = import_zod2.z.object({
2886
+ tab_id: import_zod2.z.number().finite(),
2887
+ window_id: import_zod2.z.number().finite(),
2888
+ url: import_zod2.z.string().min(1),
2889
+ title: import_zod2.z.string(),
2890
+ active: import_zod2.z.boolean().optional(),
2891
+ last_active_at: import_zod2.z.string().datetime().optional()
2892
+ });
2893
+ var DriveTabListInputSchema = SessionIdSchema;
2894
+ var DriveTabListOutputSchema = import_zod2.z.object({
2895
+ tabs: import_zod2.z.array(DriveTabInfoSchema)
2896
+ });
2897
+ var DriveTabActivateInputSchema = import_zod2.z.object({
2898
+ session_id: import_zod2.z.string().min(1),
2899
+ tab_id: import_zod2.z.number().finite()
2900
+ });
2901
+ var DriveTabActivateOutputSchema = OpResultSchema;
2902
+ var DriveTabCloseInputSchema = import_zod2.z.object({
2903
+ session_id: import_zod2.z.string().min(1),
2904
+ tab_id: import_zod2.z.number().finite()
2905
+ });
2906
+ var DriveTabCloseOutputSchema = OpResultSchema;
2907
+ var InspectDomFormatSchema = import_zod2.z.enum(["ax", "html"]);
2908
+ var InspectConsistencySchema = import_zod2.z.enum(["best_effort", "quiesce"]);
2909
+ var TargetHintSchema = import_zod2.z.object({
2910
+ url: import_zod2.z.string().min(1).optional(),
2911
+ title: import_zod2.z.string().min(1).optional(),
2912
+ last_active_at: import_zod2.z.string().optional(),
2913
+ lastActiveAt: import_zod2.z.string().optional()
2914
+ });
2915
+ var FormFieldInfoSchema = import_zod2.z.object({
2916
+ name: import_zod2.z.string(),
2917
+ type: import_zod2.z.string(),
2918
+ value: import_zod2.z.string(),
2919
+ options: import_zod2.z.array(import_zod2.z.string()).optional()
2920
+ });
2921
+ var FormInfoSchema = import_zod2.z.object({
2922
+ selector: import_zod2.z.string(),
2923
+ action: import_zod2.z.string().optional(),
2924
+ method: import_zod2.z.string().optional(),
2925
+ fields: import_zod2.z.array(FormFieldInfoSchema)
2926
+ });
2927
+ var StorageEntrySchema = import_zod2.z.object({
2928
+ key: import_zod2.z.string(),
2929
+ value: import_zod2.z.string()
2930
+ });
2931
+ var PageStateSchema = import_zod2.z.object({
2932
+ forms: import_zod2.z.array(FormInfoSchema),
2933
+ localStorage: import_zod2.z.array(StorageEntrySchema),
2934
+ sessionStorage: import_zod2.z.array(StorageEntrySchema),
2935
+ cookies: import_zod2.z.array(StorageEntrySchema),
2936
+ warnings: import_zod2.z.array(import_zod2.z.string()).optional()
2937
+ });
2938
+ var DomSnapshotSchema = import_zod2.z.object({
2939
+ format: InspectDomFormatSchema,
2940
+ snapshot: import_zod2.z.unknown()
2941
+ }).passthrough();
2942
+ var InspectDomSnapshotInputSchema = import_zod2.z.object({
2943
+ session_id: import_zod2.z.string().min(1),
2944
+ format: InspectDomFormatSchema.default("ax"),
2945
+ consistency: InspectConsistencySchema.default("best_effort"),
2946
+ interactive: import_zod2.z.boolean().default(false),
2947
+ compact: import_zod2.z.boolean().default(false),
2948
+ selector: import_zod2.z.string().min(1).optional(),
2949
+ target: TargetHintSchema.optional()
2950
+ });
2951
+ var InspectDomSnapshotOutputSchema = DomSnapshotSchema;
2952
+ var DomDiffResultSchema = import_zod2.z.object({
2953
+ added: import_zod2.z.array(import_zod2.z.string()),
2954
+ removed: import_zod2.z.array(import_zod2.z.string()),
2955
+ changed: import_zod2.z.array(import_zod2.z.string()),
2956
+ summary: import_zod2.z.string()
2957
+ });
2958
+ var InspectDomDiffInputSchema = SessionIdSchema;
2959
+ var InspectDomDiffOutputSchema = DomDiffResultSchema;
2960
+ var InspectFindRoleInputSchema = SessionIdSchema.extend({
2961
+ kind: import_zod2.z.literal("role"),
2962
+ role: import_zod2.z.string().min(1),
2963
+ name: import_zod2.z.string().min(1).optional(),
2964
+ target: TargetHintSchema.optional()
2965
+ });
2966
+ var InspectFindTextInputSchema = SessionIdSchema.extend({
2967
+ kind: import_zod2.z.literal("text"),
2968
+ text: import_zod2.z.string().min(1),
2969
+ target: TargetHintSchema.optional()
2970
+ });
2971
+ var InspectFindLabelInputSchema = SessionIdSchema.extend({
2972
+ kind: import_zod2.z.literal("label"),
2973
+ label: import_zod2.z.string().min(1),
2974
+ target: TargetHintSchema.optional()
2975
+ });
2976
+ var InspectFindInputSchema = import_zod2.z.discriminatedUnion("kind", [
2977
+ InspectFindRoleInputSchema,
2978
+ InspectFindTextInputSchema,
2979
+ InspectFindLabelInputSchema
2980
+ ]);
2981
+ var InspectFindMatchSchema = import_zod2.z.object({
2982
+ ref: import_zod2.z.string(),
2983
+ role: import_zod2.z.string().optional(),
2984
+ name: import_zod2.z.string().optional()
2985
+ });
2986
+ var InspectFindOutputSchema = import_zod2.z.object({
2987
+ matches: import_zod2.z.array(InspectFindMatchSchema),
2988
+ warnings: import_zod2.z.array(import_zod2.z.string()).optional()
2989
+ });
2990
+ var InspectPageStateInputSchema = SessionIdSchema.extend({
2991
+ target: TargetHintSchema.optional()
2992
+ });
2993
+ var InspectPageStateOutputSchema = PageStateSchema;
2994
+ var InspectExtractContentFormatSchema = import_zod2.z.enum([
2995
+ "markdown",
2996
+ "text",
2997
+ "article_json"
2998
+ ]);
2999
+ var InspectExtractContentInputSchema = SessionIdSchema.extend({
3000
+ format: InspectExtractContentFormatSchema.default("markdown"),
3001
+ include_metadata: import_zod2.z.boolean().default(true),
3002
+ target: TargetHintSchema.optional()
3003
+ });
3004
+ var InspectExtractContentOutputSchema = import_zod2.z.object({
3005
+ content: import_zod2.z.string(),
3006
+ title: import_zod2.z.string().optional(),
3007
+ byline: import_zod2.z.string().optional(),
3008
+ excerpt: import_zod2.z.string().optional(),
3009
+ siteName: import_zod2.z.string().optional(),
3010
+ warnings: import_zod2.z.array(import_zod2.z.string()).optional()
3011
+ });
3012
+ var InspectConsoleListInputSchema = SessionIdSchema.extend({
3013
+ target: TargetHintSchema.optional()
3014
+ });
3015
+ var ConsoleSourceLocationSchema = import_zod2.z.object({
3016
+ url: import_zod2.z.string().optional(),
3017
+ // 1-based line/column for human readability.
3018
+ line: import_zod2.z.number().int().positive().optional(),
3019
+ column: import_zod2.z.number().int().positive().optional()
3020
+ }).passthrough();
3021
+ var ConsoleStackFrameSchema = import_zod2.z.object({
3022
+ functionName: import_zod2.z.string().optional(),
3023
+ url: import_zod2.z.string().optional(),
3024
+ // 1-based line/column for human readability.
3025
+ line: import_zod2.z.number().int().positive().optional(),
3026
+ column: import_zod2.z.number().int().positive().optional()
3027
+ }).passthrough();
3028
+ var ConsoleRemoteObjectSchema = import_zod2.z.object({
3029
+ type: import_zod2.z.string().optional(),
3030
+ subtype: import_zod2.z.string().optional(),
3031
+ description: import_zod2.z.string().optional(),
3032
+ value: import_zod2.z.unknown().optional(),
3033
+ unserializableValue: import_zod2.z.string().optional()
3034
+ }).passthrough();
3035
+ var ConsoleEntrySchema = import_zod2.z.object({
3036
+ level: import_zod2.z.string().optional(),
3037
+ text: import_zod2.z.string().optional(),
3038
+ timestamp: import_zod2.z.string().datetime().optional(),
3039
+ source: ConsoleSourceLocationSchema.optional(),
3040
+ stack: import_zod2.z.array(ConsoleStackFrameSchema).optional(),
3041
+ exception: ConsoleRemoteObjectSchema.optional(),
3042
+ args: import_zod2.z.array(ConsoleRemoteObjectSchema).optional()
3043
+ }).passthrough();
3044
+ var ConsoleListSchema = import_zod2.z.object({
3045
+ entries: import_zod2.z.array(ConsoleEntrySchema)
3046
+ }).passthrough();
3047
+ var InspectConsoleListOutputSchema = ConsoleListSchema;
3048
+ var ArtifactInfoSchema = import_zod2.z.object({
3049
+ artifact_id: import_zod2.z.string(),
3050
+ path: import_zod2.z.string(),
3051
+ mime: import_zod2.z.string()
3052
+ });
3053
+ var InspectNetworkHarInputSchema = SessionIdSchema.extend({
3054
+ target: TargetHintSchema.optional()
3055
+ });
3056
+ var InspectNetworkHarOutputSchema = ArtifactInfoSchema;
3057
+ var InspectEvaluateInputSchema = import_zod2.z.object({
3058
+ session_id: import_zod2.z.string().min(1),
3059
+ expression: import_zod2.z.string().min(1).optional(),
3060
+ target: TargetHintSchema.optional()
3061
+ });
3062
+ var EvaluateResultSchema = import_zod2.z.object({
3063
+ value: import_zod2.z.unknown().optional(),
3064
+ exception: import_zod2.z.unknown().optional()
3065
+ }).passthrough();
3066
+ var InspectEvaluateOutputSchema = EvaluateResultSchema;
3067
+ var InspectPerformanceMetricsInputSchema = SessionIdSchema.extend({
3068
+ target: TargetHintSchema.optional()
3069
+ });
3070
+ var PerformanceMetricSchema = import_zod2.z.object({
3071
+ name: import_zod2.z.string(),
3072
+ value: import_zod2.z.number().finite()
3073
+ }).passthrough();
3074
+ var PerformanceMetricsSchema = import_zod2.z.object({
3075
+ metrics: import_zod2.z.array(PerformanceMetricSchema)
3076
+ }).passthrough();
3077
+ var InspectPerformanceMetricsOutputSchema = PerformanceMetricsSchema;
3078
+ var ArtifactsScreenshotInputSchema = import_zod2.z.object({
3079
+ session_id: import_zod2.z.string().min(1),
3080
+ target: import_zod2.z.enum(["viewport", "full"]).default("viewport"),
3081
+ fullPage: import_zod2.z.boolean().default(false),
3082
+ format: import_zod2.z.enum(["png", "jpeg", "webp"]).default("png"),
3083
+ quality: import_zod2.z.number().min(0).max(100).optional()
3084
+ });
3085
+ var ArtifactsScreenshotOutputSchema = ArtifactInfoSchema;
3086
+ var DiagnosticsDoctorInputSchema = import_zod2.z.object({
3087
+ session_id: import_zod2.z.string().min(1).optional()
3088
+ });
3089
+ var DiagnosticsDoctorOutputSchema = DiagnosticReportSchema;
3090
+
3091
+ // packages/core/src/routes/artifacts.ts
3092
+ var sendArtifactsError = (res, code, message, details, retryable = false) => {
3093
+ sendError(res, errorStatus(code), {
3094
+ code,
3095
+ message,
3096
+ retryable,
3097
+ ...details ? { details } : {}
3098
+ });
3099
+ };
3100
+ var registerArtifactsRoutes = (router, options = {}) => {
3101
+ const inspect = options.inspectService ?? (options.registry ? createInspectService({ registry: options.registry }) : void 0);
3102
+ router.post("/artifacts/screenshot", async (req, res) => {
3103
+ if (!isRecord(req.body)) {
3104
+ sendArtifactsError(
3105
+ res,
3106
+ "INVALID_ARGUMENT",
3107
+ "Request body must be an object."
3108
+ );
3109
+ return;
3110
+ }
3111
+ const parsed = ArtifactsScreenshotInputSchema.safeParse(req.body);
3112
+ if (!parsed.success) {
3113
+ const issue = parsed.error.issues[0];
3114
+ sendArtifactsError(
3115
+ res,
3116
+ "INVALID_ARGUMENT",
3117
+ issue?.message ?? "Invalid screenshot request.",
3118
+ issue?.path.length ? {
3119
+ field: issue.path.map((part) => String(part)).join(".")
3120
+ } : void 0
3121
+ );
3122
+ return;
3123
+ }
3124
+ const input = parsed.data;
3125
+ const target = input.fullPage ? "full" : input.target;
3126
+ try {
3127
+ if (!inspect) {
3128
+ sendArtifactsError(
3129
+ res,
3130
+ "NOT_IMPLEMENTED",
3131
+ "artifacts.screenshot is not implemented yet."
3132
+ );
3133
+ return;
3134
+ }
3135
+ const hint = deriveHintFromTabs(
3136
+ options.extensionBridge?.getStatus().tabs ?? []
3137
+ );
3138
+ const result = await inspect.screenshot({
3139
+ sessionId: input.session_id,
3140
+ target,
3141
+ format: input.format,
3142
+ quality: input.quality,
3143
+ targetHint: hint
3144
+ });
3145
+ sendResult(res, result);
3146
+ } catch (error) {
3147
+ if (error instanceof InspectError) {
3148
+ sendArtifactsError(
3149
+ res,
3150
+ error.code,
3151
+ error.message,
3152
+ error.details,
3153
+ error.retryable
3154
+ );
3155
+ return;
3156
+ }
3157
+ sendArtifactsError(
3158
+ res,
3159
+ "ARTIFACT_IO_ERROR",
3160
+ "Failed to capture screenshot."
3161
+ );
3162
+ }
3163
+ });
3164
+ };
3165
+
3166
+ // packages/core/src/diagnostics.ts
3167
+ var buildDiagnosticReport = (sessionId, context = {}) => {
3168
+ const extensionConnected = context.extension?.connected ?? false;
3169
+ const debuggerAttached = context.debugger?.attached ?? false;
3170
+ const sessionState = context.sessionState;
3171
+ const checks = [
3172
+ {
3173
+ name: "extension.connected",
3174
+ ok: extensionConnected,
3175
+ message: extensionConnected ? "Extension is connected." : "Extension is not connected."
3176
+ },
3177
+ {
3178
+ name: "debugger.attached",
3179
+ ok: debuggerAttached,
3180
+ message: debuggerAttached ? "Debugger is attached." : "Debugger is not attached."
3181
+ },
3182
+ {
3183
+ name: "session.state",
3184
+ ok: Boolean(sessionState),
3185
+ message: sessionState ? `Session state is ${sessionState}.` : sessionId ? "Session state unavailable." : "Session id not provided.",
3186
+ details: {
3187
+ session_id: sessionId || null,
3188
+ state: sessionState ?? "UNKNOWN"
3189
+ }
3190
+ }
3191
+ ];
3192
+ if (context.driveLastError) {
3193
+ checks.push({
3194
+ name: "drive.last_error",
3195
+ ok: false,
3196
+ message: context.driveLastError.message,
3197
+ details: {
3198
+ code: context.driveLastError.code,
3199
+ retryable: context.driveLastError.retryable,
3200
+ at: context.driveLastError.at
3201
+ }
3202
+ });
3203
+ }
3204
+ if (context.inspectLastError) {
3205
+ checks.push({
3206
+ name: "inspect.last_error",
3207
+ ok: false,
3208
+ message: context.inspectLastError.message,
3209
+ details: {
3210
+ code: context.inspectLastError.code,
3211
+ retryable: context.inspectLastError.retryable,
3212
+ at: context.inspectLastError.at
3213
+ }
3214
+ });
3215
+ }
3216
+ if (context.recoveryAttempt) {
3217
+ checks.push({
3218
+ name: "recovery.last_attempt",
3219
+ ok: context.recoveryAttempt.recovered,
3220
+ message: context.recoveryAttempt.message ?? "Recovery attempt recorded.",
3221
+ details: {
3222
+ session_id: context.recoveryAttempt.sessionId,
3223
+ state: context.recoveryAttempt.state,
3224
+ at: context.recoveryAttempt.at
3225
+ }
3226
+ });
3227
+ }
3228
+ const formatRecoveryAttempt = (attempt) => ({
3229
+ session_id: attempt.sessionId,
3230
+ recovered: attempt.recovered,
3231
+ state: attempt.state,
3232
+ message: attempt.message,
3233
+ at: attempt.at
3234
+ });
3235
+ const recoveryAttempts = context.recoveryMetrics?.attempts;
3236
+ const lastRecoveryAttempt = context.recoveryAttempt ?? (recoveryAttempts && recoveryAttempts[recoveryAttempts.length - 1]);
3237
+ const report = {
3238
+ ok: checks.every((check) => check.ok),
3239
+ session_id: sessionId,
3240
+ checks,
3241
+ extension: {
3242
+ connected: extensionConnected,
3243
+ last_seen_at: context.extension?.lastSeenAt
3244
+ },
3245
+ debugger: context.debugger ? {
3246
+ attached: debuggerAttached,
3247
+ idle_timeout_ms: context.debugger.idle_timeout_ms,
3248
+ console_buffer_size: context.debugger.console_buffer_size,
3249
+ network_buffer_size: context.debugger.network_buffer_size,
3250
+ last_error: context.debugger.last_error
3251
+ } : void 0,
3252
+ artifacts: sessionId ? {
3253
+ root_dir: getArtifactRootDir(sessionId)
3254
+ } : void 0,
3255
+ recovery: context.recoveryMetrics || lastRecoveryAttempt ? {
3256
+ ...lastRecoveryAttempt ? { last_attempt: formatRecoveryAttempt(lastRecoveryAttempt) } : {},
3257
+ ...recoveryAttempts ? { attempts: recoveryAttempts.map(formatRecoveryAttempt) } : {},
3258
+ ...context.recoveryMetrics ? {
3259
+ success_count: context.recoveryMetrics.successCount,
3260
+ failure_count: context.recoveryMetrics.failureCount,
3261
+ success_rate: context.recoveryMetrics.successRate,
3262
+ recent_failure_count: context.recoveryMetrics.recentFailureCount,
3263
+ loop_detected: context.recoveryMetrics.loopDetected
3264
+ } : {}
3265
+ } : void 0,
3266
+ notes: ["Diagnostics include runtime status; some checks may be stubbed."]
3267
+ };
3268
+ return report;
3269
+ };
3270
+
3271
+ // packages/core/src/routes/diagnostics.ts
3272
+ var registerDiagnosticsRoutes = (router, options = {}) => {
3273
+ router.post("/diagnostics/doctor", (req, res) => {
3274
+ let sessionId;
3275
+ if (req.body !== void 0) {
3276
+ if (!isRecord(req.body)) {
3277
+ sendError(res, 400, {
3278
+ code: "INVALID_ARGUMENT",
3279
+ message: "Request body must be an object.",
3280
+ retryable: false
3281
+ });
3282
+ return;
3283
+ }
3284
+ const raw = req.body.session_id;
3285
+ if (raw !== void 0 && (typeof raw !== "string" || raw.length === 0)) {
3286
+ sendError(res, 400, {
3287
+ code: "INVALID_ARGUMENT",
3288
+ message: "session_id must be a non-empty string.",
3289
+ retryable: false,
3290
+ details: { field: "session_id" }
3291
+ });
3292
+ return;
3293
+ }
3294
+ sessionId = raw;
3295
+ }
3296
+ try {
3297
+ const context = {};
3298
+ if (options.registry && sessionId) {
3299
+ try {
3300
+ const session = options.registry.require(sessionId);
3301
+ context.sessionState = session.state;
3302
+ } catch {
3303
+ }
3304
+ }
3305
+ if (options.extensionBridge) {
3306
+ const status = options.extensionBridge.getStatus();
3307
+ context.extension = {
3308
+ connected: status.connected,
3309
+ lastSeenAt: status.lastSeenAt
3310
+ };
3311
+ }
3312
+ if (options.debuggerBridge) {
3313
+ const settings = options.debuggerBridge.getSettings();
3314
+ const lastError = options.debuggerBridge.getLastError();
3315
+ context.debugger = {
3316
+ attached: options.debuggerBridge.hasAttachments(),
3317
+ idle_timeout_ms: settings.idleTimeoutMs,
3318
+ console_buffer_size: settings.consoleBufferSize,
3319
+ network_buffer_size: settings.networkBufferSize,
3320
+ last_error: lastError ? {
3321
+ code: lastError.error.code,
3322
+ message: lastError.error.message,
3323
+ retryable: lastError.error.retryable,
3324
+ details: lastError.error.details
3325
+ } : void 0
3326
+ };
3327
+ }
3328
+ if (options.drive) {
3329
+ const lastError = options.drive.getLastError();
3330
+ if (lastError) {
3331
+ context.driveLastError = {
3332
+ code: lastError.error.code,
3333
+ message: lastError.error.message,
3334
+ retryable: lastError.error.retryable,
3335
+ at: lastError.at
3336
+ };
3337
+ }
3338
+ }
3339
+ if (options.inspectService) {
3340
+ const lastError = options.inspectService.getLastError();
3341
+ if (lastError) {
3342
+ context.inspectLastError = {
3343
+ code: lastError.error.code,
3344
+ message: lastError.error.message,
3345
+ retryable: lastError.error.retryable,
3346
+ at: lastError.at
3347
+ };
3348
+ }
3349
+ }
3350
+ if (options.recoveryTracker) {
3351
+ const attempt = options.recoveryTracker.getLastAttempt();
3352
+ if (attempt) {
3353
+ context.recoveryAttempt = {
3354
+ sessionId: attempt.sessionId,
3355
+ recovered: attempt.recovered,
3356
+ state: attempt.state,
3357
+ message: attempt.message,
3358
+ at: attempt.at
3359
+ };
3360
+ }
3361
+ context.recoveryMetrics = options.recoveryTracker.getMetrics();
3362
+ }
3363
+ const report = buildDiagnosticReport(sessionId, context);
3364
+ sendResult(res, report);
3365
+ } catch {
3366
+ sendError(res, 500, {
3367
+ code: "INTERNAL",
3368
+ message: "Failed to build diagnostics report.",
3369
+ retryable: false
3370
+ });
3371
+ }
3372
+ });
3373
+ };
3374
+
3375
+ // packages/core/src/routes/drive.ts
3376
+ var parseBody = (schema, body) => {
3377
+ const result = schema.safeParse(body);
3378
+ if (result.success) {
3379
+ return { data: result.data };
3380
+ }
3381
+ const issue = result.error.issues[0];
3382
+ const details = issue && issue.path.length > 0 ? { field: issue.path.map((part) => part.toString()).join(".") } : void 0;
3383
+ return {
3384
+ error: {
3385
+ message: issue?.message ?? "Request body is invalid.",
3386
+ ...details ? { details } : {}
3387
+ }
3388
+ };
3389
+ };
3390
+ var makeHandler = (action, schema, drive, options = {}) => {
3391
+ return (req, res) => {
3392
+ const parsed = parseBody(schema, req.body ?? {});
3393
+ if (parsed.error) {
3394
+ sendError(res, errorStatus("INVALID_ARGUMENT"), {
3395
+ code: "INVALID_ARGUMENT",
3396
+ message: parsed.error.message,
3397
+ retryable: false,
3398
+ ...parsed.error.details ? { details: parsed.error.details } : {}
3399
+ });
3400
+ return;
3401
+ }
3402
+ const body = parsed.data;
3403
+ const sessionId = body.session_id;
3404
+ const params = { ...body };
3405
+ delete params.session_id;
3406
+ const timeoutMs = options.timeoutFromParams ? options.timeoutFromParams(params) : void 0;
3407
+ void drive.execute(sessionId, action, params, timeoutMs).then((result) => {
3408
+ if (result.ok) {
3409
+ const payload = result.result === void 0 ? options.defaultResult ?? { ok: true } : result.result;
3410
+ sendResult(res, payload);
3411
+ return;
3412
+ }
3413
+ sendError(res, errorStatus(result.error.code), result.error);
3414
+ }).catch((error) => {
3415
+ console.error("Drive execute failed:", error);
3416
+ sendError(res, errorStatus("INTERNAL"), {
3417
+ code: "INTERNAL",
3418
+ message: "Unexpected error while executing drive action.",
3419
+ retryable: false,
3420
+ details: {
3421
+ hint: error instanceof Error ? error.message : "Unknown error."
3422
+ }
3423
+ });
3424
+ });
3425
+ };
3426
+ };
3427
+ var makeDialogHandler = (action, schema, drive) => {
3428
+ return (req, res) => {
3429
+ const parsed = parseBody(schema, req.body ?? {});
3430
+ if (parsed.error) {
3431
+ sendError(res, errorStatus("INVALID_ARGUMENT"), {
3432
+ code: "INVALID_ARGUMENT",
3433
+ message: parsed.error.message,
3434
+ retryable: false,
3435
+ ...parsed.error.details ? { details: parsed.error.details } : {}
3436
+ });
3437
+ return;
3438
+ }
3439
+ const body = parsed.data;
3440
+ const { session_id: sessionId, ...rest } = body;
3441
+ const params = { ...rest, action };
3442
+ void drive.execute(sessionId, "drive.handle_dialog", params).then((result) => {
3443
+ if (result.ok) {
3444
+ const payload = result.result === void 0 ? { ok: true } : result.result;
3445
+ sendResult(res, payload);
3446
+ return;
3447
+ }
3448
+ sendError(res, errorStatus(result.error.code), result.error);
3449
+ }).catch((error) => {
3450
+ console.error("Drive execute failed:", error);
3451
+ sendError(res, errorStatus("INTERNAL"), {
3452
+ code: "INTERNAL",
3453
+ message: "Unexpected error while executing drive action.",
3454
+ retryable: false,
3455
+ details: {
3456
+ hint: error instanceof Error ? error.message : "Unknown error."
3457
+ }
3458
+ });
3459
+ });
3460
+ };
3461
+ };
3462
+ var registerDriveRoutes = (router, options) => {
3463
+ const { drive } = options;
3464
+ router.post(
3465
+ "/drive/navigate",
3466
+ makeHandler("drive.navigate", DriveNavigateInputSchema, drive)
3467
+ );
3468
+ router.post(
3469
+ "/drive/go_back",
3470
+ makeHandler("drive.go_back", DriveGoBackInputSchema, drive)
3471
+ );
3472
+ router.post(
3473
+ "/drive/go_forward",
3474
+ makeHandler("drive.go_forward", DriveGoForwardInputSchema, drive)
3475
+ );
3476
+ router.post(
3477
+ "/drive/back",
3478
+ makeHandler("drive.back", DriveBackInputSchema, drive)
3479
+ );
3480
+ router.post(
3481
+ "/drive/forward",
3482
+ makeHandler("drive.forward", DriveForwardInputSchema, drive)
3483
+ );
3484
+ router.post(
3485
+ "/drive/click",
3486
+ makeHandler("drive.click", DriveClickInputSchema, drive)
3487
+ );
3488
+ router.post(
3489
+ "/drive/hover",
3490
+ makeHandler("drive.hover", DriveHoverInputSchema, drive)
3491
+ );
3492
+ router.post(
3493
+ "/drive/select",
3494
+ makeHandler("drive.select", DriveSelectInputSchema, drive)
3495
+ );
3496
+ router.post(
3497
+ "/drive/type",
3498
+ makeHandler("drive.type", DriveTypeInputSchema, drive)
3499
+ );
3500
+ router.post(
3501
+ "/drive/fill_form",
3502
+ makeHandler("drive.fill_form", DriveFillFormInputSchema, drive)
3503
+ );
3504
+ router.post(
3505
+ "/drive/drag",
3506
+ makeHandler("drive.drag", DriveDragInputSchema, drive)
3507
+ );
3508
+ router.post(
3509
+ "/drive/handle_dialog",
3510
+ makeHandler("drive.handle_dialog", DriveHandleDialogInputSchema, drive)
3511
+ );
3512
+ router.post(
3513
+ "/dialog/accept",
3514
+ makeDialogHandler("accept", DialogAcceptInputSchema, drive)
3515
+ );
3516
+ router.post(
3517
+ "/dialog/dismiss",
3518
+ makeDialogHandler("dismiss", DialogDismissInputSchema, drive)
3519
+ );
3520
+ router.post(
3521
+ "/drive/key",
3522
+ makeHandler("drive.key", DriveKeyInputSchema, drive)
3523
+ );
3524
+ router.post(
3525
+ "/drive/key_press",
3526
+ makeHandler("drive.key_press", DriveKeyPressInputSchema, drive)
3527
+ );
3528
+ router.post(
3529
+ "/drive/scroll",
3530
+ makeHandler("drive.scroll", DriveScrollInputSchema, drive)
3531
+ );
3532
+ router.post(
3533
+ "/drive/wait_for",
3534
+ makeHandler("drive.wait_for", DriveWaitForInputSchema, drive, {
3535
+ timeoutFromParams: (params) => {
3536
+ const timeout = params.timeout_ms;
3537
+ if (typeof timeout === "number" && Number.isFinite(timeout)) {
3538
+ return Math.max(0, timeout + 1e3);
3539
+ }
3540
+ return void 0;
3541
+ }
3542
+ })
3543
+ );
3544
+ router.post(
3545
+ "/drive/tab_list",
3546
+ makeHandler("drive.tab_list", DriveTabListInputSchema, drive)
3547
+ );
3548
+ router.post(
3549
+ "/drive/tab_activate",
3550
+ makeHandler("drive.tab_activate", DriveTabActivateInputSchema, drive)
3551
+ );
3552
+ router.post(
3553
+ "/drive/tab_close",
3554
+ makeHandler("drive.tab_close", DriveTabCloseInputSchema, drive)
3555
+ );
3556
+ };
3557
+
3558
+ // packages/core/src/routes/inspect.ts
3559
+ var sendInspectError = (res, code, message, retryable, details) => {
3560
+ sendError(res, errorStatus(code), {
3561
+ code,
3562
+ message,
3563
+ retryable,
3564
+ ...details ? { details } : {}
3565
+ });
3566
+ };
3567
+ var parseBody2 = (schema, body) => {
3568
+ const result = schema.safeParse(body);
3569
+ if (result.success) {
3570
+ return { data: result.data };
3571
+ }
3572
+ const issue = result.error.issues[0];
3573
+ const details = issue && issue.path.length > 0 ? { field: issue.path.map((part) => part.toString()).join(".") } : void 0;
3574
+ return {
3575
+ error: {
3576
+ message: issue?.message ?? "Request body is invalid.",
3577
+ ...details ? { details } : {}
3578
+ }
3579
+ };
3580
+ };
3581
+ var readTargetHint = (target) => {
3582
+ if (!target) {
3583
+ return void 0;
3584
+ }
3585
+ const url = typeof target.url === "string" ? target.url : void 0;
3586
+ const title = typeof target.title === "string" ? target.title : void 0;
3587
+ const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
3588
+ const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
3589
+ if (!url && !title && !lastActiveAt) {
3590
+ return void 0;
3591
+ }
3592
+ return { url, title, lastActiveAt };
3593
+ };
3594
+ var resolveTargetHint = (target, options) => {
3595
+ const explicit = readTargetHint(target);
3596
+ if (explicit) {
3597
+ return explicit;
3598
+ }
3599
+ const tabs = options.extensionBridge?.getStatus().tabs ?? [];
3600
+ return deriveHintFromTabs(tabs);
3601
+ };
3602
+ var makeHandler2 = (schema, handler) => async (req, res) => {
3603
+ const parsed = parseBody2(schema, req.body ?? {});
3604
+ if (parsed.error) {
3605
+ sendInspectError(
3606
+ res,
3607
+ "INVALID_ARGUMENT",
3608
+ parsed.error.message,
3609
+ false,
3610
+ parsed.error.details
3611
+ );
3612
+ return;
3613
+ }
3614
+ try {
3615
+ const result = await handler(parsed.data);
3616
+ sendResult(res, result);
3617
+ } catch (err) {
3618
+ if (err instanceof InspectError) {
3619
+ sendInspectError(
3620
+ res,
3621
+ err.code,
3622
+ err.message,
3623
+ err.retryable,
3624
+ err.details
3625
+ );
3626
+ return;
3627
+ }
3628
+ sendInspectError(res, "INTERNAL", "Unexpected inspect error.", false);
3629
+ }
3630
+ };
3631
+ var registerInspectRoutes = (router, options) => {
3632
+ const inspect = options.inspectService ?? createInspectService({
3633
+ registry: options.registry,
3634
+ extensionBridge: options.extensionBridge
3635
+ });
3636
+ router.post(
3637
+ "/inspect/dom_snapshot",
3638
+ makeHandler2(InspectDomSnapshotInputSchema, async (body) => {
3639
+ return await inspect.domSnapshot({
3640
+ sessionId: body.session_id,
3641
+ format: body.format,
3642
+ consistency: body.consistency,
3643
+ interactive: body.interactive,
3644
+ compact: body.compact,
3645
+ selector: body.selector,
3646
+ targetHint: resolveTargetHint(body.target, options)
3647
+ });
3648
+ })
3649
+ );
3650
+ router.post(
3651
+ "/inspect/dom_diff",
3652
+ makeHandler2(InspectDomDiffInputSchema, async (body) => {
3653
+ return inspect.domDiff({ sessionId: body.session_id });
3654
+ })
3655
+ );
3656
+ router.post(
3657
+ "/inspect/find",
3658
+ makeHandler2(InspectFindInputSchema, async (body) => {
3659
+ const targetHint = resolveTargetHint(body.target, options);
3660
+ if (body.kind === "role") {
3661
+ return await inspect.find({
3662
+ sessionId: body.session_id,
3663
+ kind: "role",
3664
+ role: body.role,
3665
+ name: body.name,
3666
+ targetHint
3667
+ });
3668
+ }
3669
+ if (body.kind === "text") {
3670
+ return await inspect.find({
3671
+ sessionId: body.session_id,
3672
+ kind: "text",
3673
+ text: body.text,
3674
+ targetHint
3675
+ });
3676
+ }
3677
+ return await inspect.find({
3678
+ sessionId: body.session_id,
3679
+ kind: "label",
3680
+ label: body.label,
3681
+ targetHint
3682
+ });
3683
+ })
3684
+ );
3685
+ router.post(
3686
+ "/inspect/extract_content",
3687
+ makeHandler2(InspectExtractContentInputSchema, async (body) => {
3688
+ return await inspect.extractContent({
3689
+ sessionId: body.session_id,
3690
+ format: body.format,
3691
+ includeMetadata: body.include_metadata,
3692
+ targetHint: resolveTargetHint(body.target, options)
3693
+ });
3694
+ })
3695
+ );
3696
+ router.post(
3697
+ "/inspect/page_state",
3698
+ makeHandler2(InspectPageStateInputSchema, async (body) => {
3699
+ return await inspect.pageState({
3700
+ sessionId: body.session_id,
3701
+ targetHint: resolveTargetHint(body.target, options)
3702
+ });
3703
+ })
3704
+ );
3705
+ router.post(
3706
+ "/inspect/console_list",
3707
+ makeHandler2(InspectConsoleListInputSchema, async (body) => {
3708
+ return await inspect.consoleList({
3709
+ sessionId: body.session_id,
3710
+ targetHint: resolveTargetHint(body.target, options)
3711
+ });
3712
+ })
3713
+ );
3714
+ router.post(
3715
+ "/inspect/network_har",
3716
+ makeHandler2(InspectNetworkHarInputSchema, async (body) => {
3717
+ return await inspect.networkHar({
3718
+ sessionId: body.session_id,
3719
+ targetHint: resolveTargetHint(body.target, options)
3720
+ });
3721
+ })
3722
+ );
3723
+ router.post(
3724
+ "/inspect/evaluate",
3725
+ makeHandler2(InspectEvaluateInputSchema, async (body) => {
3726
+ return await inspect.evaluate({
3727
+ sessionId: body.session_id,
3728
+ expression: body.expression,
3729
+ targetHint: resolveTargetHint(body.target, options)
3730
+ });
3731
+ })
3732
+ );
3733
+ router.post(
3734
+ "/inspect/performance_metrics",
3735
+ makeHandler2(InspectPerformanceMetricsInputSchema, async (body) => {
3736
+ return await inspect.performanceMetrics({
3737
+ sessionId: body.session_id,
3738
+ targetHint: resolveTargetHint(body.target, options)
3739
+ });
3740
+ })
3741
+ );
3742
+ };
3743
+
3744
+ // packages/core/src/recovery.ts
3745
+ var RecoveryTracker = class {
3746
+ constructor() {
3747
+ this.attempts = [];
3748
+ this.successCount = 0;
3749
+ this.failureCount = 0;
3750
+ this.maxAttempts = 10;
3751
+ this.loopWindowMs = 6e4;
3752
+ this.loopFailureThreshold = 3;
3753
+ }
3754
+ record(attempt) {
3755
+ this.attempts.push(attempt);
3756
+ if (attempt.recovered) {
3757
+ this.successCount += 1;
3758
+ } else {
3759
+ this.failureCount += 1;
3760
+ }
3761
+ if (this.attempts.length > this.maxAttempts) {
3762
+ this.attempts.shift();
3763
+ }
3764
+ }
3765
+ getLastAttempt() {
3766
+ return this.attempts[this.attempts.length - 1];
3767
+ }
3768
+ getAttempts() {
3769
+ return [...this.attempts];
3770
+ }
3771
+ getMetrics(now = Date.now()) {
3772
+ const recentFailureCount = this.attempts.reduce((count, attempt) => {
3773
+ if (attempt.recovered) {
3774
+ return count;
3775
+ }
3776
+ const timestamp = Date.parse(attempt.at);
3777
+ if (!Number.isFinite(timestamp)) {
3778
+ return count;
3779
+ }
3780
+ return now - timestamp <= this.loopWindowMs ? count + 1 : count;
3781
+ }, 0);
3782
+ const total = this.successCount + this.failureCount;
3783
+ const successRate = total > 0 ? this.successCount / total : 0;
3784
+ return {
3785
+ attempts: this.getAttempts(),
3786
+ successCount: this.successCount,
3787
+ failureCount: this.failureCount,
3788
+ successRate,
3789
+ recentFailureCount,
3790
+ loopDetected: recentFailureCount > this.loopFailureThreshold
3791
+ };
3792
+ }
3793
+ };
3794
+
3795
+ // packages/core/src/debugger-bridge.ts
3796
+ var DEFAULT_CONSOLE_BUFFER_SIZE = 200;
3797
+ var DEFAULT_NETWORK_BUFFER_SIZE = 500;
3798
+ var DEFAULT_IDLE_TIMEOUT_MS = 15e3;
3799
+ var CONSOLE_METHODS = /* @__PURE__ */ new Set([
3800
+ "Runtime.consoleAPICalled",
3801
+ "Runtime.exceptionThrown",
3802
+ "Log.entryAdded"
3803
+ ]);
3804
+ var isConsoleEvent = (method) => {
3805
+ if (CONSOLE_METHODS.has(method)) {
3806
+ return true;
3807
+ }
3808
+ return method.startsWith("Runtime.") && method.includes("console");
3809
+ };
3810
+ var isNetworkEvent = (method) => method.startsWith("Network.");
3811
+ var RingBuffer = class {
3812
+ constructor(capacity) {
3813
+ this.entries = [];
3814
+ this.capacity = Math.max(0, capacity);
3815
+ }
3816
+ push(entry) {
3817
+ if (this.capacity <= 0) {
3818
+ return;
3819
+ }
3820
+ if (this.entries.length >= this.capacity) {
3821
+ this.entries.shift();
3822
+ }
3823
+ this.entries.push(entry);
3824
+ }
3825
+ toArray() {
3826
+ return [...this.entries];
3827
+ }
3828
+ clear() {
3829
+ this.entries.length = 0;
3830
+ }
3831
+ };
3832
+ var DebuggerBridge = class {
3833
+ constructor(options) {
3834
+ this.tabs = /* @__PURE__ */ new Map();
3835
+ this.bridge = options.extensionBridge;
3836
+ this.consoleBufferSize = options.consoleBufferSize ?? DEFAULT_CONSOLE_BUFFER_SIZE;
3837
+ this.networkBufferSize = options.networkBufferSize ?? DEFAULT_NETWORK_BUFFER_SIZE;
3838
+ this.idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
3839
+ this.unsubscribe = this.bridge.onDebuggerEvent((event) => {
3840
+ this.handleDebuggerEvent(event);
3841
+ });
3842
+ }
3843
+ getLastError() {
3844
+ if (!this.lastError || !this.lastErrorAt) {
3845
+ return void 0;
3846
+ }
3847
+ return { error: this.lastError, at: this.lastErrorAt };
3848
+ }
3849
+ getSettings() {
3850
+ return {
3851
+ idleTimeoutMs: this.idleTimeoutMs,
3852
+ consoleBufferSize: this.consoleBufferSize,
3853
+ networkBufferSize: this.networkBufferSize
3854
+ };
3855
+ }
3856
+ hasAttachments() {
3857
+ for (const state of this.tabs.values()) {
3858
+ if (state.attached) {
3859
+ return true;
3860
+ }
3861
+ }
3862
+ return false;
3863
+ }
3864
+ getConsoleEvents(tabId) {
3865
+ return this.ensureTab(tabId).console.toArray();
3866
+ }
3867
+ getNetworkEvents(tabId) {
3868
+ return this.ensureTab(tabId).network.toArray();
3869
+ }
3870
+ clearBuffers(tabId) {
3871
+ const state = this.tabs.get(tabId);
3872
+ if (!state) {
3873
+ return;
3874
+ }
3875
+ state.console.clear();
3876
+ state.network.clear();
3877
+ }
3878
+ async attach(tabId) {
3879
+ const state = this.ensureTab(tabId);
3880
+ if (state.attached) {
3881
+ this.touch(tabId, state);
3882
+ return { ok: true, result: { attached: true } };
3883
+ }
3884
+ try {
3885
+ const response = await this.bridge.requestDebugger(
3886
+ "debugger.attach",
3887
+ { tab_id: tabId },
3888
+ this.idleTimeoutMs
3889
+ );
3890
+ if (response.status === "error") {
3891
+ const error = response.error ?? {
3892
+ code: "INSPECT_UNAVAILABLE",
3893
+ message: "Debugger attach failed.",
3894
+ retryable: false
3895
+ };
3896
+ this.recordError(error);
3897
+ return { ok: false, error };
3898
+ }
3899
+ state.attached = true;
3900
+ this.touch(tabId, state);
3901
+ return { ok: true, result: { attached: true } };
3902
+ } catch (error) {
3903
+ const info = this.handleBridgeError(error);
3904
+ return { ok: false, error: info };
3905
+ }
3906
+ }
3907
+ async detach(tabId) {
3908
+ const state = this.tabs.get(tabId);
3909
+ if (!state || !state.attached) {
3910
+ return { ok: true, result: { attached: false } };
3911
+ }
3912
+ try {
3913
+ const response = await this.bridge.requestDebugger(
3914
+ "debugger.detach",
3915
+ { tab_id: tabId },
3916
+ this.idleTimeoutMs
3917
+ );
3918
+ if (response.status === "error") {
3919
+ const error = response.error ?? {
3920
+ code: "INSPECT_UNAVAILABLE",
3921
+ message: "Debugger detach failed.",
3922
+ retryable: false
3923
+ };
3924
+ this.recordError(error);
3925
+ return { ok: false, error };
3926
+ }
3927
+ this.markDetached(tabId);
3928
+ return { ok: true, result: { attached: false } };
3929
+ } catch (error) {
3930
+ const info = this.handleBridgeError(error);
3931
+ this.markDetached(tabId);
3932
+ return { ok: false, error: info };
3933
+ }
3934
+ }
3935
+ async command(tabId, method, params, timeoutMs = 1e4) {
3936
+ const attachResult = await this.attach(tabId);
3937
+ if (!attachResult.ok) {
3938
+ return attachResult;
3939
+ }
3940
+ try {
3941
+ const response = await this.bridge.requestDebugger(
3942
+ "debugger.command",
3943
+ {
3944
+ tab_id: tabId,
3945
+ method,
3946
+ params
3947
+ },
3948
+ timeoutMs
3949
+ );
3950
+ if (response.status === "error") {
3951
+ const error = response.error ?? {
3952
+ code: "INSPECT_UNAVAILABLE",
3953
+ message: "Debugger command failed.",
3954
+ retryable: false
3955
+ };
3956
+ this.recordError(error);
3957
+ return { ok: false, error };
3958
+ }
3959
+ this.touch(tabId, this.ensureTab(tabId));
3960
+ return { ok: true, result: response.result };
3961
+ } catch (error) {
3962
+ const info = this.handleBridgeError(error);
3963
+ return { ok: false, error: info };
3964
+ }
3965
+ }
3966
+ shutdown() {
3967
+ this.unsubscribe();
3968
+ for (const [tabId, state] of this.tabs) {
3969
+ if (state.idleTimer) {
3970
+ clearTimeout(state.idleTimer);
3971
+ }
3972
+ this.tabs.delete(tabId);
3973
+ }
3974
+ }
3975
+ handleDebuggerEvent(event) {
3976
+ if (event.action !== "debugger.event") {
3977
+ return;
3978
+ }
3979
+ const params = event.params;
3980
+ if (!params || typeof params.tab_id !== "number") {
3981
+ return;
3982
+ }
3983
+ const record = {
3984
+ tab_id: params.tab_id,
3985
+ method: params.method,
3986
+ params: params.params,
3987
+ timestamp: params.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
3988
+ };
3989
+ const state = this.ensureTab(params.tab_id);
3990
+ if (record.method === "Debugger.detached") {
3991
+ this.markDetached(params.tab_id);
3992
+ return;
3993
+ }
3994
+ if (isConsoleEvent(record.method)) {
3995
+ state.console.push(record);
3996
+ } else if (isNetworkEvent(record.method)) {
3997
+ state.network.push(record);
3998
+ }
3999
+ this.touch(params.tab_id, state);
4000
+ }
4001
+ ensureTab(tabId) {
4002
+ const existing = this.tabs.get(tabId);
4003
+ if (existing) {
4004
+ return existing;
4005
+ }
4006
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4007
+ const state = {
4008
+ attached: false,
4009
+ lastActivityAt: now,
4010
+ console: new RingBuffer(this.consoleBufferSize),
4011
+ network: new RingBuffer(this.networkBufferSize)
4012
+ };
4013
+ this.tabs.set(tabId, state);
4014
+ return state;
4015
+ }
4016
+ touch(tabId, state) {
4017
+ state.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4018
+ this.scheduleIdleDetach(tabId, state);
4019
+ }
4020
+ scheduleIdleDetach(tabId, state) {
4021
+ if (state.idleTimer) {
4022
+ clearTimeout(state.idleTimer);
4023
+ }
4024
+ state.idleTimer = setTimeout(() => {
4025
+ void this.detach(tabId).catch((error) => {
4026
+ console.error("DebuggerBridge idle detach failed:", error);
4027
+ });
4028
+ }, this.idleTimeoutMs);
4029
+ }
4030
+ markDetached(tabId) {
4031
+ const state = this.tabs.get(tabId);
4032
+ if (!state) {
4033
+ return;
4034
+ }
4035
+ state.attached = false;
4036
+ if (state.idleTimer) {
4037
+ clearTimeout(state.idleTimer);
4038
+ state.idleTimer = void 0;
4039
+ }
4040
+ }
4041
+ recordError(error) {
4042
+ this.lastError = error;
4043
+ this.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
4044
+ }
4045
+ handleBridgeError(error) {
4046
+ if (error instanceof ExtensionBridgeError) {
4047
+ const info2 = toDriveError(error);
4048
+ this.recordError(info2);
4049
+ return info2;
4050
+ }
4051
+ const info = {
4052
+ code: "INSPECT_UNAVAILABLE",
4053
+ message: error instanceof Error ? error.message : "Debugger request failed.",
4054
+ retryable: false
4055
+ };
4056
+ this.recordError(info);
4057
+ return info;
4058
+ }
4059
+ };
4060
+
4061
+ // packages/core/src/server.ts
4062
+ var createCoreServer = (options = {}) => {
4063
+ const app = (0, import_express2.default)();
4064
+ const registry = options.registry ?? new SessionRegistry();
4065
+ const extensionBridge = new ExtensionBridge({ registry });
4066
+ const debuggerBridge = new DebuggerBridge({ extensionBridge });
4067
+ const drive = new DriveController(extensionBridge, registry);
4068
+ const inspect = createInspectService({
4069
+ registry,
4070
+ debuggerBridge,
4071
+ extensionBridge
4072
+ });
4073
+ const recoveryTracker = new RecoveryTracker();
4074
+ app.use(import_express2.default.json({ limit: "1mb" }));
4075
+ app.get("/health", (_req, res) => {
4076
+ res.status(200).json({ ok: true });
4077
+ });
4078
+ app.use(
4079
+ "/session",
4080
+ createSessionRouter(registry, {
4081
+ driveConnected: () => extensionBridge.isConnected(),
4082
+ inspectRecover: (sessionId) => inspect.reconnect(sessionId),
4083
+ recordRecovery: (attempt) => recoveryTracker.record(attempt)
4084
+ })
4085
+ );
4086
+ registerDriveRoutes(app, { drive });
4087
+ registerInspectRoutes(app, {
4088
+ registry,
4089
+ extensionBridge,
4090
+ inspectService: inspect
4091
+ });
4092
+ registerArtifactsRoutes(app, {
4093
+ registry,
4094
+ extensionBridge,
4095
+ inspectService: inspect
4096
+ });
4097
+ registerDiagnosticsRoutes(app, {
4098
+ registry,
4099
+ extensionBridge,
4100
+ debuggerBridge,
4101
+ drive,
4102
+ inspectService: inspect,
4103
+ recoveryTracker
4104
+ });
4105
+ return {
4106
+ app,
4107
+ registry,
4108
+ extensionBridge,
4109
+ debuggerBridge,
4110
+ drive,
4111
+ inspect,
4112
+ recoveryTracker
4113
+ };
4114
+ };
4115
+ var resolveCorePort = (portOverride) => {
4116
+ if (portOverride !== void 0) {
4117
+ return portOverride;
4118
+ }
4119
+ const env = process.env.BROWSER_BRIDGE_CORE_PORT || process.env.BROWSER_VISION_CORE_PORT;
4120
+ if (env) {
4121
+ const parsed = Number(env);
4122
+ if (Number.isFinite(parsed) && parsed > 0) {
4123
+ return parsed;
4124
+ }
4125
+ }
4126
+ return 3210;
4127
+ };
4128
+ var startCoreServer = (options = {}) => {
4129
+ const host = options.host ?? "127.0.0.1";
4130
+ const port = resolveCorePort(options.port);
4131
+ const { app, registry, extensionBridge } = createCoreServer({
4132
+ registry: options.registry
4133
+ });
4134
+ return new Promise((resolve, reject) => {
4135
+ const server = (0, import_http.createServer)(app);
4136
+ extensionBridge.attach(server);
4137
+ server.listen(port, host, () => {
4138
+ const address = server.address();
4139
+ const resolvedPort = typeof address === "object" && address !== null ? address.port : port;
4140
+ resolve({ app, registry, server, host, port: resolvedPort });
4141
+ });
4142
+ server.on("error", (error) => {
4143
+ reject(error);
4144
+ });
4145
+ });
4146
+ };
4147
+
4148
+ // packages/mcp-adapter/src/core-client.ts
4149
+ var DEFAULT_HOST = "127.0.0.1";
4150
+ var DEFAULT_PORT = 3210;
4151
+ var DEFAULT_TIMEOUT_MS = 4e3;
4152
+ var resolveHost = (host) => {
4153
+ const candidate = host?.trim() || process.env.BROWSER_BRIDGE_CORE_HOST || process.env.BROWSER_VISION_CORE_HOST;
4154
+ if (candidate && candidate.length > 0) {
4155
+ return candidate;
4156
+ }
4157
+ return DEFAULT_HOST;
4158
+ };
4159
+ var resolvePort = (port) => {
4160
+ const candidate = port ?? (process.env.BROWSER_BRIDGE_CORE_PORT ? Number.parseInt(process.env.BROWSER_BRIDGE_CORE_PORT, 10) : process.env.BROWSER_VISION_CORE_PORT ? Number.parseInt(process.env.BROWSER_VISION_CORE_PORT, 10) : void 0);
4161
+ if (candidate === void 0 || candidate === null) {
4162
+ return DEFAULT_PORT;
4163
+ }
4164
+ const parsed = typeof candidate === "number" ? candidate : Number.parseInt(candidate, 10);
4165
+ if (!Number.isFinite(parsed) || parsed <= 0) {
4166
+ throw new Error(`Invalid port: ${String(candidate)}`);
4167
+ }
4168
+ return parsed;
4169
+ };
4170
+ var normalizePath = (path3) => path3.startsWith("/") ? path3 : `/${path3}`;
4171
+ var createCoreClient = (options = {}) => {
4172
+ const host = resolveHost(options.host);
4173
+ const port = resolvePort(options.port);
4174
+ const baseUrl = `http://${host}:${port}`;
4175
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
4176
+ const fetchImpl = options.fetchImpl ?? fetch;
4177
+ const requestJson = async (path3, body) => {
4178
+ const controller = new AbortController();
4179
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
4180
+ try {
4181
+ const response = await fetchImpl(`${baseUrl}${normalizePath(path3)}`, {
4182
+ method: "POST",
4183
+ headers: {
4184
+ "content-type": "application/json"
4185
+ },
4186
+ body: body === void 0 ? void 0 : JSON.stringify(body),
4187
+ signal: controller.signal
4188
+ });
4189
+ const raw = await response.text();
4190
+ if (!raw) {
4191
+ throw new Error(`Empty response from Core (${response.status}).`);
4192
+ }
4193
+ try {
4194
+ return JSON.parse(raw);
4195
+ } catch (error) {
4196
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error";
4197
+ throw new Error(`Failed to parse Core response: ${message}`);
4198
+ }
4199
+ } finally {
4200
+ clearTimeout(timeout);
4201
+ }
4202
+ };
4203
+ const post = async (path3, body) => {
4204
+ return requestJson(path3, body);
4205
+ };
4206
+ return { baseUrl, post };
4207
+ };
4208
+
4209
+ // packages/mcp-adapter/src/tools.ts
4210
+ var toToolResult = (payload) => {
4211
+ const content = [{ type: "text", text: JSON.stringify(payload) }];
4212
+ if (payload && typeof payload === "object") {
4213
+ return {
4214
+ content,
4215
+ structuredContent: payload
4216
+ };
4217
+ }
4218
+ return { content };
4219
+ };
4220
+ var toInternalErrorEnvelope = (error) => ({
4221
+ ok: false,
4222
+ error: {
4223
+ code: "INTERNAL",
4224
+ message: error instanceof Error ? error.message : "Unknown error.",
4225
+ retryable: false
4226
+ }
4227
+ });
4228
+ var envelope = (schema) => apiEnvelopeSchema(schema);
4229
+ var TOOL_DEFINITIONS = [
4230
+ {
4231
+ name: "session.create",
4232
+ config: {
4233
+ title: "Session Create",
4234
+ description: "Create a new browser session.",
4235
+ inputSchema: SessionCreateInputSchema,
4236
+ outputSchema: envelope(SessionCreateOutputSchema),
4237
+ corePath: "/session/create"
4238
+ }
4239
+ },
4240
+ {
4241
+ name: "session.status",
4242
+ config: {
4243
+ title: "Session Status",
4244
+ description: "Fetch session status.",
4245
+ inputSchema: SessionStatusInputSchema,
4246
+ outputSchema: envelope(SessionStatusOutputSchema),
4247
+ corePath: "/session/status"
4248
+ }
4249
+ },
4250
+ {
4251
+ name: "session.recover",
4252
+ config: {
4253
+ title: "Session Recover",
4254
+ description: "Recover a session after errors.",
4255
+ inputSchema: SessionRecoverInputSchema,
4256
+ outputSchema: envelope(SessionRecoverOutputSchema),
4257
+ corePath: "/session/recover"
4258
+ }
4259
+ },
4260
+ {
4261
+ name: "session.close",
4262
+ config: {
4263
+ title: "Session Close",
4264
+ description: "Close a session.",
4265
+ inputSchema: SessionCloseInputSchema,
4266
+ outputSchema: envelope(SessionCloseOutputSchema),
4267
+ corePath: "/session/close"
4268
+ }
4269
+ },
4270
+ {
4271
+ name: "drive.navigate",
4272
+ config: {
4273
+ title: "Drive Navigate",
4274
+ description: "Navigate to a URL.",
4275
+ inputSchema: DriveNavigateInputSchema,
4276
+ outputSchema: envelope(DriveNavigateOutputSchema),
4277
+ corePath: "/drive/navigate"
4278
+ }
4279
+ },
4280
+ {
4281
+ name: "drive.go_back",
4282
+ config: {
4283
+ title: "Drive Go Back",
4284
+ description: "Go back in browser history.",
4285
+ inputSchema: DriveGoBackInputSchema,
4286
+ outputSchema: envelope(DriveGoBackOutputSchema),
4287
+ corePath: "/drive/go_back"
4288
+ }
4289
+ },
4290
+ {
4291
+ name: "drive.go_forward",
4292
+ config: {
4293
+ title: "Drive Go Forward",
4294
+ description: "Go forward in browser history.",
4295
+ inputSchema: DriveGoForwardInputSchema,
4296
+ outputSchema: envelope(DriveGoForwardOutputSchema),
4297
+ corePath: "/drive/go_forward"
4298
+ }
4299
+ },
4300
+ {
4301
+ name: "drive.back",
4302
+ config: {
4303
+ title: "Drive Back",
4304
+ description: "Go back in browser history.",
4305
+ inputSchema: DriveBackInputSchema,
4306
+ outputSchema: envelope(DriveBackOutputSchema),
4307
+ corePath: "/drive/back"
4308
+ }
4309
+ },
4310
+ {
4311
+ name: "drive.forward",
4312
+ config: {
4313
+ title: "Drive Forward",
4314
+ description: "Go forward in browser history.",
4315
+ inputSchema: DriveForwardInputSchema,
4316
+ outputSchema: envelope(DriveForwardOutputSchema),
4317
+ corePath: "/drive/forward"
4318
+ }
4319
+ },
4320
+ {
4321
+ name: "drive.click",
4322
+ config: {
4323
+ title: "Drive Click",
4324
+ description: "Click an element.",
4325
+ inputSchema: DriveClickInputSchema,
4326
+ outputSchema: envelope(DriveClickOutputSchema),
4327
+ corePath: "/drive/click"
4328
+ }
4329
+ },
4330
+ {
4331
+ name: "drive.hover",
4332
+ config: {
4333
+ title: "Drive Hover",
4334
+ description: "Hover over an element.",
4335
+ inputSchema: DriveHoverInputSchema,
4336
+ outputSchema: envelope(DriveHoverOutputSchema),
4337
+ corePath: "/drive/hover"
4338
+ }
4339
+ },
4340
+ {
4341
+ name: "drive.select",
4342
+ config: {
4343
+ title: "Drive Select",
4344
+ description: "Select an option in a dropdown.",
4345
+ inputSchema: DriveSelectInputSchema,
4346
+ outputSchema: envelope(DriveSelectOutputSchema),
4347
+ corePath: "/drive/select"
4348
+ }
4349
+ },
4350
+ {
4351
+ name: "drive.type",
4352
+ config: {
4353
+ title: "Drive Type",
4354
+ description: "Type into an element.",
4355
+ inputSchema: DriveTypeInputSchema,
4356
+ outputSchema: envelope(DriveTypeOutputSchema),
4357
+ corePath: "/drive/type"
4358
+ }
4359
+ },
4360
+ {
4361
+ name: "drive.fill_form",
4362
+ config: {
4363
+ title: "Drive Fill Form",
4364
+ description: "Fill multiple form fields.",
4365
+ inputSchema: DriveFillFormInputSchema,
4366
+ outputSchema: envelope(DriveFillFormOutputSchema),
4367
+ corePath: "/drive/fill_form"
4368
+ }
4369
+ },
4370
+ {
4371
+ name: "drive.drag",
4372
+ config: {
4373
+ title: "Drive Drag",
4374
+ description: "Drag an element to a target.",
4375
+ inputSchema: DriveDragInputSchema,
4376
+ outputSchema: envelope(DriveDragOutputSchema),
4377
+ corePath: "/drive/drag"
4378
+ }
4379
+ },
4380
+ {
4381
+ name: "drive.handle_dialog",
4382
+ config: {
4383
+ title: "Drive Handle Dialog",
4384
+ description: "Handle a JavaScript dialog.",
4385
+ inputSchema: DriveHandleDialogInputSchema,
4386
+ outputSchema: envelope(DriveHandleDialogOutputSchema),
4387
+ corePath: "/drive/handle_dialog"
4388
+ }
4389
+ },
4390
+ {
4391
+ name: "dialog.accept",
4392
+ config: {
4393
+ title: "Dialog Accept",
4394
+ description: "Accept a JavaScript dialog.",
4395
+ inputSchema: DialogAcceptInputSchema,
4396
+ outputSchema: envelope(DialogAcceptOutputSchema),
4397
+ corePath: "/dialog/accept"
4398
+ }
4399
+ },
4400
+ {
4401
+ name: "dialog.dismiss",
4402
+ config: {
4403
+ title: "Dialog Dismiss",
4404
+ description: "Dismiss a JavaScript dialog.",
4405
+ inputSchema: DialogDismissInputSchema,
4406
+ outputSchema: envelope(DialogDismissOutputSchema),
4407
+ corePath: "/dialog/dismiss"
4408
+ }
4409
+ },
4410
+ {
4411
+ name: "drive.key",
4412
+ config: {
4413
+ title: "Drive Key",
4414
+ description: "Press a keyboard key.",
4415
+ inputSchema: DriveKeyInputSchema,
4416
+ outputSchema: envelope(DriveKeyOutputSchema),
4417
+ corePath: "/drive/key"
4418
+ }
4419
+ },
4420
+ {
4421
+ name: "drive.key_press",
4422
+ config: {
4423
+ title: "Drive Key Press",
4424
+ description: "Press a key on the active element.",
4425
+ inputSchema: DriveKeyPressInputSchema,
4426
+ outputSchema: envelope(DriveKeyPressOutputSchema),
4427
+ corePath: "/drive/key_press"
4428
+ }
4429
+ },
4430
+ {
4431
+ name: "drive.scroll",
4432
+ config: {
4433
+ title: "Drive Scroll",
4434
+ description: "Scroll the active tab.",
4435
+ inputSchema: DriveScrollInputSchema,
4436
+ outputSchema: envelope(DriveScrollOutputSchema),
4437
+ corePath: "/drive/scroll"
4438
+ }
4439
+ },
4440
+ {
4441
+ name: "drive.wait_for",
4442
+ config: {
4443
+ title: "Drive Wait For",
4444
+ description: "Wait for a drive condition.",
4445
+ inputSchema: DriveWaitForInputSchema,
4446
+ outputSchema: envelope(DriveWaitForOutputSchema),
4447
+ corePath: "/drive/wait_for"
4448
+ }
4449
+ },
4450
+ {
4451
+ name: "drive.tab_list",
4452
+ config: {
4453
+ title: "Drive Tab List",
4454
+ description: "List browser tabs.",
4455
+ inputSchema: DriveTabListInputSchema,
4456
+ outputSchema: envelope(DriveTabListOutputSchema),
4457
+ corePath: "/drive/tab_list"
4458
+ }
4459
+ },
4460
+ {
4461
+ name: "drive.tab_activate",
4462
+ config: {
4463
+ title: "Drive Tab Activate",
4464
+ description: "Activate a browser tab.",
4465
+ inputSchema: DriveTabActivateInputSchema,
4466
+ outputSchema: envelope(DriveTabActivateOutputSchema),
4467
+ corePath: "/drive/tab_activate"
4468
+ }
4469
+ },
4470
+ {
4471
+ name: "drive.tab_close",
4472
+ config: {
4473
+ title: "Drive Tab Close",
4474
+ description: "Close a browser tab.",
4475
+ inputSchema: DriveTabCloseInputSchema,
4476
+ outputSchema: envelope(DriveTabCloseOutputSchema),
4477
+ corePath: "/drive/tab_close"
4478
+ }
4479
+ },
4480
+ {
4481
+ name: "inspect.dom_snapshot",
4482
+ config: {
4483
+ title: "Inspect DOM Snapshot",
4484
+ description: "Capture a DOM snapshot.",
4485
+ inputSchema: InspectDomSnapshotInputSchema,
4486
+ outputSchema: envelope(InspectDomSnapshotOutputSchema),
4487
+ corePath: "/inspect/dom_snapshot"
4488
+ }
4489
+ },
4490
+ {
4491
+ name: "inspect.dom_diff",
4492
+ config: {
4493
+ title: "Inspect DOM Diff",
4494
+ description: "Compare recent DOM snapshots.",
4495
+ inputSchema: InspectDomDiffInputSchema,
4496
+ outputSchema: envelope(InspectDomDiffOutputSchema),
4497
+ corePath: "/inspect/dom_diff"
4498
+ }
4499
+ },
4500
+ {
4501
+ name: "inspect.find",
4502
+ config: {
4503
+ title: "Inspect Find",
4504
+ description: "Find elements in the accessibility tree and return refs.",
4505
+ inputSchema: InspectFindInputSchema,
4506
+ outputSchema: envelope(InspectFindOutputSchema),
4507
+ corePath: "/inspect/find"
4508
+ }
4509
+ },
4510
+ {
4511
+ name: "inspect.extract_content",
4512
+ config: {
4513
+ title: "Inspect Extract Content",
4514
+ description: "Extract main content as markdown or text.",
4515
+ inputSchema: InspectExtractContentInputSchema,
4516
+ outputSchema: envelope(InspectExtractContentOutputSchema),
4517
+ corePath: "/inspect/extract_content"
4518
+ }
4519
+ },
4520
+ {
4521
+ name: "inspect.page_state",
4522
+ config: {
4523
+ title: "Inspect Page State",
4524
+ description: "Capture form, storage, and cookie state.",
4525
+ inputSchema: InspectPageStateInputSchema,
4526
+ outputSchema: envelope(InspectPageStateOutputSchema),
4527
+ corePath: "/inspect/page_state"
4528
+ }
4529
+ },
4530
+ {
4531
+ name: "inspect.console_list",
4532
+ config: {
4533
+ title: "Inspect Console List",
4534
+ description: "List console entries.",
4535
+ inputSchema: InspectConsoleListInputSchema,
4536
+ outputSchema: envelope(InspectConsoleListOutputSchema),
4537
+ corePath: "/inspect/console_list"
4538
+ }
4539
+ },
4540
+ {
4541
+ name: "inspect.network_har",
4542
+ config: {
4543
+ title: "Inspect Network HAR",
4544
+ description: "Capture network HAR data.",
4545
+ inputSchema: InspectNetworkHarInputSchema,
4546
+ outputSchema: envelope(InspectNetworkHarOutputSchema),
4547
+ corePath: "/inspect/network_har"
4548
+ }
4549
+ },
4550
+ {
4551
+ name: "inspect.evaluate",
4552
+ config: {
4553
+ title: "Inspect Evaluate",
4554
+ description: "Evaluate an expression in the target.",
4555
+ inputSchema: InspectEvaluateInputSchema,
4556
+ outputSchema: envelope(InspectEvaluateOutputSchema),
4557
+ corePath: "/inspect/evaluate"
4558
+ }
4559
+ },
4560
+ {
4561
+ name: "inspect.performance_metrics",
4562
+ config: {
4563
+ title: "Inspect Performance Metrics",
4564
+ description: "Collect performance metrics.",
4565
+ inputSchema: InspectPerformanceMetricsInputSchema,
4566
+ outputSchema: envelope(InspectPerformanceMetricsOutputSchema),
4567
+ corePath: "/inspect/performance_metrics"
4568
+ }
4569
+ },
4570
+ {
4571
+ name: "artifacts.screenshot",
4572
+ config: {
4573
+ title: "Artifacts Screenshot",
4574
+ description: "Capture a screenshot artifact.",
4575
+ inputSchema: ArtifactsScreenshotInputSchema,
4576
+ outputSchema: envelope(ArtifactsScreenshotOutputSchema),
4577
+ corePath: "/artifacts/screenshot"
4578
+ }
4579
+ },
4580
+ {
4581
+ name: "diagnostics.doctor",
4582
+ config: {
4583
+ title: "Diagnostics Doctor",
4584
+ description: "Run diagnostics checks.",
4585
+ inputSchema: DiagnosticsDoctorInputSchema,
4586
+ outputSchema: envelope(DiagnosticsDoctorOutputSchema),
4587
+ corePath: "/diagnostics/doctor"
4588
+ }
4589
+ }
4590
+ ];
4591
+ var createToolHandler = (client, corePath) => {
4592
+ return (async (args, _extra) => {
4593
+ void _extra;
4594
+ try {
4595
+ const envelopeResult = await client.post(corePath, args);
4596
+ return toToolResult(envelopeResult);
4597
+ } catch (error) {
4598
+ const parsed = ErrorEnvelopeSchema.safeParse(error);
4599
+ if (parsed.success) {
4600
+ return toToolResult(parsed.data);
4601
+ }
4602
+ return toToolResult(toInternalErrorEnvelope(error));
4603
+ }
4604
+ });
4605
+ };
4606
+ var registerBrowserBridgeTools = (server, client) => {
4607
+ for (const tool of TOOL_DEFINITIONS) {
4608
+ server.registerTool(
4609
+ tool.name,
4610
+ {
4611
+ title: tool.config.title,
4612
+ description: tool.config.description,
4613
+ inputSchema: tool.config.inputSchema,
4614
+ outputSchema: tool.config.outputSchema
4615
+ },
4616
+ createToolHandler(client, tool.config.corePath)
4617
+ );
4618
+ }
4619
+ };
4620
+
4621
+ // packages/mcp-adapter/src/server.ts
4622
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
4623
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
4624
+ var DEFAULT_SERVER_NAME = "browser-bridge";
4625
+ var DEFAULT_SERVER_VERSION = "0.0.0";
4626
+ var createMcpServer = (options = {}) => {
4627
+ const server = new import_mcp.McpServer({
4628
+ name: options.name ?? DEFAULT_SERVER_NAME,
4629
+ version: options.version ?? DEFAULT_SERVER_VERSION
4630
+ });
4631
+ const client = options.coreClient ?? createCoreClient(options);
4632
+ registerBrowserBridgeTools(server, client);
4633
+ return { server, client };
4634
+ };
4635
+ var startMcpServer = async (options = {}) => {
4636
+ const handle = createMcpServer(options);
4637
+ const transport = new import_stdio.StdioServerTransport();
4638
+ await handle.server.connect(transport);
4639
+ return { ...handle, transport };
4640
+ };
4641
+ // Annotate the CommonJS export names for ESM import in node:
4642
+ 0 && (module.exports = {
4643
+ startCoreServer,
4644
+ startMcpServer
4645
+ });
4646
+ //# sourceMappingURL=api.js.map