@btraut/browser-bridge 0.13.2 → 0.15.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 +51 -0
- package/README.md +53 -37
- package/dist/api.js +1792 -678
- package/dist/api.js.map +4 -4
- package/dist/index.js +666 -417
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +1484 -693
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +534 -77
- 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/dist/permissions-request-ui.js +111 -0
- package/extension/dist/permissions-request-ui.js.map +7 -0
- package/extension/manifest.json +3 -3
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +18 -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(
|
|
@@ -854,9 +687,10 @@ var createCoreReadinessController = (options = {}) => {
|
|
|
854
687
|
return;
|
|
855
688
|
}
|
|
856
689
|
if (!ensurePromise) {
|
|
857
|
-
ensurePromise =
|
|
690
|
+
ensurePromise = (async () => {
|
|
691
|
+
await ensureCoreRunning();
|
|
692
|
+
})().finally(() => {
|
|
858
693
|
ensurePromise = null;
|
|
859
|
-
throw error;
|
|
860
694
|
});
|
|
861
695
|
}
|
|
862
696
|
await ensurePromise;
|
|
@@ -1169,9 +1003,7 @@ var DiagnosticsRuntimeEndpointSchema = import_zod3.z.object({
|
|
|
1169
1003
|
port: import_zod3.z.number().finite().optional(),
|
|
1170
1004
|
base_url: import_zod3.z.string().optional(),
|
|
1171
1005
|
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()
|
|
1006
|
+
port_source: import_zod3.z.string().optional()
|
|
1175
1007
|
});
|
|
1176
1008
|
var DiagnosticsRuntimeProcessSchema = import_zod3.z.object({
|
|
1177
1009
|
component: import_zod3.z.enum(["cli", "mcp", "core"]).optional(),
|
|
@@ -1192,12 +1024,13 @@ var DiagnosticsRuntimeContextSchema = import_zod3.z.object({
|
|
|
1192
1024
|
process: DiagnosticsRuntimeProcessSchema.optional()
|
|
1193
1025
|
}).optional(),
|
|
1194
1026
|
extension: import_zod3.z.object({
|
|
1027
|
+
extension_id: import_zod3.z.string().optional(),
|
|
1195
1028
|
version: import_zod3.z.string().optional(),
|
|
1196
1029
|
protocol_version: import_zod3.z.string().optional(),
|
|
1197
1030
|
capability_negotiated: import_zod3.z.boolean().optional(),
|
|
1198
1031
|
capabilities: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.boolean()).optional(),
|
|
1199
1032
|
endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
|
|
1200
|
-
port_source: import_zod3.z.enum(["default"
|
|
1033
|
+
port_source: import_zod3.z.enum(["default"]).optional()
|
|
1201
1034
|
}).optional()
|
|
1202
1035
|
});
|
|
1203
1036
|
var DiagnosticReportSchema = import_zod3.z.object({
|
|
@@ -1264,6 +1097,63 @@ var SessionCloseInputSchema = SessionIdSchema;
|
|
|
1264
1097
|
var SessionCloseOutputSchema = import_zod3.z.object({
|
|
1265
1098
|
ok: import_zod3.z.boolean()
|
|
1266
1099
|
});
|
|
1100
|
+
var PermissionsModeSchema = import_zod3.z.enum(["granular", "bypass"]);
|
|
1101
|
+
var PermissionsRequestSourceSchema = import_zod3.z.enum(["cli", "mcp", "api"]);
|
|
1102
|
+
var PermissionsPendingRequestKindSchema = import_zod3.z.enum([
|
|
1103
|
+
"allow_site",
|
|
1104
|
+
"revoke_site",
|
|
1105
|
+
"set_mode"
|
|
1106
|
+
]);
|
|
1107
|
+
var PermissionsPendingRequestStatusSchema = import_zod3.z.enum([
|
|
1108
|
+
"pending",
|
|
1109
|
+
"approved",
|
|
1110
|
+
"denied",
|
|
1111
|
+
"timed_out"
|
|
1112
|
+
]);
|
|
1113
|
+
var PermissionsSiteEntrySchema = import_zod3.z.object({
|
|
1114
|
+
site: import_zod3.z.string().min(1),
|
|
1115
|
+
created_at: import_zod3.z.string().datetime(),
|
|
1116
|
+
last_used_at: import_zod3.z.string().datetime()
|
|
1117
|
+
});
|
|
1118
|
+
var PermissionsListInputSchema = import_zod3.z.object({}).strict().default({});
|
|
1119
|
+
var PermissionsListOutputSchema = import_zod3.z.object({
|
|
1120
|
+
sites: import_zod3.z.array(PermissionsSiteEntrySchema)
|
|
1121
|
+
});
|
|
1122
|
+
var PermissionsGetModeInputSchema = import_zod3.z.object({}).strict().default({});
|
|
1123
|
+
var PermissionsGetModeOutputSchema = import_zod3.z.object({
|
|
1124
|
+
mode: PermissionsModeSchema
|
|
1125
|
+
});
|
|
1126
|
+
var PermissionsPendingRequestSchema = import_zod3.z.object({
|
|
1127
|
+
request_id: import_zod3.z.string().min(1),
|
|
1128
|
+
kind: PermissionsPendingRequestKindSchema,
|
|
1129
|
+
status: PermissionsPendingRequestStatusSchema,
|
|
1130
|
+
requested_at: import_zod3.z.string().datetime(),
|
|
1131
|
+
site: import_zod3.z.string().min(1).optional(),
|
|
1132
|
+
mode: PermissionsModeSchema.optional(),
|
|
1133
|
+
source: PermissionsRequestSourceSchema.optional(),
|
|
1134
|
+
warning: import_zod3.z.string().optional(),
|
|
1135
|
+
message: import_zod3.z.string().optional()
|
|
1136
|
+
});
|
|
1137
|
+
var PermissionsListPendingRequestsInputSchema = import_zod3.z.object({}).strict().default({});
|
|
1138
|
+
var PermissionsListPendingRequestsOutputSchema = import_zod3.z.object({
|
|
1139
|
+
requests: import_zod3.z.array(PermissionsPendingRequestSchema)
|
|
1140
|
+
});
|
|
1141
|
+
var PermissionsRequestAllowSiteInputSchema = import_zod3.z.object({
|
|
1142
|
+
site: import_zod3.z.string().min(1),
|
|
1143
|
+
timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
|
|
1144
|
+
source: PermissionsRequestSourceSchema.optional()
|
|
1145
|
+
});
|
|
1146
|
+
var PermissionsRequestRevokeSiteInputSchema = import_zod3.z.object({
|
|
1147
|
+
site: import_zod3.z.string().min(1),
|
|
1148
|
+
timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
|
|
1149
|
+
source: PermissionsRequestSourceSchema.optional()
|
|
1150
|
+
});
|
|
1151
|
+
var PermissionsRequestSetModeInputSchema = import_zod3.z.object({
|
|
1152
|
+
mode: PermissionsModeSchema,
|
|
1153
|
+
timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
|
|
1154
|
+
source: PermissionsRequestSourceSchema.optional()
|
|
1155
|
+
});
|
|
1156
|
+
var PermissionsRequestOutputSchema = PermissionsPendingRequestSchema;
|
|
1267
1157
|
var DriveWaitConditionSchema = import_zod3.z.object({
|
|
1268
1158
|
kind: import_zod3.z.enum(["locator_visible", "text_present", "url_matches"]),
|
|
1269
1159
|
value: import_zod3.z.string().min(1)
|
|
@@ -1272,7 +1162,7 @@ var DriveNavigateInputSchema = import_zod3.z.object({
|
|
|
1272
1162
|
session_id: import_zod3.z.string().min(1).optional(),
|
|
1273
1163
|
url: import_zod3.z.string().min(1),
|
|
1274
1164
|
tab_id: import_zod3.z.number().finite().optional(),
|
|
1275
|
-
wait: import_zod3.z.enum(["none", "domcontentloaded"]).default("domcontentloaded")
|
|
1165
|
+
wait: import_zod3.z.enum(["none", "domcontentloaded", "networkidle"]).default("domcontentloaded")
|
|
1276
1166
|
});
|
|
1277
1167
|
var DriveNavigateOutputSchema = OpResultSchema.extend({
|
|
1278
1168
|
session_id: import_zod3.z.string().min(1)
|
|
@@ -1441,6 +1331,8 @@ var InspectConsistencySchema = import_zod3.z.enum(["best_effort", "quiesce"]);
|
|
|
1441
1331
|
var TargetHintSchema = import_zod3.z.object({
|
|
1442
1332
|
url: import_zod3.z.string().min(1).optional(),
|
|
1443
1333
|
title: import_zod3.z.string().min(1).optional(),
|
|
1334
|
+
tab_id: import_zod3.z.number().finite().optional(),
|
|
1335
|
+
tabId: import_zod3.z.number().finite().optional(),
|
|
1444
1336
|
last_active_at: import_zod3.z.string().optional(),
|
|
1445
1337
|
lastActiveAt: import_zod3.z.string().optional()
|
|
1446
1338
|
});
|
|
@@ -1448,6 +1340,12 @@ var FormFieldInfoSchema = import_zod3.z.object({
|
|
|
1448
1340
|
name: import_zod3.z.string(),
|
|
1449
1341
|
type: import_zod3.z.string(),
|
|
1450
1342
|
value: import_zod3.z.string(),
|
|
1343
|
+
selector: import_zod3.z.string().optional(),
|
|
1344
|
+
label: import_zod3.z.string().optional(),
|
|
1345
|
+
placeholder: import_zod3.z.string().optional(),
|
|
1346
|
+
checked: import_zod3.z.boolean().optional(),
|
|
1347
|
+
disabled: import_zod3.z.boolean().optional(),
|
|
1348
|
+
visible: import_zod3.z.boolean().optional(),
|
|
1451
1349
|
options: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
1452
1350
|
});
|
|
1453
1351
|
var FormInfoSchema = import_zod3.z.object({
|
|
@@ -1460,7 +1358,31 @@ var StorageEntrySchema = import_zod3.z.object({
|
|
|
1460
1358
|
key: import_zod3.z.string(),
|
|
1461
1359
|
value: import_zod3.z.string()
|
|
1462
1360
|
});
|
|
1361
|
+
var FocusedElementSchema = import_zod3.z.object({
|
|
1362
|
+
selector: import_zod3.z.string().optional(),
|
|
1363
|
+
name: import_zod3.z.string().optional(),
|
|
1364
|
+
label: import_zod3.z.string().optional(),
|
|
1365
|
+
role: import_zod3.z.string().optional(),
|
|
1366
|
+
type: import_zod3.z.string().optional(),
|
|
1367
|
+
text: import_zod3.z.string().optional()
|
|
1368
|
+
});
|
|
1369
|
+
var PageActionSchema = import_zod3.z.object({
|
|
1370
|
+
selector: import_zod3.z.string(),
|
|
1371
|
+
role: import_zod3.z.string(),
|
|
1372
|
+
name: import_zod3.z.string()
|
|
1373
|
+
});
|
|
1374
|
+
var StorageSummarySchema = import_zod3.z.object({
|
|
1375
|
+
localStorageCount: import_zod3.z.number().int().nonnegative(),
|
|
1376
|
+
sessionStorageCount: import_zod3.z.number().int().nonnegative(),
|
|
1377
|
+
cookieCount: import_zod3.z.number().int().nonnegative()
|
|
1378
|
+
});
|
|
1463
1379
|
var PageStateSchema = import_zod3.z.object({
|
|
1380
|
+
url: import_zod3.z.string().optional(),
|
|
1381
|
+
title: import_zod3.z.string().optional(),
|
|
1382
|
+
readyState: import_zod3.z.string().optional(),
|
|
1383
|
+
focused: FocusedElementSchema.optional(),
|
|
1384
|
+
primaryActions: import_zod3.z.array(PageActionSchema).optional(),
|
|
1385
|
+
storageSummary: StorageSummarySchema.optional(),
|
|
1464
1386
|
forms: import_zod3.z.array(FormInfoSchema),
|
|
1465
1387
|
localStorage: import_zod3.z.array(StorageEntrySchema),
|
|
1466
1388
|
sessionStorage: import_zod3.z.array(StorageEntrySchema),
|
|
@@ -1523,6 +1445,7 @@ var InspectFindOutputSchema = import_zod3.z.object({
|
|
|
1523
1445
|
warnings: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
1524
1446
|
});
|
|
1525
1447
|
var InspectPageStateInputSchema = SessionIdSchema.extend({
|
|
1448
|
+
include_values: import_zod3.z.boolean().default(false),
|
|
1526
1449
|
target: TargetHintSchema.optional()
|
|
1527
1450
|
});
|
|
1528
1451
|
var InspectPageStateOutputSchema = PageStateSchema;
|
|
@@ -1533,6 +1456,7 @@ var InspectExtractContentFormatSchema = import_zod3.z.enum([
|
|
|
1533
1456
|
]);
|
|
1534
1457
|
var InspectExtractContentInputSchema = SessionIdSchema.extend({
|
|
1535
1458
|
format: InspectExtractContentFormatSchema.default("markdown"),
|
|
1459
|
+
consistency: InspectConsistencySchema.default("quiesce"),
|
|
1536
1460
|
include_metadata: import_zod3.z.boolean().default(true),
|
|
1537
1461
|
target: TargetHintSchema.optional()
|
|
1538
1462
|
});
|
|
@@ -1545,6 +1469,7 @@ var InspectExtractContentOutputSchema = import_zod3.z.object({
|
|
|
1545
1469
|
warnings: import_zod3.z.array(import_zod3.z.string()).optional()
|
|
1546
1470
|
});
|
|
1547
1471
|
var InspectConsoleListInputSchema = SessionIdSchema.extend({
|
|
1472
|
+
since: import_zod3.z.string().optional(),
|
|
1548
1473
|
target: TargetHintSchema.optional()
|
|
1549
1474
|
});
|
|
1550
1475
|
var ConsoleSourceLocationSchema = import_zod3.z.object({
|
|
@@ -1635,6 +1560,7 @@ var HealthCheckOutputSchema = import_zod3.z.object({
|
|
|
1635
1560
|
}).passthrough(),
|
|
1636
1561
|
extension: import_zod3.z.object({
|
|
1637
1562
|
connected: import_zod3.z.boolean(),
|
|
1563
|
+
extension_id: import_zod3.z.string().optional(),
|
|
1638
1564
|
last_seen_at: import_zod3.z.string().min(1).optional()
|
|
1639
1565
|
}).passthrough()
|
|
1640
1566
|
}).passthrough();
|
|
@@ -1744,7 +1670,8 @@ var SessionRegistry = class {
|
|
|
1744
1670
|
state: "INIT" /* INIT */,
|
|
1745
1671
|
createdAt: now,
|
|
1746
1672
|
updatedAt: now,
|
|
1747
|
-
lastAccessedAt: now
|
|
1673
|
+
lastAccessedAt: now,
|
|
1674
|
+
selectedTabId: void 0
|
|
1748
1675
|
};
|
|
1749
1676
|
this.sessions.set(id, session);
|
|
1750
1677
|
return session;
|
|
@@ -1843,6 +1770,18 @@ var SessionRegistry = class {
|
|
|
1843
1770
|
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1844
1771
|
return session;
|
|
1845
1772
|
}
|
|
1773
|
+
setSelectedTab(sessionId, tabId) {
|
|
1774
|
+
const session = this.require(sessionId);
|
|
1775
|
+
session.selectedTabId = tabId;
|
|
1776
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1777
|
+
return session;
|
|
1778
|
+
}
|
|
1779
|
+
clearSelectedTab(sessionId) {
|
|
1780
|
+
const session = this.require(sessionId);
|
|
1781
|
+
session.selectedTabId = void 0;
|
|
1782
|
+
session.updatedAt = /* @__PURE__ */ new Date();
|
|
1783
|
+
return session;
|
|
1784
|
+
}
|
|
1846
1785
|
};
|
|
1847
1786
|
|
|
1848
1787
|
// packages/core/src/routes/shared.ts
|
|
@@ -1883,13 +1822,14 @@ var deriveHintFromTabs = (tabs) => {
|
|
|
1883
1822
|
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
1884
1823
|
return void 0;
|
|
1885
1824
|
}
|
|
1825
|
+
const candidatePool = tabs.some((tab) => tab.active === true) ? tabs.filter((tab) => tab.active === true) : tabs;
|
|
1886
1826
|
let best;
|
|
1887
1827
|
let bestTime = -Infinity;
|
|
1888
|
-
for (const tab of
|
|
1828
|
+
for (const tab of candidatePool) {
|
|
1889
1829
|
const raw = tab.last_active_at;
|
|
1890
1830
|
const time = raw ? Date.parse(raw) : NaN;
|
|
1891
1831
|
const score = Number.isFinite(time) ? time : -Infinity;
|
|
1892
|
-
if (!best || score > bestTime) {
|
|
1832
|
+
if (!best || score > bestTime || score === bestTime && tab.tab_id < best.tab_id) {
|
|
1893
1833
|
best = tab;
|
|
1894
1834
|
bestTime = score;
|
|
1895
1835
|
}
|
|
@@ -1965,7 +1905,8 @@ var createSessionRouter = (registry, options = {}) => {
|
|
|
1965
1905
|
return sendResult(res, {
|
|
1966
1906
|
session_id: session.id,
|
|
1967
1907
|
state: session.state,
|
|
1968
|
-
updated_at: session.updatedAt.toISOString()
|
|
1908
|
+
updated_at: session.updatedAt.toISOString(),
|
|
1909
|
+
...typeof session.selectedTabId === "number" ? { selected_tab_id: session.selectedTabId } : {}
|
|
1969
1910
|
});
|
|
1970
1911
|
} catch (error) {
|
|
1971
1912
|
if (error instanceof SessionError) {
|
|
@@ -2103,9 +2044,6 @@ var InspectError = class extends Error {
|
|
|
2103
2044
|
var import_crypto3 = require("crypto");
|
|
2104
2045
|
var import_promises3 = require("node:fs/promises");
|
|
2105
2046
|
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
2047
|
|
|
2110
2048
|
// packages/core/src/artifacts.ts
|
|
2111
2049
|
var import_promises2 = require("node:fs/promises");
|
|
@@ -2136,6 +2074,7 @@ var ExtensionBridge = class {
|
|
|
2136
2074
|
constructor(options = {}) {
|
|
2137
2075
|
this.socket = null;
|
|
2138
2076
|
this.pending = /* @__PURE__ */ new Map();
|
|
2077
|
+
this.readyWaiters = /* @__PURE__ */ new Set();
|
|
2139
2078
|
this.connected = false;
|
|
2140
2079
|
this.capabilityNegotiated = false;
|
|
2141
2080
|
this.capabilities = {};
|
|
@@ -2172,6 +2111,7 @@ var ExtensionBridge = class {
|
|
|
2172
2111
|
return {
|
|
2173
2112
|
connected: this.connected,
|
|
2174
2113
|
lastSeenAt: this.lastSeenAt,
|
|
2114
|
+
extensionId: this.extensionId,
|
|
2175
2115
|
version: this.version,
|
|
2176
2116
|
protocolVersion: this.protocolVersion,
|
|
2177
2117
|
protocolMismatch: this.protocolMismatch,
|
|
@@ -2183,6 +2123,36 @@ var ExtensionBridge = class {
|
|
|
2183
2123
|
tabs: this.tabs
|
|
2184
2124
|
};
|
|
2185
2125
|
}
|
|
2126
|
+
async waitForReady(timeoutMs = 1500) {
|
|
2127
|
+
if (this.isReadyForRequests()) {
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
2131
|
+
return this.isReadyForRequests();
|
|
2132
|
+
}
|
|
2133
|
+
return await new Promise((resolve3) => {
|
|
2134
|
+
let settled = false;
|
|
2135
|
+
const finish = (value) => {
|
|
2136
|
+
if (settled) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
settled = true;
|
|
2140
|
+
clearTimeout(timeout);
|
|
2141
|
+
this.readyWaiters.delete(waiter);
|
|
2142
|
+
resolve3(value);
|
|
2143
|
+
};
|
|
2144
|
+
const waiter = () => {
|
|
2145
|
+
if (this.isReadyForRequests()) {
|
|
2146
|
+
finish(true);
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
const timeout = setTimeout(() => {
|
|
2150
|
+
finish(this.isReadyForRequests());
|
|
2151
|
+
}, timeoutMs);
|
|
2152
|
+
this.readyWaiters.add(waiter);
|
|
2153
|
+
waiter();
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2186
2156
|
async request(action, params, timeoutMs = 3e4) {
|
|
2187
2157
|
const response = await this.requestInternal(action, params, timeoutMs);
|
|
2188
2158
|
return response;
|
|
@@ -2254,6 +2224,7 @@ var ExtensionBridge = class {
|
|
|
2254
2224
|
status: "request",
|
|
2255
2225
|
params
|
|
2256
2226
|
};
|
|
2227
|
+
const envelope2 = request;
|
|
2257
2228
|
const response = await new Promise((resolve3, reject) => {
|
|
2258
2229
|
const timeout = setTimeout(() => {
|
|
2259
2230
|
this.pending.delete(id);
|
|
@@ -2271,7 +2242,7 @@ var ExtensionBridge = class {
|
|
|
2271
2242
|
reject,
|
|
2272
2243
|
timeout
|
|
2273
2244
|
});
|
|
2274
|
-
this.socket?.send(JSON.stringify(
|
|
2245
|
+
this.socket?.send(JSON.stringify(envelope2));
|
|
2275
2246
|
});
|
|
2276
2247
|
return response;
|
|
2277
2248
|
}
|
|
@@ -2352,6 +2323,7 @@ var ExtensionBridge = class {
|
|
|
2352
2323
|
this.stopHeartbeat();
|
|
2353
2324
|
this.connected = false;
|
|
2354
2325
|
this.socket = null;
|
|
2326
|
+
this.extensionId = void 0;
|
|
2355
2327
|
this.version = void 0;
|
|
2356
2328
|
this.protocolVersion = void 0;
|
|
2357
2329
|
this.protocolMismatch = void 0;
|
|
@@ -2414,6 +2386,11 @@ var ExtensionBridge = class {
|
|
|
2414
2386
|
this.tabs = tabs;
|
|
2415
2387
|
}
|
|
2416
2388
|
if (message.action === "drive.hello") {
|
|
2389
|
+
if (typeof params?.extension_id === "string") {
|
|
2390
|
+
this.extensionId = params.extension_id;
|
|
2391
|
+
} else {
|
|
2392
|
+
this.extensionId = void 0;
|
|
2393
|
+
}
|
|
2417
2394
|
if (typeof params?.version === "string") {
|
|
2418
2395
|
this.version = params.version;
|
|
2419
2396
|
}
|
|
@@ -2436,7 +2413,7 @@ var ExtensionBridge = class {
|
|
|
2436
2413
|
if (typeof params?.core_port === "number" && Number.isFinite(params.core_port)) {
|
|
2437
2414
|
this.corePort = params.core_port;
|
|
2438
2415
|
}
|
|
2439
|
-
if (params?.core_port_source === "default"
|
|
2416
|
+
if (params?.core_port_source === "default") {
|
|
2440
2417
|
this.corePortSource = params.core_port_source;
|
|
2441
2418
|
}
|
|
2442
2419
|
const capabilities = params?.capabilities;
|
|
@@ -2456,6 +2433,7 @@ var ExtensionBridge = class {
|
|
|
2456
2433
|
expected: "drive.hello.capabilities"
|
|
2457
2434
|
};
|
|
2458
2435
|
}
|
|
2436
|
+
this.flushReadyWaiters();
|
|
2459
2437
|
}
|
|
2460
2438
|
}
|
|
2461
2439
|
if (typeof message.action === "string" && message.action.startsWith("debugger.")) {
|
|
@@ -2471,6 +2449,23 @@ var ExtensionBridge = class {
|
|
|
2471
2449
|
}
|
|
2472
2450
|
}
|
|
2473
2451
|
}
|
|
2452
|
+
isReadyForRequests() {
|
|
2453
|
+
return this.connected && this.capabilityNegotiated;
|
|
2454
|
+
}
|
|
2455
|
+
flushReadyWaiters() {
|
|
2456
|
+
if (!this.isReadyForRequests() || this.readyWaiters.size === 0) {
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const waiters = Array.from(this.readyWaiters);
|
|
2460
|
+
this.readyWaiters.clear();
|
|
2461
|
+
for (const waiter of waiters) {
|
|
2462
|
+
try {
|
|
2463
|
+
waiter();
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
console.debug("Extension ready waiter failed.", error);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2474
2469
|
applyDriveConnected() {
|
|
2475
2470
|
if (!this.registry) {
|
|
2476
2471
|
return;
|
|
@@ -2524,7 +2519,27 @@ var toDriveError = (error) => ({
|
|
|
2524
2519
|
|
|
2525
2520
|
// packages/core/src/drive.ts
|
|
2526
2521
|
var LOOPBACK_NAVIGATION_PREFLIGHT_TIMEOUT_MS = 1200;
|
|
2522
|
+
var POST_CLICK_SETTLE_MS = 75;
|
|
2523
|
+
var TRANSIENT_LOCATOR_RETRY_DELAYS_MS = [150, 300, 600];
|
|
2524
|
+
var EXTENSION_READY_WAIT_MS = 1500;
|
|
2527
2525
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
2526
|
+
var ACTIONS_WITH_OPTIONAL_TAB_ID = /* @__PURE__ */ new Set([
|
|
2527
|
+
"drive.navigate",
|
|
2528
|
+
"drive.go_back",
|
|
2529
|
+
"drive.go_forward",
|
|
2530
|
+
"drive.click",
|
|
2531
|
+
"drive.hover",
|
|
2532
|
+
"drive.select",
|
|
2533
|
+
"drive.type",
|
|
2534
|
+
"drive.fill_form",
|
|
2535
|
+
"drive.drag",
|
|
2536
|
+
"drive.handle_dialog",
|
|
2537
|
+
"drive.key",
|
|
2538
|
+
"drive.key_press",
|
|
2539
|
+
"drive.scroll",
|
|
2540
|
+
"drive.screenshot",
|
|
2541
|
+
"drive.wait_for"
|
|
2542
|
+
]);
|
|
2528
2543
|
var isLikelyUnreachableLoopbackError = (message) => {
|
|
2529
2544
|
const normalized = message.toLowerCase();
|
|
2530
2545
|
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 +2636,36 @@ var DriveController = class {
|
|
|
2621
2636
|
this.lastError = void 0;
|
|
2622
2637
|
this.lastErrorAt = void 0;
|
|
2623
2638
|
}
|
|
2639
|
+
async sleep(ms) {
|
|
2640
|
+
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2641
|
+
}
|
|
2642
|
+
async waitForBridgeReady() {
|
|
2643
|
+
if (this.bridge.isConnected()) {
|
|
2644
|
+
return true;
|
|
2645
|
+
}
|
|
2646
|
+
const waitForReady = this.bridge.waitForReady;
|
|
2647
|
+
if (typeof waitForReady === "function") {
|
|
2648
|
+
return await waitForReady.call(this.bridge, EXTENSION_READY_WAIT_MS);
|
|
2649
|
+
}
|
|
2650
|
+
return this.bridge.isConnected();
|
|
2651
|
+
}
|
|
2652
|
+
isTransientLocatorError(action, error) {
|
|
2653
|
+
if (action !== "drive.click") {
|
|
2654
|
+
return false;
|
|
2655
|
+
}
|
|
2656
|
+
if (error.code.toUpperCase() !== "NOT_FOUND") {
|
|
2657
|
+
return false;
|
|
2658
|
+
}
|
|
2659
|
+
const reason = error.details?.reason;
|
|
2660
|
+
const legacyCode = error.details?.legacy_code;
|
|
2661
|
+
const resource = error.details?.resource;
|
|
2662
|
+
return reason === "locator_not_found" || legacyCode === "LOCATOR_NOT_FOUND" || resource === "locator";
|
|
2663
|
+
}
|
|
2624
2664
|
async execute(sessionId, action, params, timeoutMs) {
|
|
2625
2665
|
return await driveMutex.runExclusive(async () => {
|
|
2666
|
+
let selectedTabId;
|
|
2626
2667
|
try {
|
|
2627
|
-
this.registry.require(sessionId);
|
|
2668
|
+
selectedTabId = this.registry.require(sessionId).selectedTabId;
|
|
2628
2669
|
} catch (error) {
|
|
2629
2670
|
if (error instanceof SessionError) {
|
|
2630
2671
|
const errorInfo2 = {
|
|
@@ -2649,7 +2690,7 @@ var DriveController = class {
|
|
|
2649
2690
|
error: errorInfo
|
|
2650
2691
|
};
|
|
2651
2692
|
}
|
|
2652
|
-
if (!this.
|
|
2693
|
+
if (!await this.waitForBridgeReady()) {
|
|
2653
2694
|
const errorInfo = {
|
|
2654
2695
|
code: "EXTENSION_DISCONNECTED",
|
|
2655
2696
|
message: "Extension is not connected. Open Chrome with the Browser Bridge extension enabled, then retry.",
|
|
@@ -2668,7 +2709,12 @@ var DriveController = class {
|
|
|
2668
2709
|
return { ok: false, error: errorInfo };
|
|
2669
2710
|
}
|
|
2670
2711
|
this.ensureDriveReady(sessionId);
|
|
2671
|
-
const
|
|
2712
|
+
const prepared = this.prepareRequestParams(action, params, selectedTabId);
|
|
2713
|
+
const requestParams = prepared.params;
|
|
2714
|
+
const preflightError = await preflightLoopbackNavigation(
|
|
2715
|
+
action,
|
|
2716
|
+
requestParams
|
|
2717
|
+
);
|
|
2672
2718
|
if (preflightError) {
|
|
2673
2719
|
this.recordError(preflightError);
|
|
2674
2720
|
return {
|
|
@@ -2681,10 +2727,19 @@ var DriveController = class {
|
|
|
2681
2727
|
try {
|
|
2682
2728
|
const response = await this.bridge.request(
|
|
2683
2729
|
action,
|
|
2684
|
-
|
|
2730
|
+
requestParams,
|
|
2685
2731
|
timeoutMs
|
|
2686
2732
|
);
|
|
2687
2733
|
if (response.status === "ok") {
|
|
2734
|
+
if (action === "drive.click") {
|
|
2735
|
+
await this.sleep(POST_CLICK_SETTLE_MS);
|
|
2736
|
+
}
|
|
2737
|
+
this.applySessionTargetOnSuccess(
|
|
2738
|
+
sessionId,
|
|
2739
|
+
action,
|
|
2740
|
+
requestParams,
|
|
2741
|
+
response.result
|
|
2742
|
+
);
|
|
2688
2743
|
this.clearLastError();
|
|
2689
2744
|
return {
|
|
2690
2745
|
ok: true,
|
|
@@ -2701,6 +2756,12 @@ var DriveController = class {
|
|
|
2701
2756
|
max_attempts: 1
|
|
2702
2757
|
}
|
|
2703
2758
|
};
|
|
2759
|
+
if (attempt < TRANSIENT_LOCATOR_RETRY_DELAYS_MS.length && this.isTransientLocatorError(action, errorInfo)) {
|
|
2760
|
+
const delayMs = TRANSIENT_LOCATOR_RETRY_DELAYS_MS[attempt] ?? 0;
|
|
2761
|
+
attempt += 1;
|
|
2762
|
+
await this.sleep(delayMs);
|
|
2763
|
+
continue;
|
|
2764
|
+
}
|
|
2704
2765
|
if (shouldRetryDriveOp({
|
|
2705
2766
|
attempt,
|
|
2706
2767
|
retryable: errorInfo.retryable,
|
|
@@ -2709,6 +2770,9 @@ var DriveController = class {
|
|
|
2709
2770
|
attempt += 1;
|
|
2710
2771
|
continue;
|
|
2711
2772
|
}
|
|
2773
|
+
if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo)) {
|
|
2774
|
+
this.clearSessionTarget(sessionId);
|
|
2775
|
+
}
|
|
2712
2776
|
this.recordError(errorInfo);
|
|
2713
2777
|
return { ok: false, error: errorInfo };
|
|
2714
2778
|
} catch (error) {
|
|
@@ -2725,6 +2789,9 @@ var DriveController = class {
|
|
|
2725
2789
|
attempt += 1;
|
|
2726
2790
|
continue;
|
|
2727
2791
|
}
|
|
2792
|
+
if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo2)) {
|
|
2793
|
+
this.clearSessionTarget(sessionId);
|
|
2794
|
+
}
|
|
2728
2795
|
this.recordError(errorInfo2);
|
|
2729
2796
|
return { ok: false, error: errorInfo2 };
|
|
2730
2797
|
}
|
|
@@ -2742,6 +2809,74 @@ var DriveController = class {
|
|
|
2742
2809
|
}
|
|
2743
2810
|
});
|
|
2744
2811
|
}
|
|
2812
|
+
readTabId(params) {
|
|
2813
|
+
const value = params?.tab_id;
|
|
2814
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2815
|
+
}
|
|
2816
|
+
readTabIdFromResult(result) {
|
|
2817
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
2818
|
+
return void 0;
|
|
2819
|
+
}
|
|
2820
|
+
const value = result.tab_id;
|
|
2821
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2822
|
+
}
|
|
2823
|
+
prepareRequestParams(action, params, selectedTabId) {
|
|
2824
|
+
if (!ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
|
|
2825
|
+
return { params, injectedSessionTabId: false };
|
|
2826
|
+
}
|
|
2827
|
+
if (!selectedTabId || selectedTabId <= 0) {
|
|
2828
|
+
return { params, injectedSessionTabId: false };
|
|
2829
|
+
}
|
|
2830
|
+
const explicitTabId = this.readTabId(params);
|
|
2831
|
+
if (explicitTabId !== void 0) {
|
|
2832
|
+
return { params, injectedSessionTabId: false };
|
|
2833
|
+
}
|
|
2834
|
+
return {
|
|
2835
|
+
params: { ...params ?? {}, tab_id: selectedTabId },
|
|
2836
|
+
injectedSessionTabId: true
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
applySessionTargetOnSuccess(sessionId, action, params, result) {
|
|
2840
|
+
const tabId = this.readTabIdFromResult(result) ?? this.readTabId(params);
|
|
2841
|
+
if (tabId === void 0) {
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
try {
|
|
2845
|
+
if (action === "drive.tab_close") {
|
|
2846
|
+
const session = this.registry.require(sessionId);
|
|
2847
|
+
if (session.selectedTabId === tabId) {
|
|
2848
|
+
this.registry.clearSelectedTab(sessionId);
|
|
2849
|
+
}
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
if (action === "drive.tab_activate" || ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
|
|
2853
|
+
this.registry.setSelectedTab(sessionId, tabId);
|
|
2854
|
+
}
|
|
2855
|
+
} catch (error) {
|
|
2856
|
+
console.debug(
|
|
2857
|
+
`Drive target update ignored for session ${sessionId}.`,
|
|
2858
|
+
error
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
isMissingTabError(error) {
|
|
2863
|
+
const code = error.code.toUpperCase();
|
|
2864
|
+
if (code === "TAB_NOT_FOUND") {
|
|
2865
|
+
return true;
|
|
2866
|
+
}
|
|
2867
|
+
const resource = error.details?.resource;
|
|
2868
|
+
return code === "NOT_FOUND" && (resource === void 0 || resource === "tab");
|
|
2869
|
+
}
|
|
2870
|
+
clearSessionTarget(sessionId) {
|
|
2871
|
+
try {
|
|
2872
|
+
this.registry.clearSelectedTab(sessionId);
|
|
2873
|
+
} catch (error) {
|
|
2874
|
+
console.debug(
|
|
2875
|
+
`Drive target clear ignored for session ${sessionId}.`,
|
|
2876
|
+
error
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2745
2880
|
ensureDriveReady(sessionId) {
|
|
2746
2881
|
try {
|
|
2747
2882
|
const session = this.registry.require(sessionId);
|
|
@@ -2781,6 +2916,10 @@ var INTERACTIVE_AX_ROLES = /* @__PURE__ */ new Set([
|
|
|
2781
2916
|
"textbox",
|
|
2782
2917
|
"combobox",
|
|
2783
2918
|
"listbox",
|
|
2919
|
+
"menu",
|
|
2920
|
+
"menuitem",
|
|
2921
|
+
"menuitemcheckbox",
|
|
2922
|
+
"menuitemradio",
|
|
2784
2923
|
"checkbox",
|
|
2785
2924
|
"radio",
|
|
2786
2925
|
"switch",
|
|
@@ -2898,6 +3037,10 @@ var filterAxSnapshot = (snapshot, predicate) => {
|
|
|
2898
3037
|
}
|
|
2899
3038
|
return replaceAxNodes(snapshot, filtered);
|
|
2900
3039
|
};
|
|
3040
|
+
var filterAxSnapshotByRefs = (snapshot, allowedRefs) => filterAxSnapshot(snapshot, (node) => {
|
|
3041
|
+
const ref = node.ref;
|
|
3042
|
+
return typeof ref === "string" && allowedRefs.has(ref);
|
|
3043
|
+
});
|
|
2901
3044
|
var collectKeptDescendants = (nodeId, nodeById, keepIds, visited = /* @__PURE__ */ new Set()) => {
|
|
2902
3045
|
if (visited.has(nodeId)) {
|
|
2903
3046
|
return [];
|
|
@@ -3238,6 +3381,136 @@ var toConsoleEntry = (event) => {
|
|
|
3238
3381
|
}
|
|
3239
3382
|
};
|
|
3240
3383
|
|
|
3384
|
+
// packages/core/src/inspect/extract-content-policy.ts
|
|
3385
|
+
var import_readability = require("@mozilla/readability");
|
|
3386
|
+
var import_jsdom = require("jsdom");
|
|
3387
|
+
var import_turndown = __toESM(require("turndown"));
|
|
3388
|
+
var normalizeExtractedText = (value) => value.replace(/\s+/g, " ").trim();
|
|
3389
|
+
var collapseRepeatedMarkdownBlocks = (markdown) => {
|
|
3390
|
+
const blocks = markdown.split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.length > 0);
|
|
3391
|
+
for (let sequenceLength = Math.floor(blocks.length / 2); sequenceLength >= 1; sequenceLength -= 1) {
|
|
3392
|
+
for (let start = 0; start + sequenceLength * 2 <= blocks.length; start += 1) {
|
|
3393
|
+
const first = blocks.slice(start, start + sequenceLength).map((block) => normalizeExtractedText(block)).join("\n");
|
|
3394
|
+
const second = blocks.slice(start + sequenceLength, start + sequenceLength * 2).map((block) => normalizeExtractedText(block)).join("\n");
|
|
3395
|
+
if (first.length === 0 || first !== second) {
|
|
3396
|
+
continue;
|
|
3397
|
+
}
|
|
3398
|
+
blocks.splice(start + sequenceLength, sequenceLength);
|
|
3399
|
+
sequenceLength = Math.min(
|
|
3400
|
+
sequenceLength + 1,
|
|
3401
|
+
Math.floor(blocks.length / 2)
|
|
3402
|
+
);
|
|
3403
|
+
start = Math.max(start - 1, -1);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
return blocks.join("\n\n").trim();
|
|
3407
|
+
};
|
|
3408
|
+
var extractSemanticMainCandidate = (document) => {
|
|
3409
|
+
const candidates = Array.from(
|
|
3410
|
+
document.querySelectorAll('main, [role="main"], article')
|
|
3411
|
+
);
|
|
3412
|
+
let best = candidates[0] ?? null;
|
|
3413
|
+
let bestLength = 0;
|
|
3414
|
+
for (const candidate of candidates) {
|
|
3415
|
+
const text = normalizeExtractedText(candidate.textContent ?? "");
|
|
3416
|
+
if (text.length <= bestLength) {
|
|
3417
|
+
continue;
|
|
3418
|
+
}
|
|
3419
|
+
best = candidate;
|
|
3420
|
+
bestLength = text.length;
|
|
3421
|
+
}
|
|
3422
|
+
if (!best || bestLength === 0) {
|
|
3423
|
+
return null;
|
|
3424
|
+
}
|
|
3425
|
+
return {
|
|
3426
|
+
html: best.innerHTML,
|
|
3427
|
+
text: normalizeExtractedText(best.textContent ?? ""),
|
|
3428
|
+
tagName: best.tagName.toLowerCase()
|
|
3429
|
+
};
|
|
3430
|
+
};
|
|
3431
|
+
var shouldPreferSemanticMainCandidate = (options) => {
|
|
3432
|
+
const articleLength = normalizeExtractedText(options.articleText).length;
|
|
3433
|
+
const mainLength = normalizeExtractedText(options.mainText).length;
|
|
3434
|
+
if (mainLength === 0) {
|
|
3435
|
+
return false;
|
|
3436
|
+
}
|
|
3437
|
+
if (articleLength === 0) {
|
|
3438
|
+
return true;
|
|
3439
|
+
}
|
|
3440
|
+
if (options.mainTagName === "article") {
|
|
3441
|
+
return false;
|
|
3442
|
+
}
|
|
3443
|
+
return articleLength < 160 && mainLength > articleLength + 20;
|
|
3444
|
+
};
|
|
3445
|
+
var parseExtractContentSource = (options) => {
|
|
3446
|
+
let article = null;
|
|
3447
|
+
let semanticMainCandidate = null;
|
|
3448
|
+
try {
|
|
3449
|
+
const dom = new import_jsdom.JSDOM(options.html, { url: options.url });
|
|
3450
|
+
const reader = new import_readability.Readability(dom.window.document);
|
|
3451
|
+
article = reader.parse();
|
|
3452
|
+
semanticMainCandidate = extractSemanticMainCandidate(dom.window.document);
|
|
3453
|
+
} catch {
|
|
3454
|
+
throw new InspectError(
|
|
3455
|
+
"EVALUATION_FAILED",
|
|
3456
|
+
"Failed to parse page content.",
|
|
3457
|
+
{
|
|
3458
|
+
retryable: false
|
|
3459
|
+
}
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
if (!article) {
|
|
3463
|
+
throw new InspectError(
|
|
3464
|
+
"NOT_SUPPORTED",
|
|
3465
|
+
"Readability could not extract content.",
|
|
3466
|
+
{
|
|
3467
|
+
retryable: false
|
|
3468
|
+
}
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
return {
|
|
3472
|
+
article,
|
|
3473
|
+
semanticMainCandidate
|
|
3474
|
+
};
|
|
3475
|
+
};
|
|
3476
|
+
var renderExtractContent = (options) => {
|
|
3477
|
+
let content = "";
|
|
3478
|
+
if (options.format === "article_json") {
|
|
3479
|
+
content = JSON.stringify(options.article, null, 2);
|
|
3480
|
+
} else if (options.format === "text") {
|
|
3481
|
+
const articleText = options.article.textContent ?? "";
|
|
3482
|
+
if (options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
|
|
3483
|
+
articleText,
|
|
3484
|
+
mainText: options.semanticMainCandidate.text,
|
|
3485
|
+
mainTagName: options.semanticMainCandidate.tagName
|
|
3486
|
+
})) {
|
|
3487
|
+
content = options.semanticMainCandidate.text;
|
|
3488
|
+
} else {
|
|
3489
|
+
content = articleText;
|
|
3490
|
+
}
|
|
3491
|
+
} else {
|
|
3492
|
+
const turndown = new import_turndown.default();
|
|
3493
|
+
const articleText = options.article.textContent ?? "";
|
|
3494
|
+
const sourceHtml = options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
|
|
3495
|
+
articleText,
|
|
3496
|
+
mainText: options.semanticMainCandidate.text,
|
|
3497
|
+
mainTagName: options.semanticMainCandidate.tagName
|
|
3498
|
+
}) ? options.semanticMainCandidate.html : options.article.content ?? "";
|
|
3499
|
+
content = collapseRepeatedMarkdownBlocks(turndown.turndown(sourceHtml));
|
|
3500
|
+
}
|
|
3501
|
+
const includeMetadata = options.includeMetadata ?? true;
|
|
3502
|
+
return {
|
|
3503
|
+
content,
|
|
3504
|
+
...includeMetadata ? {
|
|
3505
|
+
title: options.article.title ?? void 0,
|
|
3506
|
+
byline: options.article.byline ?? void 0,
|
|
3507
|
+
excerpt: options.article.excerpt ?? void 0,
|
|
3508
|
+
siteName: options.article.siteName ?? void 0
|
|
3509
|
+
} : {},
|
|
3510
|
+
...options.warnings && options.warnings.length > 0 ? { warnings: options.warnings } : {}
|
|
3511
|
+
};
|
|
3512
|
+
};
|
|
3513
|
+
|
|
3241
3514
|
// packages/core/src/inspect/har.ts
|
|
3242
3515
|
var buildHar = (events, title) => {
|
|
3243
3516
|
const requests = /* @__PURE__ */ new Map();
|
|
@@ -3398,7 +3671,8 @@ var captureHtml = async (tabId, options) => {
|
|
|
3398
3671
|
const result = await options.debuggerCommand(tabId, "Runtime.evaluate", {
|
|
3399
3672
|
expression,
|
|
3400
3673
|
returnByValue: true,
|
|
3401
|
-
awaitPromise: true
|
|
3674
|
+
awaitPromise: true,
|
|
3675
|
+
...typeof options.executionContextId === "number" ? { contextId: options.executionContextId } : {}
|
|
3402
3676
|
});
|
|
3403
3677
|
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
3404
3678
|
return options.onEvaluationFailed();
|
|
@@ -3539,11 +3813,19 @@ var SnapshotHistory = class {
|
|
|
3539
3813
|
|
|
3540
3814
|
// packages/core/src/inspect/snapshot-refs.ts
|
|
3541
3815
|
var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
|
|
3816
|
+
var SNAPSHOT_REF_REGISTRY_ID = "__bb_snapshot_ref_registry__";
|
|
3542
3817
|
var MAX_REF_ASSIGNMENTS = 500;
|
|
3543
3818
|
var MAX_REF_WARNINGS = 5;
|
|
3544
3819
|
var isInspectError = (error) => Boolean(
|
|
3545
3820
|
error && typeof error === "object" && "name" in error && error.name === "InspectError" && "message" in error && typeof error.message === "string"
|
|
3546
3821
|
);
|
|
3822
|
+
var isExpectedSnapshotRefMiss = (error) => {
|
|
3823
|
+
if (!isInspectError(error)) {
|
|
3824
|
+
return false;
|
|
3825
|
+
}
|
|
3826
|
+
const message = error.message.toLowerCase();
|
|
3827
|
+
return message.includes("could not find node") || message.includes("no node with given id") || message.includes("does not belong to the document");
|
|
3828
|
+
};
|
|
3547
3829
|
var assignRefsToAxSnapshot = (snapshot) => {
|
|
3548
3830
|
const nodes = getAxNodes(snapshot);
|
|
3549
3831
|
const refs = /* @__PURE__ */ new Map();
|
|
@@ -3562,31 +3844,61 @@ var assignRefsToAxSnapshot = (snapshot) => {
|
|
|
3562
3844
|
const ref = `@e${index}`;
|
|
3563
3845
|
index += 1;
|
|
3564
3846
|
node.ref = ref;
|
|
3565
|
-
refs.set(backendId,
|
|
3847
|
+
refs.set(backendId, {
|
|
3848
|
+
ref,
|
|
3849
|
+
...getAxRole(node) ? { role: getAxRole(node) } : {},
|
|
3850
|
+
...getAxName(node) ? { name: getAxName(node) } : {},
|
|
3851
|
+
...extractUrl(node) ? { url: extractUrl(node) } : {}
|
|
3852
|
+
});
|
|
3566
3853
|
}
|
|
3567
3854
|
return refs;
|
|
3568
3855
|
};
|
|
3569
|
-
var
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3856
|
+
var extractUrl = (node) => {
|
|
3857
|
+
if (!Array.isArray(node.properties)) {
|
|
3858
|
+
return void 0;
|
|
3859
|
+
}
|
|
3860
|
+
for (const prop of node.properties) {
|
|
3861
|
+
if (!prop || typeof prop !== "object") {
|
|
3862
|
+
continue;
|
|
3863
|
+
}
|
|
3864
|
+
const name = prop.name;
|
|
3865
|
+
if (name !== "url") {
|
|
3866
|
+
continue;
|
|
3867
|
+
}
|
|
3868
|
+
const value = prop.value?.value;
|
|
3869
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3870
|
+
return value;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
return void 0;
|
|
3575
3874
|
};
|
|
3576
|
-
var
|
|
3577
|
-
const warnings = [];
|
|
3578
|
-
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3579
|
-
await debuggerCommand(tabId, "Runtime.enable", {});
|
|
3875
|
+
var clearSnapshotRefArtifacts = async (tabId, debuggerCommand) => {
|
|
3580
3876
|
try {
|
|
3581
|
-
await
|
|
3877
|
+
await debuggerCommand(tabId, "Runtime.evaluate", {
|
|
3878
|
+
expression: `(() => {
|
|
3879
|
+
document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'));
|
|
3880
|
+
document.getElementById('${SNAPSHOT_REF_REGISTRY_ID}')?.remove();
|
|
3881
|
+
})()`,
|
|
3882
|
+
returnByValue: true,
|
|
3883
|
+
awaitPromise: true
|
|
3884
|
+
});
|
|
3885
|
+
return { warnings: [] };
|
|
3582
3886
|
} catch {
|
|
3583
|
-
warnings
|
|
3887
|
+
return { warnings: ["Failed to clear prior snapshot refs."] };
|
|
3584
3888
|
}
|
|
3889
|
+
};
|
|
3890
|
+
var applySnapshotRefAttributes = async (tabId, refs, debuggerCommand) => {
|
|
3891
|
+
const warnings = [];
|
|
3892
|
+
const appliedRefs = /* @__PURE__ */ new Set();
|
|
3893
|
+
const appliedBindings = [];
|
|
3894
|
+
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3895
|
+
await debuggerCommand(tabId, "Runtime.enable", {});
|
|
3585
3896
|
if (refs.size === 0) {
|
|
3586
|
-
return warnings;
|
|
3897
|
+
return { warnings, appliedRefs, appliedBindings };
|
|
3587
3898
|
}
|
|
3588
3899
|
let applied = 0;
|
|
3589
|
-
for (const [backendNodeId,
|
|
3900
|
+
for (const [backendNodeId, binding] of refs) {
|
|
3901
|
+
const ref = binding.ref;
|
|
3590
3902
|
if (applied >= MAX_REF_ASSIGNMENTS) {
|
|
3591
3903
|
warnings.push(
|
|
3592
3904
|
`Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
|
|
@@ -3599,9 +3911,6 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
|
|
|
3599
3911
|
});
|
|
3600
3912
|
const node = described.node;
|
|
3601
3913
|
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
3914
|
continue;
|
|
3606
3915
|
}
|
|
3607
3916
|
await debuggerCommand(tabId, "DOM.setAttributeValue", {
|
|
@@ -3610,25 +3919,64 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
|
|
|
3610
3919
|
value: ref
|
|
3611
3920
|
});
|
|
3612
3921
|
applied += 1;
|
|
3613
|
-
|
|
3922
|
+
appliedRefs.add(ref);
|
|
3923
|
+
appliedBindings.push(binding);
|
|
3924
|
+
} catch (error) {
|
|
3925
|
+
if (isExpectedSnapshotRefMiss(error)) {
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3614
3928
|
if (warnings.length < MAX_REF_WARNINGS) {
|
|
3615
3929
|
warnings.push(`Ref ${ref} could not be applied.`);
|
|
3616
3930
|
}
|
|
3617
3931
|
}
|
|
3618
3932
|
}
|
|
3619
|
-
return warnings;
|
|
3933
|
+
return { warnings, appliedRefs, appliedBindings };
|
|
3620
3934
|
};
|
|
3621
|
-
var
|
|
3622
|
-
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3623
|
-
const document = await debuggerCommand(tabId, "DOM.getDocument", {
|
|
3624
|
-
depth: 1
|
|
3625
|
-
});
|
|
3626
|
-
const rootNodeId = document.root?.nodeId;
|
|
3627
|
-
if (typeof rootNodeId !== "number") {
|
|
3628
|
-
return { warnings: ["Failed to resolve DOM root for selector."] };
|
|
3629
|
-
}
|
|
3935
|
+
var persistSnapshotRefRegistry = async (tabId, bindings, debuggerCommand) => {
|
|
3630
3936
|
try {
|
|
3631
|
-
const
|
|
3937
|
+
const registry = JSON.stringify(bindings);
|
|
3938
|
+
await debuggerCommand(tabId, "Runtime.evaluate", {
|
|
3939
|
+
expression: `(() => {
|
|
3940
|
+
const id = ${JSON.stringify(SNAPSHOT_REF_REGISTRY_ID)};
|
|
3941
|
+
let el = document.getElementById(id);
|
|
3942
|
+
if (!el) {
|
|
3943
|
+
el = document.createElement('script');
|
|
3944
|
+
el.id = id;
|
|
3945
|
+
el.type = 'application/json';
|
|
3946
|
+
document.documentElement.appendChild(el);
|
|
3947
|
+
}
|
|
3948
|
+
el.textContent = ${JSON.stringify(registry)};
|
|
3949
|
+
})()`,
|
|
3950
|
+
returnByValue: true,
|
|
3951
|
+
awaitPromise: true
|
|
3952
|
+
});
|
|
3953
|
+
return { warnings: [] };
|
|
3954
|
+
} catch {
|
|
3955
|
+
return { warnings: ["Snapshot ref registry could not be applied."] };
|
|
3956
|
+
}
|
|
3957
|
+
};
|
|
3958
|
+
var pruneUnappliedRefsFromSnapshot = (snapshot, appliedRefs) => {
|
|
3959
|
+
for (const node of getAxNodes(snapshot)) {
|
|
3960
|
+
if (!node || typeof node !== "object") {
|
|
3961
|
+
continue;
|
|
3962
|
+
}
|
|
3963
|
+
const ref = node.ref;
|
|
3964
|
+
if (typeof ref === "string" && !appliedRefs.has(ref)) {
|
|
3965
|
+
delete node.ref;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
};
|
|
3969
|
+
var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
|
|
3970
|
+
await debuggerCommand(tabId, "DOM.enable", {});
|
|
3971
|
+
const document = await debuggerCommand(tabId, "DOM.getDocument", {
|
|
3972
|
+
depth: 1
|
|
3973
|
+
});
|
|
3974
|
+
const rootNodeId = document.root?.nodeId;
|
|
3975
|
+
if (typeof rootNodeId !== "number") {
|
|
3976
|
+
return { warnings: ["Failed to resolve DOM root for selector."] };
|
|
3977
|
+
}
|
|
3978
|
+
try {
|
|
3979
|
+
const result = await debuggerCommand(tabId, "DOM.querySelector", {
|
|
3632
3980
|
nodeId: rootNodeId,
|
|
3633
3981
|
selector
|
|
3634
3982
|
});
|
|
@@ -3645,107 +3993,6 @@ var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
|
|
|
3645
3993
|
}
|
|
3646
3994
|
};
|
|
3647
3995
|
|
|
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
3996
|
// packages/core/src/target-matching.ts
|
|
3750
3997
|
var normalizeText = (value) => (value ?? "").trim().toLowerCase();
|
|
3751
3998
|
var normalizeUrl = (value) => {
|
|
@@ -3851,11 +4098,302 @@ var pickBestTarget = (candidates, hint, now = Date.now()) => {
|
|
|
3851
4098
|
return ranked.length > 0 ? ranked[0] : null;
|
|
3852
4099
|
};
|
|
3853
4100
|
|
|
4101
|
+
// packages/core/src/inspect/target-selection.ts
|
|
4102
|
+
var readTargetHintInput = (target) => {
|
|
4103
|
+
if (!target) {
|
|
4104
|
+
return void 0;
|
|
4105
|
+
}
|
|
4106
|
+
const url = typeof target.url === "string" ? target.url : void 0;
|
|
4107
|
+
const title = typeof target.title === "string" ? target.title : void 0;
|
|
4108
|
+
const tabIdRaw = target.tab_id ?? target.tabId;
|
|
4109
|
+
const tabId = typeof tabIdRaw === "number" ? tabIdRaw : void 0;
|
|
4110
|
+
const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
|
|
4111
|
+
const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
|
|
4112
|
+
if (!url && !title && tabId === void 0 && !lastActiveAt) {
|
|
4113
|
+
return void 0;
|
|
4114
|
+
}
|
|
4115
|
+
return { url, title, tabId, lastActiveAt };
|
|
4116
|
+
};
|
|
4117
|
+
var readSessionTargetHint = (registry, sessionId) => {
|
|
4118
|
+
if (typeof sessionId !== "string") {
|
|
4119
|
+
return void 0;
|
|
4120
|
+
}
|
|
4121
|
+
const selectedTabId = registry.get(sessionId)?.selectedTabId;
|
|
4122
|
+
return typeof selectedTabId === "number" && Number.isFinite(selectedTabId) ? { tabId: selectedTabId } : void 0;
|
|
4123
|
+
};
|
|
4124
|
+
var resolveInspectTargetHint = (options) => {
|
|
4125
|
+
const explicit = readTargetHintInput(options.target);
|
|
4126
|
+
if (explicit) {
|
|
4127
|
+
return explicit;
|
|
4128
|
+
}
|
|
4129
|
+
const sessionHint = readSessionTargetHint(
|
|
4130
|
+
options.registry,
|
|
4131
|
+
options.sessionId
|
|
4132
|
+
);
|
|
4133
|
+
if (sessionHint) {
|
|
4134
|
+
return sessionHint;
|
|
4135
|
+
}
|
|
4136
|
+
const tabs = options.extensionBridge?.getStatus().tabs ?? [];
|
|
4137
|
+
return deriveHintFromTabs(tabs);
|
|
4138
|
+
};
|
|
4139
|
+
var selectInspectTab = (options) => {
|
|
4140
|
+
if (!options.extensionBridge || !options.extensionBridge.isConnected()) {
|
|
4141
|
+
throw new InspectError(
|
|
4142
|
+
"EXTENSION_DISCONNECTED",
|
|
4143
|
+
"Extension is not connected.",
|
|
4144
|
+
{
|
|
4145
|
+
retryable: true
|
|
4146
|
+
}
|
|
4147
|
+
);
|
|
4148
|
+
}
|
|
4149
|
+
const tabs = options.extensionBridge.getStatus().tabs ?? [];
|
|
4150
|
+
if (!Array.isArray(tabs) || tabs.length === 0) {
|
|
4151
|
+
throw new InspectError("TAB_NOT_FOUND", "No tabs available to inspect.");
|
|
4152
|
+
}
|
|
4153
|
+
const effectiveHint = options.targetHint ?? readSessionTargetHint(options.registry, options.sessionId);
|
|
4154
|
+
if (typeof effectiveHint?.tabId === "number" && Number.isFinite(effectiveHint.tabId)) {
|
|
4155
|
+
const tab2 = tabs.find((entry) => entry.tab_id === effectiveHint.tabId);
|
|
4156
|
+
if (!tab2) {
|
|
4157
|
+
throw new InspectError(
|
|
4158
|
+
"TAB_NOT_FOUND",
|
|
4159
|
+
`No matching tab found for tab_id ${effectiveHint.tabId}.`,
|
|
4160
|
+
{ details: { tab_id: effectiveHint.tabId } }
|
|
4161
|
+
);
|
|
4162
|
+
}
|
|
4163
|
+
return { tabId: effectiveHint.tabId, tab: tab2 };
|
|
4164
|
+
}
|
|
4165
|
+
const candidates = tabs.map((tab2) => ({
|
|
4166
|
+
id: String(tab2.tab_id),
|
|
4167
|
+
url: tab2.url ?? "",
|
|
4168
|
+
title: tab2.title,
|
|
4169
|
+
lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
|
|
4170
|
+
}));
|
|
4171
|
+
const ranked = pickBestTarget(candidates, effectiveHint);
|
|
4172
|
+
if (!ranked) {
|
|
4173
|
+
throw new InspectError("TAB_NOT_FOUND", "No matching tab found.");
|
|
4174
|
+
}
|
|
4175
|
+
const tabId = Number(ranked.candidate.id);
|
|
4176
|
+
if (!Number.isFinite(tabId)) {
|
|
4177
|
+
throw new InspectError("TAB_NOT_FOUND", "Resolved tab id is invalid.");
|
|
4178
|
+
}
|
|
4179
|
+
const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
|
|
4180
|
+
const warnings = [];
|
|
4181
|
+
if (!effectiveHint) {
|
|
4182
|
+
warnings.push("No target hint provided; using the most recent tab.");
|
|
4183
|
+
} else if (ranked.score < 20) {
|
|
4184
|
+
warnings.push("Weak target match; using best available tab.");
|
|
4185
|
+
}
|
|
4186
|
+
return {
|
|
4187
|
+
tabId,
|
|
4188
|
+
tab,
|
|
4189
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
4190
|
+
};
|
|
4191
|
+
};
|
|
4192
|
+
|
|
4193
|
+
// packages/core/src/page-state-script.ts
|
|
4194
|
+
var buildPageStateScript = (options) => {
|
|
4195
|
+
const includeValues = options?.includeValues === true;
|
|
4196
|
+
return [
|
|
4197
|
+
"(() => {",
|
|
4198
|
+
` const includeValues = ${includeValues ? "true" : "false"};`,
|
|
4199
|
+
" const escape = (value) => {",
|
|
4200
|
+
" if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
|
|
4201
|
+
" return CSS.escape(value);",
|
|
4202
|
+
" }",
|
|
4203
|
+
` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
|
|
4204
|
+
" };",
|
|
4205
|
+
" const truncate = (value, max) => {",
|
|
4206
|
+
" const text = String(value ?? '');",
|
|
4207
|
+
" return text.length > max ? text.slice(0, max) : text;",
|
|
4208
|
+
" };",
|
|
4209
|
+
' const redact = (value) => (includeValues ? truncate(value, 500) : "[redacted]");',
|
|
4210
|
+
' const textFor = (element) => truncate((element?.textContent || "").replace(/\\s+/g, " ").trim(), 120);',
|
|
4211
|
+
" const isVisible = (element) => {",
|
|
4212
|
+
' if (!element || typeof element.getClientRects !== "function") {',
|
|
4213
|
+
" return false;",
|
|
4214
|
+
" }",
|
|
4215
|
+
" const style = window.getComputedStyle(element);",
|
|
4216
|
+
' if (style.display === "none" || style.visibility === "hidden") {',
|
|
4217
|
+
" return false;",
|
|
4218
|
+
" }",
|
|
4219
|
+
' if (element.hidden || element.getAttribute("aria-hidden") === "true") {',
|
|
4220
|
+
" return false;",
|
|
4221
|
+
" }",
|
|
4222
|
+
" return element.getClientRects().length > 0;",
|
|
4223
|
+
" };",
|
|
4224
|
+
" const selectorFor = (element) => {",
|
|
4225
|
+
" if (!element || element.nodeType !== 1) {",
|
|
4226
|
+
' return "";',
|
|
4227
|
+
" }",
|
|
4228
|
+
" if (element.id) {",
|
|
4229
|
+
" return `#${escape(element.id)}`;",
|
|
4230
|
+
" }",
|
|
4231
|
+
' const testId = element.getAttribute("data-testid");',
|
|
4232
|
+
" if (testId) {",
|
|
4233
|
+
' return `[data-testid="${escape(testId)}"]`;',
|
|
4234
|
+
" }",
|
|
4235
|
+
' const name = element.getAttribute("name");',
|
|
4236
|
+
" if (name) {",
|
|
4237
|
+
' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
|
|
4238
|
+
" }",
|
|
4239
|
+
' const ariaLabel = element.getAttribute("aria-label");',
|
|
4240
|
+
" if (ariaLabel) {",
|
|
4241
|
+
' return `${element.tagName.toLowerCase()}[aria-label="${escape(ariaLabel)}"]`;',
|
|
4242
|
+
" }",
|
|
4243
|
+
" const parts = [];",
|
|
4244
|
+
" let node = element;",
|
|
4245
|
+
" while (node && node.nodeType === 1 && parts.length < 4) {",
|
|
4246
|
+
" let part = node.tagName.toLowerCase();",
|
|
4247
|
+
" const parent = node.parentElement;",
|
|
4248
|
+
" if (parent) {",
|
|
4249
|
+
" const siblings = Array.from(parent.children).filter(",
|
|
4250
|
+
" (child) => child.tagName === node.tagName",
|
|
4251
|
+
" );",
|
|
4252
|
+
" if (siblings.length > 1) {",
|
|
4253
|
+
" part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
|
|
4254
|
+
" }",
|
|
4255
|
+
" }",
|
|
4256
|
+
" parts.unshift(part);",
|
|
4257
|
+
" node = parent;",
|
|
4258
|
+
" }",
|
|
4259
|
+
" return parts.join('>');",
|
|
4260
|
+
" };",
|
|
4261
|
+
" const labelFor = (element) => {",
|
|
4262
|
+
" if (!element) {",
|
|
4263
|
+
" return undefined;",
|
|
4264
|
+
" }",
|
|
4265
|
+
' const ariaLabel = element.getAttribute("aria-label");',
|
|
4266
|
+
" if (ariaLabel) {",
|
|
4267
|
+
" return truncate(ariaLabel, 120);",
|
|
4268
|
+
" }",
|
|
4269
|
+
" if (element.id) {",
|
|
4270
|
+
' const explicitLabel = document.querySelector(`label[for="${escape(element.id)}"]`);',
|
|
4271
|
+
" if (explicitLabel) {",
|
|
4272
|
+
" return textFor(explicitLabel);",
|
|
4273
|
+
" }",
|
|
4274
|
+
" }",
|
|
4275
|
+
' const parentLabel = element.closest("label");',
|
|
4276
|
+
" if (parentLabel) {",
|
|
4277
|
+
" return textFor(parentLabel);",
|
|
4278
|
+
" }",
|
|
4279
|
+
" return undefined;",
|
|
4280
|
+
" };",
|
|
4281
|
+
" const readStorage = (storage, limit) => {",
|
|
4282
|
+
" try {",
|
|
4283
|
+
" const keys = Object.keys(storage);",
|
|
4284
|
+
" return {",
|
|
4285
|
+
" count: keys.length,",
|
|
4286
|
+
" entries: keys.slice(0, limit).map((key) => ({",
|
|
4287
|
+
" key,",
|
|
4288
|
+
" value: redact(storage.getItem(key)),",
|
|
4289
|
+
" })),",
|
|
4290
|
+
" };",
|
|
4291
|
+
" } catch {",
|
|
4292
|
+
" return { count: 0, entries: [] };",
|
|
4293
|
+
" }",
|
|
4294
|
+
" };",
|
|
4295
|
+
' const forms = Array.from(document.querySelectorAll("form"))',
|
|
4296
|
+
" .filter((form) => isVisible(form))",
|
|
4297
|
+
" .map((form) => {",
|
|
4298
|
+
" const fields = Array.from(form.elements)",
|
|
4299
|
+
" .filter((element) => element && element.tagName)",
|
|
4300
|
+
" .map((element) => {",
|
|
4301
|
+
" const tag = element.tagName.toLowerCase();",
|
|
4302
|
+
' const type = "type" in element && element.type ? element.type : tag;',
|
|
4303
|
+
' const name = element.name || element.getAttribute("name") || element.id || "";',
|
|
4304
|
+
' let value = "";',
|
|
4305
|
+
" let options;",
|
|
4306
|
+
' if (tag === "select") {',
|
|
4307
|
+
" const select = element;",
|
|
4308
|
+
' value = redact(select.value ?? "");',
|
|
4309
|
+
" options = Array.from(select.options).map((option) => truncate(option.text, 120));",
|
|
4310
|
+
' } else if (tag === "input" && element.type === "password") {',
|
|
4311
|
+
' value = "[redacted]";',
|
|
4312
|
+
' } else if (tag === "input" || tag === "textarea") {',
|
|
4313
|
+
' value = redact(element.value ?? "");',
|
|
4314
|
+
" } else if (element.isContentEditable) {",
|
|
4315
|
+
' value = redact(element.textContent ?? "");',
|
|
4316
|
+
' } else if ("value" in element) {',
|
|
4317
|
+
' value = redact(element.value ?? "");',
|
|
4318
|
+
" }",
|
|
4319
|
+
" return {",
|
|
4320
|
+
" name,",
|
|
4321
|
+
" type,",
|
|
4322
|
+
" value,",
|
|
4323
|
+
" selector: selectorFor(element),",
|
|
4324
|
+
" label: labelFor(element),",
|
|
4325
|
+
' placeholder: truncate(element.getAttribute?.("placeholder") ?? "", 120) || undefined,',
|
|
4326
|
+
' checked: typeof element.checked === "boolean" ? element.checked : undefined,',
|
|
4327
|
+
" disabled: !!element.disabled,",
|
|
4328
|
+
" visible: isVisible(element),",
|
|
4329
|
+
" ...(options ? { options } : {}),",
|
|
4330
|
+
" };",
|
|
4331
|
+
" });",
|
|
4332
|
+
" return {",
|
|
4333
|
+
" selector: selectorFor(form),",
|
|
4334
|
+
' action: form.getAttribute("action") || undefined,',
|
|
4335
|
+
' method: form.getAttribute("method") || undefined,',
|
|
4336
|
+
" fields,",
|
|
4337
|
+
" };",
|
|
4338
|
+
" });",
|
|
4339
|
+
" const localStorageData = readStorage(window.localStorage, 25);",
|
|
4340
|
+
" const sessionStorageData = readStorage(window.sessionStorage, 25);",
|
|
4341
|
+
' const cookies = (document.cookie ? document.cookie.split(";") : [])',
|
|
4342
|
+
" .map((entry) => entry.trim())",
|
|
4343
|
+
" .filter((entry) => entry.length > 0)",
|
|
4344
|
+
" .slice(0, 25)",
|
|
4345
|
+
" .map((entry) => {",
|
|
4346
|
+
' const [key, ...rest] = entry.split("=");',
|
|
4347
|
+
" return {",
|
|
4348
|
+
" key,",
|
|
4349
|
+
' value: includeValues ? truncate(rest.join("="), 500) : "[redacted]",',
|
|
4350
|
+
" };",
|
|
4351
|
+
" });",
|
|
4352
|
+
" const focused = document.activeElement && document.activeElement !== document.body",
|
|
4353
|
+
" ? {",
|
|
4354
|
+
" selector: selectorFor(document.activeElement),",
|
|
4355
|
+
' name: document.activeElement.getAttribute?.("name") || document.activeElement.id || undefined,',
|
|
4356
|
+
" label: labelFor(document.activeElement),",
|
|
4357
|
+
' role: document.activeElement.getAttribute?.("role") || document.activeElement.tagName?.toLowerCase(),',
|
|
4358
|
+
' type: document.activeElement.getAttribute?.("type") || undefined,',
|
|
4359
|
+
" text: textFor(document.activeElement) || undefined,",
|
|
4360
|
+
" }",
|
|
4361
|
+
" : undefined;",
|
|
4362
|
+
' const primaryActions = Array.from(document.querySelectorAll("button, a[href], input[type=button], input[type=submit]"))',
|
|
4363
|
+
" .filter((element) => isVisible(element))",
|
|
4364
|
+
" .map((element) => ({",
|
|
4365
|
+
" selector: selectorFor(element),",
|
|
4366
|
+
' role: element.getAttribute("role") || element.tagName.toLowerCase(),',
|
|
4367
|
+
' name: truncate(element.getAttribute("aria-label") || element.innerText || element.value || "", 120),',
|
|
4368
|
+
" }))",
|
|
4369
|
+
" .filter((entry) => entry.name.length > 0)",
|
|
4370
|
+
" .slice(0, 12);",
|
|
4371
|
+
" return {",
|
|
4372
|
+
" url: location.href,",
|
|
4373
|
+
" title: document.title || undefined,",
|
|
4374
|
+
" readyState: document.readyState || undefined,",
|
|
4375
|
+
" focused,",
|
|
4376
|
+
" primaryActions,",
|
|
4377
|
+
" storageSummary: {",
|
|
4378
|
+
" localStorageCount: localStorageData.count,",
|
|
4379
|
+
" sessionStorageCount: sessionStorageData.count,",
|
|
4380
|
+
" cookieCount: cookies.length,",
|
|
4381
|
+
" },",
|
|
4382
|
+
" forms,",
|
|
4383
|
+
" localStorage: localStorageData.entries,",
|
|
4384
|
+
" sessionStorage: sessionStorageData.entries,",
|
|
4385
|
+
" cookies,",
|
|
4386
|
+
" };",
|
|
4387
|
+
"})()"
|
|
4388
|
+
].join("\n");
|
|
4389
|
+
};
|
|
4390
|
+
|
|
3854
4391
|
// packages/core/src/inspect/service.ts
|
|
3855
4392
|
var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
|
|
3856
4393
|
var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
|
|
3857
4394
|
var InspectService = class {
|
|
3858
4395
|
constructor(options) {
|
|
4396
|
+
this.consoleSinceBySessionTab = /* @__PURE__ */ new Map();
|
|
3859
4397
|
this.registry = options.registry;
|
|
3860
4398
|
this.debugger = options.debuggerBridge;
|
|
3861
4399
|
this.extensionBridge = options.extensionBridge;
|
|
@@ -3895,7 +4433,7 @@ var InspectService = class {
|
|
|
3895
4433
|
async reconnect(sessionId) {
|
|
3896
4434
|
try {
|
|
3897
4435
|
this.requireSession(sessionId);
|
|
3898
|
-
const selection = await this.resolveTab();
|
|
4436
|
+
const selection = await this.resolveTab(sessionId);
|
|
3899
4437
|
const debuggerBridge = this.ensureDebugger();
|
|
3900
4438
|
const result = await debuggerBridge.attach(selection.tabId);
|
|
3901
4439
|
if (result.ok) {
|
|
@@ -3914,13 +4452,17 @@ var InspectService = class {
|
|
|
3914
4452
|
}
|
|
3915
4453
|
async domSnapshot(input) {
|
|
3916
4454
|
this.requireSession(input.sessionId);
|
|
3917
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4455
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
3918
4456
|
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
4457
|
+
const executionContextId = await this.resolveMainFrameExecutionContextId(
|
|
4458
|
+
selection.tabId
|
|
4459
|
+
);
|
|
3919
4460
|
const work = async () => {
|
|
3920
4461
|
if (input.format === "html") {
|
|
3921
4462
|
const html = await captureHtml(selection.tabId, {
|
|
3922
4463
|
selector: input.selector,
|
|
3923
4464
|
debuggerCommand,
|
|
4465
|
+
executionContextId,
|
|
3924
4466
|
onEvaluationFailed: () => {
|
|
3925
4467
|
const error = new InspectError(
|
|
3926
4468
|
"EVALUATION_FAILED",
|
|
@@ -3964,20 +4506,11 @@ var InspectService = class {
|
|
|
3964
4506
|
);
|
|
3965
4507
|
selectorWarnings.push(...resolved.warnings ?? []);
|
|
3966
4508
|
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
|
-
}
|
|
4509
|
+
const refWarnings = (await clearSnapshotRefArtifacts(selection.tabId, debuggerCommand)).warnings;
|
|
3977
4510
|
const warnings2 = [
|
|
3978
4511
|
...selection.warnings ?? [],
|
|
3979
4512
|
...selectorWarnings,
|
|
3980
|
-
...
|
|
4513
|
+
...refWarnings
|
|
3981
4514
|
];
|
|
3982
4515
|
return {
|
|
3983
4516
|
format: "ax",
|
|
@@ -4014,16 +4547,46 @@ var InspectService = class {
|
|
|
4014
4547
|
}
|
|
4015
4548
|
}
|
|
4016
4549
|
const refMap = assignRefsToAxSnapshot(snapshot);
|
|
4017
|
-
const
|
|
4550
|
+
const clearResult = await clearSnapshotRefArtifacts(
|
|
4551
|
+
selection.tabId,
|
|
4552
|
+
debuggerCommand
|
|
4553
|
+
);
|
|
4554
|
+
const refResult = await applySnapshotRefAttributes(
|
|
4018
4555
|
selection.tabId,
|
|
4019
4556
|
refMap,
|
|
4020
4557
|
debuggerCommand
|
|
4021
4558
|
);
|
|
4559
|
+
let actionableRefs = refResult.appliedRefs;
|
|
4560
|
+
let actionableBindings = refResult.appliedBindings;
|
|
4561
|
+
const actionabilityWarnings = [];
|
|
4562
|
+
if (input.interactive && refResult.appliedBindings.length > 0) {
|
|
4563
|
+
const actionableResult = await this.collectActionableSnapshotRefs(
|
|
4564
|
+
selection.tabId,
|
|
4565
|
+
refResult.appliedBindings
|
|
4566
|
+
);
|
|
4567
|
+
actionabilityWarnings.push(...actionableResult.warnings);
|
|
4568
|
+
if (actionableResult.actionableRefs) {
|
|
4569
|
+
actionableRefs = actionableResult.actionableRefs;
|
|
4570
|
+
actionableBindings = refResult.appliedBindings.filter(
|
|
4571
|
+
(binding) => actionableRefs.has(binding.ref)
|
|
4572
|
+
);
|
|
4573
|
+
snapshot = filterAxSnapshotByRefs(snapshot, actionableRefs);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
const persistResult = refMap.size === 0 ? { warnings: [] } : await persistSnapshotRefRegistry(
|
|
4577
|
+
selection.tabId,
|
|
4578
|
+
actionableBindings,
|
|
4579
|
+
debuggerCommand
|
|
4580
|
+
);
|
|
4581
|
+
pruneUnappliedRefsFromSnapshot(snapshot, actionableRefs);
|
|
4022
4582
|
const warnings = [
|
|
4023
4583
|
...selection.warnings ?? [],
|
|
4024
4584
|
...selectorWarnings,
|
|
4025
4585
|
...truncationWarnings,
|
|
4026
|
-
...
|
|
4586
|
+
...clearResult.warnings,
|
|
4587
|
+
...refResult.warnings,
|
|
4588
|
+
...actionabilityWarnings,
|
|
4589
|
+
...persistResult.warnings
|
|
4027
4590
|
];
|
|
4028
4591
|
return {
|
|
4029
4592
|
format: "ax",
|
|
@@ -4044,6 +4607,7 @@ var InspectService = class {
|
|
|
4044
4607
|
const html = await captureHtml(selection.tabId, {
|
|
4045
4608
|
selector: input.selector,
|
|
4046
4609
|
debuggerCommand,
|
|
4610
|
+
executionContextId,
|
|
4047
4611
|
onEvaluationFailed: () => {
|
|
4048
4612
|
const error2 = new InspectError(
|
|
4049
4613
|
"EVALUATION_FAILED",
|
|
@@ -4148,11 +4712,27 @@ var InspectService = class {
|
|
|
4148
4712
|
};
|
|
4149
4713
|
}
|
|
4150
4714
|
async consoleList(input) {
|
|
4151
|
-
this.requireSession(input.sessionId);
|
|
4152
|
-
const
|
|
4715
|
+
const session = this.requireSession(input.sessionId);
|
|
4716
|
+
const tabCount = this.extensionBridge?.getStatus().tabs.length ?? 0;
|
|
4717
|
+
if (!input.targetHint && typeof session.selectedTabId !== "number" && tabCount > 1) {
|
|
4718
|
+
const error = new InspectError(
|
|
4719
|
+
"TAB_NOT_FOUND",
|
|
4720
|
+
"Console inspection requires an explicit target or a session-selected tab.",
|
|
4721
|
+
{ retryable: false }
|
|
4722
|
+
);
|
|
4723
|
+
this.recordError(error);
|
|
4724
|
+
throw error;
|
|
4725
|
+
}
|
|
4726
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4153
4727
|
await this.enableConsole(selection.tabId);
|
|
4728
|
+
const since = this.resolveConsoleSince({
|
|
4729
|
+
session,
|
|
4730
|
+
tabId: selection.tabId,
|
|
4731
|
+
requestedSince: input.since,
|
|
4732
|
+
tabLastActiveAt: selection.tab.last_active_at
|
|
4733
|
+
});
|
|
4154
4734
|
const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
|
|
4155
|
-
const entries = events.map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
|
|
4735
|
+
const entries = events.filter((event) => this.isEventOnOrAfter(event.timestamp, since)).map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
|
|
4156
4736
|
const result = {
|
|
4157
4737
|
entries,
|
|
4158
4738
|
warnings: selection.warnings
|
|
@@ -4162,7 +4742,7 @@ var InspectService = class {
|
|
|
4162
4742
|
}
|
|
4163
4743
|
async networkHar(input) {
|
|
4164
4744
|
this.requireSession(input.sessionId);
|
|
4165
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4745
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4166
4746
|
await this.enableNetwork(selection.tabId);
|
|
4167
4747
|
const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
|
|
4168
4748
|
const har = buildHar(events, selection.tab.title);
|
|
@@ -4189,18 +4769,9 @@ var InspectService = class {
|
|
|
4189
4769
|
}
|
|
4190
4770
|
async evaluate(input) {
|
|
4191
4771
|
this.requireSession(input.sessionId);
|
|
4192
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4772
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4193
4773
|
const expression = input.expression ?? "undefined";
|
|
4194
|
-
await this.
|
|
4195
|
-
const result = await this.debuggerCommand(
|
|
4196
|
-
selection.tabId,
|
|
4197
|
-
"Runtime.evaluate",
|
|
4198
|
-
{
|
|
4199
|
-
expression,
|
|
4200
|
-
returnByValue: true,
|
|
4201
|
-
awaitPromise: true
|
|
4202
|
-
}
|
|
4203
|
-
);
|
|
4774
|
+
const result = await this.evaluateInMainFrame(selection.tabId, expression);
|
|
4204
4775
|
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
4205
4776
|
const output2 = {
|
|
4206
4777
|
exception: result.exceptionDetails,
|
|
@@ -4218,81 +4789,59 @@ var InspectService = class {
|
|
|
4218
4789
|
}
|
|
4219
4790
|
async extractContent(input) {
|
|
4220
4791
|
this.requireSession(input.sessionId);
|
|
4221
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4792
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4793
|
+
const consistency = input.consistency ?? "quiesce";
|
|
4222
4794
|
const debuggerCommand = this.debuggerCommand.bind(this);
|
|
4223
|
-
const
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4795
|
+
const executionContextId = await this.resolveMainFrameExecutionContextId(
|
|
4796
|
+
selection.tabId
|
|
4797
|
+
);
|
|
4798
|
+
const url = selection.tab.url ?? "about:blank";
|
|
4799
|
+
const work = async () => {
|
|
4800
|
+
if (consistency === "quiesce") {
|
|
4801
|
+
await this.waitForDomSettled(selection.tabId, executionContextId);
|
|
4802
|
+
}
|
|
4803
|
+
const html = await captureHtml(selection.tabId, {
|
|
4804
|
+
debuggerCommand,
|
|
4805
|
+
executionContextId,
|
|
4806
|
+
onEvaluationFailed: () => {
|
|
4807
|
+
const error = new InspectError(
|
|
4808
|
+
"EVALUATION_FAILED",
|
|
4809
|
+
"Failed to evaluate HTML snapshot.",
|
|
4810
|
+
{ retryable: false }
|
|
4811
|
+
);
|
|
4812
|
+
this.recordError(error);
|
|
4813
|
+
throw error;
|
|
4814
|
+
}
|
|
4815
|
+
});
|
|
4816
|
+
try {
|
|
4817
|
+
const { article, semanticMainCandidate } = parseExtractContentSource({
|
|
4818
|
+
html,
|
|
4819
|
+
url
|
|
4820
|
+
});
|
|
4821
|
+
return renderExtractContent({
|
|
4822
|
+
format: input.format,
|
|
4823
|
+
article,
|
|
4824
|
+
semanticMainCandidate,
|
|
4825
|
+
includeMetadata: input.includeMetadata,
|
|
4826
|
+
warnings: selection.warnings
|
|
4827
|
+
});
|
|
4828
|
+
} catch (error) {
|
|
4829
|
+
if (error instanceof InspectError) {
|
|
4830
|
+
this.recordError(error);
|
|
4831
|
+
}
|
|
4232
4832
|
throw error;
|
|
4233
4833
|
}
|
|
4234
|
-
});
|
|
4235
|
-
const url = selection.tab.url ?? "about:blank";
|
|
4236
|
-
let article = null;
|
|
4237
|
-
try {
|
|
4238
|
-
const dom = new import_jsdom.JSDOM(html, { url });
|
|
4239
|
-
const reader = new import_readability.Readability(dom.window.document);
|
|
4240
|
-
article = reader.parse();
|
|
4241
|
-
} catch {
|
|
4242
|
-
const err = new InspectError(
|
|
4243
|
-
"EVALUATION_FAILED",
|
|
4244
|
-
"Failed to parse page content.",
|
|
4245
|
-
{ retryable: false }
|
|
4246
|
-
);
|
|
4247
|
-
this.recordError(err);
|
|
4248
|
-
throw err;
|
|
4249
|
-
}
|
|
4250
|
-
if (!article) {
|
|
4251
|
-
const err = new InspectError(
|
|
4252
|
-
"NOT_SUPPORTED",
|
|
4253
|
-
"Readability could not extract content.",
|
|
4254
|
-
{ retryable: false }
|
|
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 ?? "");
|
|
4267
|
-
}
|
|
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
4834
|
};
|
|
4835
|
+
const output = consistency === "quiesce" ? await driveMutex.runExclusive(work) : await work();
|
|
4280
4836
|
this.markInspectConnected(input.sessionId);
|
|
4281
4837
|
return output;
|
|
4282
4838
|
}
|
|
4283
4839
|
async pageState(input) {
|
|
4284
4840
|
this.requireSession(input.sessionId);
|
|
4285
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4286
|
-
await this.
|
|
4287
|
-
const expression = PAGE_STATE_SCRIPT;
|
|
4288
|
-
const result = await this.debuggerCommand(
|
|
4841
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4842
|
+
const result = await this.evaluateInMainFrame(
|
|
4289
4843
|
selection.tabId,
|
|
4290
|
-
|
|
4291
|
-
{
|
|
4292
|
-
expression,
|
|
4293
|
-
returnByValue: true,
|
|
4294
|
-
awaitPromise: true
|
|
4295
|
-
}
|
|
4844
|
+
buildPageStateScript({ includeValues: input.includeValues })
|
|
4296
4845
|
);
|
|
4297
4846
|
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
4298
4847
|
const error = new InspectError(
|
|
@@ -4304,16 +4853,22 @@ var InspectService = class {
|
|
|
4304
4853
|
throw error;
|
|
4305
4854
|
}
|
|
4306
4855
|
const value = result?.result?.value;
|
|
4307
|
-
const
|
|
4856
|
+
const parsed = PageStateSchema.safeParse(value);
|
|
4857
|
+
if (!parsed.success) {
|
|
4858
|
+
const error = new InspectError(
|
|
4859
|
+
"EVALUATION_FAILED",
|
|
4860
|
+
"Captured page state did not match the expected schema.",
|
|
4861
|
+
{ retryable: false }
|
|
4862
|
+
);
|
|
4863
|
+
this.recordError(error);
|
|
4864
|
+
throw error;
|
|
4865
|
+
}
|
|
4308
4866
|
const warnings = [
|
|
4309
|
-
...
|
|
4867
|
+
...parsed.data.warnings ?? [],
|
|
4310
4868
|
...selection.warnings ?? []
|
|
4311
4869
|
];
|
|
4312
4870
|
const output = {
|
|
4313
|
-
|
|
4314
|
-
localStorage: Array.isArray(raw.localStorage) ? raw.localStorage : [],
|
|
4315
|
-
sessionStorage: Array.isArray(raw.sessionStorage) ? raw.sessionStorage : [],
|
|
4316
|
-
cookies: Array.isArray(raw.cookies) ? raw.cookies : [],
|
|
4871
|
+
...parsed.data,
|
|
4317
4872
|
...warnings.length > 0 ? { warnings } : {}
|
|
4318
4873
|
};
|
|
4319
4874
|
this.markInspectConnected(input.sessionId);
|
|
@@ -4321,7 +4876,7 @@ var InspectService = class {
|
|
|
4321
4876
|
}
|
|
4322
4877
|
async performanceMetrics(input) {
|
|
4323
4878
|
this.requireSession(input.sessionId);
|
|
4324
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4879
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4325
4880
|
await this.debuggerCommand(selection.tabId, "Performance.enable", {});
|
|
4326
4881
|
const result = await this.debuggerCommand(
|
|
4327
4882
|
selection.tabId,
|
|
@@ -4338,9 +4893,13 @@ var InspectService = class {
|
|
|
4338
4893
|
}
|
|
4339
4894
|
async screenshot(input) {
|
|
4340
4895
|
this.requireSession(input.sessionId);
|
|
4341
|
-
const selection = await this.resolveTab(input.targetHint);
|
|
4896
|
+
const selection = await this.resolveTab(input.sessionId, input.targetHint);
|
|
4342
4897
|
const format = input.format ?? "png";
|
|
4343
|
-
const
|
|
4898
|
+
const createScreenshotError = (code, message, retryable = false, details) => new InspectError(code, message, {
|
|
4899
|
+
retryable,
|
|
4900
|
+
...details ? { details } : {}
|
|
4901
|
+
});
|
|
4902
|
+
const writeArtifact = async (data) => {
|
|
4344
4903
|
try {
|
|
4345
4904
|
const rootDir = await ensureArtifactRootDir(input.sessionId);
|
|
4346
4905
|
const artifactId = (0, import_crypto3.randomUUID)();
|
|
@@ -4349,7 +4908,7 @@ var InspectService = class {
|
|
|
4349
4908
|
rootDir,
|
|
4350
4909
|
`screenshot-${artifactId}.${extension}`
|
|
4351
4910
|
);
|
|
4352
|
-
await (0, import_promises3.writeFile)(filePath, Buffer.from(
|
|
4911
|
+
await (0, import_promises3.writeFile)(filePath, Buffer.from(data, "base64"));
|
|
4353
4912
|
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
4354
4913
|
const output = {
|
|
4355
4914
|
artifact_id: artifactId,
|
|
@@ -4367,143 +4926,138 @@ var InspectService = class {
|
|
|
4367
4926
|
throw error;
|
|
4368
4927
|
}
|
|
4369
4928
|
};
|
|
4370
|
-
|
|
4929
|
+
const captureViaExtension = async (mode, failureMessage) => {
|
|
4371
4930
|
if (!this.extensionBridge?.request) {
|
|
4372
|
-
|
|
4931
|
+
throw createScreenshotError(
|
|
4373
4932
|
"NOT_SUPPORTED",
|
|
4374
|
-
"
|
|
4933
|
+
"Screenshots require an extension that supports drive.screenshot."
|
|
4375
4934
|
);
|
|
4376
|
-
this.recordError(error);
|
|
4377
|
-
throw error;
|
|
4378
4935
|
}
|
|
4379
4936
|
const response = await this.extensionBridge.request(
|
|
4380
4937
|
"drive.screenshot",
|
|
4381
4938
|
{
|
|
4382
4939
|
tab_id: selection.tabId,
|
|
4383
|
-
mode
|
|
4384
|
-
selector: input.selector,
|
|
4940
|
+
mode,
|
|
4941
|
+
...input.selector ? { selector: input.selector } : {},
|
|
4385
4942
|
format,
|
|
4386
4943
|
...typeof input.quality === "number" ? { quality: input.quality } : {}
|
|
4387
4944
|
},
|
|
4388
4945
|
12e4
|
|
4389
4946
|
);
|
|
4390
4947
|
if (response.status === "error") {
|
|
4391
|
-
const error =
|
|
4948
|
+
const error = createScreenshotError(
|
|
4392
4949
|
response.error?.code ?? "INSPECT_UNAVAILABLE",
|
|
4393
|
-
response.error?.message ??
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
...response.error?.details ? { details: response.error.details } : {}
|
|
4397
|
-
}
|
|
4950
|
+
response.error?.message ?? failureMessage,
|
|
4951
|
+
response.error?.retryable ?? false,
|
|
4952
|
+
response.error?.details
|
|
4398
4953
|
);
|
|
4399
4954
|
this.recordError(error);
|
|
4400
4955
|
throw error;
|
|
4401
4956
|
}
|
|
4402
|
-
const
|
|
4403
|
-
if (!
|
|
4957
|
+
const result = response.result;
|
|
4958
|
+
if (!result?.data_base64 || typeof result.data_base64 !== "string") {
|
|
4959
|
+
const error = createScreenshotError(
|
|
4960
|
+
"INSPECT_UNAVAILABLE",
|
|
4961
|
+
failureMessage
|
|
4962
|
+
);
|
|
4963
|
+
this.recordError(error);
|
|
4964
|
+
throw error;
|
|
4965
|
+
}
|
|
4966
|
+
return await writeArtifact(result.data_base64);
|
|
4967
|
+
};
|
|
4968
|
+
const shouldFallbackFromExtensionScreenshot = (error) => [
|
|
4969
|
+
"NOT_SUPPORTED",
|
|
4970
|
+
"NOT_IMPLEMENTED",
|
|
4971
|
+
"INSPECT_UNAVAILABLE",
|
|
4972
|
+
"PERMISSION_REQUIRED",
|
|
4973
|
+
"RATE_LIMITED"
|
|
4974
|
+
].includes(error.code);
|
|
4975
|
+
const shouldPreserveExtensionScreenshotError = (fallbackError) => [
|
|
4976
|
+
"INSPECT_UNAVAILABLE",
|
|
4977
|
+
"ATTACH_DENIED",
|
|
4978
|
+
"NOT_SUPPORTED",
|
|
4979
|
+
"NOT_IMPLEMENTED"
|
|
4980
|
+
].includes(fallbackError.code);
|
|
4981
|
+
const captureViaDebugger = async () => {
|
|
4982
|
+
await this.debuggerCommand(selection.tabId, "Page.enable", {});
|
|
4983
|
+
let captureParams = {
|
|
4984
|
+
format,
|
|
4985
|
+
fromSurface: true
|
|
4986
|
+
};
|
|
4987
|
+
if (format !== "png" && typeof input.quality === "number") {
|
|
4988
|
+
captureParams = { ...captureParams, quality: input.quality };
|
|
4989
|
+
}
|
|
4990
|
+
if (input.target === "full") {
|
|
4991
|
+
const layout = await this.debuggerCommand(
|
|
4992
|
+
selection.tabId,
|
|
4993
|
+
"Page.getLayoutMetrics",
|
|
4994
|
+
{}
|
|
4995
|
+
);
|
|
4996
|
+
const contentSize = layout?.contentSize;
|
|
4997
|
+
if (contentSize) {
|
|
4998
|
+
captureParams = {
|
|
4999
|
+
...captureParams,
|
|
5000
|
+
clip: {
|
|
5001
|
+
x: 0,
|
|
5002
|
+
y: 0,
|
|
5003
|
+
width: contentSize.width,
|
|
5004
|
+
height: contentSize.height,
|
|
5005
|
+
scale: 1
|
|
5006
|
+
}
|
|
5007
|
+
};
|
|
5008
|
+
} else {
|
|
5009
|
+
captureParams = { ...captureParams, captureBeyondViewport: true };
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
const result = await this.debuggerCommand(
|
|
5013
|
+
selection.tabId,
|
|
5014
|
+
"Page.captureScreenshot",
|
|
5015
|
+
captureParams
|
|
5016
|
+
);
|
|
5017
|
+
const data = result.data;
|
|
5018
|
+
if (!data) {
|
|
4404
5019
|
const error = new InspectError(
|
|
4405
5020
|
"INSPECT_UNAVAILABLE",
|
|
4406
|
-
"Failed to capture
|
|
5021
|
+
"Failed to capture screenshot.",
|
|
5022
|
+
{ retryable: false }
|
|
4407
5023
|
);
|
|
4408
5024
|
this.recordError(error);
|
|
4409
5025
|
throw error;
|
|
4410
5026
|
}
|
|
4411
|
-
return await writeArtifact(
|
|
5027
|
+
return await writeArtifact(data);
|
|
5028
|
+
};
|
|
5029
|
+
if (input.selector) {
|
|
5030
|
+
return await captureViaExtension(
|
|
5031
|
+
"element",
|
|
5032
|
+
"Failed to capture element screenshot."
|
|
5033
|
+
);
|
|
4412
5034
|
}
|
|
4413
|
-
|
|
5035
|
+
let extensionScreenshotError;
|
|
5036
|
+
if (this.extensionBridge?.request) {
|
|
4414
5037
|
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
|
|
5038
|
+
return await captureViaExtension(
|
|
5039
|
+
input.target === "full" ? "full_page" : "viewport",
|
|
5040
|
+
input.target === "full" ? "Failed to capture full page screenshot." : "Failed to capture viewport screenshot."
|
|
4424
5041
|
);
|
|
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);
|
|
5042
|
+
} catch (error) {
|
|
5043
|
+
if (!(error instanceof InspectError)) {
|
|
4435
5044
|
throw error;
|
|
4436
5045
|
}
|
|
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);
|
|
5046
|
+
if (!shouldFallbackFromExtensionScreenshot(error)) {
|
|
4444
5047
|
throw error;
|
|
4445
5048
|
}
|
|
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
|
-
}
|
|
5049
|
+
extensionScreenshotError = error;
|
|
4459
5050
|
}
|
|
4460
5051
|
}
|
|
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 };
|
|
5052
|
+
try {
|
|
5053
|
+
return await captureViaDebugger();
|
|
5054
|
+
} catch (error) {
|
|
5055
|
+
if (extensionScreenshotError && error instanceof InspectError && shouldPreserveExtensionScreenshotError(error)) {
|
|
5056
|
+
this.recordError(extensionScreenshotError);
|
|
5057
|
+
throw extensionScreenshotError;
|
|
4489
5058
|
}
|
|
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
5059
|
throw error;
|
|
4505
5060
|
}
|
|
4506
|
-
return await writeArtifact(data);
|
|
4507
5061
|
}
|
|
4508
5062
|
ensureDebugger() {
|
|
4509
5063
|
if (!this.debugger) {
|
|
@@ -4513,58 +5067,20 @@ var InspectService = class {
|
|
|
4513
5067
|
}
|
|
4514
5068
|
return this.debugger;
|
|
4515
5069
|
}
|
|
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);
|
|
5070
|
+
async resolveTab(sessionId, hint) {
|
|
5071
|
+
try {
|
|
5072
|
+
return selectInspectTab({
|
|
5073
|
+
sessionId,
|
|
5074
|
+
targetHint: hint,
|
|
5075
|
+
registry: this.registry,
|
|
5076
|
+
extensionBridge: this.extensionBridge
|
|
5077
|
+
});
|
|
5078
|
+
} catch (error) {
|
|
5079
|
+
if (error instanceof InspectError) {
|
|
5080
|
+
this.recordError(error);
|
|
5081
|
+
}
|
|
4554
5082
|
throw error;
|
|
4555
5083
|
}
|
|
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
5084
|
}
|
|
4569
5085
|
async enableConsole(tabId) {
|
|
4570
5086
|
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
@@ -4576,6 +5092,186 @@ var InspectService = class {
|
|
|
4576
5092
|
async enableAccessibility(tabId) {
|
|
4577
5093
|
await this.debuggerCommand(tabId, "Accessibility.enable", {});
|
|
4578
5094
|
}
|
|
5095
|
+
async collectActionableSnapshotRefs(tabId, bindings) {
|
|
5096
|
+
try {
|
|
5097
|
+
const result = await this.debuggerCommand(tabId, "Runtime.evaluate", {
|
|
5098
|
+
expression: `(() => {
|
|
5099
|
+
const refs = new Set(${JSON.stringify(
|
|
5100
|
+
bindings.map((binding) => binding.ref)
|
|
5101
|
+
)});
|
|
5102
|
+
const isVisible = (element) => {
|
|
5103
|
+
if (!(element instanceof HTMLElement)) {
|
|
5104
|
+
return false;
|
|
5105
|
+
}
|
|
5106
|
+
const style = window.getComputedStyle(element);
|
|
5107
|
+
if (style.visibility === 'hidden' || style.display === 'none') {
|
|
5108
|
+
return false;
|
|
5109
|
+
}
|
|
5110
|
+
const rect = element.getBoundingClientRect();
|
|
5111
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
5112
|
+
return false;
|
|
5113
|
+
}
|
|
5114
|
+
if (
|
|
5115
|
+
element.offsetWidth === 0 &&
|
|
5116
|
+
element.offsetHeight === 0 &&
|
|
5117
|
+
element.getClientRects().length === 0
|
|
5118
|
+
) {
|
|
5119
|
+
return false;
|
|
5120
|
+
}
|
|
5121
|
+
let current = element;
|
|
5122
|
+
while (current) {
|
|
5123
|
+
const currentStyle = window.getComputedStyle(current);
|
|
5124
|
+
if (currentStyle.display === 'none') {
|
|
5125
|
+
return false;
|
|
5126
|
+
}
|
|
5127
|
+
if (
|
|
5128
|
+
currentStyle.visibility === 'hidden' ||
|
|
5129
|
+
currentStyle.visibility === 'collapse'
|
|
5130
|
+
) {
|
|
5131
|
+
return false;
|
|
5132
|
+
}
|
|
5133
|
+
const opacity = Number.parseFloat(currentStyle.opacity ?? '1');
|
|
5134
|
+
if (Number.isFinite(opacity) && opacity <= 0) {
|
|
5135
|
+
return false;
|
|
5136
|
+
}
|
|
5137
|
+
if (currentStyle.pointerEvents === 'none') {
|
|
5138
|
+
return false;
|
|
5139
|
+
}
|
|
5140
|
+
current = current.parentElement;
|
|
5141
|
+
}
|
|
5142
|
+
return true;
|
|
5143
|
+
};
|
|
5144
|
+
|
|
5145
|
+
return Array.from(document.querySelectorAll('[data-bv-ref]'))
|
|
5146
|
+
.filter((element) => {
|
|
5147
|
+
const ref = element.getAttribute('data-bv-ref');
|
|
5148
|
+
return typeof ref === 'string' && refs.has(ref) && isVisible(element);
|
|
5149
|
+
})
|
|
5150
|
+
.map((element) => element.getAttribute('data-bv-ref'));
|
|
5151
|
+
})() /* browser-bridge:collect-actionable-snapshot-refs */`,
|
|
5152
|
+
returnByValue: true,
|
|
5153
|
+
awaitPromise: true
|
|
5154
|
+
});
|
|
5155
|
+
const rawRefs = result.value;
|
|
5156
|
+
if (!Array.isArray(rawRefs)) {
|
|
5157
|
+
return {
|
|
5158
|
+
warnings: [
|
|
5159
|
+
"Interactive AX snapshot could not verify live actionability."
|
|
5160
|
+
]
|
|
5161
|
+
};
|
|
5162
|
+
}
|
|
5163
|
+
const refs = rawRefs.filter(
|
|
5164
|
+
(ref) => typeof ref === "string"
|
|
5165
|
+
);
|
|
5166
|
+
return {
|
|
5167
|
+
actionableRefs: new Set(refs),
|
|
5168
|
+
warnings: []
|
|
5169
|
+
};
|
|
5170
|
+
} catch {
|
|
5171
|
+
return {
|
|
5172
|
+
warnings: ["Interactive AX snapshot could not prune hidden controls."]
|
|
5173
|
+
};
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
5176
|
+
async waitForDomSettled(tabId, executionContextId) {
|
|
5177
|
+
const result = await this.debuggerCommand(tabId, "Runtime.evaluate", {
|
|
5178
|
+
expression: `(() => {
|
|
5179
|
+
const quietMs = 100;
|
|
5180
|
+
const timeoutMs = 2000;
|
|
5181
|
+
return new Promise((resolve) => {
|
|
5182
|
+
const doc = document;
|
|
5183
|
+
const root = doc.documentElement || doc.body || doc;
|
|
5184
|
+
let finished = false;
|
|
5185
|
+
let quietTimer;
|
|
5186
|
+
let timeoutTimer;
|
|
5187
|
+
let raf1 = 0;
|
|
5188
|
+
let raf2 = 0;
|
|
5189
|
+
const finish = () => {
|
|
5190
|
+
if (finished) {
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
5193
|
+
finished = true;
|
|
5194
|
+
if (observer) {
|
|
5195
|
+
observer.disconnect();
|
|
5196
|
+
}
|
|
5197
|
+
clearTimeout(quietTimer);
|
|
5198
|
+
clearTimeout(timeoutTimer);
|
|
5199
|
+
if (raf1) cancelAnimationFrame(raf1);
|
|
5200
|
+
if (raf2) cancelAnimationFrame(raf2);
|
|
5201
|
+
resolve(true);
|
|
5202
|
+
};
|
|
5203
|
+
const scheduleQuiet = () => {
|
|
5204
|
+
clearTimeout(quietTimer);
|
|
5205
|
+
quietTimer = setTimeout(() => {
|
|
5206
|
+
raf1 = requestAnimationFrame(() => {
|
|
5207
|
+
raf2 = requestAnimationFrame(finish);
|
|
5208
|
+
});
|
|
5209
|
+
}, quietMs);
|
|
5210
|
+
};
|
|
5211
|
+
const observer =
|
|
5212
|
+
typeof MutationObserver === 'function'
|
|
5213
|
+
? new MutationObserver(() => {
|
|
5214
|
+
scheduleQuiet();
|
|
5215
|
+
})
|
|
5216
|
+
: null;
|
|
5217
|
+
if (observer && root) {
|
|
5218
|
+
observer.observe(root, {
|
|
5219
|
+
subtree: true,
|
|
5220
|
+
childList: true,
|
|
5221
|
+
attributes: true,
|
|
5222
|
+
characterData: true,
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5225
|
+
scheduleQuiet();
|
|
5226
|
+
timeoutTimer = setTimeout(finish, timeoutMs);
|
|
5227
|
+
});
|
|
5228
|
+
})()`,
|
|
5229
|
+
returnByValue: true,
|
|
5230
|
+
awaitPromise: true,
|
|
5231
|
+
...typeof executionContextId === "number" ? { contextId: executionContextId } : {}
|
|
5232
|
+
});
|
|
5233
|
+
if (result && typeof result === "object" && "exceptionDetails" in result) {
|
|
5234
|
+
const error = new InspectError(
|
|
5235
|
+
"EVALUATION_FAILED",
|
|
5236
|
+
"Failed while waiting for the page to settle.",
|
|
5237
|
+
{ retryable: false }
|
|
5238
|
+
);
|
|
5239
|
+
this.recordError(error);
|
|
5240
|
+
throw error;
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
async evaluateInMainFrame(tabId, expression) {
|
|
5244
|
+
await this.debuggerCommand(tabId, "Runtime.enable", {});
|
|
5245
|
+
const executionContextId = await this.resolveMainFrameExecutionContextId(tabId);
|
|
5246
|
+
return await this.debuggerCommand(tabId, "Runtime.evaluate", {
|
|
5247
|
+
expression,
|
|
5248
|
+
returnByValue: true,
|
|
5249
|
+
awaitPromise: true,
|
|
5250
|
+
...typeof executionContextId === "number" ? { contextId: executionContextId } : {}
|
|
5251
|
+
});
|
|
5252
|
+
}
|
|
5253
|
+
async resolveMainFrameExecutionContextId(tabId) {
|
|
5254
|
+
const debuggerBridge = this.ensureDebugger();
|
|
5255
|
+
const runOptional = async (method, params) => {
|
|
5256
|
+
const result = await debuggerBridge.command(tabId, method, params);
|
|
5257
|
+
if (!result.ok) {
|
|
5258
|
+
return void 0;
|
|
5259
|
+
}
|
|
5260
|
+
return result.result;
|
|
5261
|
+
};
|
|
5262
|
+
await runOptional("Page.enable", {});
|
|
5263
|
+
const frameTree = await runOptional("Page.getFrameTree", {});
|
|
5264
|
+
const frameId = frameTree?.frameTree?.frame?.id;
|
|
5265
|
+
if (typeof frameId !== "string" || frameId.length === 0) {
|
|
5266
|
+
return void 0;
|
|
5267
|
+
}
|
|
5268
|
+
const isolatedWorld = await runOptional("Page.createIsolatedWorld", {
|
|
5269
|
+
frameId,
|
|
5270
|
+
worldName: "browser_bridge_inspect"
|
|
5271
|
+
});
|
|
5272
|
+
const contextId = isolatedWorld?.executionContextId;
|
|
5273
|
+
return typeof contextId === "number" ? contextId : void 0;
|
|
5274
|
+
}
|
|
4579
5275
|
async debuggerCommand(tabId, method, params, timeoutMs) {
|
|
4580
5276
|
const debuggerBridge = this.ensureDebugger();
|
|
4581
5277
|
const result = await debuggerBridge.command(
|
|
@@ -4654,6 +5350,32 @@ var InspectService = class {
|
|
|
4654
5350
|
throw wrapped;
|
|
4655
5351
|
}
|
|
4656
5352
|
}
|
|
5353
|
+
resolveConsoleSince(options) {
|
|
5354
|
+
if (typeof options.requestedSince === "string") {
|
|
5355
|
+
return options.requestedSince;
|
|
5356
|
+
}
|
|
5357
|
+
const key = `${options.session.id}:${options.tabId}`;
|
|
5358
|
+
const existing = this.consoleSinceBySessionTab.get(key);
|
|
5359
|
+
if (existing) {
|
|
5360
|
+
return existing;
|
|
5361
|
+
}
|
|
5362
|
+
const candidates = [options.session.createdAt.toISOString()];
|
|
5363
|
+
if (typeof options.tabLastActiveAt === "string") {
|
|
5364
|
+
candidates.push(options.tabLastActiveAt);
|
|
5365
|
+
}
|
|
5366
|
+
const baseline = candidates.map((value) => ({ value, time: Date.parse(value) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => b.time - a.time)[0]?.value;
|
|
5367
|
+
const resolved = baseline ?? options.session.createdAt.toISOString();
|
|
5368
|
+
this.consoleSinceBySessionTab.set(key, resolved);
|
|
5369
|
+
return resolved;
|
|
5370
|
+
}
|
|
5371
|
+
isEventOnOrAfter(timestamp, since) {
|
|
5372
|
+
const eventTime = Date.parse(timestamp);
|
|
5373
|
+
const sinceTime = Date.parse(since);
|
|
5374
|
+
if (!Number.isFinite(eventTime) || !Number.isFinite(sinceTime)) {
|
|
5375
|
+
return true;
|
|
5376
|
+
}
|
|
5377
|
+
return eventTime >= sinceTime;
|
|
5378
|
+
}
|
|
4657
5379
|
};
|
|
4658
5380
|
var createInspectService = (options) => new InspectService(options);
|
|
4659
5381
|
|
|
@@ -4667,7 +5389,11 @@ var sendArtifactsError = (res, code, message, details, retryable = false) => {
|
|
|
4667
5389
|
});
|
|
4668
5390
|
};
|
|
4669
5391
|
var registerArtifactsRoutes = (router, options = {}) => {
|
|
4670
|
-
const inspect = options.inspectService ?? (options.registry ? createInspectService({
|
|
5392
|
+
const inspect = options.inspectService ?? (options.registry ? createInspectService({
|
|
5393
|
+
registry: options.registry,
|
|
5394
|
+
extensionBridge: options.extensionBridge,
|
|
5395
|
+
debuggerBridge: options.debuggerBridge
|
|
5396
|
+
}) : void 0);
|
|
4671
5397
|
router.post("/artifacts/screenshot", async (req, res) => {
|
|
4672
5398
|
if (!isRecord2(req.body)) {
|
|
4673
5399
|
sendArtifactsError(
|
|
@@ -4761,6 +5487,7 @@ var readCapability = (capabilities, name) => {
|
|
|
4761
5487
|
const candidate = capabilities[name];
|
|
4762
5488
|
return typeof candidate === "boolean" ? candidate : void 0;
|
|
4763
5489
|
};
|
|
5490
|
+
var hasCapturePermissionErrorReason = (details) => typeof details?.reason === "string" && details.reason === "capture_visible_tab_permission_required";
|
|
4764
5491
|
var hasEndpoint = (endpoint) => Boolean(
|
|
4765
5492
|
endpoint && typeof endpoint.host === "string" && endpoint.host.length > 0 && typeof endpoint.port === "number" && Number.isFinite(endpoint.port)
|
|
4766
5493
|
);
|
|
@@ -4773,9 +5500,7 @@ var toRuntimeEndpoint = (endpoint) => {
|
|
|
4773
5500
|
port: endpoint.port,
|
|
4774
5501
|
base_url: endpoint.baseUrl,
|
|
4775
5502
|
host_source: endpoint.hostSource,
|
|
4776
|
-
port_source: endpoint.portSource
|
|
4777
|
-
metadata_path: endpoint.metadataPath,
|
|
4778
|
-
isolated_mode: endpoint.isolatedMode
|
|
5503
|
+
port_source: endpoint.portSource
|
|
4779
5504
|
};
|
|
4780
5505
|
};
|
|
4781
5506
|
var toRuntimeProcess = (process2) => {
|
|
@@ -4839,23 +5564,6 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4839
5564
|
}
|
|
4840
5565
|
});
|
|
4841
5566
|
}
|
|
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
5567
|
if (extensionConnected && hasEndpoint(coreEndpoint) && hasEndpoint(extensionEndpoint)) {
|
|
4860
5568
|
const matches = coreEndpoint.host === extensionEndpoint.host && coreEndpoint.port === extensionEndpoint.port;
|
|
4861
5569
|
checks.push({
|
|
@@ -4903,7 +5611,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4903
5611
|
checks.push({
|
|
4904
5612
|
name: "inspect.capability",
|
|
4905
5613
|
ok: capabilityNegotiated && inspectEnabled,
|
|
4906
|
-
message: capabilityNegotiated && inspectEnabled ? "Inspect
|
|
5614
|
+
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
5615
|
details: {
|
|
4908
5616
|
required_capabilities: ["debugger.attach", "debugger.command"],
|
|
4909
5617
|
debugger_attach: inspectAttachCapability,
|
|
@@ -4912,7 +5620,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4912
5620
|
});
|
|
4913
5621
|
if (capabilityNegotiated && !inspectEnabled) {
|
|
4914
5622
|
warnings.push(
|
|
4915
|
-
"Inspect commands
|
|
5623
|
+
"Inspect commands are unavailable from the connected extension/runtime. Reload or update the extension, then verify inspect.capability before using inspect.* routes."
|
|
4916
5624
|
);
|
|
4917
5625
|
}
|
|
4918
5626
|
}
|
|
@@ -4945,9 +5653,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4945
5653
|
code: context.driveLastError.code,
|
|
4946
5654
|
retryable: context.driveLastError.retryable,
|
|
4947
5655
|
at: context.driveLastError.at,
|
|
5656
|
+
...context.driveLastError.details ?? {},
|
|
4948
5657
|
...ageMs !== void 0 ? { age_ms: ageMs } : {}
|
|
4949
5658
|
}
|
|
4950
5659
|
});
|
|
5660
|
+
if (hasCapturePermissionErrorReason(context.driveLastError.details)) {
|
|
5661
|
+
warnings.push(
|
|
5662
|
+
"Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
|
|
5663
|
+
);
|
|
5664
|
+
}
|
|
4951
5665
|
}
|
|
4952
5666
|
if (context.inspectLastError) {
|
|
4953
5667
|
const ageMs = getErrorAgeMs(context.inspectLastError.at);
|
|
@@ -4965,9 +5679,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
4965
5679
|
code: context.inspectLastError.code,
|
|
4966
5680
|
retryable: context.inspectLastError.retryable,
|
|
4967
5681
|
at: context.inspectLastError.at,
|
|
5682
|
+
...context.inspectLastError.details ?? {},
|
|
4968
5683
|
...ageMs !== void 0 ? { age_ms: ageMs } : {}
|
|
4969
5684
|
}
|
|
4970
5685
|
});
|
|
5686
|
+
if (hasCapturePermissionErrorReason(context.inspectLastError.details)) {
|
|
5687
|
+
warnings.push(
|
|
5688
|
+
"Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
|
|
5689
|
+
);
|
|
5690
|
+
}
|
|
4971
5691
|
}
|
|
4972
5692
|
if (context.recoveryAttempt) {
|
|
4973
5693
|
checks.push({
|
|
@@ -5001,6 +5721,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
5001
5721
|
} : void 0,
|
|
5002
5722
|
extension: {
|
|
5003
5723
|
connected: extensionConnected,
|
|
5724
|
+
extension_id: context.extension?.extensionId,
|
|
5004
5725
|
version: context.extension?.version,
|
|
5005
5726
|
last_seen_at: context.extension?.lastSeenAt
|
|
5006
5727
|
},
|
|
@@ -5037,6 +5758,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
|
|
|
5037
5758
|
process: toRuntimeProcess(context.runtime.core.process)
|
|
5038
5759
|
} : void 0,
|
|
5039
5760
|
extension: context.runtime.extension ? {
|
|
5761
|
+
extension_id: context.runtime.extension.extensionId,
|
|
5040
5762
|
version: context.runtime.extension.version,
|
|
5041
5763
|
protocol_version: context.runtime.extension.protocolVersion,
|
|
5042
5764
|
capability_negotiated: context.runtime.extension.capabilityNegotiated,
|
|
@@ -5079,17 +5801,84 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5079
5801
|
const extensionStatus = options.extensionBridge?.getStatus();
|
|
5080
5802
|
sendResult(res, {
|
|
5081
5803
|
started_at: PROCESS_STARTED_AT,
|
|
5804
|
+
pid: process.pid,
|
|
5082
5805
|
uptime_ms: Math.floor(process.uptime() * 1e3),
|
|
5083
5806
|
memory: process.memoryUsage(),
|
|
5084
5807
|
sessions: { active: sessionsActive },
|
|
5085
5808
|
extension: {
|
|
5086
5809
|
connected: extensionStatus?.connected ?? false,
|
|
5810
|
+
...extensionStatus?.extensionId ? { extension_id: extensionStatus.extensionId } : {},
|
|
5087
5811
|
...extensionStatus?.lastSeenAt ? { last_seen_at: extensionStatus.lastSeenAt } : {}
|
|
5088
5812
|
}
|
|
5089
5813
|
});
|
|
5090
5814
|
};
|
|
5091
5815
|
router.post("/health/check", handleHealthCheck);
|
|
5092
5816
|
router.post("/health_check", handleHealthCheck);
|
|
5817
|
+
router.post("/diagnostics/enable_inspect", async (req, res) => {
|
|
5818
|
+
const body = req.body ?? {};
|
|
5819
|
+
if (!isRecord2(body)) {
|
|
5820
|
+
sendError(res, 400, {
|
|
5821
|
+
code: "INVALID_ARGUMENT",
|
|
5822
|
+
message: "Request body must be an object.",
|
|
5823
|
+
retryable: false
|
|
5824
|
+
});
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
const extensionIdRaw = body.extension_id;
|
|
5828
|
+
if (extensionIdRaw !== void 0 && typeof extensionIdRaw !== "string") {
|
|
5829
|
+
sendError(res, 400, {
|
|
5830
|
+
code: "INVALID_ARGUMENT",
|
|
5831
|
+
message: "extension_id must be a string when provided.",
|
|
5832
|
+
retryable: false,
|
|
5833
|
+
details: { field: "extension_id" }
|
|
5834
|
+
});
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
if (!options.extensionBridge) {
|
|
5838
|
+
sendError(res, 503, {
|
|
5839
|
+
code: "EXTENSION_DISCONNECTED",
|
|
5840
|
+
message: "Extension bridge is unavailable.",
|
|
5841
|
+
retryable: true
|
|
5842
|
+
});
|
|
5843
|
+
return;
|
|
5844
|
+
}
|
|
5845
|
+
try {
|
|
5846
|
+
const envelope2 = await options.extensionBridge.request("drive.set_debugger_capability", {
|
|
5847
|
+
enabled: true,
|
|
5848
|
+
...extensionIdRaw ? { extension_id: extensionIdRaw } : {}
|
|
5849
|
+
});
|
|
5850
|
+
if (envelope2.status === "error") {
|
|
5851
|
+
const error = envelope2.error ?? {
|
|
5852
|
+
code: "INTERNAL",
|
|
5853
|
+
message: "Extension request failed.",
|
|
5854
|
+
retryable: false
|
|
5855
|
+
};
|
|
5856
|
+
sendError(res, 409, {
|
|
5857
|
+
code: error.code,
|
|
5858
|
+
message: error.message,
|
|
5859
|
+
retryable: error.retryable,
|
|
5860
|
+
details: error.details
|
|
5861
|
+
});
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
sendResult(res, envelope2.result ?? { ok: true, enabled: true });
|
|
5865
|
+
} catch (error) {
|
|
5866
|
+
if (error instanceof ExtensionBridgeError) {
|
|
5867
|
+
sendError(res, 409, {
|
|
5868
|
+
code: error.code,
|
|
5869
|
+
message: error.message,
|
|
5870
|
+
retryable: error.retryable,
|
|
5871
|
+
details: error.details
|
|
5872
|
+
});
|
|
5873
|
+
return;
|
|
5874
|
+
}
|
|
5875
|
+
sendError(res, 500, {
|
|
5876
|
+
code: "INTERNAL",
|
|
5877
|
+
message: "Unexpected inspect enablement error.",
|
|
5878
|
+
retryable: false
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
});
|
|
5093
5882
|
router.post("/diagnostics/doctor", (req, res) => {
|
|
5094
5883
|
const body = req.body ?? {};
|
|
5095
5884
|
if (!isRecord2(body)) {
|
|
@@ -5121,9 +5910,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5121
5910
|
port: parsedDoctorInput.data.caller.endpoint.port,
|
|
5122
5911
|
baseUrl: parsedDoctorInput.data.caller.endpoint.base_url,
|
|
5123
5912
|
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
|
|
5913
|
+
portSource: parsedDoctorInput.data.caller.endpoint.port_source
|
|
5127
5914
|
} : void 0,
|
|
5128
5915
|
process: parsedDoctorInput.data.caller.process ? {
|
|
5129
5916
|
component: parsedDoctorInput.data.caller.process.component,
|
|
@@ -5140,9 +5927,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5140
5927
|
port: options.coreRuntime.port,
|
|
5141
5928
|
baseUrl: `http://${options.coreRuntime.host}:${options.coreRuntime.port}`,
|
|
5142
5929
|
hostSource: options.coreRuntime.hostSource,
|
|
5143
|
-
portSource: options.coreRuntime.portSource
|
|
5144
|
-
metadataPath: options.coreRuntime.metadataPath,
|
|
5145
|
-
isolatedMode: options.coreRuntime.isolatedMode
|
|
5930
|
+
portSource: options.coreRuntime.portSource
|
|
5146
5931
|
} : void 0,
|
|
5147
5932
|
process: {
|
|
5148
5933
|
component: "core",
|
|
@@ -5185,11 +5970,13 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5185
5970
|
const status = options.extensionBridge.getStatus();
|
|
5186
5971
|
context.extension = {
|
|
5187
5972
|
connected: status.connected,
|
|
5973
|
+
extensionId: status.extensionId,
|
|
5188
5974
|
version: status.version,
|
|
5189
5975
|
lastSeenAt: status.lastSeenAt
|
|
5190
5976
|
};
|
|
5191
5977
|
if (status.connected) {
|
|
5192
5978
|
context.runtime.extension = {
|
|
5979
|
+
extensionId: status.extensionId,
|
|
5193
5980
|
version: status.version,
|
|
5194
5981
|
protocolVersion: status.protocolVersion,
|
|
5195
5982
|
capabilityNegotiated: status.capabilityNegotiated,
|
|
@@ -5226,7 +6013,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5226
6013
|
code: lastError.error.code,
|
|
5227
6014
|
message: lastError.error.message,
|
|
5228
6015
|
retryable: lastError.error.retryable,
|
|
5229
|
-
at: lastError.at
|
|
6016
|
+
at: lastError.at,
|
|
6017
|
+
details: lastError.error.details
|
|
5230
6018
|
};
|
|
5231
6019
|
}
|
|
5232
6020
|
}
|
|
@@ -5237,7 +6025,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5237
6025
|
code: lastError.error.code,
|
|
5238
6026
|
message: lastError.error.message,
|
|
5239
6027
|
retryable: lastError.error.retryable,
|
|
5240
|
-
at: lastError.at
|
|
6028
|
+
at: lastError.at,
|
|
6029
|
+
details: lastError.error.details
|
|
5241
6030
|
};
|
|
5242
6031
|
}
|
|
5243
6032
|
}
|
|
@@ -5267,6 +6056,11 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
|
|
|
5267
6056
|
};
|
|
5268
6057
|
|
|
5269
6058
|
// packages/core/src/routes/drive.ts
|
|
6059
|
+
var SUPPORTED_NAVIGATE_WAIT_MODES = [
|
|
6060
|
+
"none",
|
|
6061
|
+
"domcontentloaded",
|
|
6062
|
+
"networkidle"
|
|
6063
|
+
];
|
|
5270
6064
|
var parseBody = (schema, body) => {
|
|
5271
6065
|
const result = schema.safeParse(body);
|
|
5272
6066
|
if (result.success) {
|
|
@@ -5283,6 +6077,24 @@ var parseBody = (schema, body) => {
|
|
|
5283
6077
|
};
|
|
5284
6078
|
var makeNavigateHandler = (schema, drive, registry) => {
|
|
5285
6079
|
return (req, res) => {
|
|
6080
|
+
if (isRecord2(req.body) && req.body.wait !== void 0) {
|
|
6081
|
+
const waitMode = req.body.wait;
|
|
6082
|
+
if (typeof waitMode !== "string" || !SUPPORTED_NAVIGATE_WAIT_MODES.includes(
|
|
6083
|
+
waitMode
|
|
6084
|
+
)) {
|
|
6085
|
+
sendError(res, errorStatus("INVALID_ARGUMENT"), {
|
|
6086
|
+
code: "INVALID_ARGUMENT",
|
|
6087
|
+
message: `Unsupported wait mode: ${String(waitMode)}.`,
|
|
6088
|
+
retryable: false,
|
|
6089
|
+
details: {
|
|
6090
|
+
field: "wait",
|
|
6091
|
+
supported_wait_modes: SUPPORTED_NAVIGATE_WAIT_MODES,
|
|
6092
|
+
mapped_wait_mode: "domcontentloaded"
|
|
6093
|
+
}
|
|
6094
|
+
});
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
5286
6098
|
const parsed = parseBody(schema, req.body ?? {});
|
|
5287
6099
|
if (parsed.error) {
|
|
5288
6100
|
sendError(res, errorStatus("INVALID_ARGUMENT"), {
|
|
@@ -5539,27 +6351,6 @@ var parseBody2 = (schema, body) => {
|
|
|
5539
6351
|
}
|
|
5540
6352
|
};
|
|
5541
6353
|
};
|
|
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
6354
|
var makeHandler2 = (schema, handler) => async (req, res) => {
|
|
5564
6355
|
const parsed = parseBody2(schema, req.body ?? {});
|
|
5565
6356
|
if (parsed.error) {
|
|
@@ -5605,7 +6396,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5605
6396
|
compact: body.compact,
|
|
5606
6397
|
maxNodes: body.max_nodes,
|
|
5607
6398
|
selector: body.selector,
|
|
5608
|
-
targetHint:
|
|
6399
|
+
targetHint: resolveInspectTargetHint({
|
|
6400
|
+
sessionId: body.session_id,
|
|
6401
|
+
target: body.target,
|
|
6402
|
+
registry: options.registry,
|
|
6403
|
+
extensionBridge: options.extensionBridge
|
|
6404
|
+
})
|
|
5609
6405
|
});
|
|
5610
6406
|
})
|
|
5611
6407
|
);
|
|
@@ -5618,7 +6414,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5618
6414
|
router.post(
|
|
5619
6415
|
"/inspect/find",
|
|
5620
6416
|
makeHandler2(InspectFindInputSchema, async (body) => {
|
|
5621
|
-
const targetHint =
|
|
6417
|
+
const targetHint = resolveInspectTargetHint({
|
|
6418
|
+
sessionId: body.session_id,
|
|
6419
|
+
target: body.target,
|
|
6420
|
+
registry: options.registry,
|
|
6421
|
+
extensionBridge: options.extensionBridge
|
|
6422
|
+
});
|
|
5622
6423
|
if (body.kind === "role") {
|
|
5623
6424
|
return await inspect.find({
|
|
5624
6425
|
sessionId: body.session_id,
|
|
@@ -5650,8 +6451,14 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5650
6451
|
return await inspect.extractContent({
|
|
5651
6452
|
sessionId: body.session_id,
|
|
5652
6453
|
format: body.format,
|
|
6454
|
+
consistency: body.consistency,
|
|
5653
6455
|
includeMetadata: body.include_metadata,
|
|
5654
|
-
targetHint:
|
|
6456
|
+
targetHint: resolveInspectTargetHint({
|
|
6457
|
+
sessionId: body.session_id,
|
|
6458
|
+
target: body.target,
|
|
6459
|
+
registry: options.registry,
|
|
6460
|
+
extensionBridge: options.extensionBridge
|
|
6461
|
+
})
|
|
5655
6462
|
});
|
|
5656
6463
|
})
|
|
5657
6464
|
);
|
|
@@ -5660,7 +6467,13 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5660
6467
|
makeHandler2(InspectPageStateInputSchema, async (body) => {
|
|
5661
6468
|
return await inspect.pageState({
|
|
5662
6469
|
sessionId: body.session_id,
|
|
5663
|
-
|
|
6470
|
+
includeValues: body.include_values,
|
|
6471
|
+
targetHint: resolveInspectTargetHint({
|
|
6472
|
+
sessionId: body.session_id,
|
|
6473
|
+
target: body.target,
|
|
6474
|
+
registry: options.registry,
|
|
6475
|
+
extensionBridge: options.extensionBridge
|
|
6476
|
+
})
|
|
5664
6477
|
});
|
|
5665
6478
|
})
|
|
5666
6479
|
);
|
|
@@ -5669,7 +6482,13 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5669
6482
|
makeHandler2(InspectConsoleListInputSchema, async (body) => {
|
|
5670
6483
|
return await inspect.consoleList({
|
|
5671
6484
|
sessionId: body.session_id,
|
|
5672
|
-
|
|
6485
|
+
since: body.since,
|
|
6486
|
+
targetHint: resolveInspectTargetHint({
|
|
6487
|
+
sessionId: body.session_id,
|
|
6488
|
+
target: body.target,
|
|
6489
|
+
registry: options.registry,
|
|
6490
|
+
extensionBridge: options.extensionBridge
|
|
6491
|
+
})
|
|
5673
6492
|
});
|
|
5674
6493
|
})
|
|
5675
6494
|
);
|
|
@@ -5678,7 +6497,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5678
6497
|
makeHandler2(InspectNetworkHarInputSchema, async (body) => {
|
|
5679
6498
|
return await inspect.networkHar({
|
|
5680
6499
|
sessionId: body.session_id,
|
|
5681
|
-
targetHint:
|
|
6500
|
+
targetHint: resolveInspectTargetHint({
|
|
6501
|
+
sessionId: body.session_id,
|
|
6502
|
+
target: body.target,
|
|
6503
|
+
registry: options.registry,
|
|
6504
|
+
extensionBridge: options.extensionBridge
|
|
6505
|
+
})
|
|
5682
6506
|
});
|
|
5683
6507
|
})
|
|
5684
6508
|
);
|
|
@@ -5688,7 +6512,12 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5688
6512
|
return await inspect.evaluate({
|
|
5689
6513
|
sessionId: body.session_id,
|
|
5690
6514
|
expression: body.expression,
|
|
5691
|
-
targetHint:
|
|
6515
|
+
targetHint: resolveInspectTargetHint({
|
|
6516
|
+
sessionId: body.session_id,
|
|
6517
|
+
target: body.target,
|
|
6518
|
+
registry: options.registry,
|
|
6519
|
+
extensionBridge: options.extensionBridge
|
|
6520
|
+
})
|
|
5692
6521
|
});
|
|
5693
6522
|
})
|
|
5694
6523
|
);
|
|
@@ -5697,12 +6526,168 @@ var registerInspectRoutes = (router, options) => {
|
|
|
5697
6526
|
makeHandler2(InspectPerformanceMetricsInputSchema, async (body) => {
|
|
5698
6527
|
return await inspect.performanceMetrics({
|
|
5699
6528
|
sessionId: body.session_id,
|
|
5700
|
-
targetHint:
|
|
6529
|
+
targetHint: resolveInspectTargetHint({
|
|
6530
|
+
sessionId: body.session_id,
|
|
6531
|
+
target: body.target,
|
|
6532
|
+
registry: options.registry,
|
|
6533
|
+
extensionBridge: options.extensionBridge
|
|
6534
|
+
})
|
|
5701
6535
|
});
|
|
5702
6536
|
})
|
|
5703
6537
|
);
|
|
5704
6538
|
};
|
|
5705
6539
|
|
|
6540
|
+
// packages/core/src/routes/permissions.ts
|
|
6541
|
+
var EXTENSION_READY_WAIT_MS2 = 1500;
|
|
6542
|
+
var parseBody3 = (schema, body) => {
|
|
6543
|
+
const result = schema.safeParse(body);
|
|
6544
|
+
if (result.success) {
|
|
6545
|
+
return { data: result.data };
|
|
6546
|
+
}
|
|
6547
|
+
const issue = result.error.issues[0];
|
|
6548
|
+
const details = issue && issue.path.length > 0 ? { field: issue.path.map((part) => part.toString()).join(".") } : void 0;
|
|
6549
|
+
return {
|
|
6550
|
+
error: {
|
|
6551
|
+
message: issue?.message ?? "Request body is invalid.",
|
|
6552
|
+
...details ? { details } : {}
|
|
6553
|
+
}
|
|
6554
|
+
};
|
|
6555
|
+
};
|
|
6556
|
+
var sendBridgeUnavailable = (res) => {
|
|
6557
|
+
sendError(res, errorStatus("EXTENSION_DISCONNECTED"), {
|
|
6558
|
+
code: "EXTENSION_DISCONNECTED",
|
|
6559
|
+
message: "Extension bridge is unavailable.",
|
|
6560
|
+
retryable: true
|
|
6561
|
+
});
|
|
6562
|
+
};
|
|
6563
|
+
var waitForExtensionReady = async (extensionBridge) => {
|
|
6564
|
+
if (extensionBridge.isConnected()) {
|
|
6565
|
+
return true;
|
|
6566
|
+
}
|
|
6567
|
+
const waitForReady = extensionBridge.waitForReady;
|
|
6568
|
+
if (typeof waitForReady !== "function") {
|
|
6569
|
+
return extensionBridge.isConnected();
|
|
6570
|
+
}
|
|
6571
|
+
return await waitForReady.call(extensionBridge, EXTENSION_READY_WAIT_MS2);
|
|
6572
|
+
};
|
|
6573
|
+
var makePermissionsHandler = (schema, action, options) => {
|
|
6574
|
+
return (req, res) => {
|
|
6575
|
+
const parsed = parseBody3(schema, req.body ?? {});
|
|
6576
|
+
if (parsed.error) {
|
|
6577
|
+
sendError(res, errorStatus("INVALID_ARGUMENT"), {
|
|
6578
|
+
code: "INVALID_ARGUMENT",
|
|
6579
|
+
message: parsed.error.message,
|
|
6580
|
+
retryable: false,
|
|
6581
|
+
...parsed.error.details ? { details: parsed.error.details } : {}
|
|
6582
|
+
});
|
|
6583
|
+
return;
|
|
6584
|
+
}
|
|
6585
|
+
if (!options.extensionBridge) {
|
|
6586
|
+
sendBridgeUnavailable(res);
|
|
6587
|
+
return;
|
|
6588
|
+
}
|
|
6589
|
+
const extensionBridge = options.extensionBridge;
|
|
6590
|
+
void waitForExtensionReady(extensionBridge).then(async (ready) => {
|
|
6591
|
+
if (!ready) {
|
|
6592
|
+
throw new ExtensionBridgeError(
|
|
6593
|
+
"EXTENSION_DISCONNECTED",
|
|
6594
|
+
"Extension is not connected.",
|
|
6595
|
+
true
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6598
|
+
return await extensionBridge.request(
|
|
6599
|
+
action,
|
|
6600
|
+
parsed.data
|
|
6601
|
+
);
|
|
6602
|
+
}).then((envelope2) => {
|
|
6603
|
+
if (envelope2.status === "error") {
|
|
6604
|
+
const error = envelope2.error ?? {
|
|
6605
|
+
code: "INTERNAL",
|
|
6606
|
+
message: "Extension request failed.",
|
|
6607
|
+
retryable: false
|
|
6608
|
+
};
|
|
6609
|
+
sendError(res, errorStatus(error.code), {
|
|
6610
|
+
code: error.code,
|
|
6611
|
+
message: error.message,
|
|
6612
|
+
retryable: error.retryable,
|
|
6613
|
+
details: error.details
|
|
6614
|
+
});
|
|
6615
|
+
return;
|
|
6616
|
+
}
|
|
6617
|
+
sendResult(res, envelope2.result ?? {});
|
|
6618
|
+
}).catch((error) => {
|
|
6619
|
+
if (error instanceof ExtensionBridgeError) {
|
|
6620
|
+
sendError(res, errorStatus(error.code), {
|
|
6621
|
+
code: error.code,
|
|
6622
|
+
message: error.message,
|
|
6623
|
+
retryable: error.retryable,
|
|
6624
|
+
details: error.details
|
|
6625
|
+
});
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
sendError(res, errorStatus("INTERNAL"), {
|
|
6629
|
+
code: "INTERNAL",
|
|
6630
|
+
message: "Unexpected permissions route error.",
|
|
6631
|
+
retryable: false,
|
|
6632
|
+
details: {
|
|
6633
|
+
hint: error instanceof Error ? error.message : "Unknown error.",
|
|
6634
|
+
action
|
|
6635
|
+
}
|
|
6636
|
+
});
|
|
6637
|
+
});
|
|
6638
|
+
};
|
|
6639
|
+
};
|
|
6640
|
+
var registerPermissionsRoutes = (router, options) => {
|
|
6641
|
+
router.post(
|
|
6642
|
+
"/permissions/list",
|
|
6643
|
+
makePermissionsHandler(
|
|
6644
|
+
PermissionsListInputSchema,
|
|
6645
|
+
"permissions.list",
|
|
6646
|
+
options
|
|
6647
|
+
)
|
|
6648
|
+
);
|
|
6649
|
+
router.post(
|
|
6650
|
+
"/permissions/get_mode",
|
|
6651
|
+
makePermissionsHandler(
|
|
6652
|
+
PermissionsGetModeInputSchema,
|
|
6653
|
+
"permissions.get_mode",
|
|
6654
|
+
options
|
|
6655
|
+
)
|
|
6656
|
+
);
|
|
6657
|
+
router.post(
|
|
6658
|
+
"/permissions/list_pending_requests",
|
|
6659
|
+
makePermissionsHandler(
|
|
6660
|
+
PermissionsListPendingRequestsInputSchema,
|
|
6661
|
+
"permissions.list_pending_requests",
|
|
6662
|
+
options
|
|
6663
|
+
)
|
|
6664
|
+
);
|
|
6665
|
+
router.post(
|
|
6666
|
+
"/permissions/request_allow_site",
|
|
6667
|
+
makePermissionsHandler(
|
|
6668
|
+
PermissionsRequestAllowSiteInputSchema,
|
|
6669
|
+
"permissions.request_allow_site",
|
|
6670
|
+
options
|
|
6671
|
+
)
|
|
6672
|
+
);
|
|
6673
|
+
router.post(
|
|
6674
|
+
"/permissions/request_revoke_site",
|
|
6675
|
+
makePermissionsHandler(
|
|
6676
|
+
PermissionsRequestRevokeSiteInputSchema,
|
|
6677
|
+
"permissions.request_revoke_site",
|
|
6678
|
+
options
|
|
6679
|
+
)
|
|
6680
|
+
);
|
|
6681
|
+
router.post(
|
|
6682
|
+
"/permissions/request_set_mode",
|
|
6683
|
+
makePermissionsHandler(
|
|
6684
|
+
PermissionsRequestSetModeInputSchema,
|
|
6685
|
+
"permissions.request_set_mode",
|
|
6686
|
+
options
|
|
6687
|
+
)
|
|
6688
|
+
);
|
|
6689
|
+
};
|
|
6690
|
+
|
|
5706
6691
|
// packages/core/src/recovery.ts
|
|
5707
6692
|
var RecoveryTracker = class {
|
|
5708
6693
|
constructor() {
|
|
@@ -6118,6 +7103,9 @@ var createCoreServer = (options = {}) => {
|
|
|
6118
7103
|
})
|
|
6119
7104
|
);
|
|
6120
7105
|
registerDriveRoutes(app, { drive, registry });
|
|
7106
|
+
registerPermissionsRoutes(app, {
|
|
7107
|
+
extensionBridge
|
|
7108
|
+
});
|
|
6121
7109
|
registerInspectRoutes(app, {
|
|
6122
7110
|
registry,
|
|
6123
7111
|
extensionBridge,
|
|
@@ -6126,6 +7114,7 @@ var createCoreServer = (options = {}) => {
|
|
|
6126
7114
|
registerArtifactsRoutes(app, {
|
|
6127
7115
|
registry,
|
|
6128
7116
|
extensionBridge,
|
|
7117
|
+
debuggerBridge,
|
|
6129
7118
|
inspectService: inspect
|
|
6130
7119
|
});
|
|
6131
7120
|
registerDiagnosticsRoutes(app, {
|
|
@@ -6148,27 +7137,12 @@ var createCoreServer = (options = {}) => {
|
|
|
6148
7137
|
recoveryTracker
|
|
6149
7138
|
};
|
|
6150
7139
|
};
|
|
6151
|
-
var
|
|
6152
|
-
var buildRuntimeMetadataForPersist = (runtime, resolvedPort) => ({
|
|
7140
|
+
var buildRuntimeMetadataForPersist = (runtime) => ({
|
|
6153
7141
|
...runtime.metadata ?? {},
|
|
6154
|
-
|
|
6155
|
-
port: resolvedPort,
|
|
6156
|
-
git_root: runtime.gitRoot ?? runtime.metadata?.git_root,
|
|
6157
|
-
worktree_id: runtime.worktreeId ?? runtime.metadata?.worktree_id,
|
|
7142
|
+
extension_id: runtime.metadata?.extension_id,
|
|
6158
7143
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6159
7144
|
});
|
|
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
|
-
};
|
|
7145
|
+
var resolveProbePortsForRuntime = (runtime) => [runtime.port];
|
|
6172
7146
|
var resolveSessionTtlMs = () => {
|
|
6173
7147
|
const env = process.env.BROWSER_BRIDGE_SESSION_TTL_MS || process.env.BROWSER_VISION_SESSION_TTL_MS;
|
|
6174
7148
|
if (env) {
|
|
@@ -6249,11 +7223,7 @@ var startCoreServer = async (options = {}) => {
|
|
|
6249
7223
|
host: runtime.host,
|
|
6250
7224
|
port: runtime.port,
|
|
6251
7225
|
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
|
|
7226
|
+
port_source: runtime.portSource
|
|
6257
7227
|
});
|
|
6258
7228
|
const { app, registry, extensionBridge } = createCoreServer({
|
|
6259
7229
|
registry: options.registry,
|
|
@@ -6283,10 +7253,9 @@ var startCoreServer = async (options = {}) => {
|
|
|
6283
7253
|
duration_ms: Number(startupDurationMs.toFixed(3))
|
|
6284
7254
|
});
|
|
6285
7255
|
try {
|
|
6286
|
-
writeRuntimeMetadata(
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
);
|
|
7256
|
+
writeRuntimeMetadata(buildRuntimeMetadataForPersist(runtime), {
|
|
7257
|
+
metadataPath: runtime.metadataPath
|
|
7258
|
+
});
|
|
6290
7259
|
logger.info("core.runtime_metadata.persisted", {
|
|
6291
7260
|
metadata_path: runtime.metadataPath,
|
|
6292
7261
|
host: runtime.host,
|
|
@@ -6495,9 +7464,7 @@ startCoreServer({ ${startOptions.join(
|
|
|
6495
7464
|
port: readiness.runtime.port,
|
|
6496
7465
|
base_url: readiness.baseUrl,
|
|
6497
7466
|
host_source: readiness.runtime.hostSource,
|
|
6498
|
-
port_source: readiness.runtime.portSource
|
|
6499
|
-
metadata_path: readiness.runtime.metadataPath,
|
|
6500
|
-
isolated_mode: readiness.runtime.isolatedMode
|
|
7467
|
+
port_source: readiness.runtime.portSource
|
|
6501
7468
|
},
|
|
6502
7469
|
process: {
|
|
6503
7470
|
component: "mcp",
|
|
@@ -6543,6 +7510,74 @@ var toInternalErrorEnvelope = (error) => ({
|
|
|
6543
7510
|
});
|
|
6544
7511
|
var envelope = (schema) => successEnvelopeSchema(schema);
|
|
6545
7512
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7513
|
+
var readSessionId2 = (args) => {
|
|
7514
|
+
if (!isRecord3(args)) {
|
|
7515
|
+
return void 0;
|
|
7516
|
+
}
|
|
7517
|
+
const sessionId = args.session_id;
|
|
7518
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
|
|
7519
|
+
};
|
|
7520
|
+
var withPermissionsSource = (source) => (args) => isRecord3(args) ? {
|
|
7521
|
+
...args,
|
|
7522
|
+
source
|
|
7523
|
+
} : args;
|
|
7524
|
+
var supportsSessionMigration = (corePath) => corePath.startsWith("/drive/") || corePath.startsWith("/inspect/") || corePath.startsWith("/artifacts/") || corePath === "/diagnostics/doctor";
|
|
7525
|
+
var isSessionNotFoundEnvelope = (envelopeResult) => {
|
|
7526
|
+
if (envelopeResult.ok) {
|
|
7527
|
+
return false;
|
|
7528
|
+
}
|
|
7529
|
+
const details = envelopeResult.error.details;
|
|
7530
|
+
return envelopeResult.error.code === "NOT_FOUND" && isRecord3(details) && details.reason === "session_not_found";
|
|
7531
|
+
};
|
|
7532
|
+
var addSessionRecoveryHint = (envelopeResult) => {
|
|
7533
|
+
if (envelopeResult.ok) {
|
|
7534
|
+
return envelopeResult;
|
|
7535
|
+
}
|
|
7536
|
+
return {
|
|
7537
|
+
ok: false,
|
|
7538
|
+
error: {
|
|
7539
|
+
...envelopeResult.error,
|
|
7540
|
+
details: {
|
|
7541
|
+
...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
|
|
7542
|
+
recover_action: "session.create"
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
};
|
|
7546
|
+
};
|
|
7547
|
+
var addSessionMigrationNotice = (envelopeResult, staleSessionId, replacementSessionId) => {
|
|
7548
|
+
if (envelopeResult.ok) {
|
|
7549
|
+
if (!isRecord3(envelopeResult.result)) {
|
|
7550
|
+
return envelopeResult;
|
|
7551
|
+
}
|
|
7552
|
+
const warning = `Session ${staleSessionId} became stale after runtime switch; retried with ${replacementSessionId}.`;
|
|
7553
|
+
const existingWarnings = Array.isArray(envelopeResult.result.warnings) ? envelopeResult.result.warnings.filter(
|
|
7554
|
+
(item) => typeof item === "string"
|
|
7555
|
+
) : [];
|
|
7556
|
+
return {
|
|
7557
|
+
ok: true,
|
|
7558
|
+
result: {
|
|
7559
|
+
...envelopeResult.result,
|
|
7560
|
+
warnings: existingWarnings.includes(warning) ? existingWarnings : [...existingWarnings, warning],
|
|
7561
|
+
session_migration: {
|
|
7562
|
+
stale_session_id: staleSessionId,
|
|
7563
|
+
replacement_session_id: replacementSessionId
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
};
|
|
7567
|
+
}
|
|
7568
|
+
return {
|
|
7569
|
+
ok: false,
|
|
7570
|
+
error: {
|
|
7571
|
+
...envelopeResult.error,
|
|
7572
|
+
details: {
|
|
7573
|
+
...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
|
|
7574
|
+
stale_session_id: staleSessionId,
|
|
7575
|
+
replacement_session_id: replacementSessionId,
|
|
7576
|
+
recover_action: "session.recover"
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
};
|
|
7580
|
+
};
|
|
6546
7581
|
var addDeprecatedAliasWarning = (envelopeResult, deprecationAlias) => {
|
|
6547
7582
|
if (!deprecationAlias || !envelopeResult.ok || typeof envelopeResult.result !== "object" || !envelopeResult.result) {
|
|
6548
7583
|
return envelopeResult;
|
|
@@ -6599,6 +7634,69 @@ var TOOL_DEFINITIONS = [
|
|
|
6599
7634
|
corePath: "/session/close"
|
|
6600
7635
|
}
|
|
6601
7636
|
},
|
|
7637
|
+
{
|
|
7638
|
+
name: "permissions.list",
|
|
7639
|
+
config: {
|
|
7640
|
+
title: "Permissions List",
|
|
7641
|
+
description: "List allowlisted Browser Bridge sites.",
|
|
7642
|
+
inputSchema: PermissionsListInputSchema,
|
|
7643
|
+
outputSchema: envelope(PermissionsListOutputSchema),
|
|
7644
|
+
corePath: "/permissions/list"
|
|
7645
|
+
}
|
|
7646
|
+
},
|
|
7647
|
+
{
|
|
7648
|
+
name: "permissions.get_mode",
|
|
7649
|
+
config: {
|
|
7650
|
+
title: "Permissions Get Mode",
|
|
7651
|
+
description: "Read the current Browser Bridge permissions mode.",
|
|
7652
|
+
inputSchema: PermissionsGetModeInputSchema,
|
|
7653
|
+
outputSchema: envelope(PermissionsGetModeOutputSchema),
|
|
7654
|
+
corePath: "/permissions/get_mode"
|
|
7655
|
+
}
|
|
7656
|
+
},
|
|
7657
|
+
{
|
|
7658
|
+
name: "permissions.list_pending_requests",
|
|
7659
|
+
config: {
|
|
7660
|
+
title: "Permissions List Pending Requests",
|
|
7661
|
+
description: "List pending external Browser Bridge permission-change requests.",
|
|
7662
|
+
inputSchema: PermissionsListPendingRequestsInputSchema,
|
|
7663
|
+
outputSchema: envelope(PermissionsListPendingRequestsOutputSchema),
|
|
7664
|
+
corePath: "/permissions/list_pending_requests"
|
|
7665
|
+
}
|
|
7666
|
+
},
|
|
7667
|
+
{
|
|
7668
|
+
name: "permissions.request_allow_site",
|
|
7669
|
+
config: {
|
|
7670
|
+
title: "Permissions Request Allow Site",
|
|
7671
|
+
description: "Request allowlisting a site. A human must approve the change in Chrome before it applies.",
|
|
7672
|
+
inputSchema: PermissionsRequestAllowSiteInputSchema,
|
|
7673
|
+
outputSchema: envelope(PermissionsRequestOutputSchema),
|
|
7674
|
+
corePath: "/permissions/request_allow_site",
|
|
7675
|
+
transformInput: withPermissionsSource("mcp")
|
|
7676
|
+
}
|
|
7677
|
+
},
|
|
7678
|
+
{
|
|
7679
|
+
name: "permissions.request_revoke_site",
|
|
7680
|
+
config: {
|
|
7681
|
+
title: "Permissions Request Revoke Site",
|
|
7682
|
+
description: "Request revoking a site from the allowlist. A human must approve the change in Chrome before it applies.",
|
|
7683
|
+
inputSchema: PermissionsRequestRevokeSiteInputSchema,
|
|
7684
|
+
outputSchema: envelope(PermissionsRequestOutputSchema),
|
|
7685
|
+
corePath: "/permissions/request_revoke_site",
|
|
7686
|
+
transformInput: withPermissionsSource("mcp")
|
|
7687
|
+
}
|
|
7688
|
+
},
|
|
7689
|
+
{
|
|
7690
|
+
name: "permissions.request_set_mode",
|
|
7691
|
+
config: {
|
|
7692
|
+
title: "Permissions Request Set Mode",
|
|
7693
|
+
description: "Request changing Browser Bridge permission mode. A human must approve the change in Chrome before it applies.",
|
|
7694
|
+
inputSchema: PermissionsRequestSetModeInputSchema,
|
|
7695
|
+
outputSchema: envelope(PermissionsRequestOutputSchema),
|
|
7696
|
+
corePath: "/permissions/request_set_mode",
|
|
7697
|
+
transformInput: withPermissionsSource("mcp")
|
|
7698
|
+
}
|
|
7699
|
+
},
|
|
6602
7700
|
{
|
|
6603
7701
|
name: "drive.navigate",
|
|
6604
7702
|
config: {
|
|
@@ -6953,10 +8051,26 @@ var createToolHandler = (clientProvider, corePath, deprecationAlias, transformIn
|
|
|
6953
8051
|
void _extra;
|
|
6954
8052
|
try {
|
|
6955
8053
|
const client = typeof clientProvider === "function" ? await clientProvider() : clientProvider;
|
|
6956
|
-
const
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
)
|
|
8054
|
+
const transformedArgs = transformInput ? transformInput(args) : args;
|
|
8055
|
+
const staleSessionId = readSessionId2(transformedArgs);
|
|
8056
|
+
let envelopeResult = await client.post(corePath, transformedArgs);
|
|
8057
|
+
if (staleSessionId && supportsSessionMigration(corePath) && isSessionNotFoundEnvelope(envelopeResult) && isRecord3(transformedArgs)) {
|
|
8058
|
+
const createdSession = await client.post("/session/create", {});
|
|
8059
|
+
if (createdSession.ok && isRecord3(createdSession.result) && typeof createdSession.result.session_id === "string" && createdSession.result.session_id.length > 0) {
|
|
8060
|
+
const replacementSessionId = createdSession.result.session_id;
|
|
8061
|
+
const retryArgs = {
|
|
8062
|
+
...transformedArgs,
|
|
8063
|
+
session_id: replacementSessionId
|
|
8064
|
+
};
|
|
8065
|
+
envelopeResult = addSessionMigrationNotice(
|
|
8066
|
+
await client.post(corePath, retryArgs),
|
|
8067
|
+
staleSessionId,
|
|
8068
|
+
replacementSessionId
|
|
8069
|
+
);
|
|
8070
|
+
} else {
|
|
8071
|
+
envelopeResult = addSessionRecoveryHint(envelopeResult);
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
6960
8074
|
return toToolResult(
|
|
6961
8075
|
addDeprecatedAliasWarning(envelopeResult, deprecationAlias)
|
|
6962
8076
|
);
|
|
@@ -7097,7 +8211,7 @@ var DEFAULT_SERVER_NAME = "browser-bridge";
|
|
|
7097
8211
|
var DEFAULT_SERVER_VERSION = "0.0.0";
|
|
7098
8212
|
var ENV_MCP_EAGER = "BROWSER_BRIDGE_MCP_EAGER";
|
|
7099
8213
|
var ENV_LEGACY_MCP_EAGER = "BROWSER_VISION_MCP_EAGER";
|
|
7100
|
-
var
|
|
8214
|
+
var parseBoolean = (value) => {
|
|
7101
8215
|
if (value === void 0) {
|
|
7102
8216
|
return void 0;
|
|
7103
8217
|
}
|
|
@@ -7114,7 +8228,7 @@ var resolveEagerMode = (explicit) => {
|
|
|
7114
8228
|
if (typeof explicit === "boolean") {
|
|
7115
8229
|
return explicit;
|
|
7116
8230
|
}
|
|
7117
|
-
const envValue =
|
|
8231
|
+
const envValue = parseBoolean(process.env[ENV_MCP_EAGER]) ?? parseBoolean(process.env[ENV_LEGACY_MCP_EAGER]);
|
|
7118
8232
|
return envValue ?? false;
|
|
7119
8233
|
};
|
|
7120
8234
|
var toCoreClientOptions = (options, logger) => ({
|