browser-pilot-cli 0.3.0-rc.6 → 0.4.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/README.md +90 -27
- package/dist/cli.js +320 -438
- package/dist/daemon.js +1021 -719
- package/docs/architecture/browser-pilot-platform-spec.md +50 -19
- package/docs/integration/stdio-bridge.md +40 -16
- package/docs/plans/profile-context-routing.md +34 -9
- package/docs/plans/universal-agent-integration.md +18 -5
- package/docs/plans/v0.3.0-stabilization.md +24 -7
- package/docs/releases/v0.2.0.md +53 -0
- package/docs/releases/v0.2.1.md +13 -0
- package/docs/releases/v0.2.2.md +18 -0
- package/docs/releases/v0.3.0.md +68 -0
- package/docs/releases/v0.4.0.md +62 -0
- package/package.json +7 -7
package/dist/daemon.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import http2 from "http";
|
|
3
3
|
import { unlinkSync as unlinkSync2, chmodSync as chmodSync2 } from "fs";
|
|
4
4
|
import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join6 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/paths.ts
|
|
8
8
|
import { createHash } from "crypto";
|
|
@@ -143,37 +143,37 @@ function isSafeTimestamp(value) {
|
|
|
143
143
|
}
|
|
144
144
|
function validExecutable(value) {
|
|
145
145
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
146
|
-
const
|
|
147
|
-
return typeof
|
|
146
|
+
const record2 = value;
|
|
147
|
+
return typeof record2.version === "string" && record2.version.length > 0 && record2.version.length <= 128 && typeof record2.path === "string" && record2.path.length > 0 && record2.path.length <= 4096 && isAbsolute2(record2.path) && typeof record2.identity === "string" && /^executable:[a-f0-9]{64}$/.test(record2.identity);
|
|
148
148
|
}
|
|
149
149
|
function validProtocolVersion(value) {
|
|
150
150
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
151
|
-
const
|
|
152
|
-
return Number.isSafeInteger(
|
|
151
|
+
const record2 = value;
|
|
152
|
+
return Number.isSafeInteger(record2.major) && Number(record2.major) >= 0 && Number.isSafeInteger(record2.minor) && Number(record2.minor) >= 0;
|
|
153
153
|
}
|
|
154
154
|
function validProtocolRange(value) {
|
|
155
155
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
156
|
-
const
|
|
157
|
-
if (!validProtocolVersion(
|
|
158
|
-
return
|
|
156
|
+
const record2 = value;
|
|
157
|
+
if (!validProtocolVersion(record2.min) || !validProtocolVersion(record2.max)) return false;
|
|
158
|
+
return record2.min.major < record2.max.major || record2.min.major === record2.max.major && record2.min.minor <= record2.max.minor;
|
|
159
159
|
}
|
|
160
160
|
function validLocator(value, paths) {
|
|
161
161
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
162
|
-
const
|
|
163
|
-
const common = (
|
|
162
|
+
const record2 = value;
|
|
163
|
+
const common = (record2.schemaVersion === 1 || record2.schemaVersion === 2) && isSafePid(record2.pid) && record2.endpoint === paths.endpoint && record2.transport === paths.transport && isSafeTimestamp(record2.startedAt) && typeof record2.brokerProcessIdentity === "string" && record2.brokerProcessIdentity.length > 0 && record2.brokerProcessIdentity.length <= 256;
|
|
164
164
|
if (!common) return false;
|
|
165
|
-
if (
|
|
166
|
-
return typeof
|
|
165
|
+
if (record2.schemaVersion === 1) return true;
|
|
166
|
+
return typeof record2.serviceVersion === "string" && record2.serviceVersion.length > 0 && record2.serviceVersion.length <= 128 && validExecutable(record2.executable) && validProtocolRange(record2.protocol) && (record2.previousExecutable === void 0 || validExecutable(record2.previousExecutable));
|
|
167
167
|
}
|
|
168
168
|
function validVersionHistoryEntry(value) {
|
|
169
169
|
if (!validExecutable(value)) return false;
|
|
170
|
-
const
|
|
171
|
-
return isSafeTimestamp(
|
|
170
|
+
const record2 = value;
|
|
171
|
+
return isSafeTimestamp(record2.firstSeenAt) && isSafeTimestamp(record2.lastSeenAt) && Number(record2.firstSeenAt) <= Number(record2.lastSeenAt);
|
|
172
172
|
}
|
|
173
173
|
function validVersionHistory(value) {
|
|
174
174
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
175
|
-
const
|
|
176
|
-
return
|
|
175
|
+
const record2 = value;
|
|
176
|
+
return record2.schemaVersion === 1 && validVersionHistoryEntry(record2.current) && (record2.previous === void 0 || validVersionHistoryEntry(record2.previous));
|
|
177
177
|
}
|
|
178
178
|
function createExecutableMetadataSync(version, path) {
|
|
179
179
|
if (!version || version.length > 128) throw new Error("Invalid Browser Pilot executable version");
|
|
@@ -244,13 +244,13 @@ function atomicWriteJson(path, value) {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
-
function writeBrokerStartingSync(
|
|
248
|
-
if (!isSafePid(
|
|
247
|
+
function writeBrokerStartingSync(record2, paths = BROWSER_PILOT_PATHS) {
|
|
248
|
+
if (!isSafePid(record2.pid) || !isSafeTimestamp(record2.startedAt) || typeof record2.brokerProcessIdentity !== "string" || record2.brokerProcessIdentity.length === 0 || record2.brokerProcessIdentity.length > 256) {
|
|
249
249
|
throw new Error("Invalid starting Broker record");
|
|
250
250
|
}
|
|
251
251
|
atomicWriteJson(paths.pidFile, {
|
|
252
252
|
schemaVersion: 2,
|
|
253
|
-
...
|
|
253
|
+
...record2,
|
|
254
254
|
state: "starting"
|
|
255
255
|
});
|
|
256
256
|
}
|
|
@@ -364,7 +364,8 @@ function asBrowserPilotError(error) {
|
|
|
364
364
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
365
365
|
{ major: 1, minor: 0 },
|
|
366
366
|
{ major: 1, minor: 1 },
|
|
367
|
-
{ major: 1, minor: 2 }
|
|
367
|
+
{ major: 1, minor: 2 },
|
|
368
|
+
{ major: 1, minor: 3 }
|
|
368
369
|
];
|
|
369
370
|
var CAPABILITIES = [
|
|
370
371
|
"browser.discovery",
|
|
@@ -381,6 +382,12 @@ var CAPABILITIES = [
|
|
|
381
382
|
"developer.eval"
|
|
382
383
|
];
|
|
383
384
|
var DEFAULT_CAPABILITIES = CAPABILITIES;
|
|
385
|
+
var PROFILE_IDENTITY_ERROR_CODES = [
|
|
386
|
+
"profile_path_unavailable",
|
|
387
|
+
"profile_path_unverified",
|
|
388
|
+
"local_state_unavailable",
|
|
389
|
+
"profile_metadata_missing"
|
|
390
|
+
];
|
|
384
391
|
var BROWSER_OPERATIONS = [
|
|
385
392
|
"tabs.list",
|
|
386
393
|
"page.observe",
|
|
@@ -963,6 +970,12 @@ var profileRepresentativeTabSchema = objectSchema({
|
|
|
963
970
|
var profileContextSchema = objectSchema({
|
|
964
971
|
profileContextId: opaqueIdSchema,
|
|
965
972
|
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
973
|
+
identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
|
|
974
|
+
profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
975
|
+
accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
976
|
+
accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
977
|
+
profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
978
|
+
identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
|
|
966
979
|
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
967
980
|
tabCount: integerSchema({ minimum: 0 }),
|
|
968
981
|
eligibleTabCount: integerSchema({ minimum: 0 }),
|
|
@@ -991,6 +1004,7 @@ var TOOL_DEFINITIONS = [
|
|
|
991
1004
|
id: opaqueIdSchema,
|
|
992
1005
|
product: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
993
1006
|
channel: stringSchema({ maxLength: 128 }),
|
|
1007
|
+
userDataRoot: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
994
1008
|
profile: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
995
1009
|
processState: stringSchema({ enum: ["running", "not_running", "unknown"] }),
|
|
996
1010
|
remoteDebuggingState: stringSchema({ enum: ["enabled", "disabled", "stale"] }),
|
|
@@ -1051,6 +1065,25 @@ var TOOL_DEFINITIONS = [
|
|
|
1051
1065
|
sensitivity: { input: [], output: ["browser_data"] },
|
|
1052
1066
|
artifactKinds: []
|
|
1053
1067
|
}),
|
|
1068
|
+
tool({
|
|
1069
|
+
name: "browser.profiles.identify",
|
|
1070
|
+
title: "Identify browser Profiles",
|
|
1071
|
+
description: "Explicitly identify live Chrome Profiles using temporary visible chrome://version targets.",
|
|
1072
|
+
context: "workspace",
|
|
1073
|
+
inputSchema: objectSchema({
|
|
1074
|
+
profileContextId: opaqueIdSchema,
|
|
1075
|
+
refresh: booleanSchema()
|
|
1076
|
+
}),
|
|
1077
|
+
outputSchema: resultSchema("workspace", {
|
|
1078
|
+
profiles: arraySchema(profileContextSchema, { maxItems: 128 })
|
|
1079
|
+
}, ["profiles"]),
|
|
1080
|
+
requiredCapabilities: ["browser.control"],
|
|
1081
|
+
mutating: true,
|
|
1082
|
+
idempotency: "non_idempotent",
|
|
1083
|
+
cancellation: "best_effort",
|
|
1084
|
+
sensitivity: { input: [], output: ["browser_data"] },
|
|
1085
|
+
artifactKinds: []
|
|
1086
|
+
}),
|
|
1054
1087
|
tool({
|
|
1055
1088
|
name: "browser.profiles.select",
|
|
1056
1089
|
title: "Select browser Profile",
|
|
@@ -1060,6 +1093,12 @@ var TOOL_DEFINITIONS = [
|
|
|
1060
1093
|
outputSchema: resultSchema("workspace", {
|
|
1061
1094
|
profileContextId: opaqueIdSchema,
|
|
1062
1095
|
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
1096
|
+
identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
|
|
1097
|
+
profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1098
|
+
accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1099
|
+
accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1100
|
+
profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1101
|
+
identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
|
|
1063
1102
|
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data")
|
|
1064
1103
|
}, ["profileContextId", "label"]),
|
|
1065
1104
|
requiredCapabilities: ["browser.control"],
|
|
@@ -1488,10 +1527,11 @@ var TOOL_DEFINITIONS = [
|
|
|
1488
1527
|
title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
1489
1528
|
url: boundedUrl,
|
|
1490
1529
|
active: booleanSchema(),
|
|
1530
|
+
selected: booleanSchema(),
|
|
1491
1531
|
origin: stringSchema({ enum: ["managed", "managed_popup", "user_tab"] }),
|
|
1492
1532
|
managedTabSetId: opaqueIdSchema,
|
|
1493
1533
|
controlState: stringSchema({ enum: ["available", "controlled", "busy"] })
|
|
1494
|
-
}, ["targetId", "title", "url", "
|
|
1534
|
+
}, ["targetId", "title", "url", "origin", "controlState"]))
|
|
1495
1535
|
}, ["targets"]),
|
|
1496
1536
|
requiredCapabilities: ["browser.control"],
|
|
1497
1537
|
mutating: false,
|
|
@@ -1503,7 +1543,7 @@ var TOOL_DEFINITIONS = [
|
|
|
1503
1543
|
tool({
|
|
1504
1544
|
name: "browser.tabs.switch",
|
|
1505
1545
|
title: "Switch tab",
|
|
1506
|
-
description: "
|
|
1546
|
+
description: "Select a managed or user tab as the controlled target for the current Lease.",
|
|
1507
1547
|
context: "workspace",
|
|
1508
1548
|
inputSchema: objectSchema({ targetId: opaqueIdSchema }, ["targetId"]),
|
|
1509
1549
|
outputSchema: resultSchema("target"),
|
|
@@ -1835,8 +1875,16 @@ var PROTOCOL_1_2_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1835
1875
|
"browser.profiles.list",
|
|
1836
1876
|
"browser.profiles.select"
|
|
1837
1877
|
]);
|
|
1878
|
+
var PROTOCOL_1_3_TOOLS = /* @__PURE__ */ new Set([
|
|
1879
|
+
"browser.profiles.identify"
|
|
1880
|
+
]);
|
|
1881
|
+
function minimumProtocolMinorForTool(name) {
|
|
1882
|
+
if (PROTOCOL_1_3_TOOLS.has(name)) return 3;
|
|
1883
|
+
if (PROTOCOL_1_2_TOOLS.has(name)) return 2;
|
|
1884
|
+
return 0;
|
|
1885
|
+
}
|
|
1838
1886
|
function isToolAvailableInProtocol(name, protocol) {
|
|
1839
|
-
return protocol.major === 1 &&
|
|
1887
|
+
return protocol.major === 1 && protocol.minor >= minimumProtocolMinorForTool(name);
|
|
1840
1888
|
}
|
|
1841
1889
|
function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
|
|
1842
1890
|
for (const value of schema["x-browser-pilot-sensitivity"] ?? []) result.add(value);
|
|
@@ -1935,9 +1983,9 @@ var MAX_DECLARED_TRANSPORT_BYTES = 1024 * 1024 * 1024;
|
|
|
1935
1983
|
function isRecord2(value) {
|
|
1936
1984
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1937
1985
|
}
|
|
1938
|
-
function assertOnlyKeys(
|
|
1986
|
+
function assertOnlyKeys(record2, allowed, field = "params") {
|
|
1939
1987
|
const allowedSet = new Set(allowed);
|
|
1940
|
-
const unknown = Object.keys(
|
|
1988
|
+
const unknown = Object.keys(record2).filter((key) => !allowedSet.has(key));
|
|
1941
1989
|
if (unknown.length > 0) {
|
|
1942
1990
|
throw invalidArgument(`Unknown ${field} field: ${unknown[0]}`, `${field}.${unknown[0]}`, field === "message" ? -32600 : -32602);
|
|
1943
1991
|
}
|
|
@@ -1950,8 +1998,8 @@ function isJsonValue(value) {
|
|
|
1950
1998
|
if (isRecord2(value)) return Object.values(value).every(isJsonValue);
|
|
1951
1999
|
return false;
|
|
1952
2000
|
}
|
|
1953
|
-
function requireString(
|
|
1954
|
-
const value =
|
|
2001
|
+
function requireString(record2, key, options = {}) {
|
|
2002
|
+
const value = record2[key];
|
|
1955
2003
|
const maxLength = options.maxLength ?? MAX_ID_LENGTH;
|
|
1956
2004
|
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
1957
2005
|
throw invalidArgument(`${key} must be a non-empty string no longer than ${maxLength} characters`, key);
|
|
@@ -1961,8 +2009,8 @@ function requireString(record, key, options = {}) {
|
|
|
1961
2009
|
}
|
|
1962
2010
|
return value;
|
|
1963
2011
|
}
|
|
1964
|
-
function requireInteger(
|
|
1965
|
-
const value =
|
|
2012
|
+
function requireInteger(record2, key) {
|
|
2013
|
+
const value = record2[key];
|
|
1966
2014
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1967
2015
|
throw invalidArgument(`${key} must be a non-negative safe integer`, key);
|
|
1968
2016
|
}
|
|
@@ -2085,15 +2133,15 @@ function validateOptionalEmptyParams(value) {
|
|
|
2085
2133
|
assertOnlyKeys(value, []);
|
|
2086
2134
|
return {};
|
|
2087
2135
|
}
|
|
2088
|
-
function validateTtlMs(
|
|
2089
|
-
if (
|
|
2090
|
-
if (!Number.isSafeInteger(
|
|
2136
|
+
function validateTtlMs(record2) {
|
|
2137
|
+
if (record2.ttlMs === void 0) return void 0;
|
|
2138
|
+
if (!Number.isSafeInteger(record2.ttlMs)) {
|
|
2091
2139
|
throw invalidArgument("ttlMs must be a safe integer", "ttlMs");
|
|
2092
2140
|
}
|
|
2093
|
-
return
|
|
2141
|
+
return record2.ttlMs;
|
|
2094
2142
|
}
|
|
2095
|
-
function validateOpaqueId(
|
|
2096
|
-
return requireString(
|
|
2143
|
+
function validateOpaqueId(record2, key, pattern) {
|
|
2144
|
+
return requireString(record2, key, { pattern, maxLength: 128 });
|
|
2097
2145
|
}
|
|
2098
2146
|
function validateToolsListParams(value) {
|
|
2099
2147
|
return validateOptionalEmptyParams(value);
|
|
@@ -2262,22 +2310,22 @@ function canonical(value) {
|
|
|
2262
2310
|
function cloneJson(value) {
|
|
2263
2311
|
return structuredClone(value);
|
|
2264
2312
|
}
|
|
2265
|
-
function descriptor(
|
|
2313
|
+
function descriptor(record2) {
|
|
2266
2314
|
return {
|
|
2267
|
-
id:
|
|
2268
|
-
...
|
|
2269
|
-
...
|
|
2270
|
-
...
|
|
2271
|
-
...
|
|
2272
|
-
idempotencyKey:
|
|
2273
|
-
method:
|
|
2274
|
-
mutating:
|
|
2275
|
-
status:
|
|
2276
|
-
acceptedAt:
|
|
2277
|
-
deadlineAt:
|
|
2278
|
-
...
|
|
2279
|
-
...
|
|
2280
|
-
...
|
|
2315
|
+
id: record2.id,
|
|
2316
|
+
...record2.workspaceId ? { workspaceId: record2.workspaceId } : {},
|
|
2317
|
+
...record2.leaseId ? { leaseId: record2.leaseId } : {},
|
|
2318
|
+
...record2.targetId ? { targetId: record2.targetId } : {},
|
|
2319
|
+
...record2.browserConnectionGeneration !== void 0 ? { browserConnectionGeneration: record2.browserConnectionGeneration } : {},
|
|
2320
|
+
idempotencyKey: record2.idempotencyKey,
|
|
2321
|
+
method: record2.method,
|
|
2322
|
+
mutating: record2.mutating,
|
|
2323
|
+
status: record2.status,
|
|
2324
|
+
acceptedAt: record2.acceptedAt,
|
|
2325
|
+
deadlineAt: record2.deadlineAt,
|
|
2326
|
+
...record2.dispatchedAt !== void 0 ? { dispatchedAt: record2.dispatchedAt } : {},
|
|
2327
|
+
...record2.completedAt !== void 0 ? { completedAt: record2.completedAt } : {},
|
|
2328
|
+
...record2.cancellationRequested ? { cancellationRequested: true } : {}
|
|
2281
2329
|
};
|
|
2282
2330
|
}
|
|
2283
2331
|
var MemoryCommandRuntime = class {
|
|
@@ -2344,13 +2392,13 @@ var MemoryCommandRuntime = class {
|
|
|
2344
2392
|
throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
|
|
2345
2393
|
}
|
|
2346
2394
|
const acceptedAt = this.now();
|
|
2347
|
-
let
|
|
2395
|
+
let resolve3;
|
|
2348
2396
|
let reject;
|
|
2349
2397
|
const completion = new Promise((resolvePromise, rejectPromise) => {
|
|
2350
|
-
|
|
2398
|
+
resolve3 = resolvePromise;
|
|
2351
2399
|
reject = rejectPromise;
|
|
2352
2400
|
});
|
|
2353
|
-
const
|
|
2401
|
+
const record2 = {
|
|
2354
2402
|
id,
|
|
2355
2403
|
...input.workspaceId ? { workspaceId: input.workspaceId } : {},
|
|
2356
2404
|
...input.leaseId ? { leaseId: input.leaseId } : {},
|
|
@@ -2368,17 +2416,17 @@ var MemoryCommandRuntime = class {
|
|
|
2368
2416
|
scopeKey,
|
|
2369
2417
|
cancellation: input.cancellation,
|
|
2370
2418
|
abortController: new AbortController(),
|
|
2371
|
-
resolve:
|
|
2419
|
+
resolve: resolve3,
|
|
2372
2420
|
reject,
|
|
2373
2421
|
completion,
|
|
2374
2422
|
settled: false
|
|
2375
2423
|
};
|
|
2376
|
-
this.records.set(id,
|
|
2424
|
+
this.records.set(id, record2);
|
|
2377
2425
|
this.commandsByIdempotency.set(idempotencyIndex, id);
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
this.emitStatus(
|
|
2381
|
-
this.enqueue(input.actorKey, () => this.execute(
|
|
2426
|
+
record2.deadlineTimer = setTimeout(() => this.expire(record2), deadlineMs);
|
|
2427
|
+
record2.deadlineTimer.unref();
|
|
2428
|
+
this.emitStatus(record2);
|
|
2429
|
+
this.enqueue(input.actorKey, () => this.execute(record2, execute));
|
|
2382
2430
|
return completion;
|
|
2383
2431
|
}
|
|
2384
2432
|
subscribe(listener) {
|
|
@@ -2391,49 +2439,49 @@ var MemoryCommandRuntime = class {
|
|
|
2391
2439
|
}
|
|
2392
2440
|
cancel(context) {
|
|
2393
2441
|
this.sweep();
|
|
2394
|
-
const
|
|
2395
|
-
if (TERMINAL_STATUSES.has(
|
|
2396
|
-
if (
|
|
2442
|
+
const record2 = this.requireOwned(context);
|
|
2443
|
+
if (TERMINAL_STATUSES.has(record2.status)) return this.outcome(record2);
|
|
2444
|
+
if (record2.cancellation === "not_applicable") {
|
|
2397
2445
|
throw invalidArgument("Command does not support cancellation", "commandId");
|
|
2398
2446
|
}
|
|
2399
|
-
if (
|
|
2400
|
-
|
|
2447
|
+
if (record2.status === "accepted") {
|
|
2448
|
+
record2.abortController.abort();
|
|
2401
2449
|
this.finishWithError(
|
|
2402
|
-
|
|
2450
|
+
record2,
|
|
2403
2451
|
"cancelled",
|
|
2404
2452
|
new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch")
|
|
2405
2453
|
);
|
|
2406
|
-
return this.outcome(
|
|
2454
|
+
return this.outcome(record2);
|
|
2407
2455
|
}
|
|
2408
|
-
|
|
2409
|
-
this.emitStatus(
|
|
2410
|
-
if (
|
|
2411
|
-
return this.outcome(
|
|
2456
|
+
record2.cancellationRequested = true;
|
|
2457
|
+
this.emitStatus(record2);
|
|
2458
|
+
if (record2.cancellation === "best_effort") record2.abortController.abort();
|
|
2459
|
+
return this.outcome(record2);
|
|
2412
2460
|
}
|
|
2413
2461
|
releaseLease(leaseId) {
|
|
2414
|
-
for (const
|
|
2415
|
-
if (
|
|
2462
|
+
for (const record2 of this.records.values()) {
|
|
2463
|
+
if (record2.leaseId === leaseId) this.cancelForCleanup(record2);
|
|
2416
2464
|
}
|
|
2417
2465
|
}
|
|
2418
2466
|
releaseConnection(connectionId) {
|
|
2419
|
-
for (const
|
|
2420
|
-
if (
|
|
2467
|
+
for (const record2 of this.records.values()) {
|
|
2468
|
+
if (record2.connectionId === connectionId) this.cancelForCleanup(record2);
|
|
2421
2469
|
}
|
|
2422
2470
|
}
|
|
2423
2471
|
releaseWorkspace(workspaceId) {
|
|
2424
|
-
for (const
|
|
2425
|
-
if (
|
|
2472
|
+
for (const record2 of this.records.values()) {
|
|
2473
|
+
if (record2.workspaceId === workspaceId) this.cancelForCleanup(record2);
|
|
2426
2474
|
}
|
|
2427
2475
|
}
|
|
2428
2476
|
sweep() {
|
|
2429
2477
|
const now = this.now();
|
|
2430
2478
|
let removed = 0;
|
|
2431
|
-
for (const
|
|
2432
|
-
if (!TERMINAL_STATUSES.has(
|
|
2479
|
+
for (const record2 of this.records.values()) {
|
|
2480
|
+
if (!TERMINAL_STATUSES.has(record2.status) && record2.deadlineAt <= now) this.expire(record2);
|
|
2433
2481
|
}
|
|
2434
|
-
for (const [id,
|
|
2435
|
-
if (TERMINAL_STATUSES.has(
|
|
2436
|
-
this.deleteRecord(id,
|
|
2482
|
+
for (const [id, record2] of this.records) {
|
|
2483
|
+
if (TERMINAL_STATUSES.has(record2.status) && (record2.completedAt ?? record2.deadlineAt) + this.terminalTtlMs <= now) {
|
|
2484
|
+
this.deleteRecord(id, record2);
|
|
2437
2485
|
removed += 1;
|
|
2438
2486
|
}
|
|
2439
2487
|
}
|
|
@@ -2442,140 +2490,140 @@ var MemoryCommandRuntime = class {
|
|
|
2442
2490
|
size() {
|
|
2443
2491
|
return this.records.size;
|
|
2444
2492
|
}
|
|
2445
|
-
async execute(
|
|
2446
|
-
if (
|
|
2447
|
-
if (
|
|
2448
|
-
this.expire(
|
|
2493
|
+
async execute(record2, execute) {
|
|
2494
|
+
if (record2.status !== "accepted") return;
|
|
2495
|
+
if (record2.deadlineAt <= this.now()) {
|
|
2496
|
+
this.expire(record2);
|
|
2449
2497
|
return;
|
|
2450
2498
|
}
|
|
2451
2499
|
try {
|
|
2452
2500
|
const markDispatched = () => {
|
|
2453
|
-
if (
|
|
2501
|
+
if (record2.status === "cancelled") {
|
|
2454
2502
|
throw new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch", {
|
|
2455
|
-
context: { commandId:
|
|
2503
|
+
context: { commandId: record2.id }
|
|
2456
2504
|
});
|
|
2457
2505
|
}
|
|
2458
|
-
if (
|
|
2506
|
+
if (record2.status === "expired") {
|
|
2459
2507
|
throw new BrowserPilotError("command_expired", "Command expired before dispatch", {
|
|
2460
|
-
context: { commandId:
|
|
2508
|
+
context: { commandId: record2.id }
|
|
2461
2509
|
});
|
|
2462
2510
|
}
|
|
2463
|
-
if (
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
this.emitStatus(
|
|
2511
|
+
if (record2.status !== "accepted") return;
|
|
2512
|
+
record2.status = "dispatched";
|
|
2513
|
+
record2.dispatchedAt = this.now();
|
|
2514
|
+
this.emitStatus(record2);
|
|
2467
2515
|
};
|
|
2468
|
-
const result = await execute({ signal:
|
|
2469
|
-
if (TERMINAL_STATUSES.has(
|
|
2516
|
+
const result = await execute({ signal: record2.abortController.signal, markDispatched });
|
|
2517
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2470
2518
|
markDispatched();
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
this.clearDeadline(
|
|
2475
|
-
this.emitStatus(
|
|
2476
|
-
this.resolve(
|
|
2519
|
+
record2.status = "completed";
|
|
2520
|
+
record2.completedAt = this.now();
|
|
2521
|
+
record2.result = cloneJson(result);
|
|
2522
|
+
this.clearDeadline(record2);
|
|
2523
|
+
this.emitStatus(record2);
|
|
2524
|
+
this.resolve(record2);
|
|
2477
2525
|
} catch (error) {
|
|
2478
|
-
if (TERMINAL_STATUSES.has(
|
|
2479
|
-
const stable = this.withCommandContext(error,
|
|
2480
|
-
if (
|
|
2526
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2527
|
+
const stable = this.withCommandContext(error, record2);
|
|
2528
|
+
if (record2.mutating && record2.dispatchedAt !== void 0 && (stable.code === "browser_disconnected" || stable.code === "internal_error" || stable.code === "unknown_outcome")) {
|
|
2481
2529
|
if (stable.code === "unknown_outcome") {
|
|
2482
|
-
this.finishWithError(
|
|
2530
|
+
this.finishWithError(record2, "unknown_outcome", stable);
|
|
2483
2531
|
return;
|
|
2484
2532
|
}
|
|
2485
2533
|
this.finishWithError(
|
|
2486
|
-
|
|
2534
|
+
record2,
|
|
2487
2535
|
"unknown_outcome",
|
|
2488
2536
|
new BrowserPilotError("unknown_outcome", "Command outcome is unknown after dispatch", {
|
|
2489
|
-
context: { commandId:
|
|
2537
|
+
context: { commandId: record2.id },
|
|
2490
2538
|
cause: stable
|
|
2491
2539
|
})
|
|
2492
2540
|
);
|
|
2493
2541
|
} else {
|
|
2494
|
-
this.finishWithError(
|
|
2542
|
+
this.finishWithError(record2, "completed", stable);
|
|
2495
2543
|
}
|
|
2496
2544
|
}
|
|
2497
2545
|
}
|
|
2498
|
-
expire(
|
|
2499
|
-
if (TERMINAL_STATUSES.has(
|
|
2500
|
-
|
|
2501
|
-
if (
|
|
2546
|
+
expire(record2) {
|
|
2547
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2548
|
+
record2.abortController.abort();
|
|
2549
|
+
if (record2.status === "dispatched" && record2.mutating) {
|
|
2502
2550
|
this.finishWithError(
|
|
2503
|
-
|
|
2551
|
+
record2,
|
|
2504
2552
|
"unknown_outcome",
|
|
2505
2553
|
new BrowserPilotError("unknown_outcome", "Command deadline elapsed after dispatch", {
|
|
2506
|
-
context: { commandId:
|
|
2554
|
+
context: { commandId: record2.id }
|
|
2507
2555
|
})
|
|
2508
2556
|
);
|
|
2509
2557
|
return;
|
|
2510
2558
|
}
|
|
2511
2559
|
this.finishWithError(
|
|
2512
|
-
|
|
2560
|
+
record2,
|
|
2513
2561
|
"expired",
|
|
2514
2562
|
new BrowserPilotError("command_expired", "Command deadline elapsed before a result was known", {
|
|
2515
|
-
context: { commandId:
|
|
2563
|
+
context: { commandId: record2.id }
|
|
2516
2564
|
})
|
|
2517
2565
|
);
|
|
2518
2566
|
}
|
|
2519
|
-
finishWithError(
|
|
2520
|
-
if (TERMINAL_STATUSES.has(
|
|
2521
|
-
const stable = this.withCommandContext(error,
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
this.clearDeadline(
|
|
2526
|
-
this.emitStatus(
|
|
2527
|
-
if (!
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
resolve(
|
|
2533
|
-
if (
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
}
|
|
2537
|
-
cancelForCleanup(
|
|
2538
|
-
if (TERMINAL_STATUSES.has(
|
|
2539
|
-
if (
|
|
2540
|
-
|
|
2567
|
+
finishWithError(record2, status, error) {
|
|
2568
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2569
|
+
const stable = this.withCommandContext(error, record2);
|
|
2570
|
+
record2.status = status;
|
|
2571
|
+
record2.completedAt = this.now();
|
|
2572
|
+
record2.error = stable.toJsonRpcError();
|
|
2573
|
+
this.clearDeadline(record2);
|
|
2574
|
+
this.emitStatus(record2);
|
|
2575
|
+
if (!record2.settled) {
|
|
2576
|
+
record2.settled = true;
|
|
2577
|
+
record2.reject(stable);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
resolve(record2) {
|
|
2581
|
+
if (record2.settled) return;
|
|
2582
|
+
record2.settled = true;
|
|
2583
|
+
record2.resolve(this.outcome(record2));
|
|
2584
|
+
}
|
|
2585
|
+
cancelForCleanup(record2) {
|
|
2586
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2587
|
+
if (record2.status === "accepted") {
|
|
2588
|
+
record2.abortController.abort();
|
|
2541
2589
|
this.finishWithError(
|
|
2542
|
-
|
|
2590
|
+
record2,
|
|
2543
2591
|
"cancelled",
|
|
2544
2592
|
new BrowserPilotError("command_cancelled", "Command was cancelled during resource cleanup")
|
|
2545
2593
|
);
|
|
2546
2594
|
return;
|
|
2547
2595
|
}
|
|
2548
|
-
|
|
2549
|
-
this.emitStatus(
|
|
2550
|
-
|
|
2596
|
+
record2.cancellationRequested = true;
|
|
2597
|
+
this.emitStatus(record2);
|
|
2598
|
+
record2.abortController.abort();
|
|
2551
2599
|
}
|
|
2552
2600
|
requireOwned(context) {
|
|
2553
|
-
const
|
|
2554
|
-
if (!
|
|
2601
|
+
const record2 = this.records.get(context.commandId);
|
|
2602
|
+
if (!record2 || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId) {
|
|
2555
2603
|
throw invalidArgument("Command was not found for this ClientPrincipal and Workspace", "commandId");
|
|
2556
2604
|
}
|
|
2557
|
-
return
|
|
2605
|
+
return record2;
|
|
2558
2606
|
}
|
|
2559
|
-
assertDuplicate(
|
|
2560
|
-
if (
|
|
2607
|
+
assertDuplicate(record2, input, fingerprint, idempotencyKey) {
|
|
2608
|
+
if (record2.principalId !== input.principalId || record2.workspaceId !== input.workspaceId || record2.method !== input.method || record2.fingerprint !== fingerprint || record2.idempotencyKey !== idempotencyKey) {
|
|
2561
2609
|
throw invalidArgument("Command or idempotency key was reused for a different request", "idempotencyKey");
|
|
2562
2610
|
}
|
|
2563
2611
|
}
|
|
2564
|
-
outcome(
|
|
2612
|
+
outcome(record2) {
|
|
2565
2613
|
return {
|
|
2566
|
-
command: descriptor(
|
|
2567
|
-
...
|
|
2568
|
-
...
|
|
2614
|
+
command: descriptor(record2),
|
|
2615
|
+
...record2.result !== void 0 ? { result: cloneJson(record2.result) } : {},
|
|
2616
|
+
...record2.error ? { error: cloneJson(record2.error) } : {}
|
|
2569
2617
|
};
|
|
2570
2618
|
}
|
|
2571
2619
|
scopeKey(principalId, connectionId, workspaceId) {
|
|
2572
2620
|
return workspaceId ? `${principalId}\0${workspaceId}` : `${principalId}\0${connectionId}`;
|
|
2573
2621
|
}
|
|
2574
|
-
withCommandContext(error,
|
|
2622
|
+
withCommandContext(error, record2) {
|
|
2575
2623
|
const stable = asBrowserPilotError(error);
|
|
2576
2624
|
return new BrowserPilotError(stable.code, stable.message, {
|
|
2577
2625
|
retryable: stable.retryable,
|
|
2578
|
-
context: { ...stable.context ?? {}, commandId:
|
|
2626
|
+
context: { ...stable.context ?? {}, commandId: record2.id },
|
|
2579
2627
|
...stable.remediation ? { remediation: stable.remediation } : {},
|
|
2580
2628
|
rpcCode: stable.rpcCode,
|
|
2581
2629
|
cause: error
|
|
@@ -2595,9 +2643,9 @@ var MemoryCommandRuntime = class {
|
|
|
2595
2643
|
}
|
|
2596
2644
|
ensureCapacity() {
|
|
2597
2645
|
if (this.records.size < this.maxCommands) return;
|
|
2598
|
-
const terminal = [...this.records.values()].filter((
|
|
2599
|
-
for (const
|
|
2600
|
-
this.deleteRecord(
|
|
2646
|
+
const terminal = [...this.records.values()].filter((record2) => TERMINAL_STATUSES.has(record2.status)).sort((left, right) => (left.completedAt ?? left.deadlineAt) - (right.completedAt ?? right.deadlineAt));
|
|
2647
|
+
for (const record2 of terminal) {
|
|
2648
|
+
this.deleteRecord(record2.id, record2);
|
|
2601
2649
|
if (this.records.size < this.maxCommands) return;
|
|
2602
2650
|
}
|
|
2603
2651
|
throw new BrowserPilotError("result_too_large", "Command store capacity is exhausted", {
|
|
@@ -2605,19 +2653,19 @@ var MemoryCommandRuntime = class {
|
|
|
2605
2653
|
context: { maxCommands: this.maxCommands }
|
|
2606
2654
|
});
|
|
2607
2655
|
}
|
|
2608
|
-
deleteRecord(id,
|
|
2609
|
-
this.clearDeadline(
|
|
2656
|
+
deleteRecord(id, record2) {
|
|
2657
|
+
this.clearDeadline(record2);
|
|
2610
2658
|
this.records.delete(id);
|
|
2611
|
-
const index = `${
|
|
2659
|
+
const index = `${record2.scopeKey}\0${record2.idempotencyKey}`;
|
|
2612
2660
|
if (this.commandsByIdempotency.get(index) === id) this.commandsByIdempotency.delete(index);
|
|
2613
2661
|
}
|
|
2614
|
-
clearDeadline(
|
|
2615
|
-
if (!
|
|
2616
|
-
clearTimeout(
|
|
2617
|
-
|
|
2662
|
+
clearDeadline(record2) {
|
|
2663
|
+
if (!record2.deadlineTimer) return;
|
|
2664
|
+
clearTimeout(record2.deadlineTimer);
|
|
2665
|
+
record2.deadlineTimer = void 0;
|
|
2618
2666
|
}
|
|
2619
|
-
emitStatus(
|
|
2620
|
-
const outcome = this.outcome(
|
|
2667
|
+
emitStatus(record2) {
|
|
2668
|
+
const outcome = this.outcome(record2);
|
|
2621
2669
|
for (const listener of this.statusListeners) {
|
|
2622
2670
|
try {
|
|
2623
2671
|
listener(outcome);
|
|
@@ -2779,13 +2827,21 @@ var DEFAULT_PROTOCOL_LIMITS = {
|
|
|
2779
2827
|
function normalizeBrowserCandidate(candidate) {
|
|
2780
2828
|
const ready = candidate.state === "ready";
|
|
2781
2829
|
const authorizationRequired = candidate.state === "authorization_required";
|
|
2830
|
+
const userDataRoot = candidate.userDataRoot ?? candidate.profile;
|
|
2782
2831
|
return {
|
|
2783
2832
|
...candidate,
|
|
2833
|
+
...userDataRoot ? { userDataRoot, profile: userDataRoot } : {},
|
|
2784
2834
|
processState: candidate.processState ?? (ready || authorizationRequired ? "running" : "unknown"),
|
|
2785
2835
|
remoteDebuggingState: candidate.remoteDebuggingState ?? (ready || authorizationRequired ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
|
|
2786
2836
|
authorizationState: candidate.authorizationState ?? (ready ? "authorized" : authorizationRequired ? "required" : candidate.state === "disconnected" ? "unknown" : "not_applicable")
|
|
2787
2837
|
};
|
|
2788
2838
|
}
|
|
2839
|
+
function browserCandidateForProtocol(candidate, protocolMinor) {
|
|
2840
|
+
const { profile, userDataRoot, ...rest } = candidate;
|
|
2841
|
+
const root = userDataRoot ?? profile;
|
|
2842
|
+
if (!root) return rest;
|
|
2843
|
+
return protocolMinor >= 3 ? { ...rest, userDataRoot: root } : { ...rest, profile: root };
|
|
2844
|
+
}
|
|
2789
2845
|
function asJson(value) {
|
|
2790
2846
|
return value;
|
|
2791
2847
|
}
|
|
@@ -2795,8 +2851,8 @@ function cloneWorkspace(value) {
|
|
|
2795
2851
|
function cloneManagedTabSet(value) {
|
|
2796
2852
|
return { ...value };
|
|
2797
2853
|
}
|
|
2798
|
-
function runtimeManagedTabSets(
|
|
2799
|
-
return [
|
|
2854
|
+
function runtimeManagedTabSets(record2) {
|
|
2855
|
+
return [record2.managedTabSet, ...record2.additionalManagedTabSets.values()];
|
|
2800
2856
|
}
|
|
2801
2857
|
function cloneLease(value) {
|
|
2802
2858
|
return { ...value, capabilities: [...value.capabilities] };
|
|
@@ -2988,8 +3044,8 @@ var MemoryBrokerRuntime = class {
|
|
|
2988
3044
|
if (connection.notificationWaiter) {
|
|
2989
3045
|
throw invalidArgument("Only one notification poll may be active per bridge Connection");
|
|
2990
3046
|
}
|
|
2991
|
-
return new Promise((
|
|
2992
|
-
const waiter = { resolve:
|
|
3047
|
+
return new Promise((resolve3) => {
|
|
3048
|
+
const waiter = { resolve: resolve3, signal: options.signal };
|
|
2993
3049
|
const finish = () => this.resolveNotificationWaiter(connection, void 0);
|
|
2994
3050
|
waiter.timer = setTimeout(finish, waitMs);
|
|
2995
3051
|
waiter.timer.unref();
|
|
@@ -3112,9 +3168,9 @@ var MemoryBrokerRuntime = class {
|
|
|
3112
3168
|
for (const lease of this.leases.values()) {
|
|
3113
3169
|
if (lease.state === "active") leasedWorkspaceIds.add(lease.workspaceId);
|
|
3114
3170
|
}
|
|
3115
|
-
for (const
|
|
3116
|
-
if (
|
|
3117
|
-
this.releaseWorkspaceRecord(
|
|
3171
|
+
for (const record2 of this.workspaces.values()) {
|
|
3172
|
+
if (record2.value.state === "active" && record2.value.updatedAt + this.workspaceIdleTtlMs <= now && !leasedWorkspaceIds.has(record2.value.id)) {
|
|
3173
|
+
this.releaseWorkspaceRecord(record2);
|
|
3118
3174
|
}
|
|
3119
3175
|
}
|
|
3120
3176
|
return expired;
|
|
@@ -3123,7 +3179,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3123
3179
|
return {
|
|
3124
3180
|
principals: this.principals.size,
|
|
3125
3181
|
connections: this.connectionsByBridge.size,
|
|
3126
|
-
activeWorkspaces: [...this.workspaces.values()].filter((
|
|
3182
|
+
activeWorkspaces: [...this.workspaces.values()].filter((record2) => record2.value.state === "active").length,
|
|
3127
3183
|
activeLeases: [...this.leases.values()].filter((lease) => lease.state === "active").length
|
|
3128
3184
|
};
|
|
3129
3185
|
}
|
|
@@ -3175,7 +3231,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3175
3231
|
capabilities: capabilities2,
|
|
3176
3232
|
brokerProcessIdentity: this.options.brokerProcessIdentity,
|
|
3177
3233
|
connectionId: existing.value.id,
|
|
3178
|
-
browsers: this.browserBindings.map((binding) => (
|
|
3234
|
+
browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, existing.value.protocol.minor)),
|
|
3179
3235
|
limits: { ...existing.limits }
|
|
3180
3236
|
});
|
|
3181
3237
|
}
|
|
@@ -3212,7 +3268,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3212
3268
|
capabilities,
|
|
3213
3269
|
brokerProcessIdentity: this.options.brokerProcessIdentity,
|
|
3214
3270
|
connectionId,
|
|
3215
|
-
browsers: this.browserBindings.map((binding) => (
|
|
3271
|
+
browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, protocol.minor)),
|
|
3216
3272
|
limits: { ...limits }
|
|
3217
3273
|
};
|
|
3218
3274
|
const connection = {
|
|
@@ -3234,7 +3290,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3234
3290
|
throw protocolIncompatible("Keyed Workspace creation requires protocol 1.1 or newer");
|
|
3235
3291
|
}
|
|
3236
3292
|
const principalId = connection.value.principalId;
|
|
3237
|
-
const existing = params.clientKey ? [...this.workspaces.values()].find((
|
|
3293
|
+
const existing = params.clientKey ? [...this.workspaces.values()].find((record2) => record2.value.principalId === principalId && record2.value.state === "active" && record2.value.clientKey === params.clientKey) : void 0;
|
|
3238
3294
|
if (existing) {
|
|
3239
3295
|
if (params.browserId) {
|
|
3240
3296
|
const requested = this.browserBindings.find((candidate) => candidate.candidate.id === params.browserId);
|
|
@@ -3266,7 +3322,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3266
3322
|
}
|
|
3267
3323
|
});
|
|
3268
3324
|
}
|
|
3269
|
-
const activeCount = [...this.workspaces.values()].filter((
|
|
3325
|
+
const activeCount = [...this.workspaces.values()].filter((record2) => record2.value.principalId === principalId && record2.value.state === "active").length;
|
|
3270
3326
|
if (activeCount >= this.maxWorkspacesPerPrincipal) {
|
|
3271
3327
|
throw new BrowserPilotError("result_too_large", "Workspace limit reached", {
|
|
3272
3328
|
context: { maxWorkspacesPerPrincipal: this.maxWorkspacesPerPrincipal }
|
|
@@ -3313,20 +3369,20 @@ var MemoryBrokerRuntime = class {
|
|
|
3313
3369
|
getWorkspace(connection, value) {
|
|
3314
3370
|
this.assertCapabilities(connection, ["workspace.manage"]);
|
|
3315
3371
|
const params = validateWorkspaceGetParams(value);
|
|
3316
|
-
const
|
|
3372
|
+
const record2 = this.requireWorkspace(connection, params.workspaceId, true);
|
|
3317
3373
|
return asJson({
|
|
3318
|
-
workspace: cloneWorkspace(
|
|
3319
|
-
managedTabSet: cloneManagedTabSet(
|
|
3320
|
-
managedTabSets: runtimeManagedTabSets(
|
|
3321
|
-
eventCursor: this.events.currentCursor(
|
|
3374
|
+
workspace: cloneWorkspace(record2.value),
|
|
3375
|
+
managedTabSet: cloneManagedTabSet(record2.managedTabSet),
|
|
3376
|
+
managedTabSets: runtimeManagedTabSets(record2).map(cloneManagedTabSet),
|
|
3377
|
+
eventCursor: this.events.currentCursor(record2.value.id)
|
|
3322
3378
|
});
|
|
3323
3379
|
}
|
|
3324
3380
|
async releaseWorkspace(connection, value) {
|
|
3325
3381
|
this.assertCapabilities(connection, ["workspace.manage"]);
|
|
3326
3382
|
const params = validateWorkspaceReleaseParams(value);
|
|
3327
|
-
const
|
|
3328
|
-
if (
|
|
3329
|
-
await this.workspaceCleanup.get(
|
|
3383
|
+
const record2 = this.requireWorkspace(connection, params.workspaceId, true);
|
|
3384
|
+
if (record2.value.state !== "released") this.releaseWorkspaceRecord(record2);
|
|
3385
|
+
await this.workspaceCleanup.get(record2.value.id);
|
|
3330
3386
|
return asJson({ workspaceId: params.workspaceId, released: true });
|
|
3331
3387
|
}
|
|
3332
3388
|
createLease(connection, value) {
|
|
@@ -3423,52 +3479,52 @@ var MemoryBrokerRuntime = class {
|
|
|
3423
3479
|
});
|
|
3424
3480
|
}
|
|
3425
3481
|
requireWorkspace(connection, workspaceId, allowReleased) {
|
|
3426
|
-
const
|
|
3427
|
-
if (!
|
|
3482
|
+
const record2 = this.workspaces.get(workspaceId);
|
|
3483
|
+
if (!record2 || record2.value.principalId !== connection.value.principalId || !allowReleased && record2.value.state !== "active") {
|
|
3428
3484
|
throw new BrowserPilotError("workspace_not_found", "Workspace was not found for this ClientPrincipal", {
|
|
3429
3485
|
context: { workspaceId }
|
|
3430
3486
|
});
|
|
3431
3487
|
}
|
|
3432
|
-
return
|
|
3488
|
+
return record2;
|
|
3433
3489
|
}
|
|
3434
|
-
selectWorkspaceProfileContext(
|
|
3435
|
-
if (
|
|
3490
|
+
selectWorkspaceProfileContext(record2, profileContextId) {
|
|
3491
|
+
if (record2.value.state !== "active") {
|
|
3436
3492
|
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3437
|
-
context: { workspaceId:
|
|
3493
|
+
context: { workspaceId: record2.value.id }
|
|
3438
3494
|
});
|
|
3439
3495
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3496
|
+
record2.value.selectedProfileContextId = profileContextId;
|
|
3497
|
+
record2.value.updatedAt = this.now();
|
|
3442
3498
|
}
|
|
3443
|
-
managedTabSetForProfile(
|
|
3444
|
-
if (
|
|
3499
|
+
managedTabSetForProfile(record2, profileContextId) {
|
|
3500
|
+
if (record2.value.state !== "active") {
|
|
3445
3501
|
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3446
|
-
context: { workspaceId:
|
|
3502
|
+
context: { workspaceId: record2.value.id }
|
|
3447
3503
|
});
|
|
3448
3504
|
}
|
|
3449
|
-
if (
|
|
3450
|
-
return cloneManagedTabSet(
|
|
3505
|
+
if (record2.managedTabSet.profileContextId === profileContextId) {
|
|
3506
|
+
return cloneManagedTabSet(record2.managedTabSet);
|
|
3451
3507
|
}
|
|
3452
|
-
if (!
|
|
3453
|
-
|
|
3454
|
-
return cloneManagedTabSet(
|
|
3508
|
+
if (!record2.managedTabSet.profileContextId) {
|
|
3509
|
+
record2.managedTabSet.profileContextId = profileContextId;
|
|
3510
|
+
return cloneManagedTabSet(record2.managedTabSet);
|
|
3455
3511
|
}
|
|
3456
|
-
const existing =
|
|
3512
|
+
const existing = record2.additionalManagedTabSets.get(profileContextId);
|
|
3457
3513
|
if (existing) return cloneManagedTabSet(existing);
|
|
3458
|
-
if (
|
|
3514
|
+
if (record2.additionalManagedTabSets.size >= 127) {
|
|
3459
3515
|
throw new BrowserPilotError("result_too_large", "ManagedTabSet Profile context limit reached", {
|
|
3460
|
-
context: { workspaceId:
|
|
3516
|
+
context: { workspaceId: record2.value.id, maxManagedTabSets: 128 }
|
|
3461
3517
|
});
|
|
3462
3518
|
}
|
|
3463
3519
|
const managedTabSet = {
|
|
3464
3520
|
id: this.nextManagedTabSetId(),
|
|
3465
|
-
workspaceId:
|
|
3466
|
-
browserInstanceId:
|
|
3521
|
+
workspaceId: record2.value.id,
|
|
3522
|
+
browserInstanceId: record2.value.browserInstanceId,
|
|
3467
3523
|
profileContextId,
|
|
3468
3524
|
createdAt: this.now(),
|
|
3469
3525
|
state: "active"
|
|
3470
3526
|
};
|
|
3471
|
-
|
|
3527
|
+
record2.additionalManagedTabSets.set(profileContextId, managedTabSet);
|
|
3472
3528
|
return cloneManagedTabSet(managedTabSet);
|
|
3473
3529
|
}
|
|
3474
3530
|
requireLease(connection, leaseId, requireActive) {
|
|
@@ -3500,52 +3556,52 @@ var MemoryBrokerRuntime = class {
|
|
|
3500
3556
|
this.options.toolExecutor?.releaseLease?.(cloneLease(lease));
|
|
3501
3557
|
this.options.onLeaseReleased?.(cloneLease(lease));
|
|
3502
3558
|
}
|
|
3503
|
-
releaseWorkspaceRecord(
|
|
3504
|
-
|
|
3505
|
-
this.commands.releaseWorkspace(
|
|
3559
|
+
releaseWorkspaceRecord(record2) {
|
|
3560
|
+
record2.value.state = "releasing";
|
|
3561
|
+
this.commands.releaseWorkspace(record2.value.id);
|
|
3506
3562
|
for (const lease of this.leases.values()) {
|
|
3507
|
-
if (lease.workspaceId ===
|
|
3563
|
+
if (lease.workspaceId === record2.value.id && lease.state === "active") {
|
|
3508
3564
|
this.releaseLeaseRecord(lease, "released");
|
|
3509
3565
|
}
|
|
3510
3566
|
}
|
|
3511
|
-
const managedTabSets = runtimeManagedTabSets(
|
|
3567
|
+
const managedTabSets = runtimeManagedTabSets(record2);
|
|
3512
3568
|
for (const managedTabSet of managedTabSets) managedTabSet.state = "closed";
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
const principal = this.principals.get(
|
|
3569
|
+
record2.value.updatedAt = this.now();
|
|
3570
|
+
record2.value.state = "released";
|
|
3571
|
+
const principal = this.principals.get(record2.value.principalId);
|
|
3516
3572
|
if (principal) {
|
|
3517
3573
|
this.options.toolExecutor?.releaseWorkspace?.(
|
|
3518
3574
|
{ ...principal, capabilities: [...principal.capabilities] },
|
|
3519
|
-
cloneWorkspace(
|
|
3575
|
+
cloneWorkspace(record2.value),
|
|
3520
3576
|
managedTabSets.map(cloneManagedTabSet)
|
|
3521
3577
|
);
|
|
3522
3578
|
}
|
|
3523
3579
|
this.options.onWorkspaceReleased?.(
|
|
3524
|
-
cloneWorkspace(
|
|
3525
|
-
cloneManagedTabSet(
|
|
3580
|
+
cloneWorkspace(record2.value),
|
|
3581
|
+
cloneManagedTabSet(record2.managedTabSet)
|
|
3526
3582
|
);
|
|
3527
|
-
const cleanup2 = this.options.artifactStore?.releaseWorkspace(
|
|
3583
|
+
const cleanup2 = this.options.artifactStore?.releaseWorkspace(record2.value.id).catch(() => {
|
|
3528
3584
|
}) ?? Promise.resolve();
|
|
3529
|
-
this.workspaceCleanup.set(
|
|
3585
|
+
this.workspaceCleanup.set(record2.value.id, cleanup2);
|
|
3530
3586
|
void cleanup2.finally(() => {
|
|
3531
|
-
if (this.workspaceCleanup.get(
|
|
3532
|
-
this.workspaceCleanup.delete(
|
|
3587
|
+
if (this.workspaceCleanup.get(record2.value.id) === cleanup2) {
|
|
3588
|
+
this.workspaceCleanup.delete(record2.value.id);
|
|
3533
3589
|
}
|
|
3534
3590
|
});
|
|
3535
3591
|
}
|
|
3536
3592
|
pruneReleasedWorkspaces() {
|
|
3537
3593
|
if (this.workspaces.size < this.maxWorkspaceRecords) return;
|
|
3538
|
-
const released = [...this.workspaces.values()].filter((
|
|
3539
|
-
for (const
|
|
3540
|
-
this.workspaces.delete(
|
|
3541
|
-
this.events.releaseWorkspace(
|
|
3542
|
-
for (const managedTabSet of runtimeManagedTabSets(
|
|
3594
|
+
const released = [...this.workspaces.values()].filter((record2) => record2.value.state === "released").sort((left, right) => left.value.updatedAt - right.value.updatedAt);
|
|
3595
|
+
for (const record2 of released) {
|
|
3596
|
+
this.workspaces.delete(record2.value.id);
|
|
3597
|
+
this.events.releaseWorkspace(record2.value.id);
|
|
3598
|
+
for (const managedTabSet of runtimeManagedTabSets(record2)) {
|
|
3543
3599
|
this.managedTabSetIds.delete(managedTabSet.id);
|
|
3544
3600
|
}
|
|
3545
3601
|
for (const [leaseId, lease] of this.leases) {
|
|
3546
|
-
if (lease.workspaceId ===
|
|
3602
|
+
if (lease.workspaceId === record2.value.id) this.leases.delete(leaseId);
|
|
3547
3603
|
}
|
|
3548
|
-
this.cleanupUnusedPrincipal(
|
|
3604
|
+
this.cleanupUnusedPrincipal(record2.value.principalId);
|
|
3549
3605
|
if (this.workspaces.size < this.maxWorkspaceRecords) return;
|
|
3550
3606
|
}
|
|
3551
3607
|
}
|
|
@@ -3559,7 +3615,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3559
3615
|
}
|
|
3560
3616
|
cleanupUnusedPrincipal(principalId) {
|
|
3561
3617
|
const hasConnection = [...this.connectionsByBridge.values()].some((connection) => connection.value.principalId === principalId);
|
|
3562
|
-
const hasWorkspace = [...this.workspaces.values()].some((
|
|
3618
|
+
const hasWorkspace = [...this.workspaces.values()].some((record2) => record2.value.principalId === principalId);
|
|
3563
3619
|
if (hasConnection || hasWorkspace) return;
|
|
3564
3620
|
const principal = this.principals.get(principalId);
|
|
3565
3621
|
if (!principal) return;
|
|
@@ -3582,8 +3638,10 @@ var MemoryBrokerRuntime = class {
|
|
|
3582
3638
|
const params = validateToolCallParams(value);
|
|
3583
3639
|
const definition = getToolDefinition(params.name);
|
|
3584
3640
|
if (!isToolAvailableInProtocol(definition.name, connection.value.protocol)) {
|
|
3585
|
-
|
|
3641
|
+
const requiredMinor = minimumProtocolMinorForTool(definition.name);
|
|
3642
|
+
throw protocolIncompatible(`${definition.name} requires protocol 1.${requiredMinor} or newer`, {
|
|
3586
3643
|
tool: definition.name,
|
|
3644
|
+
requiredProtocol: { major: 1, minor: requiredMinor },
|
|
3587
3645
|
selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
|
|
3588
3646
|
});
|
|
3589
3647
|
}
|
|
@@ -3734,8 +3792,8 @@ var MemoryBrokerRuntime = class {
|
|
|
3734
3792
|
}));
|
|
3735
3793
|
}
|
|
3736
3794
|
async callBrowserDiscover(connection, params, args, definition) {
|
|
3737
|
-
const
|
|
3738
|
-
const filter = typeof
|
|
3795
|
+
const record2 = args;
|
|
3796
|
+
const filter = typeof record2.browser === "string" ? record2.browser.toLowerCase() : void 0;
|
|
3739
3797
|
const browsers = this.browserBindings.map((binding) => ({ ...binding.candidate })).filter((candidate) => !filter || [candidate.id, candidate.product, candidate.channel].some((value) => value?.toLowerCase().includes(filter)));
|
|
3740
3798
|
return asJson(await this.commands.run({
|
|
3741
3799
|
principalId: connection.value.principalId,
|
|
@@ -3820,8 +3878,8 @@ var MemoryBrokerRuntime = class {
|
|
|
3820
3878
|
async getArtifact(connection, value) {
|
|
3821
3879
|
const params = validateArtifactAccessParams(value);
|
|
3822
3880
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3823
|
-
const
|
|
3824
|
-
return asJson({ artifact:
|
|
3881
|
+
const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
|
|
3882
|
+
return asJson({ artifact: record2.descriptor, path: record2.path });
|
|
3825
3883
|
}
|
|
3826
3884
|
async exportArtifact(connection, value) {
|
|
3827
3885
|
const params = validateArtifactExportParams(value);
|
|
@@ -3836,18 +3894,18 @@ var MemoryBrokerRuntime = class {
|
|
|
3836
3894
|
async importArtifact(connection, value) {
|
|
3837
3895
|
const params = validateArtifactImportParams(value);
|
|
3838
3896
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3839
|
-
const
|
|
3897
|
+
const record2 = await this.requireArtifactStore().importFile(
|
|
3840
3898
|
params.workspaceId,
|
|
3841
3899
|
params.path,
|
|
3842
3900
|
params.mimeType
|
|
3843
3901
|
);
|
|
3844
|
-
return asJson({ artifact:
|
|
3902
|
+
return asJson({ artifact: record2.descriptor });
|
|
3845
3903
|
}
|
|
3846
3904
|
async retainArtifact(connection, value) {
|
|
3847
3905
|
const params = validateArtifactAccessParams(value);
|
|
3848
3906
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3849
|
-
const
|
|
3850
|
-
return asJson({ artifact:
|
|
3907
|
+
const record2 = await this.requireArtifactStore().retain(params.workspaceId, params.artifactId);
|
|
3908
|
+
return asJson({ artifact: record2.descriptor, path: record2.path });
|
|
3851
3909
|
}
|
|
3852
3910
|
async releaseArtifact(connection, value) {
|
|
3853
3911
|
const params = validateArtifactAccessParams(value);
|
|
@@ -4394,20 +4452,6 @@ var PAGE_DIMENSIONS = `JSON.stringify({
|
|
|
4394
4452
|
width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
|
|
4395
4453
|
height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
|
|
4396
4454
|
})`;
|
|
4397
|
-
var INJECT_BORDER = `(() => {
|
|
4398
|
-
if (document.getElementById('__bp_overlay')) return;
|
|
4399
|
-
const d = document.createElement('div');
|
|
4400
|
-
d.id = '__bp_overlay';
|
|
4401
|
-
d.setAttribute('aria-hidden','true');
|
|
4402
|
-
d.setAttribute('role','presentation');
|
|
4403
|
-
Object.assign(d.style, {position:'fixed',inset:'0',zIndex:'2147483647',pointerEvents:'none'});
|
|
4404
|
-
document.documentElement.appendChild(d);
|
|
4405
|
-
try{d.animate([
|
|
4406
|
-
{boxShadow:'inset 0 0 20px rgba(59,130,246,.8),inset 0 0 40px rgba(59,130,246,.4),inset 0 0 80px rgba(59,130,246,.15)'},
|
|
4407
|
-
{boxShadow:'inset 0 0 30px rgba(59,130,246,1),inset 0 0 60px rgba(59,130,246,.5),inset 0 0 100px rgba(59,130,246,.2)'},
|
|
4408
|
-
{boxShadow:'inset 0 0 20px rgba(59,130,246,.8),inset 0 0 40px rgba(59,130,246,.4),inset 0 0 80px rgba(59,130,246,.15)'},
|
|
4409
|
-
],{duration:2500,iterations:Infinity,easing:'ease-in-out'})}catch(e){}
|
|
4410
|
-
})()`;
|
|
4411
4455
|
function elementRect(selector) {
|
|
4412
4456
|
return `JSON.stringify((() => {
|
|
4413
4457
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -5403,7 +5447,7 @@ async function discoverBrowserCandidates(options = {}) {
|
|
|
5403
5447
|
id: stableBrowserId(definition, os),
|
|
5404
5448
|
product: definition.product,
|
|
5405
5449
|
channel: definition.channel,
|
|
5406
|
-
|
|
5450
|
+
userDataRoot: definition.dataDir,
|
|
5407
5451
|
processState,
|
|
5408
5452
|
remoteDebuggingState,
|
|
5409
5453
|
authorizationState,
|
|
@@ -5458,15 +5502,9 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5458
5502
|
let interactiveSince = null;
|
|
5459
5503
|
const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
|
|
5460
5504
|
let deadlineTimer;
|
|
5461
|
-
const deadline = new Promise((
|
|
5462
|
-
deadlineTimer = setTimeout(() =>
|
|
5505
|
+
const deadline = new Promise((resolve3) => {
|
|
5506
|
+
deadlineTimer = setTimeout(() => resolve3(deadlineReached), timeout);
|
|
5463
5507
|
});
|
|
5464
|
-
const injectBorder = async () => {
|
|
5465
|
-
await Promise.race([
|
|
5466
|
-
transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, sessionId).catch(() => void 0),
|
|
5467
|
-
deadline
|
|
5468
|
-
]);
|
|
5469
|
-
};
|
|
5470
5508
|
try {
|
|
5471
5509
|
while (true) {
|
|
5472
5510
|
const evaluation = await Promise.race([
|
|
@@ -5480,18 +5518,16 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5480
5518
|
]);
|
|
5481
5519
|
if (evaluation === deadlineReached) throw new PageLoadTimeoutError(timeout);
|
|
5482
5520
|
if (evaluation.state === "complete") {
|
|
5483
|
-
await injectBorder();
|
|
5484
5521
|
return;
|
|
5485
5522
|
}
|
|
5486
5523
|
if (evaluation.state === "interactive") {
|
|
5487
5524
|
if (interactiveSince === null) interactiveSince = Date.now();
|
|
5488
5525
|
if (Date.now() - interactiveSince >= interactiveGrace) {
|
|
5489
|
-
await injectBorder();
|
|
5490
5526
|
return;
|
|
5491
5527
|
}
|
|
5492
5528
|
}
|
|
5493
5529
|
const sleep = await Promise.race([
|
|
5494
|
-
new Promise((
|
|
5530
|
+
new Promise((resolve3) => setTimeout(() => resolve3("poll"), 200)),
|
|
5495
5531
|
deadline
|
|
5496
5532
|
]);
|
|
5497
5533
|
if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
|
|
@@ -5675,7 +5711,7 @@ var InputDispatcher = class {
|
|
|
5675
5711
|
}
|
|
5676
5712
|
hooks.afterCharacter?.({ character: char, index });
|
|
5677
5713
|
index += 1;
|
|
5678
|
-
if (delayMs > 0) await new Promise((
|
|
5714
|
+
if (delayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
5679
5715
|
}
|
|
5680
5716
|
}
|
|
5681
5717
|
async insertText(text) {
|
|
@@ -5719,7 +5755,7 @@ var ObservationService = class {
|
|
|
5719
5755
|
}
|
|
5720
5756
|
async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
|
|
5721
5757
|
if (this.settleDelayMs > 0) {
|
|
5722
|
-
await new Promise((
|
|
5758
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.settleDelayMs));
|
|
5723
5759
|
}
|
|
5724
5760
|
await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
|
|
5725
5761
|
return this.observe(limit);
|
|
@@ -5778,24 +5814,24 @@ function parsePointerTargetState(value) {
|
|
|
5778
5814
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5779
5815
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5780
5816
|
}
|
|
5781
|
-
const
|
|
5782
|
-
if (
|
|
5817
|
+
const record2 = value;
|
|
5818
|
+
if (record2.status === "ready" && Number.isFinite(record2.x) && Number.isFinite(record2.y)) {
|
|
5783
5819
|
return {
|
|
5784
5820
|
status: "ready",
|
|
5785
|
-
x: Number(
|
|
5786
|
-
y: Number(
|
|
5787
|
-
targetState: parseClickTargetState(
|
|
5821
|
+
x: Number(record2.x),
|
|
5822
|
+
y: Number(record2.y),
|
|
5823
|
+
targetState: parseClickTargetState(record2.targetState)
|
|
5788
5824
|
};
|
|
5789
5825
|
}
|
|
5790
|
-
if (
|
|
5826
|
+
if (record2.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record2.reason)) {
|
|
5791
5827
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5792
5828
|
}
|
|
5793
5829
|
let obstruction;
|
|
5794
|
-
if (
|
|
5795
|
-
if (typeof
|
|
5830
|
+
if (record2.obstruction !== void 0) {
|
|
5831
|
+
if (typeof record2.obstruction !== "object" || record2.obstruction === null || Array.isArray(record2.obstruction)) {
|
|
5796
5832
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5797
5833
|
}
|
|
5798
|
-
const candidate =
|
|
5834
|
+
const candidate = record2.obstruction;
|
|
5799
5835
|
if (typeof candidate.tagName !== "string" || candidate.tagName.length > 40 || candidate.role !== void 0 && (typeof candidate.role !== "string" || candidate.role.length > 40)) {
|
|
5800
5836
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5801
5837
|
}
|
|
@@ -5806,7 +5842,7 @@ function parsePointerTargetState(value) {
|
|
|
5806
5842
|
}
|
|
5807
5843
|
return {
|
|
5808
5844
|
status: "blocked",
|
|
5809
|
-
reason:
|
|
5845
|
+
reason: record2.reason,
|
|
5810
5846
|
...obstruction ? { obstruction } : {}
|
|
5811
5847
|
};
|
|
5812
5848
|
}
|
|
@@ -5823,18 +5859,18 @@ function parseClickTargetState(value) {
|
|
|
5823
5859
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5824
5860
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
|
|
5825
5861
|
}
|
|
5826
|
-
const
|
|
5827
|
-
if (typeof
|
|
5862
|
+
const record2 = value;
|
|
5863
|
+
if (typeof record2.connected !== "boolean" || !CLICK_TARGET_KINDS.has(record2.kind) || typeof record2.focused !== "boolean" || record2.checked !== void 0 && typeof record2.checked !== "boolean" && record2.checked !== "mixed" || record2.selected !== void 0 && typeof record2.selected !== "boolean" || record2.pressed !== void 0 && typeof record2.pressed !== "boolean" && record2.pressed !== "mixed" || record2.expanded !== void 0 && typeof record2.expanded !== "boolean") {
|
|
5828
5864
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
|
|
5829
5865
|
}
|
|
5830
5866
|
return {
|
|
5831
|
-
connected:
|
|
5832
|
-
kind:
|
|
5833
|
-
focused:
|
|
5834
|
-
...
|
|
5835
|
-
...
|
|
5836
|
-
...
|
|
5837
|
-
...
|
|
5867
|
+
connected: record2.connected,
|
|
5868
|
+
kind: record2.kind,
|
|
5869
|
+
focused: record2.focused,
|
|
5870
|
+
...record2.checked !== void 0 ? { checked: record2.checked } : {},
|
|
5871
|
+
...record2.selected !== void 0 ? { selected: record2.selected } : {},
|
|
5872
|
+
...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
|
|
5873
|
+
...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
|
|
5838
5874
|
};
|
|
5839
5875
|
}
|
|
5840
5876
|
function clickEvidence(before, after, button, clickCount) {
|
|
@@ -5880,20 +5916,20 @@ function parseEditableState(value) {
|
|
|
5880
5916
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5881
5917
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
|
|
5882
5918
|
}
|
|
5883
|
-
const
|
|
5884
|
-
const kind =
|
|
5885
|
-
const inputType =
|
|
5886
|
-
const editMode =
|
|
5887
|
-
const selectionMode =
|
|
5888
|
-
const reason2 =
|
|
5889
|
-
if (!["input", "contenteditable", "unsupported"].includes(String(kind)) || typeof
|
|
5919
|
+
const record2 = value;
|
|
5920
|
+
const kind = record2.kind;
|
|
5921
|
+
const inputType = record2.inputType;
|
|
5922
|
+
const editMode = record2.editMode;
|
|
5923
|
+
const selectionMode = record2.selectionMode;
|
|
5924
|
+
const reason2 = record2.reason;
|
|
5925
|
+
if (!["input", "contenteditable", "unsupported"].includes(String(kind)) || typeof record2.value !== "string" || typeof record2.sensitive !== "boolean" || typeof record2.editable !== "boolean" || inputType !== void 0 && (typeof inputType !== "string" || inputType.length > 64) || editMode !== void 0 && editMode !== "text" && editMode !== "value" || selectionMode !== void 0 && selectionMode !== "range" && selectionMode !== "select" || reason2 !== void 0 && !EDITABLE_BLOCK_REASONS.has(reason2) || record2.editable && (kind === "unsupported" || editMode === void 0 || reason2 !== void 0) || !record2.editable && reason2 === void 0 || kind === "input" && inputType === void 0 || editMode === "text" && selectionMode === void 0 || editMode === "value" && selectionMode !== void 0) {
|
|
5890
5926
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
|
|
5891
5927
|
}
|
|
5892
5928
|
return {
|
|
5893
5929
|
kind,
|
|
5894
|
-
value:
|
|
5895
|
-
sensitive:
|
|
5896
|
-
editable:
|
|
5930
|
+
value: record2.value,
|
|
5931
|
+
sensitive: record2.sensitive,
|
|
5932
|
+
editable: record2.editable,
|
|
5897
5933
|
...inputType !== void 0 ? { inputType } : {},
|
|
5898
5934
|
...editMode !== void 0 ? { editMode } : {},
|
|
5899
5935
|
...selectionMode !== void 0 ? { selectionMode } : {},
|
|
@@ -5928,19 +5964,19 @@ function parsePressTargetState(value, focusToken) {
|
|
|
5928
5964
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5929
5965
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
|
|
5930
5966
|
}
|
|
5931
|
-
const
|
|
5932
|
-
if (!PRESS_TARGET_KINDS.has(
|
|
5967
|
+
const record2 = value;
|
|
5968
|
+
if (!PRESS_TARGET_KINDS.has(record2.kind) || typeof record2.sensitive !== "boolean" || record2.valueToken !== void 0 && (typeof record2.valueToken !== "string" || record2.valueToken.length > 64) || record2.selectedToken !== void 0 && (typeof record2.selectedToken !== "string" || record2.selectedToken.length > 64) || record2.checked !== void 0 && typeof record2.checked !== "boolean" && record2.checked !== "mixed" || record2.pressed !== void 0 && typeof record2.pressed !== "boolean" && record2.pressed !== "mixed" || record2.expanded !== void 0 && typeof record2.expanded !== "boolean") {
|
|
5933
5969
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
|
|
5934
5970
|
}
|
|
5935
5971
|
return {
|
|
5936
5972
|
...focusToken ? { focusToken } : {},
|
|
5937
|
-
kind:
|
|
5938
|
-
sensitive:
|
|
5939
|
-
...
|
|
5940
|
-
...
|
|
5941
|
-
...
|
|
5942
|
-
...
|
|
5943
|
-
...
|
|
5973
|
+
kind: record2.kind,
|
|
5974
|
+
sensitive: record2.sensitive,
|
|
5975
|
+
...record2.valueToken !== void 0 ? { valueToken: record2.valueToken } : {},
|
|
5976
|
+
...record2.selectedToken !== void 0 ? { selectedToken: record2.selectedToken } : {},
|
|
5977
|
+
...record2.checked !== void 0 ? { checked: record2.checked } : {},
|
|
5978
|
+
...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
|
|
5979
|
+
...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
|
|
5944
5980
|
};
|
|
5945
5981
|
}
|
|
5946
5982
|
function pressEvidence(before, after) {
|
|
@@ -6076,7 +6112,7 @@ var ActionService = class {
|
|
|
6076
6112
|
await this.input.click(point.x, point.y, { button, clickCount });
|
|
6077
6113
|
this.markDispatched(run);
|
|
6078
6114
|
if (this.readbackDelayMs > 0) {
|
|
6079
|
-
await new Promise((
|
|
6115
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6080
6116
|
}
|
|
6081
6117
|
let after;
|
|
6082
6118
|
try {
|
|
@@ -6128,7 +6164,7 @@ var ActionService = class {
|
|
|
6128
6164
|
await this.input.press(key);
|
|
6129
6165
|
this.markDispatched(run);
|
|
6130
6166
|
if (this.readbackDelayMs > 0) {
|
|
6131
|
-
await new Promise((
|
|
6167
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6132
6168
|
}
|
|
6133
6169
|
let after;
|
|
6134
6170
|
try {
|
|
@@ -6194,7 +6230,7 @@ var ActionService = class {
|
|
|
6194
6230
|
this.markDispatched(run);
|
|
6195
6231
|
}
|
|
6196
6232
|
if (this.readbackDelayMs > 0) {
|
|
6197
|
-
await new Promise((
|
|
6233
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6198
6234
|
}
|
|
6199
6235
|
const after = await this.readElementState(objectId);
|
|
6200
6236
|
evidence = inputEvidence("type", before, after, text, !!options.clear);
|
|
@@ -6226,7 +6262,7 @@ var ActionService = class {
|
|
|
6226
6262
|
this.willDispatch();
|
|
6227
6263
|
await this.input.click(location.x, location.y);
|
|
6228
6264
|
this.markDispatched(run);
|
|
6229
|
-
if (this.focusDelayMs > 0) await new Promise((
|
|
6265
|
+
if (this.focusDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.focusDelayMs));
|
|
6230
6266
|
}
|
|
6231
6267
|
await this.captureFocus(run, "capture_input_focus");
|
|
6232
6268
|
const before = await this.readActiveState();
|
|
@@ -6248,7 +6284,7 @@ var ActionService = class {
|
|
|
6248
6284
|
},
|
|
6249
6285
|
afterCharacter: () => this.markDispatched(run)
|
|
6250
6286
|
});
|
|
6251
|
-
if (this.readbackDelayMs > 0) await new Promise((
|
|
6287
|
+
if (this.readbackDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6252
6288
|
const after = await this.readActiveState();
|
|
6253
6289
|
const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
|
|
6254
6290
|
this.requireVerification(evidence, options.verification);
|
|
@@ -6964,13 +7000,13 @@ var DownloadController = class {
|
|
|
6964
7000
|
return;
|
|
6965
7001
|
}
|
|
6966
7002
|
if (!this.isTracked(session, download)) return;
|
|
6967
|
-
const
|
|
7003
|
+
const record2 = await this.artifacts.ingestDownloadCopy(
|
|
6968
7004
|
session.context.workspaceId,
|
|
6969
7005
|
completedFile.path,
|
|
6970
7006
|
download.fileName
|
|
6971
7007
|
);
|
|
6972
7008
|
if (!this.isTracked(session, download)) {
|
|
6973
|
-
await this.artifacts.release(session.context.workspaceId,
|
|
7009
|
+
await this.artifacts.release(session.context.workspaceId, record2.descriptor.id);
|
|
6974
7010
|
return;
|
|
6975
7011
|
}
|
|
6976
7012
|
download.state = "terminal";
|
|
@@ -6978,7 +7014,7 @@ var DownloadController = class {
|
|
|
6978
7014
|
this.publish(session, {
|
|
6979
7015
|
downloadId: download.id,
|
|
6980
7016
|
state: "completed",
|
|
6981
|
-
artifact:
|
|
7017
|
+
artifact: record2.descriptor
|
|
6982
7018
|
});
|
|
6983
7019
|
} catch (error) {
|
|
6984
7020
|
if (download.state === "terminal") return;
|
|
@@ -7069,8 +7105,8 @@ var MAX_PROFILE_CONTEXT_RECORDS = 512;
|
|
|
7069
7105
|
function cloneTarget(target) {
|
|
7070
7106
|
return { ...target };
|
|
7071
7107
|
}
|
|
7072
|
-
function cloneRecord(
|
|
7073
|
-
return { ...
|
|
7108
|
+
function cloneRecord(record2) {
|
|
7109
|
+
return { ...record2, targets: record2.targets.map(cloneTarget) };
|
|
7074
7110
|
}
|
|
7075
7111
|
function contextKey(cdpBrowserContextId) {
|
|
7076
7112
|
return cdpBrowserContextId ? `cdp:${cdpBrowserContextId}` : DEFAULT_CDP_CONTEXT_KEY;
|
|
@@ -7115,22 +7151,23 @@ var MemoryProfileContextRegistry = class {
|
|
|
7115
7151
|
});
|
|
7116
7152
|
}
|
|
7117
7153
|
for (const id of this.currentByKey.values()) {
|
|
7118
|
-
const
|
|
7119
|
-
if (
|
|
7154
|
+
const record2 = this.recordsById.get(id);
|
|
7155
|
+
if (record2) record2.active = false;
|
|
7120
7156
|
}
|
|
7121
7157
|
this.contextByTarget.clear();
|
|
7122
7158
|
const activeKeys = new Set(grouped.keys());
|
|
7123
7159
|
for (const [key, group] of grouped) {
|
|
7124
7160
|
let id = this.currentByKey.get(key);
|
|
7125
|
-
let
|
|
7126
|
-
if (!
|
|
7161
|
+
let record2 = id ? this.recordsById.get(id) : void 0;
|
|
7162
|
+
if (!record2) {
|
|
7127
7163
|
id = this.nextId();
|
|
7128
7164
|
const rawContextId = group[0]?.cdpBrowserContextId;
|
|
7129
|
-
|
|
7165
|
+
record2 = {
|
|
7130
7166
|
id,
|
|
7131
7167
|
browserInstanceId: this.browserInstanceId,
|
|
7132
7168
|
browserConnectionGeneration,
|
|
7133
7169
|
label: `Profile ${++this.nextSequence}`,
|
|
7170
|
+
identityStatus: "unidentified",
|
|
7134
7171
|
tabCount: 0,
|
|
7135
7172
|
eligibleTabCount: 0,
|
|
7136
7173
|
sequence: this.nextSequence,
|
|
@@ -7138,34 +7175,34 @@ var MemoryProfileContextRegistry = class {
|
|
|
7138
7175
|
targets: [],
|
|
7139
7176
|
...rawContextId ? { cdpBrowserContextId: rawContextId } : {}
|
|
7140
7177
|
};
|
|
7141
|
-
this.recordsById.set(id,
|
|
7178
|
+
this.recordsById.set(id, record2);
|
|
7142
7179
|
this.recordOrder.push(id);
|
|
7143
7180
|
this.currentByKey.set(key, id);
|
|
7144
7181
|
}
|
|
7145
7182
|
const sorted = [...group].sort((left, right) => Number(right.eligible) - Number(left.eligible) || left.cdpTargetId.localeCompare(right.cdpTargetId));
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
for (const target of sorted) this.contextByTarget.set(target.cdpTargetId,
|
|
7183
|
+
record2.active = true;
|
|
7184
|
+
record2.tabCount = sorted.length;
|
|
7185
|
+
record2.eligibleTabCount = sorted.filter((target) => target.eligible).length;
|
|
7186
|
+
record2.targets = sorted;
|
|
7187
|
+
record2.representativeCdpTargetId = sorted[0]?.cdpTargetId;
|
|
7188
|
+
for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record2.id);
|
|
7152
7189
|
}
|
|
7153
7190
|
for (const [key, id] of this.currentByKey) {
|
|
7154
7191
|
if (activeKeys.has(key)) continue;
|
|
7155
|
-
const
|
|
7156
|
-
if (
|
|
7192
|
+
const record2 = this.recordsById.get(id);
|
|
7193
|
+
if (record2) record2.active = false;
|
|
7157
7194
|
this.currentByKey.delete(key);
|
|
7158
7195
|
}
|
|
7159
7196
|
this.pruneStaleRecords();
|
|
7160
7197
|
return this.list(browserConnectionGeneration);
|
|
7161
7198
|
}
|
|
7162
7199
|
list(browserConnectionGeneration) {
|
|
7163
|
-
return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((
|
|
7200
|
+
return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((record2) => record2 !== void 0 && record2.active && record2.browserConnectionGeneration === browserConnectionGeneration).sort((left, right) => left.sequence - right.sequence).map(cloneRecord);
|
|
7164
7201
|
}
|
|
7165
7202
|
resolve(profileContextId, browserConnectionGeneration) {
|
|
7166
|
-
const
|
|
7167
|
-
if (!
|
|
7168
|
-
if (!
|
|
7203
|
+
const record2 = this.recordsById.get(profileContextId);
|
|
7204
|
+
if (!record2) throw invalidArgument("Unknown Profile context", "profileContextId");
|
|
7205
|
+
if (!record2.active || record2.browserInstanceId !== this.browserInstanceId || record2.browserConnectionGeneration !== browserConnectionGeneration) {
|
|
7169
7206
|
throw new BrowserPilotError("profile_context_stale", "Profile context belongs to a stale browser connection", {
|
|
7170
7207
|
retryable: true,
|
|
7171
7208
|
context: {
|
|
@@ -7179,7 +7216,31 @@ var MemoryProfileContextRegistry = class {
|
|
|
7179
7216
|
}
|
|
7180
7217
|
});
|
|
7181
7218
|
}
|
|
7182
|
-
return cloneRecord(
|
|
7219
|
+
return cloneRecord(record2);
|
|
7220
|
+
}
|
|
7221
|
+
setVerifiedIdentity(profileContextId, browserConnectionGeneration, identity) {
|
|
7222
|
+
const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
|
|
7223
|
+
record2.identityStatus = "verified";
|
|
7224
|
+
record2.profileName = identity.profileName;
|
|
7225
|
+
record2.displayName = identity.profileName;
|
|
7226
|
+
record2.profileDirectory = identity.profileDirectory;
|
|
7227
|
+
if (identity.accountName) record2.accountName = identity.accountName;
|
|
7228
|
+
else delete record2.accountName;
|
|
7229
|
+
if (identity.accountEmail) record2.accountEmail = identity.accountEmail;
|
|
7230
|
+
else delete record2.accountEmail;
|
|
7231
|
+
delete record2.identityErrorCode;
|
|
7232
|
+
return cloneRecord(record2);
|
|
7233
|
+
}
|
|
7234
|
+
setIdentityUnavailable(profileContextId, browserConnectionGeneration, identityErrorCode) {
|
|
7235
|
+
const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
|
|
7236
|
+
record2.identityStatus = "unavailable";
|
|
7237
|
+
record2.identityErrorCode = identityErrorCode;
|
|
7238
|
+
delete record2.profileName;
|
|
7239
|
+
delete record2.displayName;
|
|
7240
|
+
delete record2.profileDirectory;
|
|
7241
|
+
delete record2.accountName;
|
|
7242
|
+
delete record2.accountEmail;
|
|
7243
|
+
return cloneRecord(record2);
|
|
7183
7244
|
}
|
|
7184
7245
|
forTarget(cdpTargetId, browserConnectionGeneration) {
|
|
7185
7246
|
const id = this.contextByTarget.get(cdpTargetId);
|
|
@@ -7198,19 +7259,23 @@ var MemoryProfileContextRegistry = class {
|
|
|
7198
7259
|
}
|
|
7199
7260
|
invalidateCurrent() {
|
|
7200
7261
|
for (const id of this.currentByKey.values()) {
|
|
7201
|
-
const
|
|
7202
|
-
if (
|
|
7262
|
+
const record2 = this.recordsById.get(id);
|
|
7263
|
+
if (record2) record2.active = false;
|
|
7203
7264
|
}
|
|
7204
7265
|
this.currentByKey.clear();
|
|
7205
7266
|
this.contextByTarget.clear();
|
|
7206
7267
|
}
|
|
7268
|
+
currentRecord(profileContextId, browserConnectionGeneration) {
|
|
7269
|
+
this.resolve(profileContextId, browserConnectionGeneration);
|
|
7270
|
+
return this.recordsById.get(profileContextId);
|
|
7271
|
+
}
|
|
7207
7272
|
pruneStaleRecords() {
|
|
7208
7273
|
while (this.recordsById.size > MAX_PROFILE_CONTEXT_RECORDS) {
|
|
7209
7274
|
const id = this.recordOrder.shift();
|
|
7210
7275
|
if (!id) return;
|
|
7211
|
-
const
|
|
7212
|
-
if (!
|
|
7213
|
-
if (
|
|
7276
|
+
const record2 = this.recordsById.get(id);
|
|
7277
|
+
if (!record2 || record2.active) {
|
|
7278
|
+
if (record2?.active) this.recordOrder.push(id);
|
|
7214
7279
|
continue;
|
|
7215
7280
|
}
|
|
7216
7281
|
this.recordsById.delete(id);
|
|
@@ -7233,8 +7298,10 @@ function isEligibleUserPage(target) {
|
|
|
7233
7298
|
return !(/* @__PURE__ */ new Set([
|
|
7234
7299
|
"devtools:",
|
|
7235
7300
|
"chrome:",
|
|
7301
|
+
"chrome-extension:",
|
|
7236
7302
|
"chrome-untrusted:",
|
|
7237
7303
|
"edge:",
|
|
7304
|
+
"edge-extension:",
|
|
7238
7305
|
"brave:",
|
|
7239
7306
|
"vivaldi:"
|
|
7240
7307
|
])).has(url.protocol);
|
|
@@ -7242,6 +7309,14 @@ function isEligibleUserPage(target) {
|
|
|
7242
7309
|
return target.url === "about:blank";
|
|
7243
7310
|
}
|
|
7244
7311
|
}
|
|
7312
|
+
function isExtensionPage(target) {
|
|
7313
|
+
try {
|
|
7314
|
+
const protocol = new URL(target.url).protocol;
|
|
7315
|
+
return protocol === "chrome-extension:" || protocol === "edge-extension:";
|
|
7316
|
+
} catch {
|
|
7317
|
+
return false;
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7245
7320
|
var CdpBrowserTargetCatalog = class {
|
|
7246
7321
|
constructor(transport, browserInstanceId, identity, options) {
|
|
7247
7322
|
this.transport = transport;
|
|
@@ -7272,6 +7347,7 @@ var CdpBrowserTargetCatalog = class {
|
|
|
7272
7347
|
type: typeof value.type === "string" ? value.type : "",
|
|
7273
7348
|
...typeof value.openerId === "string" ? { openerCdpTargetId: value.openerId } : {}
|
|
7274
7349
|
};
|
|
7350
|
+
if (isExtensionPage(candidate)) continue;
|
|
7275
7351
|
const eligible2 = isEligibleUserPage(candidate) && !this.options.isExcludedTarget(candidate);
|
|
7276
7352
|
snapshots.push({
|
|
7277
7353
|
...candidate,
|
|
@@ -7507,8 +7583,8 @@ var BrowserWatchdogService = class {
|
|
|
7507
7583
|
|
|
7508
7584
|
// src/services/controlled-target-registry.ts
|
|
7509
7585
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7510
|
-
function cloneRecord2(
|
|
7511
|
-
return { ...
|
|
7586
|
+
function cloneRecord2(record2) {
|
|
7587
|
+
return { ...record2 };
|
|
7512
7588
|
}
|
|
7513
7589
|
function targetKey2(workspaceId, browserInstanceId, cdpTargetId) {
|
|
7514
7590
|
return `${workspaceId}\0${browserInstanceId}\0${cdpTargetId}`;
|
|
@@ -7542,7 +7618,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7542
7618
|
existing.openerCdpTargetId = input.openerCdpTargetId;
|
|
7543
7619
|
return cloneRecord2(existing);
|
|
7544
7620
|
}
|
|
7545
|
-
const
|
|
7621
|
+
const record2 = {
|
|
7546
7622
|
id: this.nextId(),
|
|
7547
7623
|
principalId: input.principalId,
|
|
7548
7624
|
workspaceId: input.workspaceId,
|
|
@@ -7558,9 +7634,9 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7558
7634
|
createdAt: this.now(),
|
|
7559
7635
|
state: "active"
|
|
7560
7636
|
};
|
|
7561
|
-
this.records.set(
|
|
7562
|
-
this.activeKeys.set(key,
|
|
7563
|
-
return cloneRecord2(
|
|
7637
|
+
this.records.set(record2.id, record2);
|
|
7638
|
+
this.activeKeys.set(key, record2.id);
|
|
7639
|
+
return cloneRecord2(record2);
|
|
7564
7640
|
}
|
|
7565
7641
|
syncUserTargets(context, browserInstanceId, userTargets) {
|
|
7566
7642
|
const targetsByKey = /* @__PURE__ */ new Map();
|
|
@@ -7572,11 +7648,11 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7572
7648
|
);
|
|
7573
7649
|
}
|
|
7574
7650
|
const invalidated = [];
|
|
7575
|
-
for (const
|
|
7576
|
-
if (
|
|
7577
|
-
const key = targetKey2(
|
|
7651
|
+
for (const record2 of this.records.values()) {
|
|
7652
|
+
if (record2.state !== "active" || record2.origin !== "user_tab" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
|
|
7653
|
+
const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
|
|
7578
7654
|
if (!targetsByKey.has(key)) {
|
|
7579
|
-
invalidated.push(this.invalidate(
|
|
7655
|
+
invalidated.push(this.invalidate(record2, "target_ineligible"));
|
|
7580
7656
|
}
|
|
7581
7657
|
}
|
|
7582
7658
|
const targets = [];
|
|
@@ -7594,7 +7670,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7594
7670
|
targets.push(cloneRecord2(existing));
|
|
7595
7671
|
continue;
|
|
7596
7672
|
}
|
|
7597
|
-
const
|
|
7673
|
+
const record2 = {
|
|
7598
7674
|
id: this.nextId(),
|
|
7599
7675
|
principalId: context.principalId,
|
|
7600
7676
|
workspaceId: context.workspaceId,
|
|
@@ -7608,27 +7684,27 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7608
7684
|
createdAt: this.now(),
|
|
7609
7685
|
state: "active"
|
|
7610
7686
|
};
|
|
7611
|
-
this.records.set(
|
|
7612
|
-
this.activeKeys.set(key,
|
|
7613
|
-
targets.push(cloneRecord2(
|
|
7614
|
-
created.push(cloneRecord2(
|
|
7687
|
+
this.records.set(record2.id, record2);
|
|
7688
|
+
this.activeKeys.set(key, record2.id);
|
|
7689
|
+
targets.push(cloneRecord2(record2));
|
|
7690
|
+
created.push(cloneRecord2(record2));
|
|
7615
7691
|
}
|
|
7616
7692
|
return { targets, created, invalidated };
|
|
7617
7693
|
}
|
|
7618
7694
|
reconcileBrowserTargets(context, browserInstanceId, liveTargets) {
|
|
7619
7695
|
const invalidated = [];
|
|
7620
|
-
for (const
|
|
7621
|
-
if (
|
|
7622
|
-
const live = liveTargets.get(
|
|
7696
|
+
for (const record2 of this.records.values()) {
|
|
7697
|
+
if (record2.state !== "active" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
|
|
7698
|
+
const live = liveTargets.get(record2.cdpTargetId);
|
|
7623
7699
|
if (!live) {
|
|
7624
|
-
invalidated.push(this.invalidate(
|
|
7700
|
+
invalidated.push(this.invalidate(record2, "target_detached"));
|
|
7625
7701
|
continue;
|
|
7626
7702
|
}
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
if (
|
|
7631
|
-
invalidated.push(this.invalidate(
|
|
7703
|
+
record2.title = live.title;
|
|
7704
|
+
record2.url = live.url;
|
|
7705
|
+
record2.openerCdpTargetId = live.openerCdpTargetId;
|
|
7706
|
+
if (record2.profileContextId && live.profileContextId && record2.profileContextId !== live.profileContextId) {
|
|
7707
|
+
invalidated.push(this.invalidate(record2, "target_ineligible"));
|
|
7632
7708
|
}
|
|
7633
7709
|
}
|
|
7634
7710
|
return invalidated;
|
|
@@ -7636,38 +7712,38 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7636
7712
|
list(context, leaseId, scope = "all") {
|
|
7637
7713
|
const activeTargetId = this.activeByLease.get(leaseId);
|
|
7638
7714
|
const records = [...this.records.values()].filter(
|
|
7639
|
-
(
|
|
7715
|
+
(record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && (scope === "all" || scope === "managed_only" && record2.origin !== "user_tab" || scope === "user_tabs" && record2.origin === "user_tab")
|
|
7640
7716
|
).sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
|
|
7641
|
-
return records.map((
|
|
7642
|
-
targetId:
|
|
7643
|
-
profileContextId:
|
|
7644
|
-
title:
|
|
7645
|
-
url:
|
|
7646
|
-
active:
|
|
7647
|
-
origin:
|
|
7648
|
-
...
|
|
7649
|
-
controlState: this.controlState(context, leaseId,
|
|
7717
|
+
return records.map((record2) => ({
|
|
7718
|
+
targetId: record2.id,
|
|
7719
|
+
profileContextId: record2.profileContextId,
|
|
7720
|
+
title: record2.title,
|
|
7721
|
+
url: record2.url,
|
|
7722
|
+
active: record2.id === activeTargetId,
|
|
7723
|
+
origin: record2.origin,
|
|
7724
|
+
...record2.managedTabSetId ? { managedTabSetId: record2.managedTabSetId } : {},
|
|
7725
|
+
controlState: this.controlState(context, leaseId, record2)
|
|
7650
7726
|
}));
|
|
7651
7727
|
}
|
|
7652
7728
|
get(context, targetId) {
|
|
7653
|
-
const
|
|
7654
|
-
if (!
|
|
7729
|
+
const record2 = this.records.get(targetId);
|
|
7730
|
+
if (!record2 || record2.state !== "active") {
|
|
7655
7731
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7656
7732
|
context: { targetId }
|
|
7657
7733
|
});
|
|
7658
7734
|
}
|
|
7659
|
-
this.assertCallerOwns(context,
|
|
7660
|
-
return cloneRecord2(
|
|
7735
|
+
this.assertCallerOwns(context, record2);
|
|
7736
|
+
return cloneRecord2(record2);
|
|
7661
7737
|
}
|
|
7662
7738
|
acquire(context, leaseId, targetId) {
|
|
7663
|
-
const
|
|
7664
|
-
if (!
|
|
7739
|
+
const record2 = this.records.get(targetId);
|
|
7740
|
+
if (!record2 || record2.state !== "active") {
|
|
7665
7741
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7666
7742
|
context: { targetId }
|
|
7667
7743
|
});
|
|
7668
7744
|
}
|
|
7669
|
-
this.assertCallerOwns(context,
|
|
7670
|
-
const physicalKey = physicalTargetKey(
|
|
7745
|
+
this.assertCallerOwns(context, record2);
|
|
7746
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7671
7747
|
const controller = this.controllers.get(physicalKey);
|
|
7672
7748
|
if (controller && (controller.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId)) {
|
|
7673
7749
|
throw new BrowserPilotError("target_busy", "Target is controlled by another Lease", {
|
|
@@ -7682,18 +7758,18 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7682
7758
|
workspaceId: context.workspaceId,
|
|
7683
7759
|
targetId
|
|
7684
7760
|
});
|
|
7685
|
-
|
|
7686
|
-
return { target: cloneRecord2(
|
|
7761
|
+
record2.controllerLeaseId = leaseId;
|
|
7762
|
+
return { target: cloneRecord2(record2), newlyAcquired };
|
|
7687
7763
|
}
|
|
7688
7764
|
release(context, leaseId, targetId) {
|
|
7689
|
-
const
|
|
7690
|
-
if (!
|
|
7691
|
-
this.assertCallerOwns(context,
|
|
7692
|
-
const physicalKey = physicalTargetKey(
|
|
7765
|
+
const record2 = this.records.get(targetId);
|
|
7766
|
+
if (!record2 || record2.state !== "active") return false;
|
|
7767
|
+
this.assertCallerOwns(context, record2);
|
|
7768
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7693
7769
|
const controller = this.controllers.get(physicalKey);
|
|
7694
7770
|
if (controller?.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === targetId) {
|
|
7695
7771
|
this.controllers.delete(physicalKey);
|
|
7696
|
-
|
|
7772
|
+
record2.controllerLeaseId = void 0;
|
|
7697
7773
|
if (this.activeByLease.get(leaseId) === targetId) this.activeByLease.delete(leaseId);
|
|
7698
7774
|
return true;
|
|
7699
7775
|
}
|
|
@@ -7701,128 +7777,128 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7701
7777
|
return false;
|
|
7702
7778
|
}
|
|
7703
7779
|
controlledByLease(leaseId) {
|
|
7704
|
-
return [...this.records.values()].filter((
|
|
7780
|
+
return [...this.records.values()].filter((record2) => record2.state === "active" && record2.controllerLeaseId === leaseId).map(cloneRecord2);
|
|
7705
7781
|
}
|
|
7706
7782
|
releaseLease(leaseId) {
|
|
7707
7783
|
for (const [key, controller] of this.controllers) {
|
|
7708
7784
|
if (controller.leaseId === leaseId) this.controllers.delete(key);
|
|
7709
7785
|
}
|
|
7710
|
-
for (const
|
|
7711
|
-
if (
|
|
7786
|
+
for (const record2 of this.records.values()) {
|
|
7787
|
+
if (record2.controllerLeaseId === leaseId) record2.controllerLeaseId = void 0;
|
|
7712
7788
|
}
|
|
7713
7789
|
this.activeByLease.delete(leaseId);
|
|
7714
7790
|
}
|
|
7715
7791
|
activeTarget(context, leaseId) {
|
|
7716
7792
|
const targetId = this.activeByLease.get(leaseId);
|
|
7717
7793
|
if (!targetId) return void 0;
|
|
7718
|
-
const
|
|
7719
|
-
if (!
|
|
7794
|
+
const record2 = this.records.get(targetId);
|
|
7795
|
+
if (!record2 || record2.state !== "active") {
|
|
7720
7796
|
this.activeByLease.delete(leaseId);
|
|
7721
7797
|
return void 0;
|
|
7722
7798
|
}
|
|
7723
|
-
this.assertCallerOwns(context,
|
|
7724
|
-
return cloneRecord2(
|
|
7799
|
+
this.assertCallerOwns(context, record2);
|
|
7800
|
+
return cloneRecord2(record2);
|
|
7725
7801
|
}
|
|
7726
7802
|
clearActive(context, leaseId) {
|
|
7727
7803
|
const targetId = this.activeByLease.get(leaseId);
|
|
7728
7804
|
if (!targetId) return;
|
|
7729
|
-
const
|
|
7730
|
-
if (
|
|
7805
|
+
const record2 = this.records.get(targetId);
|
|
7806
|
+
if (record2?.state === "active") this.assertCallerOwns(context, record2);
|
|
7731
7807
|
this.activeByLease.delete(leaseId);
|
|
7732
7808
|
}
|
|
7733
7809
|
releaseWorkspace(context) {
|
|
7734
7810
|
const invalidated = [];
|
|
7735
|
-
for (const
|
|
7736
|
-
if (
|
|
7737
|
-
invalidated.push(this.invalidate(
|
|
7811
|
+
for (const record2 of [...this.records.values()]) {
|
|
7812
|
+
if (record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) {
|
|
7813
|
+
invalidated.push(this.invalidate(record2, "target_detached"));
|
|
7738
7814
|
}
|
|
7739
7815
|
}
|
|
7740
7816
|
return invalidated;
|
|
7741
7817
|
}
|
|
7742
7818
|
invalidateBrowserConnection(browserInstanceId) {
|
|
7743
7819
|
const invalidated = [];
|
|
7744
|
-
for (const
|
|
7745
|
-
if (
|
|
7746
|
-
invalidated.push(this.invalidate(
|
|
7820
|
+
for (const record2 of this.records.values()) {
|
|
7821
|
+
if (record2.state === "active" && record2.browserInstanceId === browserInstanceId) {
|
|
7822
|
+
invalidated.push(this.invalidate(record2, "browser_reconnected"));
|
|
7747
7823
|
}
|
|
7748
7824
|
}
|
|
7749
7825
|
return invalidated;
|
|
7750
7826
|
}
|
|
7751
7827
|
setActive(context, leaseId, targetId) {
|
|
7752
|
-
const
|
|
7753
|
-
const controller =
|
|
7754
|
-
if (!
|
|
7828
|
+
const record2 = this.records.get(targetId);
|
|
7829
|
+
const controller = record2 ? this.controllers.get(physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)) : void 0;
|
|
7830
|
+
if (!record2 || record2.state !== "active" || controller?.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId || controller.targetId !== targetId) {
|
|
7755
7831
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Lease", {
|
|
7756
7832
|
context: { targetId, leaseId }
|
|
7757
7833
|
});
|
|
7758
7834
|
}
|
|
7759
|
-
this.assertCallerOwns(context,
|
|
7835
|
+
this.assertCallerOwns(context, record2);
|
|
7760
7836
|
this.activeByLease.set(leaseId, targetId);
|
|
7761
7837
|
}
|
|
7762
7838
|
managedTargets(context, managedTabSetId) {
|
|
7763
7839
|
return [...this.records.values()].filter(
|
|
7764
|
-
(
|
|
7840
|
+
(record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && record2.origin !== "user_tab" && record2.managedTabSetId === managedTabSetId
|
|
7765
7841
|
).map(cloneRecord2);
|
|
7766
7842
|
}
|
|
7767
7843
|
activeRecords(context) {
|
|
7768
7844
|
return [...this.records.values()].filter(
|
|
7769
|
-
(
|
|
7845
|
+
(record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId
|
|
7770
7846
|
).map(cloneRecord2);
|
|
7771
7847
|
}
|
|
7772
7848
|
isManagedCdpTarget(browserInstanceId, cdpTargetId, browserConnectionGeneration) {
|
|
7773
|
-
for (const
|
|
7774
|
-
if (
|
|
7849
|
+
for (const record2 of this.records.values()) {
|
|
7850
|
+
if (record2.browserInstanceId === browserInstanceId && record2.cdpTargetId === cdpTargetId && record2.browserConnectionGeneration === browserConnectionGeneration && record2.origin !== "user_tab") return true;
|
|
7775
7851
|
}
|
|
7776
7852
|
return false;
|
|
7777
7853
|
}
|
|
7778
7854
|
markClosed(context, targetId) {
|
|
7779
|
-
const
|
|
7780
|
-
if (!
|
|
7855
|
+
const record2 = this.records.get(targetId);
|
|
7856
|
+
if (!record2 || record2.state !== "active") {
|
|
7781
7857
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7782
7858
|
context: { targetId }
|
|
7783
7859
|
});
|
|
7784
7860
|
}
|
|
7785
|
-
this.assertCallerOwns(context,
|
|
7861
|
+
this.assertCallerOwns(context, record2);
|
|
7786
7862
|
const invalidated = [];
|
|
7787
7863
|
for (const candidate of this.records.values()) {
|
|
7788
|
-
if (candidate.state === "active" && candidate.browserInstanceId ===
|
|
7864
|
+
if (candidate.state === "active" && candidate.browserInstanceId === record2.browserInstanceId && candidate.cdpTargetId === record2.cdpTargetId) {
|
|
7789
7865
|
invalidated.push(this.invalidate(candidate, "target_closed"));
|
|
7790
7866
|
}
|
|
7791
7867
|
}
|
|
7792
7868
|
return invalidated;
|
|
7793
7869
|
}
|
|
7794
|
-
invalidate(
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
const key = targetKey2(
|
|
7799
|
-
if (this.activeKeys.get(key) ===
|
|
7800
|
-
const physicalKey = physicalTargetKey(
|
|
7801
|
-
if (this.controllers.get(physicalKey)?.targetId ===
|
|
7870
|
+
invalidate(record2, reason2) {
|
|
7871
|
+
record2.state = reason2 === "target_closed" ? "closed" : "detached";
|
|
7872
|
+
record2.invalidatedBy = reason2;
|
|
7873
|
+
record2.controllerLeaseId = void 0;
|
|
7874
|
+
const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
|
|
7875
|
+
if (this.activeKeys.get(key) === record2.id) this.activeKeys.delete(key);
|
|
7876
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7877
|
+
if (this.controllers.get(physicalKey)?.targetId === record2.id) {
|
|
7802
7878
|
this.controllers.delete(physicalKey);
|
|
7803
7879
|
}
|
|
7804
7880
|
for (const [leaseId, targetId] of this.activeByLease) {
|
|
7805
|
-
if (targetId ===
|
|
7881
|
+
if (targetId === record2.id) this.activeByLease.delete(leaseId);
|
|
7806
7882
|
}
|
|
7807
7883
|
return {
|
|
7808
|
-
targetId:
|
|
7809
|
-
workspaceId:
|
|
7810
|
-
browserConnectionGeneration:
|
|
7884
|
+
targetId: record2.id,
|
|
7885
|
+
workspaceId: record2.workspaceId,
|
|
7886
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
7811
7887
|
reason: reason2
|
|
7812
7888
|
};
|
|
7813
7889
|
}
|
|
7814
|
-
assertCallerOwns(context,
|
|
7815
|
-
if (
|
|
7890
|
+
assertCallerOwns(context, record2) {
|
|
7891
|
+
if (record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) return;
|
|
7816
7892
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7817
|
-
context: { targetId:
|
|
7893
|
+
context: { targetId: record2.id }
|
|
7818
7894
|
});
|
|
7819
7895
|
}
|
|
7820
|
-
controlState(context, leaseId,
|
|
7896
|
+
controlState(context, leaseId, record2) {
|
|
7821
7897
|
const controller = this.controllers.get(
|
|
7822
|
-
physicalTargetKey(
|
|
7898
|
+
physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)
|
|
7823
7899
|
);
|
|
7824
7900
|
if (!controller) return "available";
|
|
7825
|
-
return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId ===
|
|
7901
|
+
return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record2.id ? "controlled" : "busy";
|
|
7826
7902
|
}
|
|
7827
7903
|
nextId() {
|
|
7828
7904
|
const id = this.idFactory();
|
|
@@ -8098,17 +8174,17 @@ function parseObject(value, label) {
|
|
|
8098
8174
|
return value;
|
|
8099
8175
|
}
|
|
8100
8176
|
function parseDropdown(value) {
|
|
8101
|
-
const
|
|
8102
|
-
if (
|
|
8103
|
-
if (!["native", "aria"].includes(String(
|
|
8104
|
-
const { ok: _ok, ...info } =
|
|
8177
|
+
const record2 = parseObject(value, "dropdown information");
|
|
8178
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown inspection failed"), "target");
|
|
8179
|
+
if (!["native", "aria"].includes(String(record2.kind)) || typeof record2.expanded !== "boolean" || typeof record2.multiple !== "boolean" || typeof record2.requiresOpen !== "boolean" || !Array.isArray(record2.options) || typeof record2.truncated !== "boolean") throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown information");
|
|
8180
|
+
const { ok: _ok, ...info } = record2;
|
|
8105
8181
|
return info;
|
|
8106
8182
|
}
|
|
8107
8183
|
function parseEvidence(value) {
|
|
8108
|
-
const
|
|
8109
|
-
if (
|
|
8110
|
-
if (
|
|
8111
|
-
const { ok: _ok, ...evidence } =
|
|
8184
|
+
const record2 = parseObject(value, "dropdown selection evidence");
|
|
8185
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown selection failed"), "target");
|
|
8186
|
+
if (record2.action !== "select" || !["verified", "mismatch", "unavailable"].includes(String(record2.status)) || !["native", "aria"].includes(String(record2.kind)) || !Array.isArray(record2.selected)) throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown selection evidence");
|
|
8187
|
+
const { ok: _ok, ...evidence } = record2;
|
|
8112
8188
|
return evidence;
|
|
8113
8189
|
}
|
|
8114
8190
|
function selectorExpression(selector, functionDeclaration, argument) {
|
|
@@ -8323,11 +8399,11 @@ var FrameService = class _FrameService {
|
|
|
8323
8399
|
|
|
8324
8400
|
// src/services/observation-store.ts
|
|
8325
8401
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
8326
|
-
function clone(
|
|
8402
|
+
function clone(record2) {
|
|
8327
8403
|
return {
|
|
8328
|
-
...
|
|
8329
|
-
truncationReasons: [...
|
|
8330
|
-
refs:
|
|
8404
|
+
...record2,
|
|
8405
|
+
truncationReasons: [...record2.truncationReasons],
|
|
8406
|
+
refs: record2.refs.map((ref) => ({ ...ref }))
|
|
8331
8407
|
};
|
|
8332
8408
|
}
|
|
8333
8409
|
var MemoryObservationStore = class {
|
|
@@ -8368,7 +8444,7 @@ var MemoryObservationStore = class {
|
|
|
8368
8444
|
throw new BrowserPilotError("internal_error", "Invalid or duplicate Observation ID");
|
|
8369
8445
|
}
|
|
8370
8446
|
const createdAt = this.now();
|
|
8371
|
-
const
|
|
8447
|
+
const record2 = {
|
|
8372
8448
|
id,
|
|
8373
8449
|
workspaceId: input.workspaceId,
|
|
8374
8450
|
leaseId: input.leaseId,
|
|
@@ -8388,58 +8464,58 @@ var MemoryObservationStore = class {
|
|
|
8388
8464
|
truncationReasons: [...input.truncationReasons],
|
|
8389
8465
|
refs: input.refs.map((ref) => ({ ...ref }))
|
|
8390
8466
|
};
|
|
8391
|
-
this.records.set(id,
|
|
8392
|
-
return clone(
|
|
8467
|
+
this.records.set(id, record2);
|
|
8468
|
+
return clone(record2);
|
|
8393
8469
|
}
|
|
8394
8470
|
resolve(input) {
|
|
8395
8471
|
const now = this.now();
|
|
8396
|
-
const
|
|
8397
|
-
if (!
|
|
8472
|
+
const record2 = this.records.get(input.observationId);
|
|
8473
|
+
if (!record2 || record2.workspaceId !== input.workspaceId || record2.leaseId !== input.leaseId || record2.targetId !== input.targetId) {
|
|
8398
8474
|
this.sweepAt(now);
|
|
8399
8475
|
throw this.stale(input);
|
|
8400
8476
|
}
|
|
8401
|
-
if (
|
|
8402
|
-
const reason2 =
|
|
8477
|
+
if (record2.invalidatedBy) {
|
|
8478
|
+
const reason2 = record2.invalidatedBy;
|
|
8403
8479
|
this.sweepAt(now);
|
|
8404
8480
|
throw this.stale(input, reason2);
|
|
8405
8481
|
}
|
|
8406
|
-
if (
|
|
8407
|
-
this.invalidateRecord(
|
|
8408
|
-
this.records.delete(
|
|
8482
|
+
if (record2.expiresAt <= now) {
|
|
8483
|
+
this.invalidateRecord(record2, "expired");
|
|
8484
|
+
this.records.delete(record2.id);
|
|
8409
8485
|
this.sweepAt(now);
|
|
8410
8486
|
throw this.stale(input, "expired");
|
|
8411
8487
|
}
|
|
8412
8488
|
this.sweepAt(now);
|
|
8413
|
-
if (
|
|
8414
|
-
this.invalidateRecord(
|
|
8489
|
+
if (record2.browserProcessIdentity !== input.browserProcessIdentity || record2.browserConnectionGeneration !== input.browserConnectionGeneration) {
|
|
8490
|
+
this.invalidateRecord(record2, "browser_reconnected");
|
|
8415
8491
|
throw this.stale(input, "browser_reconnected");
|
|
8416
8492
|
}
|
|
8417
|
-
if (
|
|
8418
|
-
this.invalidateRecord(
|
|
8493
|
+
if (record2.sessionId !== input.sessionId) {
|
|
8494
|
+
this.invalidateRecord(record2, "session_replaced");
|
|
8419
8495
|
throw this.stale(input, "session_replaced");
|
|
8420
8496
|
}
|
|
8421
|
-
if (
|
|
8422
|
-
this.invalidateRecord(
|
|
8497
|
+
if (record2.frameId !== input.frameId) {
|
|
8498
|
+
this.invalidateRecord(record2, "frame_changed");
|
|
8423
8499
|
throw this.stale(input, "frame_changed");
|
|
8424
8500
|
}
|
|
8425
|
-
if (
|
|
8426
|
-
this.invalidateRecord(
|
|
8501
|
+
if (record2.loaderId !== input.loaderId) {
|
|
8502
|
+
this.invalidateRecord(record2, "loader_replaced");
|
|
8427
8503
|
throw this.stale(input, "loader_replaced");
|
|
8428
8504
|
}
|
|
8429
|
-
if (
|
|
8430
|
-
this.invalidateRecord(
|
|
8505
|
+
if (record2.documentGeneration !== input.documentGeneration) {
|
|
8506
|
+
this.invalidateRecord(record2, "document_replaced");
|
|
8431
8507
|
throw this.stale(input, "document_replaced");
|
|
8432
8508
|
}
|
|
8433
|
-
if (input.ref !== void 0 && (input.ref < 1 || input.ref >
|
|
8509
|
+
if (input.ref !== void 0 && (input.ref < 1 || input.ref > record2.refs.length)) {
|
|
8434
8510
|
throw this.stale(input);
|
|
8435
8511
|
}
|
|
8436
|
-
return clone(
|
|
8512
|
+
return clone(record2);
|
|
8437
8513
|
}
|
|
8438
8514
|
latest(workspaceId, leaseId, targetId) {
|
|
8439
8515
|
this.sweep();
|
|
8440
8516
|
let latest;
|
|
8441
|
-
for (const
|
|
8442
|
-
if (
|
|
8517
|
+
for (const record2 of this.records.values()) {
|
|
8518
|
+
if (record2.workspaceId === workspaceId && record2.leaseId === leaseId && record2.targetId === targetId && !record2.invalidatedBy && (!latest || record2.createdAt >= latest.createdAt)) latest = record2;
|
|
8443
8519
|
}
|
|
8444
8520
|
if (latest) return clone(latest);
|
|
8445
8521
|
throw new BrowserPilotError("stale_ref", "No current Observation exists for this target", {
|
|
@@ -8447,25 +8523,25 @@ var MemoryObservationStore = class {
|
|
|
8447
8523
|
});
|
|
8448
8524
|
}
|
|
8449
8525
|
invalidateTarget(targetId, reason2) {
|
|
8450
|
-
for (const
|
|
8451
|
-
if (
|
|
8526
|
+
for (const record2 of this.records.values()) {
|
|
8527
|
+
if (record2.targetId === targetId && !record2.invalidatedBy) this.invalidateRecord(record2, reason2);
|
|
8452
8528
|
}
|
|
8453
8529
|
}
|
|
8454
8530
|
invalidateSession(sessionId, reason2 = "session_replaced") {
|
|
8455
|
-
for (const
|
|
8456
|
-
if (
|
|
8457
|
-
this.invalidateRecord(
|
|
8531
|
+
for (const record2 of this.records.values()) {
|
|
8532
|
+
if (record2.sessionId === sessionId && !record2.invalidatedBy) {
|
|
8533
|
+
this.invalidateRecord(record2, reason2);
|
|
8458
8534
|
}
|
|
8459
8535
|
}
|
|
8460
8536
|
}
|
|
8461
8537
|
releaseLease(leaseId) {
|
|
8462
|
-
for (const [id,
|
|
8463
|
-
if (
|
|
8538
|
+
for (const [id, record2] of this.records) {
|
|
8539
|
+
if (record2.leaseId === leaseId) this.records.delete(id);
|
|
8464
8540
|
}
|
|
8465
8541
|
}
|
|
8466
8542
|
releaseWorkspace(workspaceId) {
|
|
8467
|
-
for (const [id,
|
|
8468
|
-
if (
|
|
8543
|
+
for (const [id, record2] of this.records) {
|
|
8544
|
+
if (record2.workspaceId === workspaceId) this.records.delete(id);
|
|
8469
8545
|
}
|
|
8470
8546
|
}
|
|
8471
8547
|
sweep() {
|
|
@@ -8473,9 +8549,9 @@ var MemoryObservationStore = class {
|
|
|
8473
8549
|
}
|
|
8474
8550
|
sweepAt(now) {
|
|
8475
8551
|
let removed = 0;
|
|
8476
|
-
for (const [id,
|
|
8477
|
-
if (
|
|
8478
|
-
this.invalidateRecord(
|
|
8552
|
+
for (const [id, record2] of this.records) {
|
|
8553
|
+
if (record2.expiresAt > now) continue;
|
|
8554
|
+
this.invalidateRecord(record2, "expired");
|
|
8479
8555
|
this.records.delete(id);
|
|
8480
8556
|
removed += 1;
|
|
8481
8557
|
}
|
|
@@ -8484,9 +8560,9 @@ var MemoryObservationStore = class {
|
|
|
8484
8560
|
size() {
|
|
8485
8561
|
return this.records.size;
|
|
8486
8562
|
}
|
|
8487
|
-
invalidateRecord(
|
|
8488
|
-
|
|
8489
|
-
|
|
8563
|
+
invalidateRecord(record2, reason2) {
|
|
8564
|
+
record2.invalidatedBy = reason2;
|
|
8565
|
+
record2.refs = [];
|
|
8490
8566
|
}
|
|
8491
8567
|
stale(input, reason2) {
|
|
8492
8568
|
return new BrowserPilotError("stale_ref", "Observation ref is stale or does not belong to this context", {
|
|
@@ -8557,22 +8633,22 @@ var PageContentService = class {
|
|
|
8557
8633
|
if (typeof data !== "object" || data === null) {
|
|
8558
8634
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
|
|
8559
8635
|
}
|
|
8560
|
-
const
|
|
8561
|
-
if (
|
|
8636
|
+
const record2 = data;
|
|
8637
|
+
if (record2.ok !== true) {
|
|
8562
8638
|
throw invalidArgument(
|
|
8563
|
-
typeof
|
|
8639
|
+
typeof record2.error === "string" ? record2.error : "Failed to read page content",
|
|
8564
8640
|
selector ? "selector" : void 0
|
|
8565
8641
|
);
|
|
8566
8642
|
}
|
|
8567
|
-
if (typeof
|
|
8643
|
+
if (typeof record2.title !== "string" || typeof record2.url !== "string" || typeof record2.text !== "string" || !Number.isSafeInteger(record2.length) || typeof record2.truncated !== "boolean") {
|
|
8568
8644
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
|
|
8569
8645
|
}
|
|
8570
8646
|
return {
|
|
8571
|
-
title:
|
|
8572
|
-
url:
|
|
8573
|
-
text:
|
|
8574
|
-
length:
|
|
8575
|
-
truncated:
|
|
8647
|
+
title: record2.title,
|
|
8648
|
+
url: record2.url,
|
|
8649
|
+
text: record2.text,
|
|
8650
|
+
length: record2.length,
|
|
8651
|
+
truncated: record2.truncated
|
|
8576
8652
|
};
|
|
8577
8653
|
}
|
|
8578
8654
|
};
|
|
@@ -8951,13 +9027,13 @@ function parseEvidence2(value) {
|
|
|
8951
9027
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8952
9028
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
|
|
8953
9029
|
}
|
|
8954
|
-
const
|
|
8955
|
-
if (
|
|
9030
|
+
const record2 = value;
|
|
9031
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Scroll failed"), "target");
|
|
8956
9032
|
const numeric = ["deltaX", "deltaY", "beforeX", "beforeY", "afterX", "afterY"];
|
|
8957
|
-
if (
|
|
9033
|
+
if (record2.action !== "scroll" || !["verified", "mismatch"].includes(String(record2.status)) || !["relative", "position", "text"].includes(String(record2.mode)) || !["page", "element", "text"].includes(String(record2.target)) || typeof record2.moved !== "boolean" || numeric.some((key) => !Number.isFinite(record2[key]))) {
|
|
8958
9034
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
|
|
8959
9035
|
}
|
|
8960
|
-
const { ok: _ok, ...evidence } =
|
|
9036
|
+
const { ok: _ok, ...evidence } = record2;
|
|
8961
9037
|
return evidence;
|
|
8962
9038
|
}
|
|
8963
9039
|
function validateInput(input) {
|
|
@@ -9459,7 +9535,7 @@ var TargetInventoryService = class {
|
|
|
9459
9535
|
}
|
|
9460
9536
|
adoptManagedPopups(context, liveTargets) {
|
|
9461
9537
|
const active = new Map(
|
|
9462
|
-
this.registry.activeRecords(context).map((
|
|
9538
|
+
this.registry.activeRecords(context).map((record2) => [record2.cdpTargetId, record2])
|
|
9463
9539
|
);
|
|
9464
9540
|
let adopted = true;
|
|
9465
9541
|
while (adopted) {
|
|
@@ -9468,7 +9544,7 @@ var TargetInventoryService = class {
|
|
|
9468
9544
|
if (active.has(target.cdpTargetId) || target.type && target.type !== "page") continue;
|
|
9469
9545
|
const ancestor = this.findManagedAncestor(target, active, liveTargets);
|
|
9470
9546
|
if (!ancestor?.managedTabSetId || ancestor.profileContextId && target.profileContextId && ancestor.profileContextId !== target.profileContextId) continue;
|
|
9471
|
-
const
|
|
9547
|
+
const record2 = this.registry.registerManaged({
|
|
9472
9548
|
principalId: context.principalId,
|
|
9473
9549
|
workspaceId: context.workspaceId,
|
|
9474
9550
|
browserInstanceId: this.browserInstanceId,
|
|
@@ -9481,9 +9557,9 @@ var TargetInventoryService = class {
|
|
|
9481
9557
|
title: target.title,
|
|
9482
9558
|
url: target.url
|
|
9483
9559
|
});
|
|
9484
|
-
active.set(
|
|
9485
|
-
this.notify(this.onTargetAttached,
|
|
9486
|
-
this.notify(this.onPopup,
|
|
9560
|
+
active.set(record2.cdpTargetId, record2);
|
|
9561
|
+
this.notify(this.onTargetAttached, record2);
|
|
9562
|
+
this.notify(this.onPopup, record2);
|
|
9487
9563
|
adopted = true;
|
|
9488
9564
|
}
|
|
9489
9565
|
}
|
|
@@ -9520,8 +9596,91 @@ var TargetInventoryService = class {
|
|
|
9520
9596
|
}
|
|
9521
9597
|
};
|
|
9522
9598
|
|
|
9599
|
+
// src/services/chrome-profile-identity.ts
|
|
9600
|
+
import { open } from "fs/promises";
|
|
9601
|
+
import { basename as basename2, dirname as dirname2, join as join4, resolve } from "path";
|
|
9602
|
+
var MAX_LOCAL_STATE_BYTES = 16 * 1024 * 1024;
|
|
9603
|
+
var MAX_IDENTITY_TEXT_LENGTH = 4096;
|
|
9604
|
+
var ProfileIdentityError = class extends Error {
|
|
9605
|
+
constructor(code, message) {
|
|
9606
|
+
super(message);
|
|
9607
|
+
this.code = code;
|
|
9608
|
+
this.name = "ProfileIdentityError";
|
|
9609
|
+
}
|
|
9610
|
+
};
|
|
9611
|
+
function record(value) {
|
|
9612
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
9613
|
+
}
|
|
9614
|
+
function identityText(value) {
|
|
9615
|
+
if (typeof value !== "string") return void 0;
|
|
9616
|
+
const normalized = value.trim().slice(0, MAX_IDENTITY_TEXT_LENGTH);
|
|
9617
|
+
return normalized || void 0;
|
|
9618
|
+
}
|
|
9619
|
+
async function readVerifiedChromeProfileIdentity(userDataRoot, reportedProfilePath) {
|
|
9620
|
+
const root = resolve(userDataRoot);
|
|
9621
|
+
const profilePath = resolve(reportedProfilePath);
|
|
9622
|
+
if (dirname2(profilePath) !== root) {
|
|
9623
|
+
throw new ProfileIdentityError(
|
|
9624
|
+
"profile_path_unverified",
|
|
9625
|
+
"Chrome reported a Profile path outside the connected browser user-data root"
|
|
9626
|
+
);
|
|
9627
|
+
}
|
|
9628
|
+
const profileDirectory = basename2(profilePath);
|
|
9629
|
+
if (!profileDirectory || profileDirectory === "." || profileDirectory === "..") {
|
|
9630
|
+
throw new ProfileIdentityError("profile_path_unverified", "Chrome reported an invalid Profile directory");
|
|
9631
|
+
}
|
|
9632
|
+
const localStatePath = join4(root, "Local State");
|
|
9633
|
+
let bytes;
|
|
9634
|
+
let handle;
|
|
9635
|
+
try {
|
|
9636
|
+
handle = await open(localStatePath, "r");
|
|
9637
|
+
const metadata = await handle.stat();
|
|
9638
|
+
if (!metadata.isFile() || metadata.size > MAX_LOCAL_STATE_BYTES) {
|
|
9639
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State is missing or too large");
|
|
9640
|
+
}
|
|
9641
|
+
bytes = Buffer.alloc(metadata.size);
|
|
9642
|
+
let offset = 0;
|
|
9643
|
+
while (offset < bytes.length) {
|
|
9644
|
+
const result = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
9645
|
+
if (result.bytesRead === 0) break;
|
|
9646
|
+
offset += result.bytesRead;
|
|
9647
|
+
}
|
|
9648
|
+
if (offset !== bytes.length) bytes = bytes.subarray(0, offset);
|
|
9649
|
+
} catch (error) {
|
|
9650
|
+
if (error instanceof ProfileIdentityError) throw error;
|
|
9651
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State could not be read");
|
|
9652
|
+
} finally {
|
|
9653
|
+
await handle?.close().catch(() => {
|
|
9654
|
+
});
|
|
9655
|
+
}
|
|
9656
|
+
let rootState;
|
|
9657
|
+
try {
|
|
9658
|
+
rootState = record(JSON.parse(bytes.toString("utf8")));
|
|
9659
|
+
} catch {
|
|
9660
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State contains invalid JSON");
|
|
9661
|
+
}
|
|
9662
|
+
const profileState = record(rootState?.profile);
|
|
9663
|
+
const infoCache = record(profileState?.info_cache);
|
|
9664
|
+
const entry = record(infoCache?.[profileDirectory]);
|
|
9665
|
+
const profileName = identityText(entry?.name);
|
|
9666
|
+
if (!profileName) {
|
|
9667
|
+
throw new ProfileIdentityError(
|
|
9668
|
+
"profile_metadata_missing",
|
|
9669
|
+
"Chrome Local State has no verified metadata for the reported Profile directory"
|
|
9670
|
+
);
|
|
9671
|
+
}
|
|
9672
|
+
const accountName = identityText(entry?.gaia_name) ?? identityText(entry?.gaia_given_name);
|
|
9673
|
+
const accountEmail = identityText(entry?.user_name);
|
|
9674
|
+
return {
|
|
9675
|
+
profileName,
|
|
9676
|
+
profileDirectory,
|
|
9677
|
+
...accountName ? { accountName } : {},
|
|
9678
|
+
...accountEmail ? { accountEmail } : {}
|
|
9679
|
+
};
|
|
9680
|
+
}
|
|
9681
|
+
|
|
9523
9682
|
// src/services/upload-service.ts
|
|
9524
|
-
import { basename as
|
|
9683
|
+
import { basename as basename3 } from "path";
|
|
9525
9684
|
var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
|
|
9526
9685
|
"detached",
|
|
9527
9686
|
"disabled",
|
|
@@ -9547,17 +9706,17 @@ function parseFileInputState(value) {
|
|
|
9547
9706
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9548
9707
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
|
|
9549
9708
|
}
|
|
9550
|
-
const
|
|
9551
|
-
if (
|
|
9552
|
-
return { status: "blocked", reason:
|
|
9709
|
+
const record2 = value;
|
|
9710
|
+
if (record2.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record2.reason)) {
|
|
9711
|
+
return { status: "blocked", reason: record2.reason };
|
|
9553
9712
|
}
|
|
9554
|
-
if (
|
|
9713
|
+
if (record2.status !== "ready" || !Number.isSafeInteger(record2.fileCount) || Number(record2.fileCount) < 0 || record2.firstFileName !== void 0 && (typeof record2.firstFileName !== "string" || record2.firstFileName.length > 4096)) {
|
|
9555
9714
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
|
|
9556
9715
|
}
|
|
9557
9716
|
return {
|
|
9558
9717
|
status: "ready",
|
|
9559
|
-
fileCount: Number(
|
|
9560
|
-
...
|
|
9718
|
+
fileCount: Number(record2.fileCount),
|
|
9719
|
+
...record2.firstFileName !== void 0 ? { firstFileName: record2.firstFileName } : {}
|
|
9561
9720
|
};
|
|
9562
9721
|
}
|
|
9563
9722
|
var UploadService = class {
|
|
@@ -9644,7 +9803,7 @@ var UploadService = class {
|
|
|
9644
9803
|
backendNodeId
|
|
9645
9804
|
}, this.sessionId);
|
|
9646
9805
|
if (this.readbackDelayMs > 0) {
|
|
9647
|
-
await new Promise((
|
|
9806
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
9648
9807
|
}
|
|
9649
9808
|
let after;
|
|
9650
9809
|
try {
|
|
@@ -9666,7 +9825,7 @@ var UploadService = class {
|
|
|
9666
9825
|
reason: "target_unavailable"
|
|
9667
9826
|
};
|
|
9668
9827
|
}
|
|
9669
|
-
const nameMatched = after.firstFileName ===
|
|
9828
|
+
const nameMatched = after.firstFileName === basename3(filePath);
|
|
9670
9829
|
const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
|
|
9671
9830
|
const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
|
|
9672
9831
|
return {
|
|
@@ -9798,39 +9957,39 @@ function publicRule(rule) {
|
|
|
9798
9957
|
...rule.type === "headers" ? { headers: rule.headers } : {}
|
|
9799
9958
|
};
|
|
9800
9959
|
}
|
|
9801
|
-
function publicRequest(
|
|
9802
|
-
const durationMs =
|
|
9960
|
+
function publicRequest(record2) {
|
|
9961
|
+
const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
|
|
9803
9962
|
return {
|
|
9804
|
-
requestId:
|
|
9805
|
-
sequence:
|
|
9806
|
-
method:
|
|
9807
|
-
url:
|
|
9808
|
-
type:
|
|
9809
|
-
requestHeaders:
|
|
9810
|
-
...
|
|
9811
|
-
postDataTruncated:
|
|
9812
|
-
...
|
|
9813
|
-
...
|
|
9814
|
-
...
|
|
9815
|
-
...
|
|
9816
|
-
...
|
|
9963
|
+
requestId: record2.id,
|
|
9964
|
+
sequence: record2.sequence,
|
|
9965
|
+
method: record2.method,
|
|
9966
|
+
url: record2.url,
|
|
9967
|
+
type: record2.type,
|
|
9968
|
+
requestHeaders: record2.requestHeaders,
|
|
9969
|
+
...record2.postData !== void 0 ? { postData: record2.postData } : {},
|
|
9970
|
+
postDataTruncated: record2.postDataTruncated,
|
|
9971
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
9972
|
+
...record2.statusText !== void 0 ? { statusText: record2.statusText } : {},
|
|
9973
|
+
...record2.responseHeaders !== void 0 ? { responseHeaders: record2.responseHeaders } : {},
|
|
9974
|
+
...record2.mimeType !== void 0 ? { mimeType: record2.mimeType } : {},
|
|
9975
|
+
...record2.size !== void 0 ? { size: record2.size } : {},
|
|
9817
9976
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
9818
|
-
...
|
|
9819
|
-
bodyAvailable:
|
|
9977
|
+
...record2.error !== void 0 ? { error: record2.error } : {},
|
|
9978
|
+
bodyAvailable: record2.bodyAvailable
|
|
9820
9979
|
};
|
|
9821
9980
|
}
|
|
9822
|
-
function publicRequestSummary(
|
|
9823
|
-
const durationMs =
|
|
9981
|
+
function publicRequestSummary(record2) {
|
|
9982
|
+
const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
|
|
9824
9983
|
return {
|
|
9825
|
-
requestId:
|
|
9826
|
-
sequence:
|
|
9827
|
-
method:
|
|
9828
|
-
url:
|
|
9829
|
-
type:
|
|
9830
|
-
...
|
|
9831
|
-
...
|
|
9984
|
+
requestId: record2.id,
|
|
9985
|
+
sequence: record2.sequence,
|
|
9986
|
+
method: record2.method,
|
|
9987
|
+
url: record2.url,
|
|
9988
|
+
type: record2.type,
|
|
9989
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
9990
|
+
...record2.size !== void 0 ? { size: record2.size } : {},
|
|
9832
9991
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
9833
|
-
...
|
|
9992
|
+
...record2.error !== void 0 ? { error: record2.error } : {}
|
|
9834
9993
|
};
|
|
9835
9994
|
}
|
|
9836
9995
|
var WorkspaceNetworkController = class {
|
|
@@ -9992,15 +10151,15 @@ var WorkspaceNetworkController = class {
|
|
|
9992
10151
|
};
|
|
9993
10152
|
}
|
|
9994
10153
|
async request(workspaceId, requestId, includeBody) {
|
|
9995
|
-
const
|
|
9996
|
-
if (!
|
|
9997
|
-
const base = { request: publicRequest(
|
|
9998
|
-
if (!includeBody || !
|
|
10154
|
+
const record2 = this.state(workspaceId).requestsById.get(requestId);
|
|
10155
|
+
if (!record2) throw invalidArgument("Network request was not found in this Workspace", "requestId");
|
|
10156
|
+
const base = { request: publicRequest(record2), bodyTruncated: false };
|
|
10157
|
+
if (!includeBody || !record2.bodyAvailable) return base;
|
|
9999
10158
|
try {
|
|
10000
10159
|
const result = await this.transport.send(
|
|
10001
10160
|
"Network.getResponseBody",
|
|
10002
|
-
{ requestId:
|
|
10003
|
-
|
|
10161
|
+
{ requestId: record2.networkId },
|
|
10162
|
+
record2.sessionId
|
|
10004
10163
|
);
|
|
10005
10164
|
if (typeof result?.body !== "string") return base;
|
|
10006
10165
|
const truncated = result.body.length > MAX_BODY_CHARACTERS;
|
|
@@ -10008,7 +10167,7 @@ var WorkspaceNetworkController = class {
|
|
|
10008
10167
|
...base,
|
|
10009
10168
|
body: result.body.slice(0, MAX_BODY_CHARACTERS),
|
|
10010
10169
|
bodyEncoding: result.base64Encoded === true ? "base64" : "utf8",
|
|
10011
|
-
...
|
|
10170
|
+
...record2.mimeType ? { mimeType: record2.mimeType } : {},
|
|
10012
10171
|
bodyTruncated: truncated
|
|
10013
10172
|
};
|
|
10014
10173
|
} catch {
|
|
@@ -10098,20 +10257,20 @@ var WorkspaceNetworkController = class {
|
|
|
10098
10257
|
});
|
|
10099
10258
|
this.transport.on?.("Network.loadingFinished", (params, sessionId) => {
|
|
10100
10259
|
if (!sessionId) return;
|
|
10101
|
-
const
|
|
10102
|
-
if (!
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
this.refreshRequestSize(
|
|
10260
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10261
|
+
if (!record2) return;
|
|
10262
|
+
record2.size = Math.max(0, Math.trunc(Number(params?.encodedDataLength) || 0));
|
|
10263
|
+
record2.endedAt = this.now();
|
|
10264
|
+
record2.bodyAvailable = true;
|
|
10265
|
+
this.refreshRequestSize(record2);
|
|
10107
10266
|
});
|
|
10108
10267
|
this.transport.on?.("Network.loadingFailed", (params, sessionId) => {
|
|
10109
10268
|
if (!sessionId) return;
|
|
10110
|
-
const
|
|
10111
|
-
if (!
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
this.refreshRequestSize(
|
|
10269
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10270
|
+
if (!record2) return;
|
|
10271
|
+
record2.error = boundedString2(params?.errorText, 4096);
|
|
10272
|
+
record2.endedAt = this.now();
|
|
10273
|
+
this.refreshRequestSize(record2);
|
|
10115
10274
|
});
|
|
10116
10275
|
}
|
|
10117
10276
|
async handleAuthRequired(params, sessionId) {
|
|
@@ -10179,7 +10338,7 @@ var WorkspaceNetworkController = class {
|
|
|
10179
10338
|
this.publishResponse(redirected);
|
|
10180
10339
|
}
|
|
10181
10340
|
const rawPostData = typeof params?.request?.postData === "string" ? params.request.postData : void 0;
|
|
10182
|
-
const
|
|
10341
|
+
const record2 = {
|
|
10183
10342
|
id: this.requestIdFactory(),
|
|
10184
10343
|
sequence: state.nextSequence++,
|
|
10185
10344
|
workspaceId: session.workspaceId,
|
|
@@ -10198,55 +10357,55 @@ var WorkspaceNetworkController = class {
|
|
|
10198
10357
|
bodyAvailable: false,
|
|
10199
10358
|
retainedByteSize: 0
|
|
10200
10359
|
};
|
|
10201
|
-
|
|
10202
|
-
state.requests.push(
|
|
10203
|
-
state.requestBytes +=
|
|
10204
|
-
state.requestsById.set(
|
|
10205
|
-
state.requestsByNetworkKey.set(key,
|
|
10360
|
+
record2.retainedByteSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
|
|
10361
|
+
state.requests.push(record2);
|
|
10362
|
+
state.requestBytes += record2.retainedByteSize;
|
|
10363
|
+
state.requestsById.set(record2.id, record2);
|
|
10364
|
+
state.requestsByNetworkKey.set(key, record2);
|
|
10206
10365
|
this.compactRequests(state);
|
|
10207
10366
|
this.safePublish({
|
|
10208
|
-
workspaceId:
|
|
10209
|
-
leaseId:
|
|
10210
|
-
targetId:
|
|
10211
|
-
browserConnectionGeneration:
|
|
10367
|
+
workspaceId: record2.workspaceId,
|
|
10368
|
+
leaseId: record2.leaseId,
|
|
10369
|
+
targetId: record2.targetId,
|
|
10370
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
10212
10371
|
type: "network.request",
|
|
10213
10372
|
sensitivity: "browser_data",
|
|
10214
10373
|
payload: {
|
|
10215
|
-
requestId:
|
|
10216
|
-
method:
|
|
10217
|
-
url: sanitizedEventUrl(
|
|
10218
|
-
type:
|
|
10374
|
+
requestId: record2.id,
|
|
10375
|
+
method: record2.method,
|
|
10376
|
+
url: sanitizedEventUrl(record2.url),
|
|
10377
|
+
type: record2.type
|
|
10219
10378
|
}
|
|
10220
10379
|
});
|
|
10221
10380
|
}
|
|
10222
10381
|
trackResponse(params, sessionId) {
|
|
10223
|
-
const
|
|
10224
|
-
if (!
|
|
10225
|
-
this.applyResponse(
|
|
10226
|
-
this.refreshRequestSize(
|
|
10227
|
-
this.publishResponse(
|
|
10228
|
-
}
|
|
10229
|
-
applyResponse(
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
}
|
|
10235
|
-
publishResponse(
|
|
10236
|
-
const hint = accessBlockedAgentHint(
|
|
10382
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10383
|
+
if (!record2) return;
|
|
10384
|
+
this.applyResponse(record2, params?.response);
|
|
10385
|
+
this.refreshRequestSize(record2);
|
|
10386
|
+
this.publishResponse(record2);
|
|
10387
|
+
}
|
|
10388
|
+
applyResponse(record2, response) {
|
|
10389
|
+
record2.status = validStatus(response?.status);
|
|
10390
|
+
record2.statusText = boundedString2(response?.statusText, 4096);
|
|
10391
|
+
record2.responseHeaders = headersFromCdp(response?.headers);
|
|
10392
|
+
record2.mimeType = boundedString2(response?.mimeType, 256);
|
|
10393
|
+
}
|
|
10394
|
+
publishResponse(record2) {
|
|
10395
|
+
const hint = accessBlockedAgentHint(record2.type, record2.status);
|
|
10237
10396
|
this.safePublish({
|
|
10238
|
-
workspaceId:
|
|
10239
|
-
leaseId:
|
|
10240
|
-
targetId:
|
|
10241
|
-
browserConnectionGeneration:
|
|
10397
|
+
workspaceId: record2.workspaceId,
|
|
10398
|
+
leaseId: record2.leaseId,
|
|
10399
|
+
targetId: record2.targetId,
|
|
10400
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
10242
10401
|
type: "network.response",
|
|
10243
10402
|
sensitivity: "browser_data",
|
|
10244
10403
|
payload: {
|
|
10245
|
-
requestId:
|
|
10246
|
-
method:
|
|
10247
|
-
url: sanitizedEventUrl(
|
|
10248
|
-
type:
|
|
10249
|
-
...
|
|
10404
|
+
requestId: record2.id,
|
|
10405
|
+
method: record2.method,
|
|
10406
|
+
url: sanitizedEventUrl(record2.url),
|
|
10407
|
+
type: record2.type,
|
|
10408
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
10250
10409
|
...hint ? { hints: [hint] } : {}
|
|
10251
10410
|
}
|
|
10252
10411
|
});
|
|
@@ -10256,12 +10415,12 @@ var WorkspaceNetworkController = class {
|
|
|
10256
10415
|
if (!session || typeof requestId !== "string") return void 0;
|
|
10257
10416
|
return this.workspaces.get(session.workspaceId)?.requestsByNetworkKey.get(networkKey(sessionId, requestId));
|
|
10258
10417
|
}
|
|
10259
|
-
refreshRequestSize(
|
|
10260
|
-
const state = this.workspaces.get(
|
|
10261
|
-
if (!state || state.requestsById.get(
|
|
10262
|
-
const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(
|
|
10263
|
-
state.requestBytes += nextSize -
|
|
10264
|
-
|
|
10418
|
+
refreshRequestSize(record2) {
|
|
10419
|
+
const state = this.workspaces.get(record2.workspaceId);
|
|
10420
|
+
if (!state || state.requestsById.get(record2.id) !== record2) return;
|
|
10421
|
+
const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
|
|
10422
|
+
state.requestBytes += nextSize - record2.retainedByteSize;
|
|
10423
|
+
record2.retainedByteSize = nextSize;
|
|
10265
10424
|
this.compactRequests(state);
|
|
10266
10425
|
}
|
|
10267
10426
|
compactRequests(state) {
|
|
@@ -10301,6 +10460,7 @@ var BASE_BROWSER_TOOL_NAMES = [
|
|
|
10301
10460
|
"browser.discover",
|
|
10302
10461
|
"browser.connect",
|
|
10303
10462
|
"browser.profiles.list",
|
|
10463
|
+
"browser.profiles.identify",
|
|
10304
10464
|
"browser.profiles.select",
|
|
10305
10465
|
"browser.open",
|
|
10306
10466
|
"browser.observe",
|
|
@@ -10365,20 +10525,20 @@ function stateFingerprint(value) {
|
|
|
10365
10525
|
return createHash4("sha256").update(JSON.stringify(value) ?? "undefined").digest("hex");
|
|
10366
10526
|
}
|
|
10367
10527
|
function elementAddress(value) {
|
|
10368
|
-
const
|
|
10369
|
-
if (typeof
|
|
10528
|
+
const record2 = asRecord(value);
|
|
10529
|
+
if (typeof record2.selector === "string") return { selector: record2.selector };
|
|
10370
10530
|
return {
|
|
10371
|
-
observationId:
|
|
10372
|
-
ref: Number(
|
|
10531
|
+
observationId: record2.observationId,
|
|
10532
|
+
ref: Number(record2.ref)
|
|
10373
10533
|
};
|
|
10374
10534
|
}
|
|
10375
10535
|
function dropdownChoice(value) {
|
|
10376
|
-
const
|
|
10377
|
-
if (
|
|
10378
|
-
if (
|
|
10379
|
-
return { by: "label", label: String(
|
|
10536
|
+
const record2 = asRecord(value);
|
|
10537
|
+
if (record2.by === "index") return { by: "index", index: Number(record2.index) };
|
|
10538
|
+
if (record2.by === "label") {
|
|
10539
|
+
return { by: "label", label: String(record2.label), exact: record2.exact !== false };
|
|
10380
10540
|
}
|
|
10381
|
-
return { by: "value", value: String(
|
|
10541
|
+
return { by: "value", value: String(record2.value), exact: record2.exact !== false };
|
|
10382
10542
|
}
|
|
10383
10543
|
function optionMatches(option, choice) {
|
|
10384
10544
|
if (choice.by === "index") return option.index === choice.index;
|
|
@@ -10417,6 +10577,7 @@ var BrowserToolService = class {
|
|
|
10417
10577
|
}
|
|
10418
10578
|
this.loadWaiter = options.loadWaiter ?? waitForLoad;
|
|
10419
10579
|
this.connectBrowser = options.connectBrowser;
|
|
10580
|
+
this.readProfileIdentity = options.readProfileIdentity ?? readVerifiedChromeProfileIdentity;
|
|
10420
10581
|
this.watchdogs = new BrowserWatchdogService((event) => this.publishEvent(event), {
|
|
10421
10582
|
dialogTimeoutMs: options.dialogTimeoutMs,
|
|
10422
10583
|
noProgressThreshold: options.noProgressThreshold
|
|
@@ -10433,7 +10594,7 @@ var BrowserToolService = class {
|
|
|
10433
10594
|
transport,
|
|
10434
10595
|
binding.instance.id,
|
|
10435
10596
|
() => ({
|
|
10436
|
-
profileIdentity: binding.instance.
|
|
10597
|
+
profileIdentity: binding.instance.userDataRoot,
|
|
10437
10598
|
connectionGeneration: binding.instance.connectionGeneration
|
|
10438
10599
|
}),
|
|
10439
10600
|
{
|
|
@@ -10524,6 +10685,7 @@ var BrowserToolService = class {
|
|
|
10524
10685
|
navigationTimeoutMs;
|
|
10525
10686
|
loadWaiter;
|
|
10526
10687
|
connectBrowser;
|
|
10688
|
+
readProfileIdentity;
|
|
10527
10689
|
eventPublisher;
|
|
10528
10690
|
setEventPublisher(publisher) {
|
|
10529
10691
|
this.eventPublisher = publisher;
|
|
@@ -10590,11 +10752,13 @@ var BrowserToolService = class {
|
|
|
10590
10752
|
const args = asRecord(argsValue);
|
|
10591
10753
|
switch (definition.name) {
|
|
10592
10754
|
case "browser.discover":
|
|
10593
|
-
return this.discover();
|
|
10755
|
+
return this.discover(context);
|
|
10594
10756
|
case "browser.connect":
|
|
10595
10757
|
return this.connect(context, args);
|
|
10596
10758
|
case "browser.profiles.list":
|
|
10597
10759
|
return this.listProfiles(context);
|
|
10760
|
+
case "browser.profiles.identify":
|
|
10761
|
+
return this.identifyProfiles(context, args);
|
|
10598
10762
|
case "browser.profiles.select":
|
|
10599
10763
|
return this.selectProfile(context, args);
|
|
10600
10764
|
case "browser.tabs.list":
|
|
@@ -10673,6 +10837,9 @@ var BrowserToolService = class {
|
|
|
10673
10837
|
}
|
|
10674
10838
|
actorKey(context, definition, argsValue) {
|
|
10675
10839
|
const browserId = context.browser.instance.id;
|
|
10840
|
+
if (definition.name === "browser.profiles.identify") {
|
|
10841
|
+
return `${browserId}\0profile-identify`;
|
|
10842
|
+
}
|
|
10676
10843
|
if (definition.name.startsWith("browser.dialogs.")) {
|
|
10677
10844
|
return `${browserId}\0dialogs\0${context.workspace?.id ?? context.connection.id}`;
|
|
10678
10845
|
}
|
|
@@ -10735,14 +10902,15 @@ var BrowserToolService = class {
|
|
|
10735
10902
|
}
|
|
10736
10903
|
this.deleteDialogs((dialog) => dialog.workspaceId === workspace.id);
|
|
10737
10904
|
}
|
|
10738
|
-
discover() {
|
|
10905
|
+
discover(context) {
|
|
10739
10906
|
const candidate = this.binding.candidate;
|
|
10907
|
+
const userDataRoot = candidate.userDataRoot ?? candidate.profile;
|
|
10740
10908
|
return asJson2({
|
|
10741
10909
|
browsers: [{
|
|
10742
10910
|
id: candidate.id,
|
|
10743
10911
|
product: candidate.product,
|
|
10744
10912
|
...candidate.channel !== void 0 ? { channel: candidate.channel } : {},
|
|
10745
|
-
...
|
|
10913
|
+
...userDataRoot ? context.connection.protocol.minor >= 3 ? { userDataRoot } : { profile: userDataRoot } : {},
|
|
10746
10914
|
processState: candidate.processState ?? (candidate.state === "ready" ? "running" : "unknown"),
|
|
10747
10915
|
remoteDebuggingState: candidate.remoteDebuggingState ?? (candidate.state === "ready" ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
|
|
10748
10916
|
authorizationState: candidate.authorizationState ?? (candidate.state === "ready" ? "authorized" : candidate.state === "authorization_required" ? "required" : "not_applicable"),
|
|
@@ -10789,7 +10957,7 @@ var BrowserToolService = class {
|
|
|
10789
10957
|
return asJson2({
|
|
10790
10958
|
workspaceId: inventoryContext.workspaceId,
|
|
10791
10959
|
leaseId: inventoryContext.leaseId,
|
|
10792
|
-
targets
|
|
10960
|
+
targets: context.connection.protocol.minor >= 3 ? targets.map(({ active, ...target }) => ({ ...target, selected: active })) : targets
|
|
10793
10961
|
});
|
|
10794
10962
|
}
|
|
10795
10963
|
async listProfiles(context) {
|
|
@@ -10799,21 +10967,67 @@ var BrowserToolService = class {
|
|
|
10799
10967
|
return asJson2({
|
|
10800
10968
|
workspaceId: inventoryContext.workspaceId,
|
|
10801
10969
|
leaseId: inventoryContext.leaseId,
|
|
10802
|
-
profiles: profiles.map((profile) => (
|
|
10803
|
-
profileContextId: profile.id,
|
|
10804
|
-
label: profile.label,
|
|
10805
|
-
...profile.displayName ? { displayName: profile.displayName } : {},
|
|
10806
|
-
tabCount: profile.tabCount,
|
|
10807
|
-
eligibleTabCount: profile.eligibleTabCount,
|
|
10808
|
-
selected: context.workspace?.selectedProfileContextId === profile.id,
|
|
10809
|
-
representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
|
|
10810
|
-
targetId: target.targetId,
|
|
10811
|
-
title: target.title,
|
|
10812
|
-
url: target.url
|
|
10813
|
-
}))
|
|
10814
|
-
}))
|
|
10970
|
+
profiles: profiles.map((profile) => this.profileResult(context, profile, targets))
|
|
10815
10971
|
});
|
|
10816
10972
|
}
|
|
10973
|
+
async identifyProfiles(context, args) {
|
|
10974
|
+
const inventoryContext = this.inventoryContext(context);
|
|
10975
|
+
const targets = await this.inventory.list(inventoryContext, "all");
|
|
10976
|
+
const requestedId = args.profileContextId;
|
|
10977
|
+
const profiles = requestedId ? [this.profileContexts.resolve(requestedId, inventoryContext.browserConnectionGeneration)] : this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
10978
|
+
const refresh = args.refresh === true;
|
|
10979
|
+
for (const profile of profiles) {
|
|
10980
|
+
if (!refresh && profile.identityStatus !== "unidentified") continue;
|
|
10981
|
+
this.markDispatched(context);
|
|
10982
|
+
try {
|
|
10983
|
+
const identity = await this.probeProfileIdentity(profile);
|
|
10984
|
+
this.profileContexts.setVerifiedIdentity(
|
|
10985
|
+
profile.id,
|
|
10986
|
+
inventoryContext.browserConnectionGeneration,
|
|
10987
|
+
identity
|
|
10988
|
+
);
|
|
10989
|
+
} catch (error) {
|
|
10990
|
+
if (!(error instanceof ProfileIdentityError)) throw error;
|
|
10991
|
+
this.profileContexts.setIdentityUnavailable(
|
|
10992
|
+
profile.id,
|
|
10993
|
+
inventoryContext.browserConnectionGeneration,
|
|
10994
|
+
error.code
|
|
10995
|
+
);
|
|
10996
|
+
}
|
|
10997
|
+
}
|
|
10998
|
+
const current = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
10999
|
+
return asJson2({
|
|
11000
|
+
workspaceId: inventoryContext.workspaceId,
|
|
11001
|
+
leaseId: inventoryContext.leaseId,
|
|
11002
|
+
profiles: current.filter((profile) => !requestedId || profile.id === requestedId).map((profile) => this.profileResult(context, profile, targets))
|
|
11003
|
+
});
|
|
11004
|
+
}
|
|
11005
|
+
profileResult(context, profile, targets) {
|
|
11006
|
+
const identity = {};
|
|
11007
|
+
if (context.connection.protocol.minor >= 3) {
|
|
11008
|
+
identity.identityStatus = profile.identityStatus;
|
|
11009
|
+
if (profile.profileName) identity.profileName = profile.profileName;
|
|
11010
|
+
if (profile.accountName) identity.accountName = profile.accountName;
|
|
11011
|
+
if (profile.accountEmail) identity.accountEmail = profile.accountEmail;
|
|
11012
|
+
if (profile.profileDirectory) identity.profileDirectory = profile.profileDirectory;
|
|
11013
|
+
if (profile.identityErrorCode) identity.identityErrorCode = profile.identityErrorCode;
|
|
11014
|
+
} else if (profile.displayName) {
|
|
11015
|
+
identity.displayName = profile.displayName;
|
|
11016
|
+
}
|
|
11017
|
+
return {
|
|
11018
|
+
profileContextId: profile.id,
|
|
11019
|
+
label: profile.label,
|
|
11020
|
+
...identity,
|
|
11021
|
+
tabCount: profile.tabCount,
|
|
11022
|
+
eligibleTabCount: profile.eligibleTabCount,
|
|
11023
|
+
selected: context.workspace?.selectedProfileContextId === profile.id,
|
|
11024
|
+
representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
|
|
11025
|
+
targetId: target.targetId,
|
|
11026
|
+
title: target.title,
|
|
11027
|
+
url: target.url
|
|
11028
|
+
}))
|
|
11029
|
+
};
|
|
11030
|
+
}
|
|
10817
11031
|
async selectProfile(context, args) {
|
|
10818
11032
|
const inventoryContext = this.inventoryContext(context);
|
|
10819
11033
|
await this.inventory.refresh(inventoryContext);
|
|
@@ -10829,7 +11043,14 @@ var BrowserToolService = class {
|
|
|
10829
11043
|
leaseId: inventoryContext.leaseId,
|
|
10830
11044
|
profileContextId: profile.id,
|
|
10831
11045
|
label: profile.label,
|
|
10832
|
-
...
|
|
11046
|
+
...context.connection.protocol.minor >= 3 ? {
|
|
11047
|
+
identityStatus: profile.identityStatus,
|
|
11048
|
+
...profile.profileName ? { profileName: profile.profileName } : {},
|
|
11049
|
+
...profile.accountName ? { accountName: profile.accountName } : {},
|
|
11050
|
+
...profile.accountEmail ? { accountEmail: profile.accountEmail } : {},
|
|
11051
|
+
...profile.profileDirectory ? { profileDirectory: profile.profileDirectory } : {},
|
|
11052
|
+
...profile.identityErrorCode ? { identityErrorCode: profile.identityErrorCode } : {}
|
|
11053
|
+
} : profile.displayName ? { displayName: profile.displayName } : {}
|
|
10833
11054
|
});
|
|
10834
11055
|
}
|
|
10835
11056
|
async switchTab(context, args) {
|
|
@@ -10838,10 +11059,10 @@ var BrowserToolService = class {
|
|
|
10838
11059
|
await this.inventory.refresh(inventoryContext);
|
|
10839
11060
|
this.markDispatched(context);
|
|
10840
11061
|
const resolved = await this.inventory.activate(inventoryContext, targetId);
|
|
10841
|
-
const
|
|
11062
|
+
const record2 = this.registry.get(inventoryContext, targetId);
|
|
10842
11063
|
await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
|
|
10843
|
-
this.selectWorkspaceProfile(context,
|
|
10844
|
-
return asJson2(this.targetResult(context, targetId,
|
|
11064
|
+
this.selectWorkspaceProfile(context, record2.profileContextId);
|
|
11065
|
+
return asJson2(this.targetResult(context, targetId, record2.url));
|
|
10845
11066
|
}
|
|
10846
11067
|
async closeTab(context) {
|
|
10847
11068
|
const inventoryContext = this.inventoryContext(context);
|
|
@@ -11079,8 +11300,8 @@ var BrowserToolService = class {
|
|
|
11079
11300
|
...session.activeFrame ? { frameId: session.activeFrame.cdpFrameId } : {}
|
|
11080
11301
|
}
|
|
11081
11302
|
).locate(args.selector);
|
|
11082
|
-
const
|
|
11083
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11303
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11304
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), ...location });
|
|
11084
11305
|
}
|
|
11085
11306
|
async read(context, args) {
|
|
11086
11307
|
const targetId = this.requireTargetId(context);
|
|
@@ -11210,8 +11431,8 @@ var BrowserToolService = class {
|
|
|
11210
11431
|
address,
|
|
11211
11432
|
(objectId) => service.inspectObject(objectId)
|
|
11212
11433
|
);
|
|
11213
|
-
const
|
|
11214
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11434
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11435
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), ...result });
|
|
11215
11436
|
}
|
|
11216
11437
|
async selectDropdown(context, args) {
|
|
11217
11438
|
const targetId = this.requireTargetId(context);
|
|
@@ -11457,7 +11678,7 @@ var BrowserToolService = class {
|
|
|
11457
11678
|
);
|
|
11458
11679
|
}
|
|
11459
11680
|
const scale = this.previewScale(media.width, media.height);
|
|
11460
|
-
const
|
|
11681
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11461
11682
|
if (scale === void 0) {
|
|
11462
11683
|
const artifact2 = await this.createArtifact(context, {
|
|
11463
11684
|
kind: "screenshot",
|
|
@@ -11467,7 +11688,7 @@ var BrowserToolService = class {
|
|
|
11467
11688
|
...media.height !== void 0 ? { height: media.height } : {}
|
|
11468
11689
|
});
|
|
11469
11690
|
return asJson2({
|
|
11470
|
-
...this.targetResult(context, targetId,
|
|
11691
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11471
11692
|
artifact: artifact2,
|
|
11472
11693
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
11473
11694
|
});
|
|
@@ -11489,7 +11710,7 @@ var BrowserToolService = class {
|
|
|
11489
11710
|
...previewMedia.height !== void 0 ? { height: previewMedia.height } : {}
|
|
11490
11711
|
});
|
|
11491
11712
|
return asJson2({
|
|
11492
|
-
...this.targetResult(context, targetId,
|
|
11713
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11493
11714
|
artifact: artifact2,
|
|
11494
11715
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
11495
11716
|
});
|
|
@@ -11511,7 +11732,7 @@ var BrowserToolService = class {
|
|
|
11511
11732
|
previewOf: artifact.id
|
|
11512
11733
|
});
|
|
11513
11734
|
return asJson2({
|
|
11514
|
-
...this.targetResult(context, targetId,
|
|
11735
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11515
11736
|
artifact,
|
|
11516
11737
|
preview,
|
|
11517
11738
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
@@ -11532,15 +11753,15 @@ var BrowserToolService = class {
|
|
|
11532
11753
|
mimeType: media.mimeType,
|
|
11533
11754
|
bytes: media.bytes
|
|
11534
11755
|
});
|
|
11535
|
-
const
|
|
11536
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11756
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11757
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), artifact });
|
|
11537
11758
|
}
|
|
11538
11759
|
async cookies(context, args) {
|
|
11539
11760
|
const targetId = this.requireTargetId(context);
|
|
11540
11761
|
const session = await this.resolveTargetSession(context, targetId, "cookies.read");
|
|
11541
11762
|
const cookies = await new CookieService(this.transport, session.sessionId).list(args.domain);
|
|
11542
|
-
const
|
|
11543
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11763
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11764
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), cookies });
|
|
11544
11765
|
}
|
|
11545
11766
|
async setAuth(context, args) {
|
|
11546
11767
|
const { workspace, lease } = this.requireWorkspaceContext(context);
|
|
@@ -11620,7 +11841,7 @@ var BrowserToolService = class {
|
|
|
11620
11841
|
"expression"
|
|
11621
11842
|
);
|
|
11622
11843
|
}
|
|
11623
|
-
const
|
|
11844
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11624
11845
|
let value = result?.value ?? result?.unserializableValue ?? null;
|
|
11625
11846
|
let truncated = false;
|
|
11626
11847
|
const serialized = JSON.stringify(value);
|
|
@@ -11628,7 +11849,7 @@ var BrowserToolService = class {
|
|
|
11628
11849
|
value = serialized.slice(0, 1e6);
|
|
11629
11850
|
truncated = true;
|
|
11630
11851
|
}
|
|
11631
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11852
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), value, truncated });
|
|
11632
11853
|
}
|
|
11633
11854
|
listDialogs(context) {
|
|
11634
11855
|
const { workspace, lease } = this.requireWorkspaceContext(context);
|
|
@@ -11736,6 +11957,89 @@ var BrowserToolService = class {
|
|
|
11736
11957
|
throw error;
|
|
11737
11958
|
}
|
|
11738
11959
|
}
|
|
11960
|
+
async probeProfileIdentity(profile) {
|
|
11961
|
+
let targetId;
|
|
11962
|
+
let sessionId;
|
|
11963
|
+
let failure;
|
|
11964
|
+
try {
|
|
11965
|
+
try {
|
|
11966
|
+
targetId = await this.tryCreateManagedTarget({
|
|
11967
|
+
url: "about:blank",
|
|
11968
|
+
newWindow: true,
|
|
11969
|
+
...profile.cdpBrowserContextId ? { browserContextId: profile.cdpBrowserContextId } : {}
|
|
11970
|
+
}, profile);
|
|
11971
|
+
} catch (error) {
|
|
11972
|
+
if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
|
|
11973
|
+
}
|
|
11974
|
+
if (!targetId) targetId = await this.createManagedProfileWindow(profile);
|
|
11975
|
+
const attached = await this.transport.send("Target.attachToTarget", {
|
|
11976
|
+
targetId,
|
|
11977
|
+
flatten: true
|
|
11978
|
+
});
|
|
11979
|
+
if (typeof attached?.sessionId !== "string") {
|
|
11980
|
+
throw new ProfileIdentityError(
|
|
11981
|
+
"profile_path_unavailable",
|
|
11982
|
+
"Chrome could not attach to the temporary Profile identity target"
|
|
11983
|
+
);
|
|
11984
|
+
}
|
|
11985
|
+
const activeSessionId = attached.sessionId;
|
|
11986
|
+
sessionId = activeSessionId;
|
|
11987
|
+
await this.transport.send("Page.enable", {}, activeSessionId);
|
|
11988
|
+
const navigation = await this.transport.send("Page.navigate", {
|
|
11989
|
+
url: "chrome://version/"
|
|
11990
|
+
}, activeSessionId);
|
|
11991
|
+
if (navigation?.errorText) {
|
|
11992
|
+
throw new ProfileIdentityError(
|
|
11993
|
+
"profile_path_unavailable",
|
|
11994
|
+
"Chrome could not open its version page for Profile identification"
|
|
11995
|
+
);
|
|
11996
|
+
}
|
|
11997
|
+
await this.loadWaiter(this.transport, activeSessionId, this.navigationTimeoutMs);
|
|
11998
|
+
const evaluated = await this.transport.send("Runtime.evaluate", {
|
|
11999
|
+
expression: `(() => {
|
|
12000
|
+
/* browser-pilot.profile-path.v1 */
|
|
12001
|
+
const value = document.querySelector('#profile_path')?.textContent;
|
|
12002
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
12003
|
+
})()`,
|
|
12004
|
+
returnByValue: true
|
|
12005
|
+
}, activeSessionId);
|
|
12006
|
+
const reportedProfilePath = evaluated?.result?.value;
|
|
12007
|
+
if (typeof reportedProfilePath !== "string" || !reportedProfilePath.trim()) {
|
|
12008
|
+
throw new ProfileIdentityError(
|
|
12009
|
+
"profile_path_unavailable",
|
|
12010
|
+
"Chrome did not expose a Profile path on its version page"
|
|
12011
|
+
);
|
|
12012
|
+
}
|
|
12013
|
+
return await this.readProfileIdentity(
|
|
12014
|
+
this.binding.instance.userDataRoot,
|
|
12015
|
+
reportedProfilePath
|
|
12016
|
+
);
|
|
12017
|
+
} catch (error) {
|
|
12018
|
+
failure = error;
|
|
12019
|
+
throw error;
|
|
12020
|
+
} finally {
|
|
12021
|
+
if (sessionId) {
|
|
12022
|
+
await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
|
|
12023
|
+
});
|
|
12024
|
+
}
|
|
12025
|
+
if (targetId && !await this.closeTargetAndVerify(targetId)) {
|
|
12026
|
+
throw new BrowserPilotError(
|
|
12027
|
+
"unknown_outcome",
|
|
12028
|
+
"Temporary Profile identity target could not be safely closed",
|
|
12029
|
+
{
|
|
12030
|
+
retryable: true,
|
|
12031
|
+
context: { profileContextId: profile.id },
|
|
12032
|
+
remediation: {
|
|
12033
|
+
code: "inspect_profile_tabs",
|
|
12034
|
+
message: "List tabs and close any leftover chrome://version window before retrying.",
|
|
12035
|
+
actionRequired: true
|
|
12036
|
+
},
|
|
12037
|
+
...failure !== void 0 ? { cause: failure } : {}
|
|
12038
|
+
}
|
|
12039
|
+
);
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
11739
12043
|
async tryCreateManagedTarget(params, profile) {
|
|
11740
12044
|
let targetId;
|
|
11741
12045
|
try {
|
|
@@ -11770,7 +12074,7 @@ var BrowserToolService = class {
|
|
|
11770
12074
|
if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
|
|
11771
12075
|
} catch {
|
|
11772
12076
|
}
|
|
11773
|
-
if (attempt < 19) await new Promise((
|
|
12077
|
+
if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11774
12078
|
}
|
|
11775
12079
|
return false;
|
|
11776
12080
|
}
|
|
@@ -11918,7 +12222,7 @@ var BrowserToolService = class {
|
|
|
11918
12222
|
candidateTargetId = candidate.targetId;
|
|
11919
12223
|
break;
|
|
11920
12224
|
}
|
|
11921
|
-
await new Promise((
|
|
12225
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11922
12226
|
}
|
|
11923
12227
|
if (!candidateTargetId) {
|
|
11924
12228
|
throw this.managedTargetUnknownOutcome(profile);
|
|
@@ -11957,7 +12261,7 @@ var BrowserToolService = class {
|
|
|
11957
12261
|
if (result?.result?.value !== void 0) return false;
|
|
11958
12262
|
} catch {
|
|
11959
12263
|
}
|
|
11960
|
-
if (attempt < 9) await new Promise((
|
|
12264
|
+
if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
11961
12265
|
}
|
|
11962
12266
|
return false;
|
|
11963
12267
|
} catch {
|
|
@@ -12022,8 +12326,6 @@ var BrowserToolService = class {
|
|
|
12022
12326
|
await this.transport.send("Page.enable", {}, attached.sessionId).catch(() => {
|
|
12023
12327
|
});
|
|
12024
12328
|
await this.downloads?.attachSession(session);
|
|
12025
|
-
await this.transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, attached.sessionId).catch(() => {
|
|
12026
|
-
});
|
|
12027
12329
|
return session;
|
|
12028
12330
|
} catch (error) {
|
|
12029
12331
|
this.retireSession(key, session);
|
|
@@ -12040,7 +12342,7 @@ var BrowserToolService = class {
|
|
|
12040
12342
|
});
|
|
12041
12343
|
const snapshot = afterAction ? await service.observeAfterAction(limit) : await service.observe(limit);
|
|
12042
12344
|
const identity = await this.observationContextIdentity(session);
|
|
12043
|
-
const
|
|
12345
|
+
const record2 = this.observations.create({
|
|
12044
12346
|
workspaceId: context.workspace.id,
|
|
12045
12347
|
leaseId: context.lease.id,
|
|
12046
12348
|
targetId,
|
|
@@ -12063,7 +12365,7 @@ var BrowserToolService = class {
|
|
|
12063
12365
|
previous && previous.frameId === frameId ? previous.guidance : void 0
|
|
12064
12366
|
);
|
|
12065
12367
|
this.guidanceBySession.set(session.sessionId, { frameId, guidance: snapshot.guidance });
|
|
12066
|
-
return { snapshot, record, hints };
|
|
12368
|
+
return { snapshot, record: record2, hints };
|
|
12067
12369
|
}
|
|
12068
12370
|
async resolveObservation(context, targetId, session, observationId, ref) {
|
|
12069
12371
|
const cdpSessionId = this.activeCdpSessionId(session);
|
|
@@ -12559,12 +12861,12 @@ var BrowserToolService = class {
|
|
|
12559
12861
|
if (!this.artifactStore || !context.workspace) {
|
|
12560
12862
|
throw new BrowserPilotError("internal_error", "Browser Artifact storage is unavailable");
|
|
12561
12863
|
}
|
|
12562
|
-
const
|
|
12864
|
+
const record2 = await this.artifactStore.create({
|
|
12563
12865
|
workspaceId: context.workspace.id,
|
|
12564
12866
|
sensitivity: "browser_data",
|
|
12565
12867
|
...input
|
|
12566
12868
|
});
|
|
12567
|
-
return
|
|
12869
|
+
return record2.descriptor;
|
|
12568
12870
|
}
|
|
12569
12871
|
previewScale(width, height) {
|
|
12570
12872
|
if (!width || !height) return void 0;
|
|
@@ -13208,10 +13510,10 @@ import {
|
|
|
13208
13510
|
writeFile
|
|
13209
13511
|
} from "fs/promises";
|
|
13210
13512
|
import { constants as fsConstants } from "fs";
|
|
13211
|
-
import { basename as
|
|
13513
|
+
import { basename as basename4, dirname as dirname3, extname, isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
13212
13514
|
var ARTIFACT_ID_PATTERN2 = /^artifact:[A-Za-z0-9][A-Za-z0-9._:-]{0,118}$/;
|
|
13213
|
-
function clone2(
|
|
13214
|
-
return { descriptor: { ...
|
|
13515
|
+
function clone2(record2) {
|
|
13516
|
+
return { descriptor: { ...record2.descriptor }, path: record2.path };
|
|
13215
13517
|
}
|
|
13216
13518
|
function inferredMimeType(path) {
|
|
13217
13519
|
const known = {
|
|
@@ -13246,7 +13548,7 @@ var ArtifactStore = class {
|
|
|
13246
13548
|
initialized = false;
|
|
13247
13549
|
mutationTail = Promise.resolve();
|
|
13248
13550
|
constructor(options = {}) {
|
|
13249
|
-
this.directory =
|
|
13551
|
+
this.directory = resolve2(options.directory ?? ARTIFACT_DIR);
|
|
13250
13552
|
this.ttlMs = options.ttlMs ?? 15 * 6e4;
|
|
13251
13553
|
this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
|
|
13252
13554
|
this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
|
|
@@ -13304,15 +13606,15 @@ var ArtifactStore = class {
|
|
|
13304
13606
|
}
|
|
13305
13607
|
async export(workspaceId, artifactId, destination, overwrite = false) {
|
|
13306
13608
|
if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
|
|
13307
|
-
const target =
|
|
13609
|
+
const target = resolve2(destination);
|
|
13308
13610
|
if (this.isInsideStore(target)) {
|
|
13309
13611
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13310
13612
|
}
|
|
13311
13613
|
return this.withLock(async () => {
|
|
13312
|
-
const
|
|
13614
|
+
const record2 = await this.requireRecordUnlocked(workspaceId, artifactId);
|
|
13313
13615
|
let canonicalParent;
|
|
13314
13616
|
try {
|
|
13315
|
-
canonicalParent = await realpath(
|
|
13617
|
+
canonicalParent = await realpath(dirname3(target));
|
|
13316
13618
|
} catch (cause) {
|
|
13317
13619
|
throw new BrowserPilotError("invalid_argument", "Artifact export directory is not accessible", {
|
|
13318
13620
|
context: { field: "path" },
|
|
@@ -13321,7 +13623,7 @@ var ArtifactStore = class {
|
|
|
13321
13623
|
});
|
|
13322
13624
|
}
|
|
13323
13625
|
const canonicalStore = await realpath(this.directory);
|
|
13324
|
-
if (this.isWithin(canonicalStore,
|
|
13626
|
+
if (this.isWithin(canonicalStore, join5(canonicalParent, basename4(target)))) {
|
|
13325
13627
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13326
13628
|
}
|
|
13327
13629
|
if (overwrite) {
|
|
@@ -13331,7 +13633,7 @@ var ArtifactStore = class {
|
|
|
13331
13633
|
}
|
|
13332
13634
|
}
|
|
13333
13635
|
await copyFile(
|
|
13334
|
-
|
|
13636
|
+
record2.path,
|
|
13335
13637
|
target,
|
|
13336
13638
|
overwrite ? 0 : fsConstants.COPYFILE_EXCL
|
|
13337
13639
|
).catch((cause) => {
|
|
@@ -13343,7 +13645,7 @@ var ArtifactStore = class {
|
|
|
13343
13645
|
});
|
|
13344
13646
|
await chmod(target, 384).catch(() => {
|
|
13345
13647
|
});
|
|
13346
|
-
return { artifact: { ...
|
|
13648
|
+
return { artifact: { ...record2.descriptor }, path: target };
|
|
13347
13649
|
});
|
|
13348
13650
|
}
|
|
13349
13651
|
async retain(workspaceId, artifactId) {
|
|
@@ -13357,21 +13659,21 @@ var ArtifactStore = class {
|
|
|
13357
13659
|
}
|
|
13358
13660
|
async release(workspaceId, artifactId) {
|
|
13359
13661
|
await this.withLock(async () => {
|
|
13360
|
-
const
|
|
13361
|
-
if (!
|
|
13662
|
+
const record2 = this.records.get(artifactId);
|
|
13663
|
+
if (!record2 || record2.descriptor.workspaceId !== workspaceId) return;
|
|
13362
13664
|
this.records.delete(artifactId);
|
|
13363
13665
|
this.expired.delete(artifactId);
|
|
13364
|
-
await this.removeStoredFile(
|
|
13666
|
+
await this.removeStoredFile(record2.path);
|
|
13365
13667
|
});
|
|
13366
13668
|
}
|
|
13367
13669
|
async releaseWorkspace(workspaceId) {
|
|
13368
13670
|
await this.withLock(async () => {
|
|
13369
|
-
const records = [...this.records.values()].filter((
|
|
13370
|
-
for (const
|
|
13671
|
+
const records = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId);
|
|
13672
|
+
for (const record2 of records) this.records.delete(record2.descriptor.id);
|
|
13371
13673
|
for (const [artifactId, tombstone] of this.expired) {
|
|
13372
13674
|
if (tombstone.workspaceId === workspaceId) this.expired.delete(artifactId);
|
|
13373
13675
|
}
|
|
13374
|
-
await Promise.all(records.map((
|
|
13676
|
+
await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
|
|
13375
13677
|
});
|
|
13376
13678
|
}
|
|
13377
13679
|
async sweep() {
|
|
@@ -13382,7 +13684,7 @@ var ArtifactStore = class {
|
|
|
13382
13684
|
const records = [...this.records.values()];
|
|
13383
13685
|
this.records.clear();
|
|
13384
13686
|
this.expired.clear();
|
|
13385
|
-
await Promise.all(records.map((
|
|
13687
|
+
await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
|
|
13386
13688
|
});
|
|
13387
13689
|
}
|
|
13388
13690
|
size() {
|
|
@@ -13399,7 +13701,7 @@ var ArtifactStore = class {
|
|
|
13399
13701
|
}
|
|
13400
13702
|
async ingestFile(input) {
|
|
13401
13703
|
if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
|
|
13402
|
-
const source =
|
|
13704
|
+
const source = resolve2(input.sourcePath);
|
|
13403
13705
|
if (this.isInsideStore(source)) {
|
|
13404
13706
|
throw invalidArgument("Artifact import path must be outside Broker storage", "path");
|
|
13405
13707
|
}
|
|
@@ -13447,8 +13749,8 @@ var ArtifactStore = class {
|
|
|
13447
13749
|
}
|
|
13448
13750
|
if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
|
|
13449
13751
|
this.assertQuota(input.workspaceId, sourceInfo.size);
|
|
13450
|
-
const fileName = input.fileName ??
|
|
13451
|
-
if (!fileName || fileName === "." || fileName === ".." ||
|
|
13752
|
+
const fileName = input.fileName ?? basename4(source);
|
|
13753
|
+
if (!fileName || fileName === "." || fileName === ".." || basename4(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
|
|
13452
13754
|
throw invalidArgument("Artifact import filename is invalid or too long", "path");
|
|
13453
13755
|
}
|
|
13454
13756
|
const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
|
|
@@ -13456,8 +13758,8 @@ var ArtifactStore = class {
|
|
|
13456
13758
|
throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
|
|
13457
13759
|
}
|
|
13458
13760
|
const id = this.nextArtifactId();
|
|
13459
|
-
const storageDirectory =
|
|
13460
|
-
const destination =
|
|
13761
|
+
const storageDirectory = join5(this.directory, randomUUID12());
|
|
13762
|
+
const destination = join5(storageDirectory, fileName);
|
|
13461
13763
|
await mkdir(storageDirectory, { mode: 448 });
|
|
13462
13764
|
try {
|
|
13463
13765
|
await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
|
|
@@ -13483,9 +13785,9 @@ var ArtifactStore = class {
|
|
|
13483
13785
|
expiresAt: createdAt + this.ttlMs,
|
|
13484
13786
|
retained: false
|
|
13485
13787
|
};
|
|
13486
|
-
const
|
|
13487
|
-
this.records.set(id,
|
|
13488
|
-
return clone2(
|
|
13788
|
+
const record2 = { descriptor: descriptor2, path: destination };
|
|
13789
|
+
this.records.set(id, record2);
|
|
13790
|
+
return clone2(record2);
|
|
13489
13791
|
} catch (error) {
|
|
13490
13792
|
await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
|
|
13491
13793
|
});
|
|
@@ -13499,7 +13801,7 @@ var ArtifactStore = class {
|
|
|
13499
13801
|
this.assertQuota(input.workspaceId, byteSize);
|
|
13500
13802
|
await this.ensureDirectory();
|
|
13501
13803
|
const id = this.nextArtifactId();
|
|
13502
|
-
const file =
|
|
13804
|
+
const file = join5(this.directory, `${randomUUID12()}.bin`);
|
|
13503
13805
|
const createdAt = this.now();
|
|
13504
13806
|
await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
|
|
13505
13807
|
await chmod(file, 384).catch(() => {
|
|
@@ -13519,39 +13821,39 @@ var ArtifactStore = class {
|
|
|
13519
13821
|
retained: false,
|
|
13520
13822
|
...input.previewOf ? { previewOf: input.previewOf } : {}
|
|
13521
13823
|
};
|
|
13522
|
-
const
|
|
13523
|
-
this.records.set(id,
|
|
13524
|
-
return clone2(
|
|
13824
|
+
const record2 = { descriptor: descriptor2, path: file };
|
|
13825
|
+
this.records.set(id, record2);
|
|
13826
|
+
return clone2(record2);
|
|
13525
13827
|
}
|
|
13526
13828
|
async requireRecordUnlocked(workspaceId, artifactId) {
|
|
13527
13829
|
await this.sweepUnlocked();
|
|
13528
|
-
const
|
|
13529
|
-
if (!
|
|
13830
|
+
const record2 = this.records.get(artifactId);
|
|
13831
|
+
if (!record2 || record2.descriptor.workspaceId !== workspaceId) {
|
|
13530
13832
|
const tombstone = this.expired.get(artifactId);
|
|
13531
13833
|
if (tombstone?.workspaceId === workspaceId) throw this.artifactExpired(artifactId);
|
|
13532
13834
|
throw this.notFound(artifactId);
|
|
13533
13835
|
}
|
|
13534
13836
|
try {
|
|
13535
|
-
const info = await stat(
|
|
13536
|
-
if (!info.isFile() || info.size !==
|
|
13837
|
+
const info = await stat(record2.path);
|
|
13838
|
+
if (!info.isFile() || info.size !== record2.descriptor.byteSize) throw new Error("Artifact file mismatch");
|
|
13537
13839
|
} catch {
|
|
13538
13840
|
this.records.delete(artifactId);
|
|
13539
13841
|
throw this.notFound(artifactId);
|
|
13540
13842
|
}
|
|
13541
|
-
return
|
|
13843
|
+
return record2;
|
|
13542
13844
|
}
|
|
13543
13845
|
async sweepUnlocked() {
|
|
13544
13846
|
const now = this.now();
|
|
13545
|
-
const expiredRecords = [...this.records.values()].filter((
|
|
13546
|
-
for (const
|
|
13547
|
-
this.records.delete(
|
|
13548
|
-
this.expired.set(
|
|
13549
|
-
workspaceId:
|
|
13847
|
+
const expiredRecords = [...this.records.values()].filter((record2) => record2.descriptor.expiresAt <= now);
|
|
13848
|
+
for (const record2 of expiredRecords) {
|
|
13849
|
+
this.records.delete(record2.descriptor.id);
|
|
13850
|
+
this.expired.set(record2.descriptor.id, {
|
|
13851
|
+
workspaceId: record2.descriptor.workspaceId,
|
|
13550
13852
|
expiredAt: now
|
|
13551
13853
|
});
|
|
13552
13854
|
}
|
|
13553
13855
|
this.pruneExpiredTombstones(now);
|
|
13554
|
-
await Promise.all(expiredRecords.map((
|
|
13856
|
+
await Promise.all(expiredRecords.map((record2) => this.removeStoredFile(record2.path)));
|
|
13555
13857
|
return expiredRecords.length;
|
|
13556
13858
|
}
|
|
13557
13859
|
async ensureDirectory() {
|
|
@@ -13559,7 +13861,7 @@ var ArtifactStore = class {
|
|
|
13559
13861
|
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
13560
13862
|
if (this.purgeOrphansOnInitialize) {
|
|
13561
13863
|
const entries = await readdir(this.directory, { withFileTypes: true });
|
|
13562
|
-
await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(
|
|
13864
|
+
await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(join5(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
|
|
13563
13865
|
})));
|
|
13564
13866
|
}
|
|
13565
13867
|
this.initialized = true;
|
|
@@ -13598,8 +13900,8 @@ var ArtifactStore = class {
|
|
|
13598
13900
|
context: { maxArtifactBytes: this.maxArtifactBytes, byteSize }
|
|
13599
13901
|
});
|
|
13600
13902
|
}
|
|
13601
|
-
const workspaceBytes = [...this.records.values()].filter((
|
|
13602
|
-
const totalBytes = [...this.records.values()].reduce((sum,
|
|
13903
|
+
const workspaceBytes = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId).reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
|
|
13904
|
+
const totalBytes = [...this.records.values()].reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
|
|
13603
13905
|
if (workspaceBytes + byteSize > this.maxWorkspaceBytes || totalBytes + byteSize > this.maxTotalBytes) {
|
|
13604
13906
|
throw new BrowserPilotError("result_too_large", "Artifact Store quota exceeded", {
|
|
13605
13907
|
context: {
|
|
@@ -13613,7 +13915,7 @@ var ArtifactStore = class {
|
|
|
13613
13915
|
async removeStoredFile(path) {
|
|
13614
13916
|
await unlink(path).catch(() => {
|
|
13615
13917
|
});
|
|
13616
|
-
const parent =
|
|
13918
|
+
const parent = dirname3(path);
|
|
13617
13919
|
if (parent !== this.directory && this.isInsideStore(parent)) {
|
|
13618
13920
|
await rm(parent, { recursive: true, force: true }).catch(() => {
|
|
13619
13921
|
});
|
|
@@ -13827,8 +14129,8 @@ var ManagedTargetJanitorClient = class {
|
|
|
13827
14129
|
});
|
|
13828
14130
|
this.worker = worker;
|
|
13829
14131
|
this.ready = false;
|
|
13830
|
-
const ready = new Promise((
|
|
13831
|
-
this.readyResolve =
|
|
14132
|
+
const ready = new Promise((resolve3, reject) => {
|
|
14133
|
+
this.readyResolve = resolve3;
|
|
13832
14134
|
this.readyReject = reject;
|
|
13833
14135
|
});
|
|
13834
14136
|
worker.on("message", (value) => this.handleWorkerMessage(worker, value));
|
|
@@ -13933,7 +14235,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13933
14235
|
}
|
|
13934
14236
|
const id = this.nextRequestId++;
|
|
13935
14237
|
const request = { id, method, params };
|
|
13936
|
-
return new Promise((
|
|
14238
|
+
return new Promise((resolve3, reject) => {
|
|
13937
14239
|
const timer = setTimeout(() => {
|
|
13938
14240
|
this.pendingRequests.delete(id);
|
|
13939
14241
|
reject(method === "create" ? new BrowserPilotError(
|
|
@@ -13943,7 +14245,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13943
14245
|
) : new Error(`Managed browser connection timeout: ${method}`));
|
|
13944
14246
|
}, REQUEST_TIMEOUT_MS);
|
|
13945
14247
|
timer.unref();
|
|
13946
|
-
this.pendingRequests.set(id, { resolve:
|
|
14248
|
+
this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
|
|
13947
14249
|
worker.send?.(request, (error) => {
|
|
13948
14250
|
if (!error) return;
|
|
13949
14251
|
const pending = this.pendingRequests.get(id);
|
|
@@ -13973,19 +14275,19 @@ var ManagedTargetJanitorClient = class {
|
|
|
13973
14275
|
this.expectedExits.add(worker);
|
|
13974
14276
|
if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
|
|
13975
14277
|
else worker.kill("SIGTERM");
|
|
13976
|
-
await new Promise((
|
|
14278
|
+
await new Promise((resolve3) => {
|
|
13977
14279
|
if (worker.exitCode !== null || worker.signalCode !== null) {
|
|
13978
|
-
|
|
14280
|
+
resolve3();
|
|
13979
14281
|
return;
|
|
13980
14282
|
}
|
|
13981
14283
|
const timer = setTimeout(() => {
|
|
13982
14284
|
worker.kill("SIGKILL");
|
|
13983
|
-
|
|
14285
|
+
resolve3();
|
|
13984
14286
|
}, STOP_TIMEOUT_MS);
|
|
13985
14287
|
timer.unref();
|
|
13986
14288
|
worker.once("exit", () => {
|
|
13987
14289
|
clearTimeout(timer);
|
|
13988
|
-
|
|
14290
|
+
resolve3();
|
|
13989
14291
|
});
|
|
13990
14292
|
});
|
|
13991
14293
|
if (this.worker === worker) this.failWorker(worker, this.unavailableError());
|
|
@@ -14015,7 +14317,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
14015
14317
|
};
|
|
14016
14318
|
|
|
14017
14319
|
// src/version.ts
|
|
14018
|
-
var BROWSER_PILOT_VERSION = "0.
|
|
14320
|
+
var BROWSER_PILOT_VERSION = "0.4.0";
|
|
14019
14321
|
|
|
14020
14322
|
// src/daemon.ts
|
|
14021
14323
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -14029,7 +14331,7 @@ var browserProfile = process.argv[4] || "";
|
|
|
14029
14331
|
ensureBrokerDirectoriesSync();
|
|
14030
14332
|
for (const legacyFile of ["state.json", "refs.json"]) {
|
|
14031
14333
|
try {
|
|
14032
|
-
unlinkSync2(
|
|
14334
|
+
unlinkSync2(join6(STATE_DIR, legacyFile));
|
|
14033
14335
|
} catch {
|
|
14034
14336
|
}
|
|
14035
14337
|
}
|
|
@@ -14049,7 +14351,7 @@ function cleanup(brokerProcessIdentity) {
|
|
|
14049
14351
|
removeBrokerLocatorSync(brokerProcessIdentity);
|
|
14050
14352
|
}
|
|
14051
14353
|
function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
14052
|
-
return new Promise((
|
|
14354
|
+
return new Promise((resolve3, reject) => {
|
|
14053
14355
|
const chunks = [];
|
|
14054
14356
|
let byteLength = 0;
|
|
14055
14357
|
req.on("data", (value) => {
|
|
@@ -14062,7 +14364,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
|
14062
14364
|
}
|
|
14063
14365
|
chunks.push(chunk);
|
|
14064
14366
|
});
|
|
14065
|
-
req.on("end", () =>
|
|
14367
|
+
req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
14066
14368
|
req.on("error", reject);
|
|
14067
14369
|
});
|
|
14068
14370
|
}
|
|
@@ -14161,7 +14463,7 @@ async function main() {
|
|
|
14161
14463
|
candidate: {
|
|
14162
14464
|
id: customBrowserId,
|
|
14163
14465
|
product: browserProduct || "Chromium",
|
|
14164
|
-
|
|
14466
|
+
userDataRoot: browserProfile,
|
|
14165
14467
|
processState: initialWsUrl ? "running" : "unknown",
|
|
14166
14468
|
remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
|
|
14167
14469
|
authorizationState: initialWsUrl ? "authorized" : "not_applicable",
|
|
@@ -14193,7 +14495,7 @@ async function main() {
|
|
|
14193
14495
|
} : {
|
|
14194
14496
|
id: browserId,
|
|
14195
14497
|
product: browserProduct || "Unavailable browser",
|
|
14196
|
-
|
|
14498
|
+
userDataRoot: browserProfile,
|
|
14197
14499
|
processState: initialWsUrl ? "running" : "unknown",
|
|
14198
14500
|
remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
|
|
14199
14501
|
authorizationState: initialWsUrl ? "authorized" : "not_applicable",
|
|
@@ -14202,7 +14504,7 @@ async function main() {
|
|
|
14202
14504
|
instance: {
|
|
14203
14505
|
id: browserInstanceId,
|
|
14204
14506
|
product: selectedProduct || "Unavailable browser",
|
|
14205
|
-
|
|
14507
|
+
userDataRoot: selectedProfile,
|
|
14206
14508
|
processIdentity: initialWsUrl ?? "",
|
|
14207
14509
|
connectionGeneration: initialWsUrl ? 1 : 0,
|
|
14208
14510
|
state: initialWsUrl ? "connected" : "disconnected"
|
|
@@ -14242,7 +14544,7 @@ async function main() {
|
|
|
14242
14544
|
instance: {
|
|
14243
14545
|
id: instanceId,
|
|
14244
14546
|
product: discovered.candidate.product,
|
|
14245
|
-
|
|
14547
|
+
userDataRoot: discovered.dataDir,
|
|
14246
14548
|
processIdentity: "",
|
|
14247
14549
|
connectionGeneration: 0,
|
|
14248
14550
|
state: "disconnected"
|
|
@@ -14298,7 +14600,7 @@ async function main() {
|
|
|
14298
14600
|
if (existing) return existing;
|
|
14299
14601
|
const attempt = (async () => {
|
|
14300
14602
|
const chrome = discoverChromeAtDataDir(
|
|
14301
|
-
controller.binding.instance.
|
|
14603
|
+
controller.binding.instance.userDataRoot,
|
|
14302
14604
|
controller.binding.instance.product
|
|
14303
14605
|
);
|
|
14304
14606
|
if (!chrome) {
|
|
@@ -14553,7 +14855,7 @@ async function main() {
|
|
|
14553
14855
|
id: browserBinding.candidate.id,
|
|
14554
14856
|
product: selectedProduct,
|
|
14555
14857
|
...browserBinding.candidate.channel ? { channel: browserBinding.candidate.channel } : {},
|
|
14556
|
-
|
|
14858
|
+
userDataRoot: selectedProfile,
|
|
14557
14859
|
state: browserBinding.instance.state,
|
|
14558
14860
|
connectionGeneration: browserBinding.instance.connectionGeneration
|
|
14559
14861
|
} } : {}
|
|
@@ -14710,13 +15012,13 @@ async function main() {
|
|
|
14710
15012
|
});
|
|
14711
15013
|
}
|
|
14712
15014
|
const clients = broker.lifecycleSummary();
|
|
14713
|
-
if (clients.embeddedConnections > 0) {
|
|
14714
|
-
throw new BrowserPilotError("broker_in_use", "Browser Pilot has live
|
|
15015
|
+
if (clients.embeddedConnections > 0 || clients.activeLeases > 0) {
|
|
15016
|
+
throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
|
|
14715
15017
|
retryable: true,
|
|
14716
15018
|
context: clients,
|
|
14717
15019
|
remediation: {
|
|
14718
|
-
code: "
|
|
14719
|
-
message: "Close the Agent
|
|
15020
|
+
code: "close_active_clients",
|
|
15021
|
+
message: "Close the other Agent clients using Browser Pilot, then retry disconnect.",
|
|
14720
15022
|
actionRequired: true
|
|
14721
15023
|
}
|
|
14722
15024
|
});
|