@btraut/browser-bridge 0.13.1 → 0.14.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/CHANGELOG.md +31 -0
- package/README.md +28 -37
- package/dist/api.js +1013 -626
- package/dist/api.js.map +4 -4
- package/dist/index.js +323 -308
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +1016 -675
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +470 -76
- package/extension/dist/content.js.map +4 -4
- package/extension/dist/options-ui.js +2 -113
- package/extension/dist/options-ui.js.map +2 -2
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +9 -0
- package/skills/browser-bridge/skill.json +1 -1
package/dist/api.js
CHANGED
|
@@ -51,14 +51,11 @@ var import_node_path2 = require("node:path");
|
|
|
51
51
|
var import_node_fs = require("node:fs");
|
|
52
52
|
var import_node_path = require("node:path");
|
|
53
53
|
var DEFAULT_HOST = "127.0.0.1";
|
|
54
|
-
var
|
|
55
|
-
var DETERMINISTIC_PORT_WINDOW = 2e3;
|
|
54
|
+
var DEFAULT_PORT = 3210;
|
|
56
55
|
var ENV_CORE_HOST = "BROWSER_BRIDGE_CORE_HOST";
|
|
57
56
|
var ENV_VISION_HOST = "BROWSER_VISION_CORE_HOST";
|
|
58
57
|
var ENV_CORE_PORT = "BROWSER_BRIDGE_CORE_PORT";
|
|
59
58
|
var ENV_VISION_PORT = "BROWSER_VISION_CORE_PORT";
|
|
60
|
-
var ENV_ISOLATED_MODE = "BROWSER_BRIDGE_ISOLATED_MODE";
|
|
61
|
-
var ENV_VISION_ISOLATED_MODE = "BROWSER_VISION_ISOLATED_MODE";
|
|
62
59
|
var ENV_BRIDGE_CWD = "BROWSER_BRIDGE_CWD";
|
|
63
60
|
var ENV_PROCESS_PWD = "PWD";
|
|
64
61
|
var ENV_PROCESS_INIT_CWD = "INIT_CWD";
|
|
@@ -115,22 +112,6 @@ var normalizeHost = (value) => {
|
|
|
115
112
|
const trimmed = value.trim();
|
|
116
113
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
117
114
|
};
|
|
118
|
-
var parseBoolean = (value) => {
|
|
119
|
-
if (typeof value === "boolean") {
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
if (typeof value !== "string") {
|
|
123
|
-
return void 0;
|
|
124
|
-
}
|
|
125
|
-
const normalized = value.trim().toLowerCase();
|
|
126
|
-
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
return void 0;
|
|
133
|
-
};
|
|
134
115
|
var parsePort = (value, label, invalidPolicy) => {
|
|
135
116
|
if (value === void 0 || value === null) {
|
|
136
117
|
return void 0;
|
|
@@ -147,60 +128,6 @@ var parsePort = (value, label, invalidPolicy) => {
|
|
|
147
128
|
}
|
|
148
129
|
return void 0;
|
|
149
130
|
};
|
|
150
|
-
var hashString = (value) => {
|
|
151
|
-
let hash = 2166136261;
|
|
152
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
153
|
-
hash ^= value.charCodeAt(index);
|
|
154
|
-
hash = Math.imul(hash, 16777619);
|
|
155
|
-
}
|
|
156
|
-
return hash >>> 0;
|
|
157
|
-
};
|
|
158
|
-
var normalizePathForHash = (value) => value.replace(/\\/g, "/").toLowerCase();
|
|
159
|
-
var sanitizeToken = (value) => value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
160
|
-
var fallbackWorktreeId = (gitRoot) => {
|
|
161
|
-
const hash = hashString(normalizePathForHash(gitRoot)).toString(16).padStart(8, "0");
|
|
162
|
-
return `wt-${hash}`;
|
|
163
|
-
};
|
|
164
|
-
var extractWorktreeIdFromGitDir = (gitDir) => {
|
|
165
|
-
const normalized = gitDir.replace(/\\/g, "/");
|
|
166
|
-
const marker = "/.git/worktrees/";
|
|
167
|
-
const markerIndex = normalized.lastIndexOf(marker);
|
|
168
|
-
if (markerIndex < 0) {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
const remainder = normalized.slice(markerIndex + marker.length);
|
|
172
|
-
const rawId = remainder.split("/")[0];
|
|
173
|
-
if (!rawId) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
const sanitized = sanitizeToken(rawId);
|
|
177
|
-
return sanitized.length > 0 ? sanitized : null;
|
|
178
|
-
};
|
|
179
|
-
var readWorktreeGitDir = (gitRoot) => {
|
|
180
|
-
const gitPath = (0, import_node_path.join)(gitRoot, ".git");
|
|
181
|
-
try {
|
|
182
|
-
const stats = (0, import_node_fs.statSync)(gitPath);
|
|
183
|
-
if (stats.isDirectory()) {
|
|
184
|
-
return gitPath;
|
|
185
|
-
}
|
|
186
|
-
if (!stats.isFile()) {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
} catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
const raw = (0, import_node_fs.readFileSync)(gitPath, "utf8");
|
|
194
|
-
const match = raw.match(/^gitdir:\s*(.+)$/m);
|
|
195
|
-
if (!match?.[1]) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
const candidate = match[1].trim();
|
|
199
|
-
return (0, import_node_path.isAbsolute)(candidate) ? candidate : (0, import_node_path.resolve)(gitRoot, candidate);
|
|
200
|
-
} catch {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
131
|
var resolveEnvHost = (env) => {
|
|
205
132
|
const bridgeHost = normalizeHost(env[ENV_CORE_HOST]);
|
|
206
133
|
if (bridgeHost) {
|
|
@@ -214,35 +141,18 @@ var resolveEnvPortRaw = (env) => {
|
|
|
214
141
|
}
|
|
215
142
|
return env[ENV_VISION_PORT];
|
|
216
143
|
};
|
|
217
|
-
var resolveEnvIsolatedMode = (env) => {
|
|
218
|
-
const bridge = parseBoolean(env[ENV_ISOLATED_MODE]);
|
|
219
|
-
if (bridge !== void 0) {
|
|
220
|
-
return bridge;
|
|
221
|
-
}
|
|
222
|
-
return parseBoolean(env[ENV_VISION_ISOLATED_MODE]);
|
|
223
|
-
};
|
|
224
144
|
var sanitizeMetadata = (raw) => {
|
|
225
145
|
if (!raw || typeof raw !== "object") {
|
|
226
146
|
return null;
|
|
227
147
|
}
|
|
228
148
|
const candidate = raw;
|
|
229
|
-
const host = normalizeHost(candidate.host);
|
|
230
|
-
const port = parsePort(candidate.port, "port", "ignore");
|
|
231
|
-
const gitRoot = normalizeHost(candidate.git_root);
|
|
232
|
-
const worktreeId = normalizeHost(candidate.worktree_id);
|
|
233
149
|
const extensionId = normalizeHost(candidate.extension_id);
|
|
234
|
-
const isolatedMode = parseBoolean(candidate.isolated_mode);
|
|
235
150
|
const updatedAt = normalizeHost(candidate.updated_at);
|
|
236
|
-
if (!
|
|
151
|
+
if (!extensionId && !updatedAt) {
|
|
237
152
|
return null;
|
|
238
153
|
}
|
|
239
154
|
return {
|
|
240
|
-
host,
|
|
241
|
-
port,
|
|
242
|
-
git_root: gitRoot,
|
|
243
|
-
worktree_id: worktreeId,
|
|
244
155
|
extension_id: extensionId,
|
|
245
|
-
isolated_mode: isolatedMode,
|
|
246
156
|
updated_at: updatedAt
|
|
247
157
|
};
|
|
248
158
|
};
|
|
@@ -259,32 +169,6 @@ var findGitRoot = (cwd) => {
|
|
|
259
169
|
current = parent;
|
|
260
170
|
}
|
|
261
171
|
};
|
|
262
|
-
var resolveWorktreeId = ({
|
|
263
|
-
cwd,
|
|
264
|
-
gitRoot
|
|
265
|
-
} = {}) => {
|
|
266
|
-
const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
|
|
267
|
-
if (!resolvedGitRoot) {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
const gitDir = readWorktreeGitDir(resolvedGitRoot);
|
|
271
|
-
const parsedId = gitDir ? extractWorktreeIdFromGitDir(gitDir) : null;
|
|
272
|
-
if (parsedId) {
|
|
273
|
-
return parsedId;
|
|
274
|
-
}
|
|
275
|
-
return fallbackWorktreeId(resolvedGitRoot);
|
|
276
|
-
};
|
|
277
|
-
var resolveDeterministicCorePort = ({
|
|
278
|
-
cwd,
|
|
279
|
-
gitRoot
|
|
280
|
-
} = {}) => {
|
|
281
|
-
const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
|
|
282
|
-
if (!resolvedGitRoot) {
|
|
283
|
-
return LEGACY_DEFAULT_PORT;
|
|
284
|
-
}
|
|
285
|
-
const seed = normalizePathForHash(resolvedGitRoot);
|
|
286
|
-
return LEGACY_DEFAULT_PORT + hashString(seed) % DETERMINISTIC_PORT_WINDOW;
|
|
287
|
-
};
|
|
288
172
|
var resolveRuntimeMetadataPath = ({
|
|
289
173
|
cwd,
|
|
290
174
|
gitRoot,
|
|
@@ -342,26 +226,6 @@ var writeRuntimeMetadata = (metadata, {
|
|
|
342
226
|
`, "utf8");
|
|
343
227
|
return path3;
|
|
344
228
|
};
|
|
345
|
-
var createBoundedPortProbeSequence = (startPort, maxAttempts = 20, maxPort = 65535) => {
|
|
346
|
-
const normalizedStart = parsePort(startPort, "startPort", "throw");
|
|
347
|
-
if (normalizedStart === void 0) {
|
|
348
|
-
throw new Error(`Invalid startPort: ${String(startPort)}`);
|
|
349
|
-
}
|
|
350
|
-
const attempts = Math.max(1, Math.floor(maxAttempts));
|
|
351
|
-
const ceiling = Math.min(65535, Math.max(1, Math.floor(maxPort)));
|
|
352
|
-
if (normalizedStart > ceiling) {
|
|
353
|
-
throw new Error(`startPort ${normalizedStart} exceeds maxPort ${ceiling}.`);
|
|
354
|
-
}
|
|
355
|
-
const sequence = [];
|
|
356
|
-
for (let offset = 0; offset < attempts; offset += 1) {
|
|
357
|
-
const port = normalizedStart + offset;
|
|
358
|
-
if (port > ceiling) {
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
sequence.push(port);
|
|
362
|
-
}
|
|
363
|
-
return sequence;
|
|
364
|
-
};
|
|
365
229
|
var resolveCoreRuntime = (options = {}) => {
|
|
366
230
|
const env = options.env ?? process.env;
|
|
367
231
|
const resolvedCwd = resolveCwd(options.cwd);
|
|
@@ -372,45 +236,18 @@ var resolveCoreRuntime = (options = {}) => {
|
|
|
372
236
|
metadataPath: options.metadataPath
|
|
373
237
|
});
|
|
374
238
|
const metadata = options.metadata === void 0 ? readRuntimeMetadata({ metadataPath }) : sanitizeMetadata(options.metadata);
|
|
375
|
-
const deterministicPort = resolveDeterministicCorePort({
|
|
376
|
-
cwd: resolvedCwd,
|
|
377
|
-
gitRoot
|
|
378
|
-
});
|
|
379
239
|
const optionHost = normalizeHost(options.host);
|
|
380
240
|
const envHost = resolveEnvHost(env);
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
const hostSource = optionHost ? "option" : envHost ? "env" : metadataHost ? "metadata" : "default";
|
|
241
|
+
const host = optionHost ?? envHost ?? DEFAULT_HOST;
|
|
242
|
+
const hostSource = optionHost ? "option" : envHost ? "env" : "default";
|
|
384
243
|
const optionPort = parsePort(options.port, "port", "throw");
|
|
385
244
|
const envPort = parsePort(
|
|
386
245
|
resolveEnvPortRaw(env),
|
|
387
246
|
"port",
|
|
388
247
|
options.strictEnvPort ? "throw" : "ignore"
|
|
389
248
|
);
|
|
390
|
-
const
|
|
391
|
-
const
|
|
392
|
-
const envIsolatedMode = resolveEnvIsolatedMode(env);
|
|
393
|
-
const metadataIsolatedMode = metadata?.isolated_mode;
|
|
394
|
-
const isolatedMode = optionIsolatedMode ?? envIsolatedMode ?? metadataIsolatedMode ?? false;
|
|
395
|
-
const isolatedModeSource = optionIsolatedMode !== void 0 ? "option" : envIsolatedMode !== void 0 ? "env" : metadataIsolatedMode !== void 0 ? "metadata" : "default";
|
|
396
|
-
let port;
|
|
397
|
-
let portSource;
|
|
398
|
-
if (optionPort !== void 0) {
|
|
399
|
-
port = optionPort;
|
|
400
|
-
portSource = "option";
|
|
401
|
-
} else if (envPort !== void 0) {
|
|
402
|
-
port = envPort;
|
|
403
|
-
portSource = "env";
|
|
404
|
-
} else if (metadataPort !== void 0 && isolatedMode) {
|
|
405
|
-
port = metadataPort;
|
|
406
|
-
portSource = "metadata";
|
|
407
|
-
} else if (isolatedMode) {
|
|
408
|
-
port = deterministicPort;
|
|
409
|
-
portSource = "deterministic";
|
|
410
|
-
} else {
|
|
411
|
-
port = LEGACY_DEFAULT_PORT;
|
|
412
|
-
portSource = "default";
|
|
413
|
-
}
|
|
249
|
+
const port = optionPort ?? envPort ?? DEFAULT_PORT;
|
|
250
|
+
const portSource = optionPort !== void 0 ? "option" : envPort !== void 0 ? "env" : "default";
|
|
414
251
|
return {
|
|
415
252
|
host,
|
|
416
253
|
port,
|
|
@@ -418,11 +255,7 @@ var resolveCoreRuntime = (options = {}) => {
|
|
|
418
255
|
portSource,
|
|
419
256
|
metadataPath,
|
|
420
257
|
metadata,
|
|
421
|
-
gitRoot
|
|
422
|
-
worktreeId: resolveWorktreeId({ cwd: resolvedCwd, gitRoot }),
|
|
423
|
-
deterministicPort,
|
|
424
|
-
isolatedMode,
|
|
425
|
-
isolatedModeSource
|
|
258
|
+
gitRoot
|
|
426
259
|
};
|
|
427
260
|
};
|
|
428
261
|
|
|
@@ -841,7 +674,7 @@ var createCoreReadinessController = (options = {}) => {
|
|
|
841
674
|
}
|
|
842
675
|
if (portOccupied) {
|
|
843
676
|
throw new Error(
|
|
844
|
-
`Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse
|
|
677
|
+
`Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse the existing process or stop whatever is already bound to that port.`
|
|
845
678
|
);
|
|
846
679
|
}
|
|
847
680
|
throw new Error(
|
|
@@ -1169,9 +1002,7 @@ var DiagnosticsRuntimeEndpointSchema = import_zod3.z.object({
|
|
|
1169
1002
|
port: import_zod3.z.number().finite().optional(),
|
|
1170
1003
|
base_url: import_zod3.z.string().optional(),
|
|
1171
1004
|
host_source: import_zod3.z.string().optional(),
|
|
1172
|
-
port_source: import_zod3.z.string().optional()
|
|
1173
|
-
metadata_path: import_zod3.z.string().optional(),
|
|
1174
|
-
isolated_mode: import_zod3.z.boolean().optional()
|
|
1005
|
+
port_source: import_zod3.z.string().optional()
|
|
1175
1006
|
});
|
|
1176
1007
|
var DiagnosticsRuntimeProcessSchema = import_zod3.z.object({
|
|
1177
1008
|
component: import_zod3.z.enum(["cli", "mcp", "core"]).optional(),
|
|
@@ -1192,12 +1023,13 @@ var DiagnosticsRuntimeContextSchema = import_zod3.z.object({
|
|
|
1192
1023
|
process: DiagnosticsRuntimeProcessSchema.optional()
|
|
1193
1024
|
}).optional(),
|
|
1194
1025
|
extension: import_zod3.z.object({
|
|
1026
|
+
extension_id: import_zod3.z.string().optional(),
|
|
1195
1027
|
version: import_zod3.z.string().optional(),
|
|
1196
1028
|
protocol_version: import_zod3.z.string().optional(),
|
|
1197
1029
|
capability_negotiated: import_zod3.z.boolean().optional(),
|
|
1198
1030
|
capabilities: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.boolean()).optional(),
|
|
1199
1031
|
endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
|
|
1200
|
-
port_source: import_zod3.z.enum(["default"
|
|
1032
|
+
port_source: import_zod3.z.enum(["default"]).optional()
|
|
1201
1033
|
}).optional()
|
|
1202
1034
|
});
|
|
1203
1035
|
var DiagnosticReportSchema = import_zod3.z.object({
|
|
@@ -1272,7 +1104,7 @@ var DriveNavigateInputSchema = import_zod3.z.object({
|
|
|
1272
1104
|
session_id: import_zod3.z.string().min(1).optional(),
|
|
1273
1105
|
url: import_zod3.z.string().min(1),
|
|
1274
1106
|
tab_id: import_zod3.z.number().finite().optional(),
|
|
1275
|
-
wait: import_zod3.z.enum(["none", "domcontentloaded"]).default("domcontentloaded")
|
|
1107
|
+
wait: import_zod3.z.enum(["none", "domcontentloaded", "networkidle"]).default("domcontentloaded")
|
|
1276
1108
|
});
|
|
1277
1109
|
var DriveNavigateOutputSchema = OpResultSchema.extend({
|
|
1278
1110
|
session_id: import_zod3.z.string().min(1)
|
|
@@ -1441,6 +1273,8 @@ var InspectConsistencySchema = import_zod3.z.enum(["best_effort", "quiesce"]);
|
|
|
1441
1273
|
var TargetHintSchema = import_zod3.z.object({
|
|
1442
1274
|
url: import_zod3.z.string().min(1).optional(),
|
|
1443
1275
|
title: import_zod3.z.string().min(1).optional(),
|
|
1276
|
+
tab_id: import_zod3.z.number().finite().optional(),
|
|
1277
|
+
tabId: import_zod3.z.number().finite().optional(),
|
|
1444
1278
|
last_active_at: import_zod3.z.string().optional(),
|
|
1445
1279
|
lastActiveAt: import_zod3.z.string().optional()
|
|
1446
1280
|
});
|
|
@@ -1635,6 +1469,7 @@ var HealthCheckOutputSchema = import_zod3.z.object({
|
|
|
1635
1469
|
}).passthrough(),
|
|
1636
1470
|
extension: import_zod3.z.object({
|
|
1637
1471
|
connected: import_zod3.z.boolean(),
|
|
1472
|
+
extension_id: import_zod3.z.string().optional(),
|
|
1638
1473
|
last_seen_at: import_zod3.z.string().min(1).optional()
|
|
1639
1474
|
}).passthrough()
|
|
1640
1475
|
}).passthrough();
|
|
@@ -1744,7 +1579,8 @@ var SessionRegistry = class {
|
|
|
1744
1579
|
state: "INIT" /* INIT */,
|
|
1745
1580
|
createdAt: now,
|
|
1746
1581
|
updatedAt: now,
|
|
1747
|
-
lastAccessedAt: now
|
|
1582
|
+
lastAccessedAt: now,
|
|
1583
|
+
selectedTabId: void 0
|
|
1748
1584
|
};
|
|
1749
1585
|
this.sessions.set(id, session);
|
|
1750
1586
|
return session;
|
|
@@ -1843,6 +1679,18 @@ var SessionRegistry = class {
|
|
|
1843
1679
|
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1844
1680
|
return session;
|
|
1845
1681
|
}
|
|
1682
|
+
setSelectedTab(sessionId, tabId) {
|
|
1683
|
+
const session = this.require(sessionId);
|
|
1684
|
+
session.selectedTabId = tabId;
|
|
1685
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1686
|
+
return session;
|
|
1687
|
+
}
|
|
1688
|
+
clearSelectedTab(sessionId) {
|
|
1689
|
+
const session = this.require(sessionId);
|
|
1690
|
+
session.selectedTabId = void 0;
|
|
1691
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1692
|
+
return session;
|
|
1693
|
+
}
|
|
1846
1694
|
};
|
|
1847
1695
|
|
|
1848
1696
|
// packages/core/src/routes/shared.ts
|
|
@@ -1883,13 +1731,14 @@ var deriveHintFromTabs = (tabs) => {
|
|
|
1883
1731
|
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
1884
1732
|
return void 0;
|
|
1885
1733
|
}
|
|
1734
|
+
const candidatePool = tabs.some((tab) => tab.active === true) ? tabs.filter((tab) => tab.active === true) : tabs;
|
|
1886
1735
|
let best;
|
|
1887
1736
|
let bestTime = -Infinity;
|
|
1888
|
-
for (const tab of
|
|
1737
|
+
for (const tab of candidatePool) {
|
|
1889
1738
|
const raw = tab.last_active_at;
|
|
1890
1739
|
const time = raw ? Date.parse(raw) : NaN;
|
|
1891
1740
|
const score = Number.isFinite(time) ? time : -Infinity;
|
|
1892
|
-
if (!best || score > bestTime) {
|
|
1741
|
+
if (!best || score > bestTime || score === bestTime && tab.tab_id < best.tab_id) {
|
|
1893
1742
|
best = tab;
|
|
1894
1743
|
bestTime = score;
|
|
1895
1744
|
}
|
|
@@ -1965,7 +1814,8 @@ var createSessionRouter = (registry, options = {}) => {
|
|
|
1965
1814
|
return sendResult(res, {
|
|
1966
1815
|
session_id: session.id,
|
|
1967
1816
|
state: session.state,
|
|
1968
|
-
updated_at: session.updatedAt.toISOString()
|
|
1817
|
+
updated_at: session.updatedAt.toISOString(),
|
|
1818
|
+
...typeof session.selectedTabId === "number" ? { selected_tab_id: session.selectedTabId } : {}
|
|
1969
1819
|
});
|
|
1970
1820
|
} catch (error) {
|
|
1971
1821
|
if (error instanceof SessionError) {
|
|
@@ -2103,9 +1953,6 @@ var InspectError = class extends Error {
|
|
|
2103
1953
|
var import_crypto3 = require("crypto");
|
|
2104
1954
|
var import_promises3 = require("node:fs/promises");
|
|
2105
1955
|
var import_node_path4 = __toESM(require("node:path"));
|
|
2106
|
-
var import_readability = require("@mozilla/readability");
|
|
2107
|
-
var import_jsdom = require("jsdom");
|
|
2108
|
-
var import_turndown = __toESM(require("turndown"));
|
|
2109
1956
|
|
|
2110
1957
|
// packages/core/src/artifacts.ts
|
|
2111
1958
|
var import_promises2 = require("node:fs/promises");
|
|
@@ -2172,6 +2019,7 @@ var ExtensionBridge = class {
|
|
|
2172
2019
|
return {
|
|
2173
2020
|
connected: this.connected,
|
|
2174
2021
|
lastSeenAt: this.lastSeenAt,
|
|
2022
|
+
extensionId: this.extensionId,
|
|
2175
2023
|
version: this.version,
|
|
2176
2024
|
protocolVersion: this.protocolVersion,
|
|
2177
2025
|
protocolMismatch: this.protocolMismatch,
|
|
@@ -2352,6 +2200,7 @@ var ExtensionBridge = class {
|
|
|
2352
2200
|
this.stopHeartbeat();
|
|
2353
2201
|
this.connected = false;
|
|
2354
2202
|
this.socket = null;
|
|
2203
|
+
this.extensionId = void 0;
|
|
2355
2204
|
this.version = void 0;
|
|
2356
2205
|
this.protocolVersion = void 0;
|
|
2357
2206
|
this.protocolMismatch = void 0;
|
|
@@ -2414,6 +2263,11 @@ var ExtensionBridge = class {
|
|
|
2414
2263
|
this.tabs = tabs;
|
|
2415
2264
|
}
|
|
2416
2265
|
if (message.action === "drive.hello") {
|
|
2266
|
+
if (typeof params?.extension_id === "string") {
|
|
2267
|
+
this.extensionId = params.extension_id;
|
|
2268
|
+
} else {
|
|
2269
|
+
this.extensionId = void 0;
|
|
2270
|
+
}
|
|
2417
2271
|
if (typeof params?.version === "string") {
|
|
2418
2272
|
this.version = params.version;
|
|
2419
2273
|
}
|
|
@@ -2436,7 +2290,7 @@ var ExtensionBridge = class {
|
|
|
2436
2290
|
if (typeof params?.core_port === "number" && Number.isFinite(params.core_port)) {
|
|
2437
2291
|
this.corePort = params.core_port;
|
|
2438
2292
|
}
|
|
2439
|
-
if (params?.core_port_source === "default"
|
|
2293
|
+
if (params?.core_port_source === "default") {
|
|
2440
2294
|
this.corePortSource = params.core_port_source;
|
|
2441
2295
|
}
|
|
2442
2296
|
const capabilities = params?.capabilities;
|
|
@@ -2524,7 +2378,26 @@ var toDriveError = (error) => ({
|
|
|
2524
2378
|
|
|
2525
2379
|
// packages/core/src/drive.ts
|
|
2526
2380
|
var LOOPBACK_NAVIGATION_PREFLIGHT_TIMEOUT_MS = 1200;
|
|
2381
|
+
var POST_CLICK_SETTLE_MS = 75;
|
|
2382
|
+
var TRANSIENT_LOCATOR_RETRY_DELAY_MS = 150;
|
|
2527
2383
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
2384
|
+
var ACTIONS_WITH_OPTIONAL_TAB_ID = /* @__PURE__ */ new Set([
|
|
2385
|
+
"drive.navigate",
|
|
2386
|
+
"drive.go_back",
|
|
2387
|
+
"drive.go_forward",
|
|
2388
|
+
"drive.click",
|
|
2389
|
+
"drive.hover",
|
|
2390
|
+
"drive.select",
|
|
2391
|
+
"drive.type",
|
|
2392
|
+
"drive.fill_form",
|
|
2393
|
+
"drive.drag",
|
|
2394
|
+
"drive.handle_dialog",
|
|
2395
|
+
"drive.key",
|
|
2396
|
+
"drive.key_press",
|
|
2397
|
+
"drive.scroll",
|
|
2398
|
+
"drive.screenshot",
|
|
2399
|
+
"drive.wait_for"
|
|
2400
|
+
]);
|
|
2528
2401
|
var isLikelyUnreachableLoopbackError = (message) => {
|
|
2529
2402
|
const normalized = message.toLowerCase();
|
|
2530
2403
|
return normalized.includes("connection refused") || normalized.includes("econnrefused") || normalized.includes("err_connection_refused") || normalized.includes("enotfound") || normalized.includes("err_name_not_resolved") || normalized.includes("eai_again") || normalized.includes("ehostunreach") || normalized.includes("enetunreach");
|
|
@@ -2621,10 +2494,26 @@ var DriveController = class {
|
|
|
2621
2494
|
this.lastError = void 0;
|
|
2622
2495
|
this.lastErrorAt = void 0;
|
|
2623
2496
|
}
|
|
2497
|
+
async sleep(ms) {
|
|
2498
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2499
|
+
}
|
|
2500
|
+
isTransientLocatorError(action, error) {
|
|
2501
|
+
if (action !== "drive.click") {
|
|
2502
|
+
return false;
|
|
2503
|
+
}
|
|
2504
|
+
if (error.code.toUpperCase() !== "NOT_FOUND") {
|
|
2505
|
+
return false;
|
|
2506
|
+
}
|
|
2507
|
+
const reason = error.details?.reason;
|
|
2508
|
+
const legacyCode = error.details?.legacy_code;
|
|
2509
|
+
const resource = error.details?.resource;
|
|
2510
|
+
return reason === "locator_not_found" || legacyCode === "LOCATOR_NOT_FOUND" || resource === "locator";
|
|
2511
|
+
}
|
|
2624
2512
|
async execute(sessionId, action, params, timeoutMs) {
|
|
2625
2513
|
return await driveMutex.runExclusive(async () => {
|
|
2514
|
+
let selectedTabId;
|
|
2626
2515
|
try {
|
|
2627
|
-
this.registry.require(sessionId);
|
|
2516
|
+
selectedTabId = this.registry.require(sessionId).selectedTabId;
|
|
2628
2517
|
} catch (error) {
|
|
2629
2518
|
if (error instanceof SessionError) {
|
|
2630
2519
|
const errorInfo2 = {
|
|
@@ -2668,7 +2557,12 @@ var DriveController = class {
|
|
|
2668
2557
|
return { ok: false, error: errorInfo };
|
|
2669
2558
|
}
|
|
2670
2559
|
this.ensureDriveReady(sessionId);
|
|
2671
|
-
const
|
|
2560
|
+
const prepared = this.prepareRequestParams(action, params, selectedTabId);
|
|
2561
|
+
const requestParams = prepared.params;
|
|
2562
|
+
const preflightError = await preflightLoopbackNavigation(
|
|
2563
|
+
action,
|
|
2564
|
+
requestParams
|
|
2565
|
+
);
|
|
2672
2566
|
if (preflightError) {
|
|
2673
2567
|
this.recordError(preflightError);
|
|
2674
2568
|
return {
|
|
@@ -2681,10 +2575,19 @@ var DriveController = class {
|
|
|
2681
2575
|
try {
|
|
2682
2576
|
const response = await this.bridge.request(
|
|
2683
2577
|
action,
|
|
2684
|
-
|
|
2578
|
+
requestParams,
|
|
2685
2579
|
timeoutMs
|
|
2686
2580
|
);
|
|
2687
2581
|
if (response.status === "ok") {
|
|
2582
|
+
if (action === "drive.click") {
|
|
2583
|
+
await this.sleep(POST_CLICK_SETTLE_MS);
|
|
2584
|
+
}
|
|
2585
|
+
this.applySessionTargetOnSuccess(
|
|
2586
|
+
sessionId,
|
|
2587
|
+
action,
|
|
2588
|
+
requestParams,
|
|
2589
|
+
response.result
|
|
2590
|
+
);
|
|
2688
2591
|
this.clearLastError();
|
|
2689
2592
|
return {
|
|
2690
2593
|
ok: true,
|
|
@@ -2701,6 +2604,11 @@ var DriveController = class {
|
|
|
2701
2604
|
max_attempts: 1
|
|
2702
2605
|
}
|
|
2703
2606
|
};
|
|
2607
|
+
if (attempt === 0 && this.isTransientLocatorError(action, errorInfo)) {
|
|
2608
|
+
attempt += 1;
|
|
2609
|
+
await this.sleep(TRANSIENT_LOCATOR_RETRY_DELAY_MS);
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2704
2612
|
if (shouldRetryDriveOp({
|
|
2705
2613
|
attempt,
|
|
2706
2614
|
retryable: errorInfo.retryable,
|
|
@@ -2709,6 +2617,9 @@ var DriveController = class {
|
|
|
2709
2617
|
attempt += 1;
|
|
2710
2618
|
continue;
|
|
2711
2619
|
}
|
|
2620
|
+
if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo)) {
|
|
2621
|
+
this.clearSessionTarget(sessionId);
|
|
2622
|
+
}
|
|
2712
2623
|
this.recordError(errorInfo);
|
|
2713
2624
|
return { ok: false, error: errorInfo };
|
|
2714
2625
|
} catch (error) {
|
|
@@ -2725,6 +2636,9 @@ var DriveController = class {
|
|
|
2725
2636
|
attempt += 1;
|
|
2726
2637
|
continue;
|
|
2727
2638
|
}
|
|
2639
|
+
if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo2)) {
|
|
2640
|
+
this.clearSessionTarget(sessionId);
|
|
2641
|
+
}
|
|
2728
2642
|
this.recordError(errorInfo2);
|
|
2729
2643
|
return { ok: false, error: errorInfo2 };
|
|
2730
2644
|
}
|
|
@@ -2742,6 +2656,74 @@ var DriveController = class {
|
|
|
2742
2656
|
}
|
|
2743
2657
|
});
|
|
2744
2658
|
}
|
|
2659
|
+
readTabId(params) {
|
|
2660
|
+
const value = params?.tab_id;
|
|
2661
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2662
|
+
}
|
|
2663
|
+
readTabIdFromResult(result) {
|
|
2664
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
2665
|
+
return void 0;
|
|
2666
|
+
}
|
|
2667
|
+
const value = result.tab_id;
|
|
2668
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2669
|
+
}
|
|
2670
|
+
prepareRequestParams(action, params, selectedTabId) {
|
|
2671
|
+
if (!ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
|
|
2672
|
+
return { params, injectedSessionTabId: false };
|
|
2673
|
+
}
|
|
2674
|
+
if (!selectedTabId || selectedTabId <= 0) {
|
|
2675
|
+
return { params, injectedSessionTabId: false };
|
|
2676
|
+
}
|
|
2677
|
+
const explicitTabId = this.readTabId(params);
|
|
2678
|
+
if (explicitTabId !== void 0) {
|
|
2679
|
+
return { params, injectedSessionTabId: false };
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
params: { ...params ?? {}, tab_id: selectedTabId },
|
|
2683
|
+
injectedSessionTabId: true
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
applySessionTargetOnSuccess(sessionId, action, params, result) {
|
|
2687
|
+
const tabId = this.readTabIdFromResult(result) ?? this.readTabId(params);
|
|
2688
|
+
if (tabId === void 0) {
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
try {
|
|
2692
|
+
if (action === "drive.tab_close") {
|
|
2693
|
+
const session = this.registry.require(sessionId);
|
|
2694
|
+
if (session.selectedTabId === tabId) {
|
|
2695
|
+
this.registry.clearSelectedTab(sessionId);
|
|
2696
|
+
}
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (action === "drive.tab_activate" || ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
|
|
2700
|
+
this.registry.setSelectedTab(sessionId, tabId);
|
|
2701
|
+
}
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
console.debug(
|
|
2704
|
+
`Drive target update ignored for session ${sessionId}.`,
|
|
2705
|
+
error
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
isMissingTabError(error) {
|
|
2710
|
+
const code = error.code.toUpperCase();
|
|
2711
|
+
if (code === "TAB_NOT_FOUND") {
|
|
2712
|
+
return true;
|
|
2713
|
+
}
|
|
2714
|
+
const resource = error.details?.resource;
|
|
2715
|
+
return code === "NOT_FOUND" && (resource === void 0 || resource === "tab");
|
|
2716
|
+
}
|
|
2717
|
+
clearSessionTarget(sessionId) {
|
|
2718
|
+
try {
|
|
2719
|
+
this.registry.clearSelectedTab(sessionId);
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
console.debug(
|
|
2722
|
+
`Drive target clear ignored for session ${sessionId}.`,
|
|
2723
|
+
error
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2745
2727
|
ensureDriveReady(sessionId) {
|
|
2746
2728
|
try {
|
|
2747
2729
|
const session = this.registry.require(sessionId);
|
|
@@ -2781,6 +2763,10 @@ var INTERACTIVE_AX_ROLES = /* @__PURE__ */ new Set([
|
|
|
2781
2763
|
"textbox",
|
|
2782
2764
|
"combobox",
|
|
2783
2765
|
"listbox",
|
|
2766
|
+
"menu",
|
|
2767
|
+
"menuitem",
|
|
2768
|
+
"menuitemcheckbox",
|
|
2769
|
+
"menuitemradio",
|
|
2784
2770
|
"checkbox",
|
|
2785
2771
|
"radio",
|
|
2786
2772
|
"switch",
|
|
@@ -3238,6 +3224,136 @@ var toConsoleEntry = (event) => {
|
|
|
3238
3224
|
}
|
|
3239
3225
|
};
|
|
3240
3226
|
|
|
3227
|
+
// packages/core/src/inspect/extract-content-policy.ts
|
|
3228
|
+
var import_readability = require("@mozilla/readability");
|
|
3229
|
+
var import_jsdom = require("jsdom");
|
|
3230
|
+
var import_turndown = __toESM(require("turndown"));
|
|
3231
|
+
var normalizeExtractedText = (value) => value.replace(/\s+/g, " ").trim();
|
|
3232
|
+
var collapseRepeatedMarkdownBlocks = (markdown) => {
|
|
3233
|
+
const blocks = markdown.split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.length > 0);
|
|
3234
|
+
for (let sequenceLength = Math.floor(blocks.length / 2); sequenceLength >= 1; sequenceLength -= 1) {
|
|
3235
|
+
for (let start = 0; start + sequenceLength * 2 <= blocks.length; start += 1) {
|
|
3236
|
+
const first = blocks.slice(start, start + sequenceLength).map((block) => normalizeExtractedText(block)).join("\n");
|
|
3237
|
+
const second = blocks.slice(start + sequenceLength, start + sequenceLength * 2).map((block) => normalizeExtractedText(block)).join("\n");
|
|
3238
|
+
if (first.length === 0 || first !== second) {
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
blocks.splice(start + sequenceLength, sequenceLength);
|
|
3242
|
+
sequenceLength = Math.min(
|
|
3243
|
+
sequenceLength + 1,
|
|
3244
|
+
Math.floor(blocks.length / 2)
|
|
3245
|
+
);
|
|
3246
|
+
start = Math.max(start - 1, -1);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
return blocks.join("\n\n").trim();
|
|
3250
|
+
};
|
|
3251
|
+
var extractSemanticMainCandidate = (document) => {
|
|
3252
|
+
const candidates = Array.from(
|
|
3253
|
+
document.querySelectorAll('main, [role="main"], article')
|
|
3254
|
+
);
|
|
3255
|
+
let best = candidates[0] ?? null;
|
|
3256
|
+
let bestLength = 0;
|
|
3257
|
+
for (const candidate of candidates) {
|
|
3258
|
+
const text = normalizeExtractedText(candidate.textContent ?? "");
|
|
3259
|
+
if (text.length <= bestLength) {
|
|
3260
|
+
continue;
|
|
3261
|
+
}
|
|
3262
|
+
best = candidate;
|
|
3263
|
+
bestLength = text.length;
|
|
3264
|
+
}
|
|
3265
|
+
if (!best || bestLength === 0) {
|
|
3266
|
+
return null;
|
|
3267
|
+
}
|
|
3268
|
+
return {
|
|
3269
|
+
html: best.innerHTML,
|
|
3270
|
+
text: normalizeExtractedText(best.textContent ?? ""),
|
|
3271
|
+
tagName: best.tagName.toLowerCase()
|
|
3272
|
+
};
|
|
3273
|
+
};
|
|
3274
|
+
var shouldPreferSemanticMainCandidate = (options) => {
|
|
3275
|
+
const articleLength = normalizeExtractedText(options.articleText).length;
|
|
3276
|
+
const mainLength = normalizeExtractedText(options.mainText).length;
|
|
3277
|
+
if (mainLength === 0) {
|
|
3278
|
+
return false;
|
|
3279
|
+
}
|
|
3280
|
+
if (articleLength === 0) {
|
|
3281
|
+
return true;
|
|
3282
|
+
}
|
|
3283
|
+
if (options.mainTagName === "article") {
|
|
3284
|
+
return false;
|
|
3285
|
+
}
|
|
3286
|
+
return articleLength < 160 && mainLength > articleLength + 20;
|
|
3287
|
+
};
|
|
3288
|
+
var parseExtractContentSource = (options) => {
|
|
3289
|
+
let article = null;
|
|
3290
|
+
let semanticMainCandidate = null;
|
|
3291
|
+
try {
|
|
3292
|
+
const dom = new import_jsdom.JSDOM(options.html, { url: options.url });
|
|
3293
|
+
const reader = new import_readability.Readability(dom.window.document);
|
|
3294
|
+
article = reader.parse();
|
|
3295
|
+
semanticMainCandidate = extractSemanticMainCandidate(dom.window.document);
|
|
3296
|
+
} catch {
|
|
3297
|
+
throw new InspectError(
|
|
3298
|
+
"EVALUATION_FAILED",
|
|
3299
|
+
"Failed to parse page content.",
|
|
3300
|
+
{
|
|
3301
|
+
retryable: false
|
|
3302
|
+
}
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
if (!article) {
|
|
3306
|
+
throw new InspectError(
|
|
3307
|
+
"NOT_SUPPORTED",
|
|
3308
|
+
"Readability could not extract content.",
|
|
3309
|
+
{
|
|
3310
|
+
retryable: false
|
|
3311
|
+
}
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
return {
|
|
3315
|
+
article,
|
|
3316
|
+
semanticMainCandidate
|
|
3317
|
+
};
|
|
3318
|
+
};
|
|
3319
|
+
var renderExtractContent = (options) => {
|
|
3320
|
+
let content = "";
|
|
3321
|
+
if (options.format === "article_json") {
|
|
3322
|
+
content = JSON.stringify(options.article, null, 2);
|
|
3323
|
+
} else if (options.format === "text") {
|
|
3324
|
+
const articleText = options.article.textContent ?? "";
|
|
3325
|
+
if (options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
|
|
3326
|
+
articleText,
|
|
3327
|
+
mainText: options.semanticMainCandidate.text,
|
|
3328
|
+
mainTagName: options.semanticMainCandidate.tagName
|
|
3329
|
+
})) {
|
|
3330
|
+
content = options.semanticMainCandidate.text;
|
|
3331
|
+
} else {
|
|
3332
|
+
content = articleText;
|
|
3333
|
+
}
|
|
3334
|
+
} else {
|
|
3335
|
+
const turndown = new import_turndown.default();
|
|
3336
|
+
const articleText = options.article.textContent ?? "";
|
|
3337
|
+
const sourceHtml = options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
|
|
3338
|
+
articleText,
|
|
3339
|
+
mainText: options.semanticMainCandidate.text,
|
|
3340
|
+
mainTagName: options.semanticMainCandidate.tagName
|
|
3341
|
+
}) ? options.semanticMainCandidate.html : options.article.content ?? "";
|
|
3342
|
+
content = collapseRepeatedMarkdownBlocks(turndown.turndown(sourceHtml));
|
|
3343
|
+
}
|
|
3344
|
+
const includeMetadata = options.includeMetadata ?? true;
|
|
3345
|
+
return {
|
|
3346
|
+
content,
|
|
3347
|
+
...includeMetadata ? {
|
|
3348
|
+
title: options.article.title ?? void 0,
|
|
3349
|
+
byline: options.article.byline ?? void 0,
|
|
3350
|
+
excerpt: options.article.excerpt ?? void 0,
|
|
3351
|
+
siteName: options.article.siteName ?? void 0
|
|
3352
|
+
} : {},
|
|
3353
|
+
...options.warnings && options.warnings.length > 0 ? { warnings: options.warnings } : {}
|
|
3354
|
+
};
|
|
3355
|
+
};
|
|
3356
|
+
|
|
3241
3357
|
// packages/core/src/inspect/har.ts
|
|
3242
3358
|
var buildHar = (events, title) => {
|
|
3243
3359
|
const requests = /* @__PURE__ */ new Map();
|
|
@@ -3539,11 +3655,19 @@ var SnapshotHistory = class {
|
|
|
3539
3655
|
|
|
3540
3656
|
// packages/core/src/inspect/snapshot-refs.ts
|
|
3541
3657
|
var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
|
|
3658
|
+
var SNAPSHOT_REF_REGISTRY_ID = "__bb_snapshot_ref_registry__";
|
|
3542
3659
|
var MAX_REF_ASSIGNMENTS = 500;
|
|
3543
3660
|
var MAX_REF_WARNINGS = 5;
|
|
3544
3661
|
var isInspectError = (error) => Boolean(
|
|
3545
3662
|
error && typeof error === "object" && "name" in error && error.name === "InspectError" && "message" in error && typeof error.message === "string"
|
|
3546
3663
|
);
|
|
3664
|
+
var isExpectedSnapshotRefMiss = (error) => {
|
|
3665
|
+
if (!isInspectError(error)) {
|
|
3666
|
+
return false;
|
|
3667
|
+
}
|
|
3668
|
+
const message = error.message.toLowerCase();
|
|
3669
|
+
return message.includes("could not find node") || message.includes("no node with given id") || message.includes("does not belong to the document");
|
|
3670
|
+
};
|
|
3547
3671
|
var assignRefsToAxSnapshot = (snapshot) => {
|
|
3548
3672
|
const nodes = getAxNodes(snapshot);
|
|
3549
3673
|
const refs = /* @__PURE__ */ new Map();
|
|
@@ -3562,31 +3686,61 @@ var assignRefsToAxSnapshot = (snapshot) => {
|
|
|
3562
3686
|
const ref = `@e${index}`;
|
|
3563
3687
|
index += 1;
|
|
3564
3688
|
node.ref = ref;
|
|
3565
|
-
refs.set(backendId,
|
|
3689
|
+
refs.set(backendId, {
|
|
3690
|
+
ref,
|
|
3691
|
+
...getAxRole(node) ? { role: getAxRole(node) } : {},
|
|
3692
|
+
...getAxName(node) ? { name: getAxName(node) } : {},
|
|
3693
|
+
...extractUrl(node) ? { url: extractUrl(node) } : {}
|
|
3694
|
+
});
|
|
3566
3695
|
}
|
|
3567
3696
|
return refs;
|
|
3568
3697
|
};
|
|
3569
|
-
var
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3698
|
+
var extractUrl = (node) => {
|
|
3699
|
+
if (!Array.isArray(node.properties)) {
|
|
3700
|
+
return void 0;
|
|
3701
|
+
}
|
|
3702
|
+
for (const prop of node.properties) {
|
|
3703
|
+
if (!prop || typeof prop !== "object") {
|
|
3704
|
+
continue;
|
|
3705
|
+
}
|
|
3706
|
+
const name = prop.name;
|
|
3707
|
+
if (name !== "url") {
|
|
3708
|
+
continue;
|
|
3709
|
+
}
|
|
3710
|
+
const value = prop.value?.value;
|
|
3711
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3712
|
+
return value;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
return void 0;
|
|
3575
3716
|
};
|
|
3576
|
-
var
|
|
3577
|
-
const warnings = [];
|
|
3578
|
-
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3579
|
-
await debuggerCommand(tabId, "Runtime.enable", {});
|
|
3717
|
+
var clearSnapshotRefArtifacts = async (tabId, debuggerCommand) => {
|
|
3580
3718
|
try {
|
|
3581
|
-
await
|
|
3719
|
+
await debuggerCommand(tabId, "Runtime.evaluate", {
|
|
3720
|
+
expression: `(() => {
|
|
3721
|
+
document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'));
|
|
3722
|
+
document.getElementById('${SNAPSHOT_REF_REGISTRY_ID}')?.remove();
|
|
3723
|
+
})()`,
|
|
3724
|
+
returnByValue: true,
|
|
3725
|
+
awaitPromise: true
|
|
3726
|
+
});
|
|
3727
|
+
return { warnings: [] };
|
|
3582
3728
|
} catch {
|
|
3583
|
-
warnings
|
|
3729
|
+
return { warnings: ["Failed to clear prior snapshot refs."] };
|
|
3584
3730
|
}
|
|
3731
|
+
};
|
|
3732
|
+
var applySnapshotRefAttributes = async (tabId, refs, debuggerCommand) => {
|
|
3733
|
+
const warnings = [];
|
|
3734
|
+
const appliedRefs = /* @__PURE__ */ new Set();
|
|
3735
|
+
const appliedBindings = [];
|
|
3736
|
+
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3737
|
+
await debuggerCommand(tabId, "Runtime.enable", {});
|
|
3585
3738
|
if (refs.size === 0) {
|
|
3586
|
-
return warnings;
|
|
3739
|
+
return { warnings, appliedRefs, appliedBindings };
|
|
3587
3740
|
}
|
|
3588
3741
|
let applied = 0;
|
|
3589
|
-
for (const [backendNodeId,
|
|
3742
|
+
for (const [backendNodeId, binding] of refs) {
|
|
3743
|
+
const ref = binding.ref;
|
|
3590
3744
|
if (applied >= MAX_REF_ASSIGNMENTS) {
|
|
3591
3745
|
warnings.push(
|
|
3592
3746
|
`Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
|
|
@@ -3599,9 +3753,6 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
|
|
|
3599
3753
|
});
|
|
3600
3754
|
const node = described.node;
|
|
3601
3755
|
if (!node || node.nodeType !== 1 || typeof node.nodeId !== "number") {
|
|
3602
|
-
if (warnings.length < MAX_REF_WARNINGS) {
|
|
3603
|
-
warnings.push(`Ref ${ref} could not be applied to a DOM element.`);
|
|
3604
|
-
}
|
|
3605
3756
|
continue;
|
|
3606
3757
|
}
|
|
3607
3758
|
await debuggerCommand(tabId, "DOM.setAttributeValue", {
|
|
@@ -3610,13 +3761,52 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
|
|
|
3610
3761
|
value: ref
|
|
3611
3762
|
});
|
|
3612
3763
|
applied += 1;
|
|
3613
|
-
|
|
3764
|
+
appliedRefs.add(ref);
|
|
3765
|
+
appliedBindings.push(binding);
|
|
3766
|
+
} catch (error) {
|
|
3767
|
+
if (isExpectedSnapshotRefMiss(error)) {
|
|
3768
|
+
continue;
|
|
3769
|
+
}
|
|
3614
3770
|
if (warnings.length < MAX_REF_WARNINGS) {
|
|
3615
3771
|
warnings.push(`Ref ${ref} could not be applied.`);
|
|
3616
3772
|
}
|
|
3617
3773
|
}
|
|
3618
3774
|
}
|
|
3619
|
-
return warnings;
|
|
3775
|
+
return { warnings, appliedRefs, appliedBindings };
|
|
3776
|
+
};
|
|
3777
|
+
var persistSnapshotRefRegistry = async (tabId, bindings, debuggerCommand) => {
|
|
3778
|
+
try {
|
|
3779
|
+
const registry = JSON.stringify(bindings);
|
|
3780
|
+
await debuggerCommand(tabId, "Runtime.evaluate", {
|
|
3781
|
+
expression: `(() => {
|
|
3782
|
+
const id = ${JSON.stringify(SNAPSHOT_REF_REGISTRY_ID)};
|
|
3783
|
+
let el = document.getElementById(id);
|
|
3784
|
+
if (!el) {
|
|
3785
|
+
el = document.createElement('script');
|
|
3786
|
+
el.id = id;
|
|
3787
|
+
el.type = 'application/json';
|
|
3788
|
+
document.documentElement.appendChild(el);
|
|
3789
|
+
}
|
|
3790
|
+
el.textContent = ${JSON.stringify(registry)};
|
|
3791
|
+
})()`,
|
|
3792
|
+
returnByValue: true,
|
|
3793
|
+
awaitPromise: true
|
|
3794
|
+
});
|
|
3795
|
+
return { warnings: [] };
|
|
3796
|
+
} catch {
|
|
3797
|
+
return { warnings: ["Snapshot ref registry could not be applied."] };
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
var pruneUnappliedRefsFromSnapshot = (snapshot, appliedRefs) => {
|
|
3801
|
+
for (const node of getAxNodes(snapshot)) {
|
|
3802
|
+
if (!node || typeof node !== "object") {
|
|
3803
|
+
continue;
|
|
3804
|
+
}
|
|
3805
|
+
const ref = node.ref;
|
|
3806
|
+
if (typeof ref === "string" && !appliedRefs.has(ref)) {
|
|
3807
|
+
delete node.ref;
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3620
3810
|
};
|
|
3621
3811
|
var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
|
|
3622
3812
|
await debuggerCommand(tabId, "DOM.enable", {});
|
|
@@ -3645,107 +3835,6 @@ var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
|
|
|
3645
3835
|
}
|
|
3646
3836
|
};
|
|
3647
3837
|
|
|
3648
|
-
// packages/core/src/page-state-script.ts
|
|
3649
|
-
var PAGE_STATE_SCRIPT = [
|
|
3650
|
-
"(() => {",
|
|
3651
|
-
" const escape = (value) => {",
|
|
3652
|
-
" if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
|
|
3653
|
-
" return CSS.escape(value);",
|
|
3654
|
-
" }",
|
|
3655
|
-
` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
|
|
3656
|
-
" };",
|
|
3657
|
-
" const truncate = (value, max) => {",
|
|
3658
|
-
" const text = String(value ?? '');",
|
|
3659
|
-
" return text.length > max ? text.slice(0, max) : text;",
|
|
3660
|
-
" };",
|
|
3661
|
-
" const selectorFor = (element) => {",
|
|
3662
|
-
" if (element.id) {",
|
|
3663
|
-
" return `#${escape(element.id)}`;",
|
|
3664
|
-
" }",
|
|
3665
|
-
" const name = element.getAttribute('name');",
|
|
3666
|
-
" if (name) {",
|
|
3667
|
-
' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
|
|
3668
|
-
" }",
|
|
3669
|
-
" const parts = [];",
|
|
3670
|
-
" let node = element;",
|
|
3671
|
-
" while (node && node.nodeType === 1 && parts.length < 4) {",
|
|
3672
|
-
" let part = node.tagName.toLowerCase();",
|
|
3673
|
-
" const parent = node.parentElement;",
|
|
3674
|
-
" if (parent) {",
|
|
3675
|
-
" const siblings = Array.from(parent.children).filter(",
|
|
3676
|
-
" (child) => child.tagName === node.tagName",
|
|
3677
|
-
" );",
|
|
3678
|
-
" if (siblings.length > 1) {",
|
|
3679
|
-
" part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
|
|
3680
|
-
" }",
|
|
3681
|
-
" }",
|
|
3682
|
-
" parts.unshift(part);",
|
|
3683
|
-
" node = parent;",
|
|
3684
|
-
" }",
|
|
3685
|
-
" return parts.join('>');",
|
|
3686
|
-
" };",
|
|
3687
|
-
" const readStorage = (storage, limit) => {",
|
|
3688
|
-
" try {",
|
|
3689
|
-
" return Object.keys(storage)",
|
|
3690
|
-
" .slice(0, limit)",
|
|
3691
|
-
" .map((key) => ({",
|
|
3692
|
-
" key,",
|
|
3693
|
-
" value: truncate(storage.getItem(key), 500),",
|
|
3694
|
-
" }));",
|
|
3695
|
-
" } catch {",
|
|
3696
|
-
" return [];",
|
|
3697
|
-
" }",
|
|
3698
|
-
" };",
|
|
3699
|
-
" const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
|
|
3700
|
-
" const fields = Array.from(form.elements)",
|
|
3701
|
-
" .filter((element) => element && element.tagName)",
|
|
3702
|
-
" .map((element) => {",
|
|
3703
|
-
" const tag = element.tagName.toLowerCase();",
|
|
3704
|
-
" const type = 'type' in element && element.type ? element.type : tag;",
|
|
3705
|
-
" const name = element.name || element.getAttribute('name') || element.id || '';",
|
|
3706
|
-
" let value = '';",
|
|
3707
|
-
" let options;",
|
|
3708
|
-
" if (tag === 'select') {",
|
|
3709
|
-
" const select = element;",
|
|
3710
|
-
" value = select.value ?? '';",
|
|
3711
|
-
" options = Array.from(select.options).map((option) => option.text);",
|
|
3712
|
-
" } else if (tag === 'input' && element.type === 'password') {",
|
|
3713
|
-
" value = '[redacted]';",
|
|
3714
|
-
" } else if (tag === 'input' || tag === 'textarea') {",
|
|
3715
|
-
" value = element.value ?? '';",
|
|
3716
|
-
" } else if (element.isContentEditable) {",
|
|
3717
|
-
" value = element.textContent ?? '';",
|
|
3718
|
-
" } else if ('value' in element) {",
|
|
3719
|
-
" value = element.value ?? '';",
|
|
3720
|
-
" }",
|
|
3721
|
-
" return {",
|
|
3722
|
-
" name,",
|
|
3723
|
-
" type,",
|
|
3724
|
-
" value: truncate(value, 500),",
|
|
3725
|
-
" ...(options ? { options } : {}),",
|
|
3726
|
-
" };",
|
|
3727
|
-
" });",
|
|
3728
|
-
" return {",
|
|
3729
|
-
" selector: selectorFor(form),",
|
|
3730
|
-
" action: form.getAttribute('action') || undefined,",
|
|
3731
|
-
" method: form.getAttribute('method') || undefined,",
|
|
3732
|
-
" fields,",
|
|
3733
|
-
" };",
|
|
3734
|
-
" });",
|
|
3735
|
-
" const localStorage = readStorage(window.localStorage, 100);",
|
|
3736
|
-
" const sessionStorage = readStorage(window.sessionStorage, 100);",
|
|
3737
|
-
" const cookies = (document.cookie ? document.cookie.split(';') : [])",
|
|
3738
|
-
" .map((entry) => entry.trim())",
|
|
3739
|
-
" .filter((entry) => entry.length > 0)",
|
|
3740
|
-
" .slice(0, 50)",
|
|
3741
|
-
" .map((entry) => {",
|
|
3742
|
-
" const [key, ...rest] = entry.split('=');",
|
|
3743
|
-
" return { key, value: truncate(rest.join('='), 500) };",
|
|
3744
|
-
" });",
|
|
3745
|
-
" return { forms, localStorage, sessionStorage, cookies };",
|
|
3746
|
-
"})()"
|
|
3747
|
-
].join("\n");
|
|
3748
|
-
|
|
3749
3838
|
// packages/core/src/target-matching.ts
|
|
3750
3839
|
var normalizeText = (value) => (value ?? "").trim().toLowerCase();
|
|
3751
3840
|
var normalizeUrl = (value) => {
|
|
@@ -3851,6 +3940,199 @@ var pickBestTarget = (candidates, hint, now = Date.now()) => {
|
|
|
3851
3940
|
return ranked.length > 0 ? ranked[0] : null;
|
|
3852
3941
|
};
|
|
3853
3942
|
|
|
3943
|
+
// packages/core/src/inspect/target-selection.ts
|
|
3944
|
+
var readTargetHintInput = (target) => {
|
|
3945
|
+
if (!target) {
|
|
3946
|
+
return void 0;
|
|
3947
|
+
}
|
|
3948
|
+
const url = typeof target.url === "string" ? target.url : void 0;
|
|
3949
|
+
const title = typeof target.title === "string" ? target.title : void 0;
|
|
3950
|
+
const tabIdRaw = target.tab_id ?? target.tabId;
|
|
3951
|
+
const tabId = typeof tabIdRaw === "number" ? tabIdRaw : void 0;
|
|
3952
|
+
const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
|
|
3953
|
+
const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
|
|
3954
|
+
if (!url && !title && tabId === void 0 && !lastActiveAt) {
|
|
3955
|
+
return void 0;
|
|
3956
|
+
}
|
|
3957
|
+
return { url, title, tabId, lastActiveAt };
|
|
3958
|
+
};
|
|
3959
|
+
var readSessionTargetHint = (registry, sessionId) => {
|
|
3960
|
+
if (typeof sessionId !== "string") {
|
|
3961
|
+
return void 0;
|
|
3962
|
+
}
|
|
3963
|
+
const selectedTabId = registry.get(sessionId)?.selectedTabId;
|
|
3964
|
+
return typeof selectedTabId === "number" && Number.isFinite(selectedTabId) ? { tabId: selectedTabId } : void 0;
|
|
3965
|
+
};
|
|
3966
|
+
var resolveInspectTargetHint = (options) => {
|
|
3967
|
+
const explicit = readTargetHintInput(options.target);
|
|
3968
|
+
if (explicit) {
|
|
3969
|
+
return explicit;
|
|
3970
|
+
}
|
|
3971
|
+
const sessionHint = readSessionTargetHint(
|
|
3972
|
+
options.registry,
|
|
3973
|
+
options.sessionId
|
|
3974
|
+
);
|
|
3975
|
+
if (sessionHint) {
|
|
3976
|
+
return sessionHint;
|
|
3977
|
+
}
|
|
3978
|
+
const tabs = options.extensionBridge?.getStatus().tabs ?? [];
|
|
3979
|
+
return deriveHintFromTabs(tabs);
|
|
3980
|
+
};
|
|
3981
|
+
var selectInspectTab = (options) => {
|
|
3982
|
+
if (!options.extensionBridge || !options.extensionBridge.isConnected()) {
|
|
3983
|
+
throw new InspectError(
|
|
3984
|
+
"EXTENSION_DISCONNECTED",
|
|
3985
|
+
"Extension is not connected.",
|
|
3986
|
+
{
|
|
3987
|
+
retryable: true
|
|
3988
|
+
}
|
|
3989
|
+
);
|
|
3990
|
+
}
|
|
3991
|
+
const tabs = options.extensionBridge.getStatus().tabs ?? [];
|
|
3992
|
+
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
3993
|
+
throw new InspectError("TAB_NOT_FOUND", "No tabs available to inspect.");
|
|
3994
|
+
}
|
|
3995
|
+
const effectiveHint = options.targetHint ?? readSessionTargetHint(options.registry, options.sessionId);
|
|
3996
|
+
if (typeof effectiveHint?.tabId === "number" && Number.isFinite(effectiveHint.tabId)) {
|
|
3997
|
+
const tab2 = tabs.find((entry) => entry.tab_id === effectiveHint.tabId);
|
|
3998
|
+
if (!tab2) {
|
|
3999
|
+
throw new InspectError(
|
|
4000
|
+
"TAB_NOT_FOUND",
|
|
4001
|
+
`No matching tab found for tab_id ${effectiveHint.tabId}.`,
|
|
4002
|
+
{ details: { tab_id: effectiveHint.tabId } }
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
return { tabId: effectiveHint.tabId, tab: tab2 };
|
|
4006
|
+
}
|
|
4007
|
+
const candidates = tabs.map((tab2) => ({
|
|
4008
|
+
id: String(tab2.tab_id),
|
|
4009
|
+
url: tab2.url ?? "",
|
|
4010
|
+
title: tab2.title,
|
|
4011
|
+
lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
|
|
4012
|
+
}));
|
|
4013
|
+
const ranked = pickBestTarget(candidates, effectiveHint);
|
|
4014
|
+
if (!ranked) {
|
|
4015
|
+
throw new InspectError("TAB_NOT_FOUND", "No matching tab found.");
|
|
4016
|
+
}
|
|
4017
|
+
const tabId = Number(ranked.candidate.id);
|
|
4018
|
+
if (!Number.isFinite(tabId)) {
|
|
4019
|
+
throw new InspectError("TAB_NOT_FOUND", "Resolved tab id is invalid.");
|
|
4020
|
+
}
|
|
4021
|
+
const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
|
|
4022
|
+
const warnings = [];
|
|
4023
|
+
if (!effectiveHint) {
|
|
4024
|
+
warnings.push("No target hint provided; using the most recent tab.");
|
|
4025
|
+
} else if (ranked.score < 20) {
|
|
4026
|
+
warnings.push("Weak target match; using best available tab.");
|
|
4027
|
+
}
|
|
4028
|
+
return {
|
|
4029
|
+
tabId,
|
|
4030
|
+
tab,
|
|
4031
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
4032
|
+
};
|
|
4033
|
+
};
|
|
4034
|
+
|
|
4035
|
+
// packages/core/src/page-state-script.ts
|
|
4036
|
+
var PAGE_STATE_SCRIPT = [
|
|
4037
|
+
"(() => {",
|
|
4038
|
+
" const escape = (value) => {",
|
|
4039
|
+
" if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
|
|
4040
|
+
" return CSS.escape(value);",
|
|
4041
|
+
" }",
|
|
4042
|
+
` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
|
|
4043
|
+
" };",
|
|
4044
|
+
" const truncate = (value, max) => {",
|
|
4045
|
+
" const text = String(value ?? '');",
|
|
4046
|
+
" return text.length > max ? text.slice(0, max) : text;",
|
|
4047
|
+
" };",
|
|
4048
|
+
" const selectorFor = (element) => {",
|
|
4049
|
+
" if (element.id) {",
|
|
4050
|
+
" return `#${escape(element.id)}`;",
|
|
4051
|
+
" }",
|
|
4052
|
+
" const name = element.getAttribute('name');",
|
|
4053
|
+
" if (name) {",
|
|
4054
|
+
' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
|
|
4055
|
+
" }",
|
|
4056
|
+
" const parts = [];",
|
|
4057
|
+
" let node = element;",
|
|
4058
|
+
" while (node && node.nodeType === 1 && parts.length < 4) {",
|
|
4059
|
+
" let part = node.tagName.toLowerCase();",
|
|
4060
|
+
" const parent = node.parentElement;",
|
|
4061
|
+
" if (parent) {",
|
|
4062
|
+
" const siblings = Array.from(parent.children).filter(",
|
|
4063
|
+
" (child) => child.tagName === node.tagName",
|
|
4064
|
+
" );",
|
|
4065
|
+
" if (siblings.length > 1) {",
|
|
4066
|
+
" part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
|
|
4067
|
+
" }",
|
|
4068
|
+
" }",
|
|
4069
|
+
" parts.unshift(part);",
|
|
4070
|
+
" node = parent;",
|
|
4071
|
+
" }",
|
|
4072
|
+
" return parts.join('>');",
|
|
4073
|
+
" };",
|
|
4074
|
+
" const readStorage = (storage, limit) => {",
|
|
4075
|
+
" try {",
|
|
4076
|
+
" return Object.keys(storage)",
|
|
4077
|
+
" .slice(0, limit)",
|
|
4078
|
+
" .map((key) => ({",
|
|
4079
|
+
" key,",
|
|
4080
|
+
" value: truncate(storage.getItem(key), 500),",
|
|
4081
|
+
" }));",
|
|
4082
|
+
" } catch {",
|
|
4083
|
+
" return [];",
|
|
4084
|
+
" }",
|
|
4085
|
+
" };",
|
|
4086
|
+
" const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
|
|
4087
|
+
" const fields = Array.from(form.elements)",
|
|
4088
|
+
" .filter((element) => element && element.tagName)",
|
|
4089
|
+
" .map((element) => {",
|
|
4090
|
+
" const tag = element.tagName.toLowerCase();",
|
|
4091
|
+
" const type = 'type' in element && element.type ? element.type : tag;",
|
|
4092
|
+
" const name = element.name || element.getAttribute('name') || element.id || '';",
|
|
4093
|
+
" let value = '';",
|
|
4094
|
+
" let options;",
|
|
4095
|
+
" if (tag === 'select') {",
|
|
4096
|
+
" const select = element;",
|
|
4097
|
+
" value = select.value ?? '';",
|
|
4098
|
+
" options = Array.from(select.options).map((option) => option.text);",
|
|
4099
|
+
" } else if (tag === 'input' && element.type === 'password') {",
|
|
4100
|
+
" value = '[redacted]';",
|
|
4101
|
+
" } else if (tag === 'input' || tag === 'textarea') {",
|
|
4102
|
+
" value = element.value ?? '';",
|
|
4103
|
+
" } else if (element.isContentEditable) {",
|
|
4104
|
+
" value = element.textContent ?? '';",
|
|
4105
|
+
" } else if ('value' in element) {",
|
|
4106
|
+
" value = element.value ?? '';",
|
|
4107
|
+
" }",
|
|
4108
|
+
" return {",
|
|
4109
|
+
" name,",
|
|
4110
|
+
" type,",
|
|
4111
|
+
" value: truncate(value, 500),",
|
|
4112
|
+
" ...(options ? { options } : {}),",
|
|
4113
|
+
" };",
|
|
4114
|
+
" });",
|
|
4115
|
+
" return {",
|
|
4116
|
+
" selector: selectorFor(form),",
|
|
4117
|
+
" action: form.getAttribute('action') || undefined,",
|
|
4118
|
+
" method: form.getAttribute('method') || undefined,",
|
|
4119
|
+
" fields,",
|
|
4120
|
+
" };",
|
|
4121
|
+
" });",
|
|
4122
|
+
" const localStorage = readStorage(window.localStorage, 100);",
|
|
4123
|
+
" const sessionStorage = readStorage(window.sessionStorage, 100);",
|
|
4124
|
+
" const cookies = (document.cookie ? document.cookie.split(';') : [])",
|
|
4125
|
+
" .map((entry) => entry.trim())",
|
|
4126
|
+
" .filter((entry) => entry.length > 0)",
|
|
4127
|
+
" .slice(0, 50)",
|
|
4128
|
+
" .map((entry) => {",
|
|
4129
|
+
" const [key, ...rest] = entry.split('=');",
|
|
4130
|
+
" return { key, value: truncate(rest.join('='), 500) };",
|
|
4131
|
+
" });",
|
|
4132
|
+
" return { forms, localStorage, sessionStorage, cookies };",
|
|
4133
|
+
"})()"
|
|
4134
|
+
].join("\n");
|
|
4135
|
+
|
|
3854
4136
|
// packages/core/src/inspect/service.ts
|
|
3855
4137
|
var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
|
|
3856
4138
|
var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
|
|
@@ -3895,7 +4177,7 @@ var InspectService = class {
|
|
|
3895
4177
|
async reconnect(sessionId) {
|
|
3896
4178
|
try {
|
|
3897
4179
|
this.requireSession(sessionId);
|
|
3898
|
-
const selection = await this.resolveTab();
|
|
4180
|
+
const selection = await this.resolveTab(sessionId);
|
|
3899
4181
|
const debuggerBridge = this.ensureDebugger();
|
|
3900
4182
|
const result = await debuggerBridge.attach(selection.tabId);
|
|
3901
4183
|
if (result.ok) {
|
|
@@ -3914,7 +4196,7 @@ var InspectService = class {
|
|
|
3914
4196
|
}
|
|
3915
4197
|
async domSnapshot(input) {
|
|
3916
4198
|
this.requireSession(input.sessionId);
|
|
3917
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4199
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
3918
4200
|
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
3919
4201
|
const work = async () => {
|
|
3920
4202
|
if (input.format === "html") {
|
|
@@ -3964,20 +4246,11 @@ var InspectService = class {
|
|
|
3964
4246
|
);
|
|
3965
4247
|
selectorWarnings.push(...resolved.warnings ?? []);
|
|
3966
4248
|
if (!resolved.nodeId) {
|
|
3967
|
-
|
|
3968
|
-
try {
|
|
3969
|
-
refWarnings2 = await applySnapshotRefs(
|
|
3970
|
-
selection.tabId,
|
|
3971
|
-
/* @__PURE__ */ new Map(),
|
|
3972
|
-
debuggerCommand
|
|
3973
|
-
);
|
|
3974
|
-
} catch {
|
|
3975
|
-
refWarnings2 = ["Failed to clear prior snapshot refs."];
|
|
3976
|
-
}
|
|
4249
|
+
const refWarnings = (await clearSnapshotRefArtifacts(selection.tabId, debuggerCommand)).warnings;
|
|
3977
4250
|
const warnings2 = [
|
|
3978
4251
|
...selection.warnings ?? [],
|
|
3979
4252
|
...selectorWarnings,
|
|
3980
|
-
...
|
|
4253
|
+
...refWarnings
|
|
3981
4254
|
];
|
|
3982
4255
|
return {
|
|
3983
4256
|
format: "ax",
|
|
@@ -4014,16 +4287,28 @@ var InspectService = class {
|
|
|
4014
4287
|
}
|
|
4015
4288
|
}
|
|
4016
4289
|
const refMap = assignRefsToAxSnapshot(snapshot);
|
|
4017
|
-
const
|
|
4290
|
+
const clearResult = await clearSnapshotRefArtifacts(
|
|
4291
|
+
selection.tabId,
|
|
4292
|
+
debuggerCommand
|
|
4293
|
+
);
|
|
4294
|
+
const refResult = await applySnapshotRefAttributes(
|
|
4018
4295
|
selection.tabId,
|
|
4019
4296
|
refMap,
|
|
4020
4297
|
debuggerCommand
|
|
4021
4298
|
);
|
|
4299
|
+
const persistResult = refMap.size === 0 ? { warnings: [] } : await persistSnapshotRefRegistry(
|
|
4300
|
+
selection.tabId,
|
|
4301
|
+
refResult.appliedBindings,
|
|
4302
|
+
debuggerCommand
|
|
4303
|
+
);
|
|
4304
|
+
pruneUnappliedRefsFromSnapshot(snapshot, refResult.appliedRefs);
|
|
4022
4305
|
const warnings = [
|
|
4023
4306
|
...selection.warnings ?? [],
|
|
4024
4307
|
...selectorWarnings,
|
|
4025
4308
|
...truncationWarnings,
|
|
4026
|
-
...
|
|
4309
|
+
...clearResult.warnings,
|
|
4310
|
+
...refResult.warnings,
|
|
4311
|
+
...persistResult.warnings
|
|
4027
4312
|
];
|
|
4028
4313
|
return {
|
|
4029
4314
|
format: "ax",
|
|
@@ -4149,7 +4434,7 @@ var InspectService = class {
|
|
|
4149
4434
|
}
|
|
4150
4435
|
async consoleList(input) {
|
|
4151
4436
|
this.requireSession(input.sessionId);
|
|
4152
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4437
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4153
4438
|
await this.enableConsole(selection.tabId);
|
|
4154
4439
|
const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
|
|
4155
4440
|
const entries = events.map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
|
|
@@ -4162,7 +4447,7 @@ var InspectService = class {
|
|
|
4162
4447
|
}
|
|
4163
4448
|
async networkHar(input) {
|
|
4164
4449
|
this.requireSession(input.sessionId);
|
|
4165
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4450
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4166
4451
|
await this.enableNetwork(selection.tabId);
|
|
4167
4452
|
const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
|
|
4168
4453
|
const har = buildHar(events, selection.tab.title);
|
|
@@ -4189,7 +4474,7 @@ var InspectService = class {
|
|
|
4189
4474
|
}
|
|
4190
4475
|
async evaluate(input) {
|
|
4191
4476
|
this.requireSession(input.sessionId);
|
|
4192
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4477
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4193
4478
|
const expression = input.expression ?? "undefined";
|
|
4194
4479
|
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
4195
4480
|
const result = await this.debuggerCommand(
|
|
@@ -4218,7 +4503,7 @@ var InspectService = class {
|
|
|
4218
4503
|
}
|
|
4219
4504
|
async extractContent(input) {
|
|
4220
4505
|
this.requireSession(input.sessionId);
|
|
4221
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4506
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4222
4507
|
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
4223
4508
|
const html = await captureHtml(selection.tabId, {
|
|
4224
4509
|
debuggerCommand,
|
|
@@ -4233,56 +4518,30 @@ var InspectService = class {
|
|
|
4233
4518
|
}
|
|
4234
4519
|
});
|
|
4235
4520
|
const url = selection.tab.url ?? "about:blank";
|
|
4236
|
-
let article = null;
|
|
4237
4521
|
try {
|
|
4238
|
-
const
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
this.recordError(err);
|
|
4257
|
-
throw err;
|
|
4258
|
-
}
|
|
4259
|
-
let content = "";
|
|
4260
|
-
if (input.format === "article_json") {
|
|
4261
|
-
content = JSON.stringify(article, null, 2);
|
|
4262
|
-
} else if (input.format === "text") {
|
|
4263
|
-
content = article.textContent ?? "";
|
|
4264
|
-
} else {
|
|
4265
|
-
const turndown = new import_turndown.default();
|
|
4266
|
-
content = turndown.turndown(article.content ?? "");
|
|
4522
|
+
const { article, semanticMainCandidate } = parseExtractContentSource({
|
|
4523
|
+
html,
|
|
4524
|
+
url
|
|
4525
|
+
});
|
|
4526
|
+
const output = renderExtractContent({
|
|
4527
|
+
format: input.format,
|
|
4528
|
+
article,
|
|
4529
|
+
semanticMainCandidate,
|
|
4530
|
+
includeMetadata: input.includeMetadata,
|
|
4531
|
+
warnings: selection.warnings
|
|
4532
|
+
});
|
|
4533
|
+
this.markInspectConnected(input.sessionId);
|
|
4534
|
+
return output;
|
|
4535
|
+
} catch (error) {
|
|
4536
|
+
if (error instanceof InspectError) {
|
|
4537
|
+
this.recordError(error);
|
|
4538
|
+
}
|
|
4539
|
+
throw error;
|
|
4267
4540
|
}
|
|
4268
|
-
const warnings = selection.warnings ?? [];
|
|
4269
|
-
const includeMetadata = input.includeMetadata ?? true;
|
|
4270
|
-
const output = {
|
|
4271
|
-
content,
|
|
4272
|
-
...includeMetadata ? {
|
|
4273
|
-
title: article.title ?? void 0,
|
|
4274
|
-
byline: article.byline ?? void 0,
|
|
4275
|
-
excerpt: article.excerpt ?? void 0,
|
|
4276
|
-
siteName: article.siteName ?? void 0
|
|
4277
|
-
} : {},
|
|
4278
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
4279
|
-
};
|
|
4280
|
-
this.markInspectConnected(input.sessionId);
|
|
4281
|
-
return output;
|
|
4282
4541
|
}
|
|
4283
4542
|
async pageState(input) {
|
|
4284
4543
|
this.requireSession(input.sessionId);
|
|
4285
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4544
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4286
4545
|
await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
|
|
4287
4546
|
const expression = PAGE_STATE_SCRIPT;
|
|
4288
4547
|
const result = await this.debuggerCommand(
|
|
@@ -4321,7 +4580,7 @@ var InspectService = class {
|
|
|
4321
4580
|
}
|
|
4322
4581
|
async performanceMetrics(input) {
|
|
4323
4582
|
this.requireSession(input.sessionId);
|
|
4324
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4583
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4325
4584
|
await this.debuggerCommand(selection.tabId, "Performance.enable", {});
|
|
4326
4585
|
const result = await this.debuggerCommand(
|
|
4327
4586
|
selection.tabId,
|
|
@@ -4338,9 +4597,13 @@ var InspectService = class {
|
|
|
4338
4597
|
}
|
|
4339
4598
|
async screenshot(input) {
|
|
4340
4599
|
this.requireSession(input.sessionId);
|
|
4341
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4600
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4342
4601
|
const format = input.format ?? "png";
|
|
4343
|
-
const
|
|
4602
|
+
const createScreenshotError = (code, message, retryable = false, details) => new InspectError(code, message, {
|
|
4603
|
+
retryable,
|
|
4604
|
+
...details ? { details } : {}
|
|
4605
|
+
});
|
|
4606
|
+
const writeArtifact = async (data) => {
|
|
4344
4607
|
try {
|
|
4345
4608
|
const rootDir = await ensureArtifactRootDir(input.sessionId);
|
|
4346
4609
|
const artifactId = (0, import_crypto3.randomUUID)();
|
|
@@ -4349,7 +4612,7 @@ var InspectService = class {
|
|
|
4349
4612
|
rootDir,
|
|
4350
4613
|
`screenshot-${artifactId}.${extension}`
|
|
4351
4614
|
);
|
|
4352
|
-
await (0, import_promises3.writeFile)(filePath, Buffer.from(
|
|
4615
|
+
await (0, import_promises3.writeFile)(filePath, Buffer.from(data, "base64"));
|
|
4353
4616
|
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
4354
4617
|
const output = {
|
|
4355
4618
|
artifact_id: artifactId,
|
|
@@ -4367,143 +4630,138 @@ var InspectService = class {
|
|
|
4367
4630
|
throw error;
|
|
4368
4631
|
}
|
|
4369
4632
|
};
|
|
4370
|
-
|
|
4633
|
+
const captureViaExtension = async (mode, failureMessage) => {
|
|
4371
4634
|
if (!this.extensionBridge?.request) {
|
|
4372
|
-
|
|
4635
|
+
throw createScreenshotError(
|
|
4373
4636
|
"NOT_SUPPORTED",
|
|
4374
|
-
"
|
|
4637
|
+
"Screenshots require an extension that supports drive.screenshot."
|
|
4375
4638
|
);
|
|
4376
|
-
this.recordError(error);
|
|
4377
|
-
throw error;
|
|
4378
4639
|
}
|
|
4379
4640
|
const response = await this.extensionBridge.request(
|
|
4380
4641
|
"drive.screenshot",
|
|
4381
4642
|
{
|
|
4382
4643
|
tab_id: selection.tabId,
|
|
4383
|
-
mode
|
|
4384
|
-
selector: input.selector,
|
|
4644
|
+
mode,
|
|
4645
|
+
...input.selector ? { selector: input.selector } : {},
|
|
4385
4646
|
format,
|
|
4386
4647
|
...typeof input.quality === "number" ? { quality: input.quality } : {}
|
|
4387
4648
|
},
|
|
4388
4649
|
12e4
|
|
4389
4650
|
);
|
|
4390
4651
|
if (response.status === "error") {
|
|
4391
|
-
const error =
|
|
4652
|
+
const error = createScreenshotError(
|
|
4392
4653
|
response.error?.code ?? "INSPECT_UNAVAILABLE",
|
|
4393
|
-
response.error?.message ??
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4654
|
+
response.error?.message ?? failureMessage,
|
|
4655
|
+
response.error?.retryable ?? false,
|
|
4656
|
+
response.error?.details
|
|
4657
|
+
);
|
|
4658
|
+
this.recordError(error);
|
|
4659
|
+
throw error;
|
|
4660
|
+
}
|
|
4661
|
+
const result = response.result;
|
|
4662
|
+
if (!result?.data_base64 || typeof result.data_base64 !== "string") {
|
|
4663
|
+
const error = createScreenshotError(
|
|
4664
|
+
"INSPECT_UNAVAILABLE",
|
|
4665
|
+
failureMessage
|
|
4398
4666
|
);
|
|
4399
4667
|
this.recordError(error);
|
|
4400
4668
|
throw error;
|
|
4401
4669
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
4670
|
+
return await writeArtifact(result.data_base64);
|
|
4671
|
+
};
|
|
4672
|
+
const shouldFallbackFromExtensionScreenshot = (error) => [
|
|
4673
|
+
"NOT_SUPPORTED",
|
|
4674
|
+
"NOT_IMPLEMENTED",
|
|
4675
|
+
"INSPECT_UNAVAILABLE",
|
|
4676
|
+
"PERMISSION_REQUIRED",
|
|
4677
|
+
"RATE_LIMITED"
|
|
4678
|
+
].includes(error.code);
|
|
4679
|
+
const shouldPreserveExtensionScreenshotError = (fallbackError) => [
|
|
4680
|
+
"INSPECT_UNAVAILABLE",
|
|
4681
|
+
"ATTACH_DENIED",
|
|
4682
|
+
"NOT_SUPPORTED",
|
|
4683
|
+
"NOT_IMPLEMENTED"
|
|
4684
|
+
].includes(fallbackError.code);
|
|
4685
|
+
const captureViaDebugger = async () => {
|
|
4686
|
+
await this.debuggerCommand(selection.tabId, "Page.enable", {});
|
|
4687
|
+
let captureParams = {
|
|
4688
|
+
format,
|
|
4689
|
+
fromSurface: true
|
|
4690
|
+
};
|
|
4691
|
+
if (format !== "png" && typeof input.quality === "number") {
|
|
4692
|
+
captureParams = { ...captureParams, quality: input.quality };
|
|
4693
|
+
}
|
|
4694
|
+
if (input.target === "full") {
|
|
4695
|
+
const layout = await this.debuggerCommand(
|
|
4696
|
+
selection.tabId,
|
|
4697
|
+
"Page.getLayoutMetrics",
|
|
4698
|
+
{}
|
|
4699
|
+
);
|
|
4700
|
+
const contentSize = layout?.contentSize;
|
|
4701
|
+
if (contentSize) {
|
|
4702
|
+
captureParams = {
|
|
4703
|
+
...captureParams,
|
|
4704
|
+
clip: {
|
|
4705
|
+
x: 0,
|
|
4706
|
+
y: 0,
|
|
4707
|
+
width: contentSize.width,
|
|
4708
|
+
height: contentSize.height,
|
|
4709
|
+
scale: 1
|
|
4710
|
+
}
|
|
4711
|
+
};
|
|
4712
|
+
} else {
|
|
4713
|
+
captureParams = { ...captureParams, captureBeyondViewport: true };
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
const result = await this.debuggerCommand(
|
|
4717
|
+
selection.tabId,
|
|
4718
|
+
"Page.captureScreenshot",
|
|
4719
|
+
captureParams
|
|
4720
|
+
);
|
|
4721
|
+
const data = result.data;
|
|
4722
|
+
if (!data) {
|
|
4404
4723
|
const error = new InspectError(
|
|
4405
4724
|
"INSPECT_UNAVAILABLE",
|
|
4406
|
-
"Failed to capture
|
|
4725
|
+
"Failed to capture screenshot.",
|
|
4726
|
+
{ retryable: false }
|
|
4407
4727
|
);
|
|
4408
4728
|
this.recordError(error);
|
|
4409
4729
|
throw error;
|
|
4410
4730
|
}
|
|
4411
|
-
return await writeArtifact(
|
|
4731
|
+
return await writeArtifact(data);
|
|
4732
|
+
};
|
|
4733
|
+
if (input.selector) {
|
|
4734
|
+
return await captureViaExtension(
|
|
4735
|
+
"element",
|
|
4736
|
+
"Failed to capture element screenshot."
|
|
4737
|
+
);
|
|
4412
4738
|
}
|
|
4413
|
-
|
|
4739
|
+
let extensionScreenshotError;
|
|
4740
|
+
if (this.extensionBridge?.request) {
|
|
4414
4741
|
try {
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
tab_id: selection.tabId,
|
|
4419
|
-
mode: "full_page",
|
|
4420
|
-
format,
|
|
4421
|
-
...typeof input.quality === "number" ? { quality: input.quality } : {}
|
|
4422
|
-
},
|
|
4423
|
-
12e4
|
|
4742
|
+
return await captureViaExtension(
|
|
4743
|
+
input.target === "full" ? "full_page" : "viewport",
|
|
4744
|
+
input.target === "full" ? "Failed to capture full page screenshot." : "Failed to capture viewport screenshot."
|
|
4424
4745
|
);
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
response.error?.code ?? "INSPECT_UNAVAILABLE",
|
|
4428
|
-
response.error?.message ?? "Failed to capture full page screenshot.",
|
|
4429
|
-
{
|
|
4430
|
-
retryable: response.error?.retryable ?? false,
|
|
4431
|
-
...response.error?.details ? { details: response.error.details } : {}
|
|
4432
|
-
}
|
|
4433
|
-
);
|
|
4434
|
-
this.recordError(error);
|
|
4746
|
+
} catch (error) {
|
|
4747
|
+
if (!(error instanceof InspectError)) {
|
|
4435
4748
|
throw error;
|
|
4436
4749
|
}
|
|
4437
|
-
|
|
4438
|
-
if (!result2?.data_base64 || typeof result2.data_base64 !== "string") {
|
|
4439
|
-
const error = new InspectError(
|
|
4440
|
-
"INSPECT_UNAVAILABLE",
|
|
4441
|
-
"Failed to capture full page screenshot."
|
|
4442
|
-
);
|
|
4443
|
-
this.recordError(error);
|
|
4750
|
+
if (!shouldFallbackFromExtensionScreenshot(error)) {
|
|
4444
4751
|
throw error;
|
|
4445
4752
|
}
|
|
4446
|
-
|
|
4447
|
-
} catch (error) {
|
|
4448
|
-
if (error instanceof InspectError) {
|
|
4449
|
-
const code = String(error.code);
|
|
4450
|
-
if (!error.retryable && ![
|
|
4451
|
-
"NOT_SUPPORTED",
|
|
4452
|
-
"NOT_IMPLEMENTED",
|
|
4453
|
-
"INSPECT_UNAVAILABLE",
|
|
4454
|
-
"RATE_LIMITED"
|
|
4455
|
-
].includes(code)) {
|
|
4456
|
-
throw error;
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4753
|
+
extensionScreenshotError = error;
|
|
4459
4754
|
}
|
|
4460
4755
|
}
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
captureParams = { ...captureParams, quality: input.quality };
|
|
4468
|
-
}
|
|
4469
|
-
if (input.target === "full") {
|
|
4470
|
-
const layout = await this.debuggerCommand(
|
|
4471
|
-
selection.tabId,
|
|
4472
|
-
"Page.getLayoutMetrics",
|
|
4473
|
-
{}
|
|
4474
|
-
);
|
|
4475
|
-
const contentSize = layout?.contentSize;
|
|
4476
|
-
if (contentSize) {
|
|
4477
|
-
captureParams = {
|
|
4478
|
-
...captureParams,
|
|
4479
|
-
clip: {
|
|
4480
|
-
x: 0,
|
|
4481
|
-
y: 0,
|
|
4482
|
-
width: contentSize.width,
|
|
4483
|
-
height: contentSize.height,
|
|
4484
|
-
scale: 1
|
|
4485
|
-
}
|
|
4486
|
-
};
|
|
4487
|
-
} else {
|
|
4488
|
-
captureParams = { ...captureParams, captureBeyondViewport: true };
|
|
4756
|
+
try {
|
|
4757
|
+
return await captureViaDebugger();
|
|
4758
|
+
} catch (error) {
|
|
4759
|
+
if (extensionScreenshotError && error instanceof InspectError && shouldPreserveExtensionScreenshotError(error)) {
|
|
4760
|
+
this.recordError(extensionScreenshotError);
|
|
4761
|
+
throw extensionScreenshotError;
|
|
4489
4762
|
}
|
|
4490
|
-
}
|
|
4491
|
-
const result = await this.debuggerCommand(
|
|
4492
|
-
selection.tabId,
|
|
4493
|
-
"Page.captureScreenshot",
|
|
4494
|
-
captureParams
|
|
4495
|
-
);
|
|
4496
|
-
const data = result.data;
|
|
4497
|
-
if (!data) {
|
|
4498
|
-
const error = new InspectError(
|
|
4499
|
-
"INSPECT_UNAVAILABLE",
|
|
4500
|
-
"Failed to capture screenshot.",
|
|
4501
|
-
{ retryable: false }
|
|
4502
|
-
);
|
|
4503
|
-
this.recordError(error);
|
|
4504
4763
|
throw error;
|
|
4505
4764
|
}
|
|
4506
|
-
return await writeArtifact(data);
|
|
4507
4765
|
}
|
|
4508
4766
|
ensureDebugger() {
|
|
4509
4767
|
if (!this.debugger) {
|
|
@@ -4513,58 +4771,20 @@ var InspectService = class {
|
|
|
4513
4771
|
}
|
|
4514
4772
|
return this.debugger;
|
|
4515
4773
|
}
|
|
4516
|
-
async resolveTab(hint) {
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
const error = new InspectError(
|
|
4529
|
-
"TAB_NOT_FOUND",
|
|
4530
|
-
"No tabs available to inspect."
|
|
4531
|
-
);
|
|
4532
|
-
this.recordError(error);
|
|
4533
|
-
throw error;
|
|
4534
|
-
}
|
|
4535
|
-
const candidates = tabs.map((tab2) => ({
|
|
4536
|
-
id: String(tab2.tab_id),
|
|
4537
|
-
url: tab2.url ?? "",
|
|
4538
|
-
title: tab2.title,
|
|
4539
|
-
lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
|
|
4540
|
-
}));
|
|
4541
|
-
const ranked = pickBestTarget(candidates, hint);
|
|
4542
|
-
if (!ranked) {
|
|
4543
|
-
const error = new InspectError("TAB_NOT_FOUND", "No matching tab found.");
|
|
4544
|
-
this.recordError(error);
|
|
4545
|
-
throw error;
|
|
4546
|
-
}
|
|
4547
|
-
const tabId = Number(ranked.candidate.id);
|
|
4548
|
-
if (!Number.isFinite(tabId)) {
|
|
4549
|
-
const error = new InspectError(
|
|
4550
|
-
"TAB_NOT_FOUND",
|
|
4551
|
-
"Resolved tab id is invalid."
|
|
4552
|
-
);
|
|
4553
|
-
this.recordError(error);
|
|
4774
|
+
async resolveTab(sessionId, hint) {
|
|
4775
|
+
try {
|
|
4776
|
+
return selectInspectTab({
|
|
4777
|
+
sessionId,
|
|
4778
|
+
targetHint: hint,
|
|
4779
|
+
registry: this.registry,
|
|
4780
|
+
extensionBridge: this.extensionBridge
|
|
4781
|
+
});
|
|
4782
|
+
} catch (error) {
|
|
4783
|
+
if (error instanceof InspectError) {
|
|
4784
|
+
this.recordError(error);
|
|
4785
|
+
}
|
|
4554
4786
|
throw error;
|
|
4555
4787
|
}
|
|
4556
|
-
const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
|
|
4557
|
-
const warnings = [];
|
|
4558
|
-
if (!hint) {
|
|
4559
|
-
warnings.push("No target hint provided; using the most recent tab.");
|
|
4560
|
-
} else if (ranked.score < 20) {
|
|
4561
|
-
warnings.push("Weak target match; using best available tab.");
|
|
4562
|
-
}
|
|
4563
|
-
return {
|
|
4564
|
-
tabId,
|
|
4565
|
-
tab,
|
|
4566
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
4567
|
-
};
|
|
4568
4788
|
}
|
|
4569
4789
|
async enableConsole(tabId) {
|
|
4570
4790
|
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
@@ -4667,7 +4887,11 @@ var sendArtifactsError = (res, code, message, details, retryable = false) => {
|
|
|
4667
4887
|
});
|
|
4668
4888
|
};
|
|
4669
4889
|
var registerArtifactsRoutes = (router, options = {}) => {
|
|
4670
|
-
const inspect = options.inspectService ?? (options.registry ? createInspectService({
|
|
4890
|
+
const inspect = options.inspectService ?? (options.registry ? createInspectService({
|
|
4891
|
+
registry: options.registry,
|
|
4892
|
+
extensionBridge: options.extensionBridge,
|
|
4893
|
+
debuggerBridge: options.debuggerBridge
|
|
4894
|
+
}) : void 0);
|
|
4671
4895
|
router.post("/artifacts/screenshot", async (req, res) => {
|
|
4672
4896
|
if (!isRecord2(req.body)) {
|
|
4673
4897
|
sendArtifactsError(
|
|
@@ -4761,6 +4985,7 @@ var readCapability = (capabilities, name) => {
|
|
|
4761
4985
|
const candidate = capabilities[name];
|
|
4762
4986
|
return typeof candidate === "boolean" ? candidate : void 0;
|
|
4763
4987
|
};
|
|
4988
|
+
var hasCapturePermissionErrorReason = (details) => typeof details?.reason === "string" && details.reason === "capture_visible_tab_permission_required";
|
|
4764
4989
|
var hasEndpoint = (endpoint) => Boolean(
|
|
4765
4990
|
endpoint && typeof endpoint.host === "string" && endpoint.host.length > 0 && typeof endpoint.port === "number" && Number.isFinite(endpoint.port)
|
|
4766
4991
|
);
|
|
@@ -4773,9 +4998,7 @@ var toRuntimeEndpoint = (endpoint) => {
|
|
|
4773
4998
|
port: endpoint.port,
|
|
4774
4999
|
base_url: endpoint.baseUrl,
|
|
4775
5000
|
host_source: endpoint.hostSource,
|
|
4776
|
-
port_source: endpoint.portSource
|
|
4777
|
-
metadata_path: endpoint.metadataPath,
|
|
4778
|
-
isolated_mode: endpoint.isolatedMode
|
|
5001
|
+
port_source: endpoint.portSource
|
|
4779
5002
|
};
|
|
4780
5003
|
};
|
|
4781
5004
|
var toRuntimeProcess = (process2) => {
|
|
@@ -4839,23 +5062,6 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4839
5062
|
}
|
|
4840
5063
|
});
|
|
4841
5064
|
}
|
|
4842
|
-
if (callerEndpoint?.metadataPath && coreEndpoint?.metadataPath) {
|
|
4843
|
-
const matches = callerEndpoint.metadataPath === coreEndpoint.metadataPath;
|
|
4844
|
-
checks.push({
|
|
4845
|
-
name: "runtime.caller.metadata_path_match",
|
|
4846
|
-
ok: matches,
|
|
4847
|
-
message: matches ? "Caller metadata path matches the active core runtime metadata path." : "Caller metadata path differs from core runtime metadata path (shared core across worktrees).",
|
|
4848
|
-
details: {
|
|
4849
|
-
caller_metadata_path: callerEndpoint.metadataPath,
|
|
4850
|
-
core_metadata_path: coreEndpoint.metadataPath
|
|
4851
|
-
}
|
|
4852
|
-
});
|
|
4853
|
-
if (!matches) {
|
|
4854
|
-
warnings.push(
|
|
4855
|
-
"CLI is talking to a core process started from a different worktree metadata path. If daemon auto-start fails, retry with --no-daemon or enable isolated mode."
|
|
4856
|
-
);
|
|
4857
|
-
}
|
|
4858
|
-
}
|
|
4859
5065
|
if (extensionConnected && hasEndpoint(coreEndpoint) && hasEndpoint(extensionEndpoint)) {
|
|
4860
5066
|
const matches = coreEndpoint.host === extensionEndpoint.host && coreEndpoint.port === extensionEndpoint.port;
|
|
4861
5067
|
checks.push({
|
|
@@ -4903,7 +5109,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4903
5109
|
checks.push({
|
|
4904
5110
|
name: "inspect.capability",
|
|
4905
5111
|
ok: capabilityNegotiated && inspectEnabled,
|
|
4906
|
-
message: capabilityNegotiated && inspectEnabled ? "Inspect
|
|
5112
|
+
message: capabilityNegotiated && inspectEnabled ? "Inspect capability is available." : capabilityNegotiated ? "Inspect capability is unavailable from the connected extension/runtime." : "Inspect capability is unknown until extension capability negotiation completes.",
|
|
4907
5113
|
details: {
|
|
4908
5114
|
required_capabilities: ["debugger.attach", "debugger.command"],
|
|
4909
5115
|
debugger_attach: inspectAttachCapability,
|
|
@@ -4912,7 +5118,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4912
5118
|
});
|
|
4913
5119
|
if (capabilityNegotiated && !inspectEnabled) {
|
|
4914
5120
|
warnings.push(
|
|
4915
|
-
"Inspect commands
|
|
5121
|
+
"Inspect commands are unavailable from the connected extension/runtime. Reload or update the extension, then verify inspect.capability before using inspect.* routes."
|
|
4916
5122
|
);
|
|
4917
5123
|
}
|
|
4918
5124
|
}
|
|
@@ -4945,9 +5151,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4945
5151
|
code: context.driveLastError.code,
|
|
4946
5152
|
retryable: context.driveLastError.retryable,
|
|
4947
5153
|
at: context.driveLastError.at,
|
|
5154
|
+
...context.driveLastError.details ?? {},
|
|
4948
5155
|
...ageMs !== void 0 ? { age_ms: ageMs } : {}
|
|
4949
5156
|
}
|
|
4950
5157
|
});
|
|
5158
|
+
if (hasCapturePermissionErrorReason(context.driveLastError.details)) {
|
|
5159
|
+
warnings.push(
|
|
5160
|
+
"Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
|
|
5161
|
+
);
|
|
5162
|
+
}
|
|
4951
5163
|
}
|
|
4952
5164
|
if (context.inspectLastError) {
|
|
4953
5165
|
const ageMs = getErrorAgeMs(context.inspectLastError.at);
|
|
@@ -4965,9 +5177,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4965
5177
|
code: context.inspectLastError.code,
|
|
4966
5178
|
retryable: context.inspectLastError.retryable,
|
|
4967
5179
|
at: context.inspectLastError.at,
|
|
5180
|
+
...context.inspectLastError.details ?? {},
|
|
4968
5181
|
...ageMs !== void 0 ? { age_ms: ageMs } : {}
|
|
4969
5182
|
}
|
|
4970
5183
|
});
|
|
5184
|
+
if (hasCapturePermissionErrorReason(context.inspectLastError.details)) {
|
|
5185
|
+
warnings.push(
|
|
5186
|
+
"Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
|
|
5187
|
+
);
|
|
5188
|
+
}
|
|
4971
5189
|
}
|
|
4972
5190
|
if (context.recoveryAttempt) {
|
|
4973
5191
|
checks.push({
|
|
@@ -5001,6 +5219,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
5001
5219
|
} : void 0,
|
|
5002
5220
|
extension: {
|
|
5003
5221
|
connected: extensionConnected,
|
|
5222
|
+
extension_id: context.extension?.extensionId,
|
|
5004
5223
|
version: context.extension?.version,
|
|
5005
5224
|
last_seen_at: context.extension?.lastSeenAt
|
|
5006
5225
|
},
|
|
@@ -5037,6 +5256,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
5037
5256
|
process: toRuntimeProcess(context.runtime.core.process)
|
|
5038
5257
|
} : void 0,
|
|
5039
5258
|
extension: context.runtime.extension ? {
|
|
5259
|
+
extension_id: context.runtime.extension.extensionId,
|
|
5040
5260
|
version: context.runtime.extension.version,
|
|
5041
5261
|
protocol_version: context.runtime.extension.protocolVersion,
|
|
5042
5262
|
capability_negotiated: context.runtime.extension.capabilityNegotiated,
|
|
@@ -5084,12 +5304,78 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5084
5304
|
sessions: { active: sessionsActive },
|
|
5085
5305
|
extension: {
|
|
5086
5306
|
connected: extensionStatus?.connected ?? false,
|
|
5307
|
+
...extensionStatus?.extensionId ? { extension_id: extensionStatus.extensionId } : {},
|
|
5087
5308
|
...extensionStatus?.lastSeenAt ? { last_seen_at: extensionStatus.lastSeenAt } : {}
|
|
5088
5309
|
}
|
|
5089
5310
|
});
|
|
5090
5311
|
};
|
|
5091
5312
|
router.post("/health/check", handleHealthCheck);
|
|
5092
5313
|
router.post("/health_check", handleHealthCheck);
|
|
5314
|
+
router.post("/diagnostics/enable_inspect", async (req, res) => {
|
|
5315
|
+
const body = req.body ?? {};
|
|
5316
|
+
if (!isRecord2(body)) {
|
|
5317
|
+
sendError(res, 400, {
|
|
5318
|
+
code: "INVALID_ARGUMENT",
|
|
5319
|
+
message: "Request body must be an object.",
|
|
5320
|
+
retryable: false
|
|
5321
|
+
});
|
|
5322
|
+
return;
|
|
5323
|
+
}
|
|
5324
|
+
const extensionIdRaw = body.extension_id;
|
|
5325
|
+
if (extensionIdRaw !== void 0 && typeof extensionIdRaw !== "string") {
|
|
5326
|
+
sendError(res, 400, {
|
|
5327
|
+
code: "INVALID_ARGUMENT",
|
|
5328
|
+
message: "extension_id must be a string when provided.",
|
|
5329
|
+
retryable: false,
|
|
5330
|
+
details: { field: "extension_id" }
|
|
5331
|
+
});
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5334
|
+
if (!options.extensionBridge) {
|
|
5335
|
+
sendError(res, 503, {
|
|
5336
|
+
code: "EXTENSION_DISCONNECTED",
|
|
5337
|
+
message: "Extension bridge is unavailable.",
|
|
5338
|
+
retryable: true
|
|
5339
|
+
});
|
|
5340
|
+
return;
|
|
5341
|
+
}
|
|
5342
|
+
try {
|
|
5343
|
+
const envelope2 = await options.extensionBridge.request("drive.set_debugger_capability", {
|
|
5344
|
+
enabled: true,
|
|
5345
|
+
...extensionIdRaw ? { extension_id: extensionIdRaw } : {}
|
|
5346
|
+
});
|
|
5347
|
+
if (envelope2.status === "error") {
|
|
5348
|
+
const error = envelope2.error ?? {
|
|
5349
|
+
code: "INTERNAL",
|
|
5350
|
+
message: "Extension request failed.",
|
|
5351
|
+
retryable: false
|
|
5352
|
+
};
|
|
5353
|
+
sendError(res, 409, {
|
|
5354
|
+
code: error.code,
|
|
5355
|
+
message: error.message,
|
|
5356
|
+
retryable: error.retryable,
|
|
5357
|
+
details: error.details
|
|
5358
|
+
});
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
sendResult(res, envelope2.result ?? { ok: true, enabled: true });
|
|
5362
|
+
} catch (error) {
|
|
5363
|
+
if (error instanceof ExtensionBridgeError) {
|
|
5364
|
+
sendError(res, 409, {
|
|
5365
|
+
code: error.code,
|
|
5366
|
+
message: error.message,
|
|
5367
|
+
retryable: error.retryable,
|
|
5368
|
+
details: error.details
|
|
5369
|
+
});
|
|
5370
|
+
return;
|
|
5371
|
+
}
|
|
5372
|
+
sendError(res, 500, {
|
|
5373
|
+
code: "INTERNAL",
|
|
5374
|
+
message: "Unexpected inspect enablement error.",
|
|
5375
|
+
retryable: false
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5378
|
+
});
|
|
5093
5379
|
router.post("/diagnostics/doctor", (req, res) => {
|
|
5094
5380
|
const body = req.body ?? {};
|
|
5095
5381
|
if (!isRecord2(body)) {
|
|
@@ -5121,9 +5407,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5121
5407
|
port: parsedDoctorInput.data.caller.endpoint.port,
|
|
5122
5408
|
baseUrl: parsedDoctorInput.data.caller.endpoint.base_url,
|
|
5123
5409
|
hostSource: parsedDoctorInput.data.caller.endpoint.host_source,
|
|
5124
|
-
portSource: parsedDoctorInput.data.caller.endpoint.port_source
|
|
5125
|
-
metadataPath: parsedDoctorInput.data.caller.endpoint.metadata_path,
|
|
5126
|
-
isolatedMode: parsedDoctorInput.data.caller.endpoint.isolated_mode
|
|
5410
|
+
portSource: parsedDoctorInput.data.caller.endpoint.port_source
|
|
5127
5411
|
} : void 0,
|
|
5128
5412
|
process: parsedDoctorInput.data.caller.process ? {
|
|
5129
5413
|
component: parsedDoctorInput.data.caller.process.component,
|
|
@@ -5140,9 +5424,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5140
5424
|
port: options.coreRuntime.port,
|
|
5141
5425
|
baseUrl: `http://${options.coreRuntime.host}:${options.coreRuntime.port}`,
|
|
5142
5426
|
hostSource: options.coreRuntime.hostSource,
|
|
5143
|
-
portSource: options.coreRuntime.portSource
|
|
5144
|
-
metadataPath: options.coreRuntime.metadataPath,
|
|
5145
|
-
isolatedMode: options.coreRuntime.isolatedMode
|
|
5427
|
+
portSource: options.coreRuntime.portSource
|
|
5146
5428
|
} : void 0,
|
|
5147
5429
|
process: {
|
|
5148
5430
|
component: "core",
|
|
@@ -5185,11 +5467,13 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5185
5467
|
const status = options.extensionBridge.getStatus();
|
|
5186
5468
|
context.extension = {
|
|
5187
5469
|
connected: status.connected,
|
|
5470
|
+
extensionId: status.extensionId,
|
|
5188
5471
|
version: status.version,
|
|
5189
5472
|
lastSeenAt: status.lastSeenAt
|
|
5190
5473
|
};
|
|
5191
5474
|
if (status.connected) {
|
|
5192
5475
|
context.runtime.extension = {
|
|
5476
|
+
extensionId: status.extensionId,
|
|
5193
5477
|
version: status.version,
|
|
5194
5478
|
protocolVersion: status.protocolVersion,
|
|
5195
5479
|
capabilityNegotiated: status.capabilityNegotiated,
|
|
@@ -5226,7 +5510,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5226
5510
|
code: lastError.error.code,
|
|
5227
5511
|
message: lastError.error.message,
|
|
5228
5512
|
retryable: lastError.error.retryable,
|
|
5229
|
-
at: lastError.at
|
|
5513
|
+
at: lastError.at,
|
|
5514
|
+
details: lastError.error.details
|
|
5230
5515
|
};
|
|
5231
5516
|
}
|
|
5232
5517
|
}
|
|
@@ -5237,7 +5522,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5237
5522
|
code: lastError.error.code,
|
|
5238
5523
|
message: lastError.error.message,
|
|
5239
5524
|
retryable: lastError.error.retryable,
|
|
5240
|
-
at: lastError.at
|
|
5525
|
+
at: lastError.at,
|
|
5526
|
+
details: lastError.error.details
|
|
5241
5527
|
};
|
|
5242
5528
|
}
|
|
5243
5529
|
}
|
|
@@ -5267,6 +5553,11 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5267
5553
|
};
|
|
5268
5554
|
|
|
5269
5555
|
// packages/core/src/routes/drive.ts
|
|
5556
|
+
var SUPPORTED_NAVIGATE_WAIT_MODES = [
|
|
5557
|
+
"none",
|
|
5558
|
+
"domcontentloaded",
|
|
5559
|
+
"networkidle"
|
|
5560
|
+
];
|
|
5270
5561
|
var parseBody = (schema, body) => {
|
|
5271
5562
|
const result = schema.safeParse(body);
|
|
5272
5563
|
if (result.success) {
|
|
@@ -5283,6 +5574,24 @@ var parseBody = (schema, body) => {
|
|
|
5283
5574
|
};
|
|
5284
5575
|
var makeNavigateHandler = (schema, drive, registry) => {
|
|
5285
5576
|
return (req, res) => {
|
|
5577
|
+
if (isRecord2(req.body) && req.body.wait !== void 0) {
|
|
5578
|
+
const waitMode = req.body.wait;
|
|
5579
|
+
if (typeof waitMode !== "string" || !SUPPORTED_NAVIGATE_WAIT_MODES.includes(
|
|
5580
|
+
waitMode
|
|
5581
|
+
)) {
|
|
5582
|
+
sendError(res, errorStatus("INVALID_ARGUMENT"), {
|
|
5583
|
+
code: "INVALID_ARGUMENT",
|
|
5584
|
+
message: `Unsupported wait mode: ${String(waitMode)}.`,
|
|
5585
|
+
retryable: false,
|
|
5586
|
+
details: {
|
|
5587
|
+
field: "wait",
|
|
5588
|
+
supported_wait_modes: SUPPORTED_NAVIGATE_WAIT_MODES,
|
|
5589
|
+
mapped_wait_mode: "domcontentloaded"
|
|
5590
|
+
}
|
|
5591
|
+
});
|
|
5592
|
+
return;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5286
5595
|
const parsed = parseBody(schema, req.body ?? {});
|
|
5287
5596
|
if (parsed.error) {
|
|
5288
5597
|
sendError(res, errorStatus("INVALID_ARGUMENT"), {
|
|
@@ -5539,27 +5848,6 @@ var parseBody2 = (schema, body) => {
|
|
|
5539
5848
|
}
|
|
5540
5849
|
};
|
|
5541
5850
|
};
|
|
5542
|
-
var readTargetHint = (target) => {
|
|
5543
|
-
if (!target) {
|
|
5544
|
-
return void 0;
|
|
5545
|
-
}
|
|
5546
|
-
const url = typeof target.url === "string" ? target.url : void 0;
|
|
5547
|
-
const title = typeof target.title === "string" ? target.title : void 0;
|
|
5548
|
-
const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
|
|
5549
|
-
const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
|
|
5550
|
-
if (!url && !title && !lastActiveAt) {
|
|
5551
|
-
return void 0;
|
|
5552
|
-
}
|
|
5553
|
-
return { url, title, lastActiveAt };
|
|
5554
|
-
};
|
|
5555
|
-
var resolveTargetHint = (target, options) => {
|
|
5556
|
-
const explicit = readTargetHint(target);
|
|
5557
|
-
if (explicit) {
|
|
5558
|
-
return explicit;
|
|
5559
|
-
}
|
|
5560
|
-
const tabs = options.extensionBridge?.getStatus().tabs ?? [];
|
|
5561
|
-
return deriveHintFromTabs(tabs);
|
|
5562
|
-
};
|
|
5563
5851
|
var makeHandler2 = (schema, handler) => async (req, res) => {
|
|
5564
5852
|
const parsed = parseBody2(schema, req.body ?? {});
|
|
5565
5853
|
if (parsed.error) {
|
|
@@ -5605,7 +5893,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5605
5893
|
compact: body.compact,
|
|
5606
5894
|
maxNodes: body.max_nodes,
|
|
5607
5895
|
selector: body.selector,
|
|
5608
|
-
targetHint:
|
|
5896
|
+
targetHint: resolveInspectTargetHint({
|
|
5897
|
+
sessionId: body.session_id,
|
|
5898
|
+
target: body.target,
|
|
5899
|
+
registry: options.registry,
|
|
5900
|
+
extensionBridge: options.extensionBridge
|
|
5901
|
+
})
|
|
5609
5902
|
});
|
|
5610
5903
|
})
|
|
5611
5904
|
);
|
|
@@ -5618,7 +5911,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5618
5911
|
router.post(
|
|
5619
5912
|
"/inspect/find",
|
|
5620
5913
|
makeHandler2(InspectFindInputSchema, async (body) => {
|
|
5621
|
-
const targetHint =
|
|
5914
|
+
const targetHint = resolveInspectTargetHint({
|
|
5915
|
+
sessionId: body.session_id,
|
|
5916
|
+
target: body.target,
|
|
5917
|
+
registry: options.registry,
|
|
5918
|
+
extensionBridge: options.extensionBridge
|
|
5919
|
+
});
|
|
5622
5920
|
if (body.kind === "role") {
|
|
5623
5921
|
return await inspect.find({
|
|
5624
5922
|
sessionId: body.session_id,
|
|
@@ -5651,7 +5949,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5651
5949
|
sessionId: body.session_id,
|
|
5652
5950
|
format: body.format,
|
|
5653
5951
|
includeMetadata: body.include_metadata,
|
|
5654
|
-
targetHint:
|
|
5952
|
+
targetHint: resolveInspectTargetHint({
|
|
5953
|
+
sessionId: body.session_id,
|
|
5954
|
+
target: body.target,
|
|
5955
|
+
registry: options.registry,
|
|
5956
|
+
extensionBridge: options.extensionBridge
|
|
5957
|
+
})
|
|
5655
5958
|
});
|
|
5656
5959
|
})
|
|
5657
5960
|
);
|
|
@@ -5660,7 +5963,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5660
5963
|
makeHandler2(InspectPageStateInputSchema, async (body) => {
|
|
5661
5964
|
return await inspect.pageState({
|
|
5662
5965
|
sessionId: body.session_id,
|
|
5663
|
-
targetHint:
|
|
5966
|
+
targetHint: resolveInspectTargetHint({
|
|
5967
|
+
sessionId: body.session_id,
|
|
5968
|
+
target: body.target,
|
|
5969
|
+
registry: options.registry,
|
|
5970
|
+
extensionBridge: options.extensionBridge
|
|
5971
|
+
})
|
|
5664
5972
|
});
|
|
5665
5973
|
})
|
|
5666
5974
|
);
|
|
@@ -5669,7 +5977,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5669
5977
|
makeHandler2(InspectConsoleListInputSchema, async (body) => {
|
|
5670
5978
|
return await inspect.consoleList({
|
|
5671
5979
|
sessionId: body.session_id,
|
|
5672
|
-
targetHint:
|
|
5980
|
+
targetHint: resolveInspectTargetHint({
|
|
5981
|
+
sessionId: body.session_id,
|
|
5982
|
+
target: body.target,
|
|
5983
|
+
registry: options.registry,
|
|
5984
|
+
extensionBridge: options.extensionBridge
|
|
5985
|
+
})
|
|
5673
5986
|
});
|
|
5674
5987
|
})
|
|
5675
5988
|
);
|
|
@@ -5678,7 +5991,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5678
5991
|
makeHandler2(InspectNetworkHarInputSchema, async (body) => {
|
|
5679
5992
|
return await inspect.networkHar({
|
|
5680
5993
|
sessionId: body.session_id,
|
|
5681
|
-
targetHint:
|
|
5994
|
+
targetHint: resolveInspectTargetHint({
|
|
5995
|
+
sessionId: body.session_id,
|
|
5996
|
+
target: body.target,
|
|
5997
|
+
registry: options.registry,
|
|
5998
|
+
extensionBridge: options.extensionBridge
|
|
5999
|
+
})
|
|
5682
6000
|
});
|
|
5683
6001
|
})
|
|
5684
6002
|
);
|
|
@@ -5688,7 +6006,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5688
6006
|
return await inspect.evaluate({
|
|
5689
6007
|
sessionId: body.session_id,
|
|
5690
6008
|
expression: body.expression,
|
|
5691
|
-
targetHint:
|
|
6009
|
+
targetHint: resolveInspectTargetHint({
|
|
6010
|
+
sessionId: body.session_id,
|
|
6011
|
+
target: body.target,
|
|
6012
|
+
registry: options.registry,
|
|
6013
|
+
extensionBridge: options.extensionBridge
|
|
6014
|
+
})
|
|
5692
6015
|
});
|
|
5693
6016
|
})
|
|
5694
6017
|
);
|
|
@@ -5697,7 +6020,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5697
6020
|
makeHandler2(InspectPerformanceMetricsInputSchema, async (body) => {
|
|
5698
6021
|
return await inspect.performanceMetrics({
|
|
5699
6022
|
sessionId: body.session_id,
|
|
5700
|
-
targetHint:
|
|
6023
|
+
targetHint: resolveInspectTargetHint({
|
|
6024
|
+
sessionId: body.session_id,
|
|
6025
|
+
target: body.target,
|
|
6026
|
+
registry: options.registry,
|
|
6027
|
+
extensionBridge: options.extensionBridge
|
|
6028
|
+
})
|
|
5701
6029
|
});
|
|
5702
6030
|
})
|
|
5703
6031
|
);
|
|
@@ -6126,6 +6454,7 @@ var createCoreServer = (options = {}) => {
|
|
|
6126
6454
|
registerArtifactsRoutes(app, {
|
|
6127
6455
|
registry,
|
|
6128
6456
|
extensionBridge,
|
|
6457
|
+
debuggerBridge,
|
|
6129
6458
|
inspectService: inspect
|
|
6130
6459
|
});
|
|
6131
6460
|
registerDiagnosticsRoutes(app, {
|
|
@@ -6148,27 +6477,12 @@ var createCoreServer = (options = {}) => {
|
|
|
6148
6477
|
recoveryTracker
|
|
6149
6478
|
};
|
|
6150
6479
|
};
|
|
6151
|
-
var
|
|
6152
|
-
var buildRuntimeMetadataForPersist = (runtime, resolvedPort) => ({
|
|
6480
|
+
var buildRuntimeMetadataForPersist = (runtime) => ({
|
|
6153
6481
|
...runtime.metadata ?? {},
|
|
6154
|
-
|
|
6155
|
-
port: resolvedPort,
|
|
6156
|
-
git_root: runtime.gitRoot ?? runtime.metadata?.git_root,
|
|
6157
|
-
worktree_id: runtime.worktreeId ?? runtime.metadata?.worktree_id,
|
|
6482
|
+
extension_id: runtime.metadata?.extension_id,
|
|
6158
6483
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6159
6484
|
});
|
|
6160
|
-
var resolveProbePortsForRuntime = (runtime) =>
|
|
6161
|
-
if (!runtime.isolatedMode) {
|
|
6162
|
-
return [runtime.port];
|
|
6163
|
-
}
|
|
6164
|
-
if (runtime.portSource === "metadata" || runtime.portSource === "deterministic") {
|
|
6165
|
-
return createBoundedPortProbeSequence(
|
|
6166
|
-
runtime.port,
|
|
6167
|
-
CORE_PORT_PROBE_ATTEMPTS
|
|
6168
|
-
);
|
|
6169
|
-
}
|
|
6170
|
-
return [runtime.port];
|
|
6171
|
-
};
|
|
6485
|
+
var resolveProbePortsForRuntime = (runtime) => [runtime.port];
|
|
6172
6486
|
var resolveSessionTtlMs = () => {
|
|
6173
6487
|
const env = process.env.BROWSER_BRIDGE_SESSION_TTL_MS || process.env.BROWSER_VISION_SESSION_TTL_MS;
|
|
6174
6488
|
if (env) {
|
|
@@ -6249,11 +6563,7 @@ var startCoreServer = async (options = {}) => {
|
|
|
6249
6563
|
host: runtime.host,
|
|
6250
6564
|
port: runtime.port,
|
|
6251
6565
|
host_source: runtime.hostSource,
|
|
6252
|
-
port_source: runtime.portSource
|
|
6253
|
-
metadata_path: runtime.metadataPath,
|
|
6254
|
-
git_root: runtime.gitRoot,
|
|
6255
|
-
worktree_id: runtime.worktreeId,
|
|
6256
|
-
deterministic_port: runtime.deterministicPort
|
|
6566
|
+
port_source: runtime.portSource
|
|
6257
6567
|
});
|
|
6258
6568
|
const { app, registry, extensionBridge } = createCoreServer({
|
|
6259
6569
|
registry: options.registry,
|
|
@@ -6283,10 +6593,9 @@ var startCoreServer = async (options = {}) => {
|
|
|
6283
6593
|
duration_ms: Number(startupDurationMs.toFixed(3))
|
|
6284
6594
|
});
|
|
6285
6595
|
try {
|
|
6286
|
-
writeRuntimeMetadata(
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
);
|
|
6596
|
+
writeRuntimeMetadata(buildRuntimeMetadataForPersist(runtime), {
|
|
6597
|
+
metadataPath: runtime.metadataPath
|
|
6598
|
+
});
|
|
6290
6599
|
logger.info("core.runtime_metadata.persisted", {
|
|
6291
6600
|
metadata_path: runtime.metadataPath,
|
|
6292
6601
|
host: runtime.host,
|
|
@@ -6495,9 +6804,7 @@ startCoreServer({ ${startOptions.join(
|
|
|
6495
6804
|
port: readiness.runtime.port,
|
|
6496
6805
|
base_url: readiness.baseUrl,
|
|
6497
6806
|
host_source: readiness.runtime.hostSource,
|
|
6498
|
-
port_source: readiness.runtime.portSource
|
|
6499
|
-
metadata_path: readiness.runtime.metadataPath,
|
|
6500
|
-
isolated_mode: readiness.runtime.isolatedMode
|
|
6807
|
+
port_source: readiness.runtime.portSource
|
|
6501
6808
|
},
|
|
6502
6809
|
process: {
|
|
6503
6810
|
component: "mcp",
|
|
@@ -6543,6 +6850,70 @@ var toInternalErrorEnvelope = (error) => ({
|
|
|
6543
6850
|
});
|
|
6544
6851
|
var envelope = (schema) => successEnvelopeSchema(schema);
|
|
6545
6852
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6853
|
+
var readSessionId2 = (args) => {
|
|
6854
|
+
if (!isRecord3(args)) {
|
|
6855
|
+
return void 0;
|
|
6856
|
+
}
|
|
6857
|
+
const sessionId = args.session_id;
|
|
6858
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
|
|
6859
|
+
};
|
|
6860
|
+
var supportsSessionMigration = (corePath) => corePath.startsWith("/drive/") || corePath.startsWith("/inspect/") || corePath.startsWith("/artifacts/") || corePath === "/diagnostics/doctor";
|
|
6861
|
+
var isSessionNotFoundEnvelope = (envelopeResult) => {
|
|
6862
|
+
if (envelopeResult.ok) {
|
|
6863
|
+
return false;
|
|
6864
|
+
}
|
|
6865
|
+
const details = envelopeResult.error.details;
|
|
6866
|
+
return envelopeResult.error.code === "NOT_FOUND" && isRecord3(details) && details.reason === "session_not_found";
|
|
6867
|
+
};
|
|
6868
|
+
var addSessionRecoveryHint = (envelopeResult) => {
|
|
6869
|
+
if (envelopeResult.ok) {
|
|
6870
|
+
return envelopeResult;
|
|
6871
|
+
}
|
|
6872
|
+
return {
|
|
6873
|
+
ok: false,
|
|
6874
|
+
error: {
|
|
6875
|
+
...envelopeResult.error,
|
|
6876
|
+
details: {
|
|
6877
|
+
...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
|
|
6878
|
+
recover_action: "session.create"
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
};
|
|
6882
|
+
};
|
|
6883
|
+
var addSessionMigrationNotice = (envelopeResult, staleSessionId, replacementSessionId) => {
|
|
6884
|
+
if (envelopeResult.ok) {
|
|
6885
|
+
if (!isRecord3(envelopeResult.result)) {
|
|
6886
|
+
return envelopeResult;
|
|
6887
|
+
}
|
|
6888
|
+
const warning = `Session ${staleSessionId} became stale after runtime switch; retried with ${replacementSessionId}.`;
|
|
6889
|
+
const existingWarnings = Array.isArray(envelopeResult.result.warnings) ? envelopeResult.result.warnings.filter(
|
|
6890
|
+
(item) => typeof item === "string"
|
|
6891
|
+
) : [];
|
|
6892
|
+
return {
|
|
6893
|
+
ok: true,
|
|
6894
|
+
result: {
|
|
6895
|
+
...envelopeResult.result,
|
|
6896
|
+
warnings: existingWarnings.includes(warning) ? existingWarnings : [...existingWarnings, warning],
|
|
6897
|
+
session_migration: {
|
|
6898
|
+
stale_session_id: staleSessionId,
|
|
6899
|
+
replacement_session_id: replacementSessionId
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
};
|
|
6903
|
+
}
|
|
6904
|
+
return {
|
|
6905
|
+
ok: false,
|
|
6906
|
+
error: {
|
|
6907
|
+
...envelopeResult.error,
|
|
6908
|
+
details: {
|
|
6909
|
+
...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
|
|
6910
|
+
stale_session_id: staleSessionId,
|
|
6911
|
+
replacement_session_id: replacementSessionId,
|
|
6912
|
+
recover_action: "session.recover"
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
};
|
|
6916
|
+
};
|
|
6546
6917
|
var addDeprecatedAliasWarning = (envelopeResult, deprecationAlias) => {
|
|
6547
6918
|
if (!deprecationAlias || !envelopeResult.ok || typeof envelopeResult.result !== "object" || !envelopeResult.result) {
|
|
6548
6919
|
return envelopeResult;
|
|
@@ -6953,10 +7324,26 @@ var createToolHandler = (clientProvider, corePath, deprecationAlias, transformIn
|
|
|
6953
7324
|
void _extra;
|
|
6954
7325
|
try {
|
|
6955
7326
|
const client = typeof clientProvider === "function" ? await clientProvider() : clientProvider;
|
|
6956
|
-
const
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
)
|
|
7327
|
+
const transformedArgs = transformInput ? transformInput(args) : args;
|
|
7328
|
+
const staleSessionId = readSessionId2(transformedArgs);
|
|
7329
|
+
let envelopeResult = await client.post(corePath, transformedArgs);
|
|
7330
|
+
if (staleSessionId && supportsSessionMigration(corePath) && isSessionNotFoundEnvelope(envelopeResult) && isRecord3(transformedArgs)) {
|
|
7331
|
+
const createdSession = await client.post("/session/create", {});
|
|
7332
|
+
if (createdSession.ok && isRecord3(createdSession.result) && typeof createdSession.result.session_id === "string" && createdSession.result.session_id.length > 0) {
|
|
7333
|
+
const replacementSessionId = createdSession.result.session_id;
|
|
7334
|
+
const retryArgs = {
|
|
7335
|
+
...transformedArgs,
|
|
7336
|
+
session_id: replacementSessionId
|
|
7337
|
+
};
|
|
7338
|
+
envelopeResult = addSessionMigrationNotice(
|
|
7339
|
+
await client.post(corePath, retryArgs),
|
|
7340
|
+
staleSessionId,
|
|
7341
|
+
replacementSessionId
|
|
7342
|
+
);
|
|
7343
|
+
} else {
|
|
7344
|
+
envelopeResult = addSessionRecoveryHint(envelopeResult);
|
|
7345
|
+
}
|
|
7346
|
+
}
|
|
6960
7347
|
return toToolResult(
|
|
6961
7348
|
addDeprecatedAliasWarning(envelopeResult, deprecationAlias)
|
|
6962
7349
|
);
|
|
@@ -7097,7 +7484,7 @@ var DEFAULT_SERVER_NAME = "browser-bridge";
|
|
|
7097
7484
|
var DEFAULT_SERVER_VERSION = "0.0.0";
|
|
7098
7485
|
var ENV_MCP_EAGER = "BROWSER_BRIDGE_MCP_EAGER";
|
|
7099
7486
|
var ENV_LEGACY_MCP_EAGER = "BROWSER_VISION_MCP_EAGER";
|
|
7100
|
-
var
|
|
7487
|
+
var parseBoolean = (value) => {
|
|
7101
7488
|
if (value === void 0) {
|
|
7102
7489
|
return void 0;
|
|
7103
7490
|
}
|
|
@@ -7114,7 +7501,7 @@ var resolveEagerMode = (explicit) => {
|
|
|
7114
7501
|
if (typeof explicit === "boolean") {
|
|
7115
7502
|
return explicit;
|
|
7116
7503
|
}
|
|
7117
|
-
const envValue =
|
|
7504
|
+
const envValue = parseBoolean(process.env[ENV_MCP_EAGER]) ?? parseBoolean(process.env[ENV_LEGACY_MCP_EAGER]);
|
|
7118
7505
|
return envValue ?? false;
|
|
7119
7506
|
};
|
|
7120
7507
|
var toCoreClientOptions = (options, logger) => ({
|