browser-pilot-cli 0.3.0 → 0.5.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 +270 -354
- package/dist/cli.js +1285 -2864
- package/dist/daemon.js +1142 -887
- package/docs/architecture/browser-pilot-platform-spec.md +440 -1013
- package/docs/plans/browser-capability-evolution.md +6 -6
- package/docs/plans/profile-context-routing.md +35 -10
- package/docs/plans/universal-agent-integration.md +121 -335
- package/docs/plans/v0.3.0-stabilization.md +10 -4
- 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.4.0.md +62 -0
- package/docs/releases/v0.5.0.md +93 -0
- package/package.json +8 -15
- package/docs/integration/stdio-bridge.md +0 -670
- package/docs/integration/stdio-conformance.md +0 -51
- package/scripts/run-stdio-conformance.mjs +0 -698
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
|
}
|
|
@@ -291,6 +291,7 @@ var ERROR_CODES = [
|
|
|
291
291
|
"action_not_verified",
|
|
292
292
|
"command_cancelled",
|
|
293
293
|
"command_expired",
|
|
294
|
+
"wait_timeout",
|
|
294
295
|
"unknown_outcome",
|
|
295
296
|
"artifact_not_found",
|
|
296
297
|
"artifact_expired",
|
|
@@ -364,7 +365,8 @@ function asBrowserPilotError(error) {
|
|
|
364
365
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
365
366
|
{ major: 1, minor: 0 },
|
|
366
367
|
{ major: 1, minor: 1 },
|
|
367
|
-
{ major: 1, minor: 2 }
|
|
368
|
+
{ major: 1, minor: 2 },
|
|
369
|
+
{ major: 1, minor: 3 }
|
|
368
370
|
];
|
|
369
371
|
var CAPABILITIES = [
|
|
370
372
|
"browser.discovery",
|
|
@@ -381,6 +383,12 @@ var CAPABILITIES = [
|
|
|
381
383
|
"developer.eval"
|
|
382
384
|
];
|
|
383
385
|
var DEFAULT_CAPABILITIES = CAPABILITIES;
|
|
386
|
+
var PROFILE_IDENTITY_ERROR_CODES = [
|
|
387
|
+
"profile_path_unavailable",
|
|
388
|
+
"profile_path_unverified",
|
|
389
|
+
"local_state_unavailable",
|
|
390
|
+
"profile_metadata_missing"
|
|
391
|
+
];
|
|
384
392
|
var BROWSER_OPERATIONS = [
|
|
385
393
|
"tabs.list",
|
|
386
394
|
"page.observe",
|
|
@@ -963,6 +971,12 @@ var profileRepresentativeTabSchema = objectSchema({
|
|
|
963
971
|
var profileContextSchema = objectSchema({
|
|
964
972
|
profileContextId: opaqueIdSchema,
|
|
965
973
|
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
974
|
+
identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
|
|
975
|
+
profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
976
|
+
accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
977
|
+
accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
978
|
+
profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
979
|
+
identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
|
|
966
980
|
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
967
981
|
tabCount: integerSchema({ minimum: 0 }),
|
|
968
982
|
eligibleTabCount: integerSchema({ minimum: 0 }),
|
|
@@ -991,6 +1005,7 @@ var TOOL_DEFINITIONS = [
|
|
|
991
1005
|
id: opaqueIdSchema,
|
|
992
1006
|
product: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
993
1007
|
channel: stringSchema({ maxLength: 128 }),
|
|
1008
|
+
userDataRoot: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
994
1009
|
profile: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
995
1010
|
processState: stringSchema({ enum: ["running", "not_running", "unknown"] }),
|
|
996
1011
|
remoteDebuggingState: stringSchema({ enum: ["enabled", "disabled", "stale"] }),
|
|
@@ -1051,6 +1066,25 @@ var TOOL_DEFINITIONS = [
|
|
|
1051
1066
|
sensitivity: { input: [], output: ["browser_data"] },
|
|
1052
1067
|
artifactKinds: []
|
|
1053
1068
|
}),
|
|
1069
|
+
tool({
|
|
1070
|
+
name: "browser.profiles.identify",
|
|
1071
|
+
title: "Identify browser Profiles",
|
|
1072
|
+
description: "Explicitly identify live Chrome Profiles using temporary visible chrome://version targets.",
|
|
1073
|
+
context: "workspace",
|
|
1074
|
+
inputSchema: objectSchema({
|
|
1075
|
+
profileContextId: opaqueIdSchema,
|
|
1076
|
+
refresh: booleanSchema()
|
|
1077
|
+
}),
|
|
1078
|
+
outputSchema: resultSchema("workspace", {
|
|
1079
|
+
profiles: arraySchema(profileContextSchema, { maxItems: 128 })
|
|
1080
|
+
}, ["profiles"]),
|
|
1081
|
+
requiredCapabilities: ["browser.control"],
|
|
1082
|
+
mutating: true,
|
|
1083
|
+
idempotency: "non_idempotent",
|
|
1084
|
+
cancellation: "best_effort",
|
|
1085
|
+
sensitivity: { input: [], output: ["browser_data"] },
|
|
1086
|
+
artifactKinds: []
|
|
1087
|
+
}),
|
|
1054
1088
|
tool({
|
|
1055
1089
|
name: "browser.profiles.select",
|
|
1056
1090
|
title: "Select browser Profile",
|
|
@@ -1060,6 +1094,12 @@ var TOOL_DEFINITIONS = [
|
|
|
1060
1094
|
outputSchema: resultSchema("workspace", {
|
|
1061
1095
|
profileContextId: opaqueIdSchema,
|
|
1062
1096
|
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
1097
|
+
identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
|
|
1098
|
+
profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1099
|
+
accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1100
|
+
accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1101
|
+
profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
1102
|
+
identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
|
|
1063
1103
|
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data")
|
|
1064
1104
|
}, ["profileContextId", "label"]),
|
|
1065
1105
|
requiredCapabilities: ["browser.control"],
|
|
@@ -1488,10 +1528,11 @@ var TOOL_DEFINITIONS = [
|
|
|
1488
1528
|
title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
1489
1529
|
url: boundedUrl,
|
|
1490
1530
|
active: booleanSchema(),
|
|
1531
|
+
selected: booleanSchema(),
|
|
1491
1532
|
origin: stringSchema({ enum: ["managed", "managed_popup", "user_tab"] }),
|
|
1492
1533
|
managedTabSetId: opaqueIdSchema,
|
|
1493
1534
|
controlState: stringSchema({ enum: ["available", "controlled", "busy"] })
|
|
1494
|
-
}, ["targetId", "title", "url", "
|
|
1535
|
+
}, ["targetId", "title", "url", "origin", "controlState"]))
|
|
1495
1536
|
}, ["targets"]),
|
|
1496
1537
|
requiredCapabilities: ["browser.control"],
|
|
1497
1538
|
mutating: false,
|
|
@@ -1503,7 +1544,7 @@ var TOOL_DEFINITIONS = [
|
|
|
1503
1544
|
tool({
|
|
1504
1545
|
name: "browser.tabs.switch",
|
|
1505
1546
|
title: "Switch tab",
|
|
1506
|
-
description: "
|
|
1547
|
+
description: "Select a managed or user tab as the controlled target for the current Lease.",
|
|
1507
1548
|
context: "workspace",
|
|
1508
1549
|
inputSchema: objectSchema({ targetId: opaqueIdSchema }, ["targetId"]),
|
|
1509
1550
|
outputSchema: resultSchema("target"),
|
|
@@ -1835,8 +1876,16 @@ var PROTOCOL_1_2_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1835
1876
|
"browser.profiles.list",
|
|
1836
1877
|
"browser.profiles.select"
|
|
1837
1878
|
]);
|
|
1879
|
+
var PROTOCOL_1_3_TOOLS = /* @__PURE__ */ new Set([
|
|
1880
|
+
"browser.profiles.identify"
|
|
1881
|
+
]);
|
|
1882
|
+
function minimumProtocolMinorForTool(name) {
|
|
1883
|
+
if (PROTOCOL_1_3_TOOLS.has(name)) return 3;
|
|
1884
|
+
if (PROTOCOL_1_2_TOOLS.has(name)) return 2;
|
|
1885
|
+
return 0;
|
|
1886
|
+
}
|
|
1838
1887
|
function isToolAvailableInProtocol(name, protocol) {
|
|
1839
|
-
return protocol.major === 1 &&
|
|
1888
|
+
return protocol.major === 1 && protocol.minor >= minimumProtocolMinorForTool(name);
|
|
1840
1889
|
}
|
|
1841
1890
|
function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
|
|
1842
1891
|
for (const value of schema["x-browser-pilot-sensitivity"] ?? []) result.add(value);
|
|
@@ -1848,7 +1897,7 @@ function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
|
|
|
1848
1897
|
}
|
|
1849
1898
|
return result;
|
|
1850
1899
|
}
|
|
1851
|
-
function
|
|
1900
|
+
function assertToolDefinitions(definitions = TOOL_DEFINITIONS) {
|
|
1852
1901
|
const names = /* @__PURE__ */ new Set();
|
|
1853
1902
|
const knownCapabilities = new Set(CAPABILITIES);
|
|
1854
1903
|
for (const definition of definitions) {
|
|
@@ -1912,13 +1961,7 @@ function validateToolResult(name, value) {
|
|
|
1912
1961
|
assertSchemaValue(definition.outputSchema, value, `${name} result`);
|
|
1913
1962
|
return value;
|
|
1914
1963
|
}
|
|
1915
|
-
|
|
1916
|
-
const granted = capabilities ? new Set(capabilities) : null;
|
|
1917
|
-
const available = availableTools ? new Set(availableTools) : null;
|
|
1918
|
-
const tools = TOOL_DEFINITIONS.filter((definition) => (!granted || definition.requiredCapabilities.every((capability) => granted.has(capability))) && (!available || available.has(definition.name)) && (!protocol || isToolAvailableInProtocol(definition.name, protocol)));
|
|
1919
|
-
return { schemaVersion: 1, tools };
|
|
1920
|
-
}
|
|
1921
|
-
assertToolManifest();
|
|
1964
|
+
assertToolDefinitions();
|
|
1922
1965
|
|
|
1923
1966
|
// src/protocol/validation.ts
|
|
1924
1967
|
var CLIENT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{2,127}$/;
|
|
@@ -1930,14 +1973,29 @@ var TARGET_ID_PATTERN = /^target:[A-Za-z0-9._:-]+$/;
|
|
|
1930
1973
|
var ARTIFACT_ID_PATTERN = /^artifact:[A-Za-z0-9._:-]+$/;
|
|
1931
1974
|
var COMMAND_ID_PATTERN = /^command:[A-Za-z0-9._:-]+$/;
|
|
1932
1975
|
var EVENT_CURSOR_PATTERN = /^cursor:(0|[1-9][0-9]{0,15})$/;
|
|
1976
|
+
var COMMAND_STATUSES = /* @__PURE__ */ new Set([
|
|
1977
|
+
"accepted",
|
|
1978
|
+
"dispatched",
|
|
1979
|
+
"completed",
|
|
1980
|
+
"unknown_outcome",
|
|
1981
|
+
"cancelled",
|
|
1982
|
+
"expired"
|
|
1983
|
+
]);
|
|
1984
|
+
var ARTIFACT_KINDS = /* @__PURE__ */ new Set([
|
|
1985
|
+
"screenshot",
|
|
1986
|
+
"screenshot_preview",
|
|
1987
|
+
"pdf",
|
|
1988
|
+
"download",
|
|
1989
|
+
"upload_input"
|
|
1990
|
+
]);
|
|
1933
1991
|
var MIN_NEGOTIATED_TRANSPORT_BYTES = 64 * 1024;
|
|
1934
1992
|
var MAX_DECLARED_TRANSPORT_BYTES = 1024 * 1024 * 1024;
|
|
1935
1993
|
function isRecord2(value) {
|
|
1936
1994
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1937
1995
|
}
|
|
1938
|
-
function assertOnlyKeys(
|
|
1996
|
+
function assertOnlyKeys(record2, allowed, field = "params") {
|
|
1939
1997
|
const allowedSet = new Set(allowed);
|
|
1940
|
-
const unknown = Object.keys(
|
|
1998
|
+
const unknown = Object.keys(record2).filter((key) => !allowedSet.has(key));
|
|
1941
1999
|
if (unknown.length > 0) {
|
|
1942
2000
|
throw invalidArgument(`Unknown ${field} field: ${unknown[0]}`, `${field}.${unknown[0]}`, field === "message" ? -32600 : -32602);
|
|
1943
2001
|
}
|
|
@@ -1950,8 +2008,8 @@ function isJsonValue(value) {
|
|
|
1950
2008
|
if (isRecord2(value)) return Object.values(value).every(isJsonValue);
|
|
1951
2009
|
return false;
|
|
1952
2010
|
}
|
|
1953
|
-
function requireString(
|
|
1954
|
-
const value =
|
|
2011
|
+
function requireString(record2, key, options = {}) {
|
|
2012
|
+
const value = record2[key];
|
|
1955
2013
|
const maxLength = options.maxLength ?? MAX_ID_LENGTH;
|
|
1956
2014
|
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
|
|
1957
2015
|
throw invalidArgument(`${key} must be a non-empty string no longer than ${maxLength} characters`, key);
|
|
@@ -1961,8 +2019,8 @@ function requireString(record, key, options = {}) {
|
|
|
1961
2019
|
}
|
|
1962
2020
|
return value;
|
|
1963
2021
|
}
|
|
1964
|
-
function requireInteger(
|
|
1965
|
-
const value =
|
|
2022
|
+
function requireInteger(record2, key) {
|
|
2023
|
+
const value = record2[key];
|
|
1966
2024
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1967
2025
|
throw invalidArgument(`${key} must be a non-negative safe integer`, key);
|
|
1968
2026
|
}
|
|
@@ -2055,17 +2113,13 @@ function parseTransportLimitPreferences(value) {
|
|
|
2055
2113
|
}
|
|
2056
2114
|
function validateInitializeParams(value) {
|
|
2057
2115
|
if (!isRecord2(value)) throw invalidArgument("initialize params must be an object", "params");
|
|
2058
|
-
assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "
|
|
2116
|
+
assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "limits"]);
|
|
2059
2117
|
if (!isRecord2(value.client)) throw invalidArgument("client must be an object", "client");
|
|
2060
2118
|
assertOnlyKeys(value.client, ["id", "name", "version", "instanceId"], "client");
|
|
2061
2119
|
const requested = value.requestedCapabilities;
|
|
2062
2120
|
if (!Array.isArray(requested) || requested.some((item) => typeof item !== "string" || item.length === 0 || item.length > 128)) {
|
|
2063
2121
|
throw invalidArgument("requestedCapabilities must be an array of non-empty strings", "requestedCapabilities");
|
|
2064
2122
|
}
|
|
2065
|
-
const launchMode = value.launchMode;
|
|
2066
|
-
if (launchMode !== "one-shot" && launchMode !== "embedded") {
|
|
2067
|
-
throw invalidArgument("launchMode must be one-shot or embedded", "launchMode");
|
|
2068
|
-
}
|
|
2069
2123
|
return {
|
|
2070
2124
|
client: {
|
|
2071
2125
|
id: requireString(value.client, "id", { pattern: CLIENT_ID_PATTERN, maxLength: 128 }),
|
|
@@ -2075,7 +2129,6 @@ function validateInitializeParams(value) {
|
|
|
2075
2129
|
},
|
|
2076
2130
|
protocol: parseProtocolRange(value.protocol),
|
|
2077
2131
|
requestedCapabilities: [...new Set(requested)],
|
|
2078
|
-
launchMode,
|
|
2079
2132
|
...value.limits !== void 0 ? { limits: parseTransportLimitPreferences(value.limits) } : {}
|
|
2080
2133
|
};
|
|
2081
2134
|
}
|
|
@@ -2085,18 +2138,15 @@ function validateOptionalEmptyParams(value) {
|
|
|
2085
2138
|
assertOnlyKeys(value, []);
|
|
2086
2139
|
return {};
|
|
2087
2140
|
}
|
|
2088
|
-
function validateTtlMs(
|
|
2089
|
-
if (
|
|
2090
|
-
if (!Number.isSafeInteger(
|
|
2141
|
+
function validateTtlMs(record2) {
|
|
2142
|
+
if (record2.ttlMs === void 0) return void 0;
|
|
2143
|
+
if (!Number.isSafeInteger(record2.ttlMs)) {
|
|
2091
2144
|
throw invalidArgument("ttlMs must be a safe integer", "ttlMs");
|
|
2092
2145
|
}
|
|
2093
|
-
return
|
|
2094
|
-
}
|
|
2095
|
-
function validateOpaqueId(record, key, pattern) {
|
|
2096
|
-
return requireString(record, key, { pattern, maxLength: 128 });
|
|
2146
|
+
return record2.ttlMs;
|
|
2097
2147
|
}
|
|
2098
|
-
function
|
|
2099
|
-
return
|
|
2148
|
+
function validateOpaqueId(record2, key, pattern) {
|
|
2149
|
+
return requireString(record2, key, { pattern, maxLength: 128 });
|
|
2100
2150
|
}
|
|
2101
2151
|
function validateShutdownParams(value) {
|
|
2102
2152
|
return validateOptionalEmptyParams(value);
|
|
@@ -2153,6 +2203,25 @@ function validateCommandAccessParams(value) {
|
|
|
2153
2203
|
} : {}
|
|
2154
2204
|
};
|
|
2155
2205
|
}
|
|
2206
|
+
function validateCommandListParams(value) {
|
|
2207
|
+
if (!isRecord2(value)) throw invalidArgument("Command list params must be an object", "params");
|
|
2208
|
+
assertOnlyKeys(value, ["workspaceId", "limit", "statuses"]);
|
|
2209
|
+
if (value.limit !== void 0 && (!Number.isSafeInteger(value.limit) || Number(value.limit) < 1 || Number(value.limit) > 100)) {
|
|
2210
|
+
throw invalidArgument("limit must be an integer from 1 through 100", "limit");
|
|
2211
|
+
}
|
|
2212
|
+
if (value.statuses !== void 0 && (!Array.isArray(value.statuses) || value.statuses.length === 0 || value.statuses.some((status) => typeof status !== "string" || !COMMAND_STATUSES.has(status)))) {
|
|
2213
|
+
throw invalidArgument("statuses must contain valid Command statuses", "statuses");
|
|
2214
|
+
}
|
|
2215
|
+
return {
|
|
2216
|
+
workspaceId: validateOpaqueId(
|
|
2217
|
+
value,
|
|
2218
|
+
"workspaceId",
|
|
2219
|
+
WORKSPACE_ID_PATTERN
|
|
2220
|
+
),
|
|
2221
|
+
...value.limit !== void 0 ? { limit: Number(value.limit) } : {},
|
|
2222
|
+
...value.statuses !== void 0 ? { statuses: value.statuses } : {}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2156
2225
|
function validateEventsPollParams(value) {
|
|
2157
2226
|
if (!isRecord2(value)) throw invalidArgument("events/poll params must be an object", "params");
|
|
2158
2227
|
assertOnlyKeys(value, ["workspaceId", "cursor", "limit"]);
|
|
@@ -2178,6 +2247,18 @@ function validateArtifactAccessParams(value) {
|
|
|
2178
2247
|
artifactId: validateOpaqueId(value, "artifactId", ARTIFACT_ID_PATTERN)
|
|
2179
2248
|
};
|
|
2180
2249
|
}
|
|
2250
|
+
function validateArtifactListParams(value) {
|
|
2251
|
+
if (!isRecord2(value)) throw invalidArgument("Artifact list params must be an object", "params");
|
|
2252
|
+
assertOnlyKeys(value, ["workspaceId", "leaseId", "kinds"]);
|
|
2253
|
+
if (value.kinds !== void 0 && (!Array.isArray(value.kinds) || value.kinds.length === 0 || value.kinds.some((kind) => typeof kind !== "string" || !ARTIFACT_KINDS.has(kind)))) {
|
|
2254
|
+
throw invalidArgument("kinds must contain valid Artifact kinds", "kinds");
|
|
2255
|
+
}
|
|
2256
|
+
return {
|
|
2257
|
+
workspaceId: validateOpaqueId(value, "workspaceId", WORKSPACE_ID_PATTERN),
|
|
2258
|
+
leaseId: validateOpaqueId(value, "leaseId", LEASE_ID_PATTERN),
|
|
2259
|
+
...value.kinds !== void 0 ? { kinds: value.kinds } : {}
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2181
2262
|
function validateArtifactExportParams(value) {
|
|
2182
2263
|
if (!isRecord2(value)) throw invalidArgument("artifacts/export params must be an object", "params");
|
|
2183
2264
|
assertOnlyKeys(value, ["workspaceId", "leaseId", "artifactId", "path", "overwrite"]);
|
|
@@ -2262,22 +2343,22 @@ function canonical(value) {
|
|
|
2262
2343
|
function cloneJson(value) {
|
|
2263
2344
|
return structuredClone(value);
|
|
2264
2345
|
}
|
|
2265
|
-
function descriptor(
|
|
2346
|
+
function descriptor(record2) {
|
|
2266
2347
|
return {
|
|
2267
|
-
id:
|
|
2268
|
-
...
|
|
2269
|
-
...
|
|
2270
|
-
...
|
|
2271
|
-
...
|
|
2272
|
-
idempotencyKey:
|
|
2273
|
-
method:
|
|
2274
|
-
mutating:
|
|
2275
|
-
status:
|
|
2276
|
-
acceptedAt:
|
|
2277
|
-
deadlineAt:
|
|
2278
|
-
...
|
|
2279
|
-
...
|
|
2280
|
-
...
|
|
2348
|
+
id: record2.id,
|
|
2349
|
+
...record2.workspaceId ? { workspaceId: record2.workspaceId } : {},
|
|
2350
|
+
...record2.leaseId ? { leaseId: record2.leaseId } : {},
|
|
2351
|
+
...record2.targetId ? { targetId: record2.targetId } : {},
|
|
2352
|
+
...record2.browserConnectionGeneration !== void 0 ? { browserConnectionGeneration: record2.browserConnectionGeneration } : {},
|
|
2353
|
+
idempotencyKey: record2.idempotencyKey,
|
|
2354
|
+
method: record2.method,
|
|
2355
|
+
mutating: record2.mutating,
|
|
2356
|
+
status: record2.status,
|
|
2357
|
+
acceptedAt: record2.acceptedAt,
|
|
2358
|
+
deadlineAt: record2.deadlineAt,
|
|
2359
|
+
...record2.dispatchedAt !== void 0 ? { dispatchedAt: record2.dispatchedAt } : {},
|
|
2360
|
+
...record2.completedAt !== void 0 ? { completedAt: record2.completedAt } : {},
|
|
2361
|
+
...record2.cancellationRequested ? { cancellationRequested: true } : {}
|
|
2281
2362
|
};
|
|
2282
2363
|
}
|
|
2283
2364
|
var MemoryCommandRuntime = class {
|
|
@@ -2344,13 +2425,13 @@ var MemoryCommandRuntime = class {
|
|
|
2344
2425
|
throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
|
|
2345
2426
|
}
|
|
2346
2427
|
const acceptedAt = this.now();
|
|
2347
|
-
let
|
|
2428
|
+
let resolve3;
|
|
2348
2429
|
let reject;
|
|
2349
2430
|
const completion = new Promise((resolvePromise, rejectPromise) => {
|
|
2350
|
-
|
|
2431
|
+
resolve3 = resolvePromise;
|
|
2351
2432
|
reject = rejectPromise;
|
|
2352
2433
|
});
|
|
2353
|
-
const
|
|
2434
|
+
const record2 = {
|
|
2354
2435
|
id,
|
|
2355
2436
|
...input.workspaceId ? { workspaceId: input.workspaceId } : {},
|
|
2356
2437
|
...input.leaseId ? { leaseId: input.leaseId } : {},
|
|
@@ -2368,17 +2449,17 @@ var MemoryCommandRuntime = class {
|
|
|
2368
2449
|
scopeKey,
|
|
2369
2450
|
cancellation: input.cancellation,
|
|
2370
2451
|
abortController: new AbortController(),
|
|
2371
|
-
resolve:
|
|
2452
|
+
resolve: resolve3,
|
|
2372
2453
|
reject,
|
|
2373
2454
|
completion,
|
|
2374
2455
|
settled: false
|
|
2375
2456
|
};
|
|
2376
|
-
this.records.set(id,
|
|
2457
|
+
this.records.set(id, record2);
|
|
2377
2458
|
this.commandsByIdempotency.set(idempotencyIndex, id);
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
this.emitStatus(
|
|
2381
|
-
this.enqueue(input.actorKey, () => this.execute(
|
|
2459
|
+
record2.deadlineTimer = setTimeout(() => this.expire(record2), deadlineMs);
|
|
2460
|
+
record2.deadlineTimer.unref();
|
|
2461
|
+
this.emitStatus(record2);
|
|
2462
|
+
this.enqueue(input.actorKey, () => this.execute(record2, execute));
|
|
2382
2463
|
return completion;
|
|
2383
2464
|
}
|
|
2384
2465
|
subscribe(listener) {
|
|
@@ -2389,51 +2470,56 @@ var MemoryCommandRuntime = class {
|
|
|
2389
2470
|
this.sweep();
|
|
2390
2471
|
return this.outcome(this.requireOwned(context));
|
|
2391
2472
|
}
|
|
2473
|
+
list(context) {
|
|
2474
|
+
this.sweep();
|
|
2475
|
+
const statuses = context.statuses ? new Set(context.statuses) : void 0;
|
|
2476
|
+
return [...this.records.values()].filter((record2) => record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && (!statuses || statuses.has(record2.status))).sort((left, right) => right.acceptedAt - left.acceptedAt).slice(0, context.limit ?? 20).map(descriptor);
|
|
2477
|
+
}
|
|
2392
2478
|
cancel(context) {
|
|
2393
2479
|
this.sweep();
|
|
2394
|
-
const
|
|
2395
|
-
if (TERMINAL_STATUSES.has(
|
|
2396
|
-
if (
|
|
2480
|
+
const record2 = this.requireOwned(context);
|
|
2481
|
+
if (TERMINAL_STATUSES.has(record2.status)) return this.outcome(record2);
|
|
2482
|
+
if (record2.cancellation === "not_applicable") {
|
|
2397
2483
|
throw invalidArgument("Command does not support cancellation", "commandId");
|
|
2398
2484
|
}
|
|
2399
|
-
if (
|
|
2400
|
-
|
|
2485
|
+
if (record2.status === "accepted") {
|
|
2486
|
+
record2.abortController.abort();
|
|
2401
2487
|
this.finishWithError(
|
|
2402
|
-
|
|
2488
|
+
record2,
|
|
2403
2489
|
"cancelled",
|
|
2404
2490
|
new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch")
|
|
2405
2491
|
);
|
|
2406
|
-
return this.outcome(
|
|
2492
|
+
return this.outcome(record2);
|
|
2407
2493
|
}
|
|
2408
|
-
|
|
2409
|
-
this.emitStatus(
|
|
2410
|
-
if (
|
|
2411
|
-
return this.outcome(
|
|
2494
|
+
record2.cancellationRequested = true;
|
|
2495
|
+
this.emitStatus(record2);
|
|
2496
|
+
if (record2.cancellation === "best_effort") record2.abortController.abort();
|
|
2497
|
+
return this.outcome(record2);
|
|
2412
2498
|
}
|
|
2413
2499
|
releaseLease(leaseId) {
|
|
2414
|
-
for (const
|
|
2415
|
-
if (
|
|
2500
|
+
for (const record2 of this.records.values()) {
|
|
2501
|
+
if (record2.leaseId === leaseId) this.cancelForCleanup(record2);
|
|
2416
2502
|
}
|
|
2417
2503
|
}
|
|
2418
2504
|
releaseConnection(connectionId) {
|
|
2419
|
-
for (const
|
|
2420
|
-
if (
|
|
2505
|
+
for (const record2 of this.records.values()) {
|
|
2506
|
+
if (record2.connectionId === connectionId) this.cancelForCleanup(record2);
|
|
2421
2507
|
}
|
|
2422
2508
|
}
|
|
2423
2509
|
releaseWorkspace(workspaceId) {
|
|
2424
|
-
for (const
|
|
2425
|
-
if (
|
|
2510
|
+
for (const record2 of this.records.values()) {
|
|
2511
|
+
if (record2.workspaceId === workspaceId) this.cancelForCleanup(record2);
|
|
2426
2512
|
}
|
|
2427
2513
|
}
|
|
2428
2514
|
sweep() {
|
|
2429
2515
|
const now = this.now();
|
|
2430
2516
|
let removed = 0;
|
|
2431
|
-
for (const
|
|
2432
|
-
if (!TERMINAL_STATUSES.has(
|
|
2517
|
+
for (const record2 of this.records.values()) {
|
|
2518
|
+
if (!TERMINAL_STATUSES.has(record2.status) && record2.deadlineAt <= now) this.expire(record2);
|
|
2433
2519
|
}
|
|
2434
|
-
for (const [id,
|
|
2435
|
-
if (TERMINAL_STATUSES.has(
|
|
2436
|
-
this.deleteRecord(id,
|
|
2520
|
+
for (const [id, record2] of this.records) {
|
|
2521
|
+
if (TERMINAL_STATUSES.has(record2.status) && (record2.completedAt ?? record2.deadlineAt) + this.terminalTtlMs <= now) {
|
|
2522
|
+
this.deleteRecord(id, record2);
|
|
2437
2523
|
removed += 1;
|
|
2438
2524
|
}
|
|
2439
2525
|
}
|
|
@@ -2442,140 +2528,140 @@ var MemoryCommandRuntime = class {
|
|
|
2442
2528
|
size() {
|
|
2443
2529
|
return this.records.size;
|
|
2444
2530
|
}
|
|
2445
|
-
async execute(
|
|
2446
|
-
if (
|
|
2447
|
-
if (
|
|
2448
|
-
this.expire(
|
|
2531
|
+
async execute(record2, execute) {
|
|
2532
|
+
if (record2.status !== "accepted") return;
|
|
2533
|
+
if (record2.deadlineAt <= this.now()) {
|
|
2534
|
+
this.expire(record2);
|
|
2449
2535
|
return;
|
|
2450
2536
|
}
|
|
2451
2537
|
try {
|
|
2452
2538
|
const markDispatched = () => {
|
|
2453
|
-
if (
|
|
2539
|
+
if (record2.status === "cancelled") {
|
|
2454
2540
|
throw new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch", {
|
|
2455
|
-
context: { commandId:
|
|
2541
|
+
context: { commandId: record2.id }
|
|
2456
2542
|
});
|
|
2457
2543
|
}
|
|
2458
|
-
if (
|
|
2544
|
+
if (record2.status === "expired") {
|
|
2459
2545
|
throw new BrowserPilotError("command_expired", "Command expired before dispatch", {
|
|
2460
|
-
context: { commandId:
|
|
2546
|
+
context: { commandId: record2.id }
|
|
2461
2547
|
});
|
|
2462
2548
|
}
|
|
2463
|
-
if (
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
this.emitStatus(
|
|
2549
|
+
if (record2.status !== "accepted") return;
|
|
2550
|
+
record2.status = "dispatched";
|
|
2551
|
+
record2.dispatchedAt = this.now();
|
|
2552
|
+
this.emitStatus(record2);
|
|
2467
2553
|
};
|
|
2468
|
-
const result = await execute({ signal:
|
|
2469
|
-
if (TERMINAL_STATUSES.has(
|
|
2554
|
+
const result = await execute({ signal: record2.abortController.signal, markDispatched });
|
|
2555
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2470
2556
|
markDispatched();
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
this.clearDeadline(
|
|
2475
|
-
this.emitStatus(
|
|
2476
|
-
this.resolve(
|
|
2557
|
+
record2.status = "completed";
|
|
2558
|
+
record2.completedAt = this.now();
|
|
2559
|
+
record2.result = cloneJson(result);
|
|
2560
|
+
this.clearDeadline(record2);
|
|
2561
|
+
this.emitStatus(record2);
|
|
2562
|
+
this.resolve(record2);
|
|
2477
2563
|
} catch (error) {
|
|
2478
|
-
if (TERMINAL_STATUSES.has(
|
|
2479
|
-
const stable = this.withCommandContext(error,
|
|
2480
|
-
if (
|
|
2564
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2565
|
+
const stable = this.withCommandContext(error, record2);
|
|
2566
|
+
if (record2.mutating && record2.dispatchedAt !== void 0 && (stable.code === "browser_disconnected" || stable.code === "internal_error" || stable.code === "unknown_outcome")) {
|
|
2481
2567
|
if (stable.code === "unknown_outcome") {
|
|
2482
|
-
this.finishWithError(
|
|
2568
|
+
this.finishWithError(record2, "unknown_outcome", stable);
|
|
2483
2569
|
return;
|
|
2484
2570
|
}
|
|
2485
2571
|
this.finishWithError(
|
|
2486
|
-
|
|
2572
|
+
record2,
|
|
2487
2573
|
"unknown_outcome",
|
|
2488
2574
|
new BrowserPilotError("unknown_outcome", "Command outcome is unknown after dispatch", {
|
|
2489
|
-
context: { commandId:
|
|
2575
|
+
context: { commandId: record2.id },
|
|
2490
2576
|
cause: stable
|
|
2491
2577
|
})
|
|
2492
2578
|
);
|
|
2493
2579
|
} else {
|
|
2494
|
-
this.finishWithError(
|
|
2580
|
+
this.finishWithError(record2, "completed", stable);
|
|
2495
2581
|
}
|
|
2496
2582
|
}
|
|
2497
2583
|
}
|
|
2498
|
-
expire(
|
|
2499
|
-
if (TERMINAL_STATUSES.has(
|
|
2500
|
-
|
|
2501
|
-
if (
|
|
2584
|
+
expire(record2) {
|
|
2585
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2586
|
+
record2.abortController.abort();
|
|
2587
|
+
if (record2.status === "dispatched" && record2.mutating) {
|
|
2502
2588
|
this.finishWithError(
|
|
2503
|
-
|
|
2589
|
+
record2,
|
|
2504
2590
|
"unknown_outcome",
|
|
2505
2591
|
new BrowserPilotError("unknown_outcome", "Command deadline elapsed after dispatch", {
|
|
2506
|
-
context: { commandId:
|
|
2592
|
+
context: { commandId: record2.id }
|
|
2507
2593
|
})
|
|
2508
2594
|
);
|
|
2509
2595
|
return;
|
|
2510
2596
|
}
|
|
2511
2597
|
this.finishWithError(
|
|
2512
|
-
|
|
2598
|
+
record2,
|
|
2513
2599
|
"expired",
|
|
2514
2600
|
new BrowserPilotError("command_expired", "Command deadline elapsed before a result was known", {
|
|
2515
|
-
context: { commandId:
|
|
2601
|
+
context: { commandId: record2.id }
|
|
2516
2602
|
})
|
|
2517
2603
|
);
|
|
2518
2604
|
}
|
|
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
|
-
|
|
2605
|
+
finishWithError(record2, status, error) {
|
|
2606
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2607
|
+
const stable = this.withCommandContext(error, record2);
|
|
2608
|
+
record2.status = status;
|
|
2609
|
+
record2.completedAt = this.now();
|
|
2610
|
+
record2.error = stable.toJsonRpcError();
|
|
2611
|
+
this.clearDeadline(record2);
|
|
2612
|
+
this.emitStatus(record2);
|
|
2613
|
+
if (!record2.settled) {
|
|
2614
|
+
record2.settled = true;
|
|
2615
|
+
record2.reject(stable);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
resolve(record2) {
|
|
2619
|
+
if (record2.settled) return;
|
|
2620
|
+
record2.settled = true;
|
|
2621
|
+
record2.resolve(this.outcome(record2));
|
|
2622
|
+
}
|
|
2623
|
+
cancelForCleanup(record2) {
|
|
2624
|
+
if (TERMINAL_STATUSES.has(record2.status)) return;
|
|
2625
|
+
if (record2.status === "accepted") {
|
|
2626
|
+
record2.abortController.abort();
|
|
2541
2627
|
this.finishWithError(
|
|
2542
|
-
|
|
2628
|
+
record2,
|
|
2543
2629
|
"cancelled",
|
|
2544
2630
|
new BrowserPilotError("command_cancelled", "Command was cancelled during resource cleanup")
|
|
2545
2631
|
);
|
|
2546
2632
|
return;
|
|
2547
2633
|
}
|
|
2548
|
-
|
|
2549
|
-
this.emitStatus(
|
|
2550
|
-
|
|
2634
|
+
record2.cancellationRequested = true;
|
|
2635
|
+
this.emitStatus(record2);
|
|
2636
|
+
record2.abortController.abort();
|
|
2551
2637
|
}
|
|
2552
2638
|
requireOwned(context) {
|
|
2553
|
-
const
|
|
2554
|
-
if (!
|
|
2639
|
+
const record2 = this.records.get(context.commandId);
|
|
2640
|
+
if (!record2 || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId) {
|
|
2555
2641
|
throw invalidArgument("Command was not found for this ClientPrincipal and Workspace", "commandId");
|
|
2556
2642
|
}
|
|
2557
|
-
return
|
|
2643
|
+
return record2;
|
|
2558
2644
|
}
|
|
2559
|
-
assertDuplicate(
|
|
2560
|
-
if (
|
|
2645
|
+
assertDuplicate(record2, input, fingerprint, idempotencyKey) {
|
|
2646
|
+
if (record2.principalId !== input.principalId || record2.workspaceId !== input.workspaceId || record2.method !== input.method || record2.fingerprint !== fingerprint || record2.idempotencyKey !== idempotencyKey) {
|
|
2561
2647
|
throw invalidArgument("Command or idempotency key was reused for a different request", "idempotencyKey");
|
|
2562
2648
|
}
|
|
2563
2649
|
}
|
|
2564
|
-
outcome(
|
|
2650
|
+
outcome(record2) {
|
|
2565
2651
|
return {
|
|
2566
|
-
command: descriptor(
|
|
2567
|
-
...
|
|
2568
|
-
...
|
|
2652
|
+
command: descriptor(record2),
|
|
2653
|
+
...record2.result !== void 0 ? { result: cloneJson(record2.result) } : {},
|
|
2654
|
+
...record2.error ? { error: cloneJson(record2.error) } : {}
|
|
2569
2655
|
};
|
|
2570
2656
|
}
|
|
2571
2657
|
scopeKey(principalId, connectionId, workspaceId) {
|
|
2572
2658
|
return workspaceId ? `${principalId}\0${workspaceId}` : `${principalId}\0${connectionId}`;
|
|
2573
2659
|
}
|
|
2574
|
-
withCommandContext(error,
|
|
2660
|
+
withCommandContext(error, record2) {
|
|
2575
2661
|
const stable = asBrowserPilotError(error);
|
|
2576
2662
|
return new BrowserPilotError(stable.code, stable.message, {
|
|
2577
2663
|
retryable: stable.retryable,
|
|
2578
|
-
context: { ...stable.context ?? {}, commandId:
|
|
2664
|
+
context: { ...stable.context ?? {}, commandId: record2.id },
|
|
2579
2665
|
...stable.remediation ? { remediation: stable.remediation } : {},
|
|
2580
2666
|
rpcCode: stable.rpcCode,
|
|
2581
2667
|
cause: error
|
|
@@ -2595,9 +2681,9 @@ var MemoryCommandRuntime = class {
|
|
|
2595
2681
|
}
|
|
2596
2682
|
ensureCapacity() {
|
|
2597
2683
|
if (this.records.size < this.maxCommands) return;
|
|
2598
|
-
const terminal = [...this.records.values()].filter((
|
|
2599
|
-
for (const
|
|
2600
|
-
this.deleteRecord(
|
|
2684
|
+
const terminal = [...this.records.values()].filter((record2) => TERMINAL_STATUSES.has(record2.status)).sort((left, right) => (left.completedAt ?? left.deadlineAt) - (right.completedAt ?? right.deadlineAt));
|
|
2685
|
+
for (const record2 of terminal) {
|
|
2686
|
+
this.deleteRecord(record2.id, record2);
|
|
2601
2687
|
if (this.records.size < this.maxCommands) return;
|
|
2602
2688
|
}
|
|
2603
2689
|
throw new BrowserPilotError("result_too_large", "Command store capacity is exhausted", {
|
|
@@ -2605,19 +2691,19 @@ var MemoryCommandRuntime = class {
|
|
|
2605
2691
|
context: { maxCommands: this.maxCommands }
|
|
2606
2692
|
});
|
|
2607
2693
|
}
|
|
2608
|
-
deleteRecord(id,
|
|
2609
|
-
this.clearDeadline(
|
|
2694
|
+
deleteRecord(id, record2) {
|
|
2695
|
+
this.clearDeadline(record2);
|
|
2610
2696
|
this.records.delete(id);
|
|
2611
|
-
const index = `${
|
|
2697
|
+
const index = `${record2.scopeKey}\0${record2.idempotencyKey}`;
|
|
2612
2698
|
if (this.commandsByIdempotency.get(index) === id) this.commandsByIdempotency.delete(index);
|
|
2613
2699
|
}
|
|
2614
|
-
clearDeadline(
|
|
2615
|
-
if (!
|
|
2616
|
-
clearTimeout(
|
|
2617
|
-
|
|
2700
|
+
clearDeadline(record2) {
|
|
2701
|
+
if (!record2.deadlineTimer) return;
|
|
2702
|
+
clearTimeout(record2.deadlineTimer);
|
|
2703
|
+
record2.deadlineTimer = void 0;
|
|
2618
2704
|
}
|
|
2619
|
-
emitStatus(
|
|
2620
|
-
const outcome = this.outcome(
|
|
2705
|
+
emitStatus(record2) {
|
|
2706
|
+
const outcome = this.outcome(record2);
|
|
2621
2707
|
for (const listener of this.statusListeners) {
|
|
2622
2708
|
try {
|
|
2623
2709
|
listener(outcome);
|
|
@@ -2779,13 +2865,21 @@ var DEFAULT_PROTOCOL_LIMITS = {
|
|
|
2779
2865
|
function normalizeBrowserCandidate(candidate) {
|
|
2780
2866
|
const ready = candidate.state === "ready";
|
|
2781
2867
|
const authorizationRequired = candidate.state === "authorization_required";
|
|
2868
|
+
const userDataRoot = candidate.userDataRoot ?? candidate.profile;
|
|
2782
2869
|
return {
|
|
2783
2870
|
...candidate,
|
|
2871
|
+
...userDataRoot ? { userDataRoot, profile: userDataRoot } : {},
|
|
2784
2872
|
processState: candidate.processState ?? (ready || authorizationRequired ? "running" : "unknown"),
|
|
2785
2873
|
remoteDebuggingState: candidate.remoteDebuggingState ?? (ready || authorizationRequired ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
|
|
2786
2874
|
authorizationState: candidate.authorizationState ?? (ready ? "authorized" : authorizationRequired ? "required" : candidate.state === "disconnected" ? "unknown" : "not_applicable")
|
|
2787
2875
|
};
|
|
2788
2876
|
}
|
|
2877
|
+
function browserCandidateForProtocol(candidate, protocolMinor) {
|
|
2878
|
+
const { profile, userDataRoot, ...rest } = candidate;
|
|
2879
|
+
const root = userDataRoot ?? profile;
|
|
2880
|
+
if (!root) return rest;
|
|
2881
|
+
return protocolMinor >= 3 ? { ...rest, userDataRoot: root } : { ...rest, profile: root };
|
|
2882
|
+
}
|
|
2789
2883
|
function asJson(value) {
|
|
2790
2884
|
return value;
|
|
2791
2885
|
}
|
|
@@ -2795,8 +2889,8 @@ function cloneWorkspace(value) {
|
|
|
2795
2889
|
function cloneManagedTabSet(value) {
|
|
2796
2890
|
return { ...value };
|
|
2797
2891
|
}
|
|
2798
|
-
function runtimeManagedTabSets(
|
|
2799
|
-
return [
|
|
2892
|
+
function runtimeManagedTabSets(record2) {
|
|
2893
|
+
return [record2.managedTabSet, ...record2.additionalManagedTabSets.values()];
|
|
2800
2894
|
}
|
|
2801
2895
|
function cloneLease(value) {
|
|
2802
2896
|
return { ...value, capabilities: [...value.capabilities] };
|
|
@@ -2843,7 +2937,6 @@ var MemoryBrokerRuntime = class {
|
|
|
2843
2937
|
preserveIfGenerationStale: true
|
|
2844
2938
|
});
|
|
2845
2939
|
}));
|
|
2846
|
-
this.listenerCleanup.push(this.events.subscribe((event) => this.deliverNotification(event)));
|
|
2847
2940
|
options.toolExecutor?.setEventPublisher?.((event) => {
|
|
2848
2941
|
this.publishBrowserEvent(event);
|
|
2849
2942
|
});
|
|
@@ -2880,7 +2973,7 @@ var MemoryBrokerRuntime = class {
|
|
|
2880
2973
|
limits;
|
|
2881
2974
|
principals = /* @__PURE__ */ new Map();
|
|
2882
2975
|
principalIdsByKey = /* @__PURE__ */ new Map();
|
|
2883
|
-
|
|
2976
|
+
connectionsByClientSession = /* @__PURE__ */ new Map();
|
|
2884
2977
|
connectionsById = /* @__PURE__ */ new Map();
|
|
2885
2978
|
workspaces = /* @__PURE__ */ new Map();
|
|
2886
2979
|
leases = /* @__PURE__ */ new Map();
|
|
@@ -2903,30 +2996,27 @@ var MemoryBrokerRuntime = class {
|
|
|
2903
2996
|
events;
|
|
2904
2997
|
listenerCleanup = [];
|
|
2905
2998
|
workspaceCleanup = /* @__PURE__ */ new Map();
|
|
2906
|
-
async call(
|
|
2907
|
-
this.
|
|
2999
|
+
async call(clientSessionId, method, params) {
|
|
3000
|
+
this.assertClientSessionId(clientSessionId);
|
|
2908
3001
|
this.sweepExpiredLeases();
|
|
2909
|
-
if (method === "initialize") return this.initialize(
|
|
2910
|
-
const connection = this.requireConnection(
|
|
3002
|
+
if (method === "initialize") return this.initialize(clientSessionId, params);
|
|
3003
|
+
const connection = this.requireConnection(clientSessionId);
|
|
2911
3004
|
connection.value.lastSeenAt = this.now();
|
|
2912
3005
|
switch (method) {
|
|
2913
|
-
case "tools/list":
|
|
2914
|
-
validateToolsListParams(params);
|
|
2915
|
-
return asJson(getToolManifest(
|
|
2916
|
-
connection.grantedCapabilities,
|
|
2917
|
-
this.options.toolExecutor?.supportedTools,
|
|
2918
|
-
connection.value.protocol
|
|
2919
|
-
));
|
|
2920
3006
|
case "tools/call":
|
|
2921
3007
|
return this.callTool(connection, params);
|
|
2922
3008
|
case "commands/get":
|
|
2923
3009
|
return this.getCommand(connection, params);
|
|
3010
|
+
case "commands/list":
|
|
3011
|
+
return this.listCommands(connection, params);
|
|
2924
3012
|
case "commands/cancel":
|
|
2925
3013
|
return this.cancelCommand(connection, params);
|
|
2926
3014
|
case "events/poll":
|
|
2927
3015
|
return this.pollEvents(connection, params);
|
|
2928
3016
|
case "artifacts/get":
|
|
2929
3017
|
return this.getArtifact(connection, params);
|
|
3018
|
+
case "artifacts/list":
|
|
3019
|
+
return this.listArtifacts(connection, params);
|
|
2930
3020
|
case "artifacts/export":
|
|
2931
3021
|
return this.exportArtifact(connection, params);
|
|
2932
3022
|
case "artifacts/import":
|
|
@@ -2957,12 +3047,11 @@ var MemoryBrokerRuntime = class {
|
|
|
2957
3047
|
});
|
|
2958
3048
|
}
|
|
2959
3049
|
}
|
|
2960
|
-
disconnect(
|
|
2961
|
-
const connection = this.
|
|
3050
|
+
disconnect(clientSessionId) {
|
|
3051
|
+
const connection = this.connectionsByClientSession.get(clientSessionId);
|
|
2962
3052
|
if (!connection) return;
|
|
2963
3053
|
this.commands.releaseConnection(connection.value.id);
|
|
2964
|
-
this.
|
|
2965
|
-
this.connectionsByBridge.delete(bridgeSessionId);
|
|
3054
|
+
this.connectionsByClientSession.delete(clientSessionId);
|
|
2966
3055
|
this.connectionsById.delete(connection.value.id);
|
|
2967
3056
|
for (const [leaseId, lease] of this.leases) {
|
|
2968
3057
|
if (lease.connectionId !== connection.value.id) continue;
|
|
@@ -2972,40 +3061,11 @@ var MemoryBrokerRuntime = class {
|
|
|
2972
3061
|
this.cleanupUnusedPrincipal(connection.value.principalId);
|
|
2973
3062
|
}
|
|
2974
3063
|
close() {
|
|
2975
|
-
for (const
|
|
2976
|
-
this.disconnect(
|
|
3064
|
+
for (const clientSessionId of [...this.connectionsByClientSession.keys()]) {
|
|
3065
|
+
this.disconnect(clientSessionId);
|
|
2977
3066
|
}
|
|
2978
3067
|
for (const cleanup2 of this.listenerCleanup.splice(0)) cleanup2();
|
|
2979
3068
|
}
|
|
2980
|
-
async nextNotification(bridgeSessionId, options = {}) {
|
|
2981
|
-
const connection = this.requireConnection(bridgeSessionId);
|
|
2982
|
-
const waitMs = options.waitMs ?? 25e3;
|
|
2983
|
-
if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > 3e4) {
|
|
2984
|
-
throw invalidArgument("Notification waitMs must be from 0 through 30000", "waitMs");
|
|
2985
|
-
}
|
|
2986
|
-
const queued = connection.notifications.shift();
|
|
2987
|
-
if (queued || waitMs === 0 || options.signal?.aborted) return queued;
|
|
2988
|
-
if (connection.notificationWaiter) {
|
|
2989
|
-
throw invalidArgument("Only one notification poll may be active per bridge Connection");
|
|
2990
|
-
}
|
|
2991
|
-
return new Promise((resolve2) => {
|
|
2992
|
-
const waiter = { resolve: resolve2, signal: options.signal };
|
|
2993
|
-
const finish = () => this.resolveNotificationWaiter(connection, void 0);
|
|
2994
|
-
waiter.timer = setTimeout(finish, waitMs);
|
|
2995
|
-
waiter.timer.unref();
|
|
2996
|
-
if (options.signal) {
|
|
2997
|
-
waiter.onAbort = finish;
|
|
2998
|
-
options.signal.addEventListener("abort", finish, { once: true });
|
|
2999
|
-
}
|
|
3000
|
-
connection.notificationWaiter = waiter;
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
async *notifications(bridgeSessionId, signal) {
|
|
3004
|
-
while (!signal.aborted) {
|
|
3005
|
-
const notification = await this.nextNotification(bridgeSessionId, { waitMs: 3e4, signal });
|
|
3006
|
-
if (notification) yield notification;
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3009
3069
|
publishBrowserEvent(input) {
|
|
3010
3070
|
const workspace = this.workspaces.get(input.workspaceId);
|
|
3011
3071
|
if (!workspace || workspace.value.state !== "active") return void 0;
|
|
@@ -3103,18 +3163,18 @@ var MemoryBrokerRuntime = class {
|
|
|
3103
3163
|
expired += 1;
|
|
3104
3164
|
}
|
|
3105
3165
|
}
|
|
3106
|
-
for (const connection of [...this.
|
|
3166
|
+
for (const connection of [...this.connectionsByClientSession.values()]) {
|
|
3107
3167
|
if (connection.value.lastSeenAt + this.connectionIdleTtlMs <= now) {
|
|
3108
|
-
this.disconnect(connection.
|
|
3168
|
+
this.disconnect(connection.clientSessionId);
|
|
3109
3169
|
}
|
|
3110
3170
|
}
|
|
3111
3171
|
const leasedWorkspaceIds = /* @__PURE__ */ new Set();
|
|
3112
3172
|
for (const lease of this.leases.values()) {
|
|
3113
3173
|
if (lease.state === "active") leasedWorkspaceIds.add(lease.workspaceId);
|
|
3114
3174
|
}
|
|
3115
|
-
for (const
|
|
3116
|
-
if (
|
|
3117
|
-
this.releaseWorkspaceRecord(
|
|
3175
|
+
for (const record2 of this.workspaces.values()) {
|
|
3176
|
+
if (record2.value.state === "active" && record2.value.updatedAt + this.workspaceIdleTtlMs <= now && !leasedWorkspaceIds.has(record2.value.id)) {
|
|
3177
|
+
this.releaseWorkspaceRecord(record2);
|
|
3118
3178
|
}
|
|
3119
3179
|
}
|
|
3120
3180
|
return expired;
|
|
@@ -3122,32 +3182,25 @@ var MemoryBrokerRuntime = class {
|
|
|
3122
3182
|
stats() {
|
|
3123
3183
|
return {
|
|
3124
3184
|
principals: this.principals.size,
|
|
3125
|
-
connections: this.
|
|
3126
|
-
activeWorkspaces: [...this.workspaces.values()].filter((
|
|
3185
|
+
connections: this.connectionsByClientSession.size,
|
|
3186
|
+
activeWorkspaces: [...this.workspaces.values()].filter((record2) => record2.value.state === "active").length,
|
|
3127
3187
|
activeLeases: [...this.leases.values()].filter((lease) => lease.state === "active").length
|
|
3128
3188
|
};
|
|
3129
3189
|
}
|
|
3130
3190
|
lifecycleSummary() {
|
|
3131
|
-
let embeddedConnections = 0;
|
|
3132
|
-
let oneShotConnections = 0;
|
|
3133
|
-
for (const connection of this.connectionsByBridge.values()) {
|
|
3134
|
-
if (connection.value.launchMode === "embedded") embeddedConnections += 1;
|
|
3135
|
-
else oneShotConnections += 1;
|
|
3136
|
-
}
|
|
3137
3191
|
const stats = this.stats();
|
|
3138
3192
|
return {
|
|
3139
|
-
|
|
3140
|
-
oneShotConnections,
|
|
3193
|
+
connections: stats.connections,
|
|
3141
3194
|
activeWorkspaces: stats.activeWorkspaces,
|
|
3142
3195
|
activeLeases: stats.activeLeases
|
|
3143
3196
|
};
|
|
3144
3197
|
}
|
|
3145
|
-
initialize(
|
|
3198
|
+
initialize(clientSessionId, value) {
|
|
3146
3199
|
const params = validateInitializeParams(value);
|
|
3147
|
-
const existing = this.
|
|
3200
|
+
const existing = this.connectionsByClientSession.get(clientSessionId);
|
|
3148
3201
|
if (existing) {
|
|
3149
|
-
if (
|
|
3150
|
-
throw invalidArgument("This
|
|
3202
|
+
if (existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
|
|
3203
|
+
throw invalidArgument("This client session is already initialized", "method");
|
|
3151
3204
|
}
|
|
3152
3205
|
const protocol2 = negotiateProtocol(params.protocol);
|
|
3153
3206
|
if (params.limits && (protocol2.major < 1 || protocol2.major === 1 && protocol2.minor < 1)) {
|
|
@@ -3158,10 +3211,10 @@ var MemoryBrokerRuntime = class {
|
|
|
3158
3211
|
const capabilities2 = negotiateCapabilities(params.requestedCapabilities, this.allowedCapabilities);
|
|
3159
3212
|
const limits2 = negotiateProtocolLimits(params.limits, this.limits);
|
|
3160
3213
|
if (protocol2.major !== existing.value.protocol.major || protocol2.minor !== existing.value.protocol.minor || capabilities2.granted.length !== existing.grantedCapabilities.length || capabilities2.granted.some((capability, index) => capability !== existing.grantedCapabilities[index]) || limits2.maxMessageBytes !== existing.limits.maxMessageBytes || limits2.maxResultBytes !== existing.limits.maxResultBytes || limits2.maxArtifactBytes !== existing.limits.maxArtifactBytes || limits2.eventJournalSize !== existing.limits.eventJournalSize) {
|
|
3161
|
-
throw new BrowserPilotError("protocol_incompatible", "
|
|
3214
|
+
throw new BrowserPilotError("protocol_incompatible", "Client session cannot change its negotiated contract", {
|
|
3162
3215
|
remediation: {
|
|
3163
3216
|
code: "restart_browser_pilot",
|
|
3164
|
-
message: "Restart Browser Pilot before using a different
|
|
3217
|
+
message: "Restart Browser Pilot before using a different client protocol contract.",
|
|
3165
3218
|
actionRequired: true
|
|
3166
3219
|
}
|
|
3167
3220
|
});
|
|
@@ -3175,11 +3228,11 @@ var MemoryBrokerRuntime = class {
|
|
|
3175
3228
|
capabilities: capabilities2,
|
|
3176
3229
|
brokerProcessIdentity: this.options.brokerProcessIdentity,
|
|
3177
3230
|
connectionId: existing.value.id,
|
|
3178
|
-
browsers: this.browserBindings.map((binding) => (
|
|
3231
|
+
browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, existing.value.protocol.minor)),
|
|
3179
3232
|
limits: { ...existing.limits }
|
|
3180
3233
|
});
|
|
3181
3234
|
}
|
|
3182
|
-
if (this.
|
|
3235
|
+
if (this.connectionsByClientSession.size >= this.maxConnections) {
|
|
3183
3236
|
throw new BrowserPilotError("result_too_large", "Broker connection limit reached", {
|
|
3184
3237
|
context: { maxConnections: this.maxConnections }
|
|
3185
3238
|
});
|
|
@@ -3199,7 +3252,6 @@ var MemoryBrokerRuntime = class {
|
|
|
3199
3252
|
id: connectionId,
|
|
3200
3253
|
principalId: principal.id,
|
|
3201
3254
|
clientInstanceId: params.client.instanceId,
|
|
3202
|
-
launchMode: params.launchMode,
|
|
3203
3255
|
protocol,
|
|
3204
3256
|
connectedAt: now,
|
|
3205
3257
|
lastSeenAt: now
|
|
@@ -3212,18 +3264,17 @@ var MemoryBrokerRuntime = class {
|
|
|
3212
3264
|
capabilities,
|
|
3213
3265
|
brokerProcessIdentity: this.options.brokerProcessIdentity,
|
|
3214
3266
|
connectionId,
|
|
3215
|
-
browsers: this.browserBindings.map((binding) => (
|
|
3267
|
+
browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, protocol.minor)),
|
|
3216
3268
|
limits: { ...limits }
|
|
3217
3269
|
};
|
|
3218
3270
|
const connection = {
|
|
3219
3271
|
value: connectionValue,
|
|
3220
|
-
|
|
3272
|
+
clientSessionId,
|
|
3221
3273
|
client: { ...params.client },
|
|
3222
3274
|
grantedCapabilities: [...capabilities.granted],
|
|
3223
|
-
limits
|
|
3224
|
-
notifications: []
|
|
3275
|
+
limits
|
|
3225
3276
|
};
|
|
3226
|
-
this.
|
|
3277
|
+
this.connectionsByClientSession.set(clientSessionId, connection);
|
|
3227
3278
|
this.connectionsById.set(connectionId, connection);
|
|
3228
3279
|
return asJson(initializeResult);
|
|
3229
3280
|
}
|
|
@@ -3234,7 +3285,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3234
3285
|
throw protocolIncompatible("Keyed Workspace creation requires protocol 1.1 or newer");
|
|
3235
3286
|
}
|
|
3236
3287
|
const principalId = connection.value.principalId;
|
|
3237
|
-
const existing = params.clientKey ? [...this.workspaces.values()].find((
|
|
3288
|
+
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
3289
|
if (existing) {
|
|
3239
3290
|
if (params.browserId) {
|
|
3240
3291
|
const requested = this.browserBindings.find((candidate) => candidate.candidate.id === params.browserId);
|
|
@@ -3266,7 +3317,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3266
3317
|
}
|
|
3267
3318
|
});
|
|
3268
3319
|
}
|
|
3269
|
-
const activeCount = [...this.workspaces.values()].filter((
|
|
3320
|
+
const activeCount = [...this.workspaces.values()].filter((record2) => record2.value.principalId === principalId && record2.value.state === "active").length;
|
|
3270
3321
|
if (activeCount >= this.maxWorkspacesPerPrincipal) {
|
|
3271
3322
|
throw new BrowserPilotError("result_too_large", "Workspace limit reached", {
|
|
3272
3323
|
context: { maxWorkspacesPerPrincipal: this.maxWorkspacesPerPrincipal }
|
|
@@ -3313,20 +3364,20 @@ var MemoryBrokerRuntime = class {
|
|
|
3313
3364
|
getWorkspace(connection, value) {
|
|
3314
3365
|
this.assertCapabilities(connection, ["workspace.manage"]);
|
|
3315
3366
|
const params = validateWorkspaceGetParams(value);
|
|
3316
|
-
const
|
|
3367
|
+
const record2 = this.requireWorkspace(connection, params.workspaceId, true);
|
|
3317
3368
|
return asJson({
|
|
3318
|
-
workspace: cloneWorkspace(
|
|
3319
|
-
managedTabSet: cloneManagedTabSet(
|
|
3320
|
-
managedTabSets: runtimeManagedTabSets(
|
|
3321
|
-
eventCursor: this.events.currentCursor(
|
|
3369
|
+
workspace: cloneWorkspace(record2.value),
|
|
3370
|
+
managedTabSet: cloneManagedTabSet(record2.managedTabSet),
|
|
3371
|
+
managedTabSets: runtimeManagedTabSets(record2).map(cloneManagedTabSet),
|
|
3372
|
+
eventCursor: this.events.currentCursor(record2.value.id)
|
|
3322
3373
|
});
|
|
3323
3374
|
}
|
|
3324
3375
|
async releaseWorkspace(connection, value) {
|
|
3325
3376
|
this.assertCapabilities(connection, ["workspace.manage"]);
|
|
3326
3377
|
const params = validateWorkspaceReleaseParams(value);
|
|
3327
|
-
const
|
|
3328
|
-
if (
|
|
3329
|
-
await this.workspaceCleanup.get(
|
|
3378
|
+
const record2 = this.requireWorkspace(connection, params.workspaceId, true);
|
|
3379
|
+
if (record2.value.state !== "released") this.releaseWorkspaceRecord(record2);
|
|
3380
|
+
await this.workspaceCleanup.get(record2.value.id);
|
|
3330
3381
|
return asJson({ workspaceId: params.workspaceId, released: true });
|
|
3331
3382
|
}
|
|
3332
3383
|
createLease(connection, value) {
|
|
@@ -3415,60 +3466,60 @@ var MemoryBrokerRuntime = class {
|
|
|
3415
3466
|
this.principals.set(id, principal);
|
|
3416
3467
|
return principal;
|
|
3417
3468
|
}
|
|
3418
|
-
requireConnection(
|
|
3419
|
-
const connection = this.
|
|
3469
|
+
requireConnection(clientSessionId) {
|
|
3470
|
+
const connection = this.connectionsByClientSession.get(clientSessionId);
|
|
3420
3471
|
if (connection) return connection;
|
|
3421
|
-
throw new BrowserPilotError("not_initialized", "
|
|
3472
|
+
throw new BrowserPilotError("not_initialized", "Client session is not initialized", {
|
|
3422
3473
|
rpcCode: -32002
|
|
3423
3474
|
});
|
|
3424
3475
|
}
|
|
3425
3476
|
requireWorkspace(connection, workspaceId, allowReleased) {
|
|
3426
|
-
const
|
|
3427
|
-
if (!
|
|
3477
|
+
const record2 = this.workspaces.get(workspaceId);
|
|
3478
|
+
if (!record2 || record2.value.principalId !== connection.value.principalId || !allowReleased && record2.value.state !== "active") {
|
|
3428
3479
|
throw new BrowserPilotError("workspace_not_found", "Workspace was not found for this ClientPrincipal", {
|
|
3429
3480
|
context: { workspaceId }
|
|
3430
3481
|
});
|
|
3431
3482
|
}
|
|
3432
|
-
return
|
|
3483
|
+
return record2;
|
|
3433
3484
|
}
|
|
3434
|
-
selectWorkspaceProfileContext(
|
|
3435
|
-
if (
|
|
3485
|
+
selectWorkspaceProfileContext(record2, profileContextId) {
|
|
3486
|
+
if (record2.value.state !== "active") {
|
|
3436
3487
|
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3437
|
-
context: { workspaceId:
|
|
3488
|
+
context: { workspaceId: record2.value.id }
|
|
3438
3489
|
});
|
|
3439
3490
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3491
|
+
record2.value.selectedProfileContextId = profileContextId;
|
|
3492
|
+
record2.value.updatedAt = this.now();
|
|
3442
3493
|
}
|
|
3443
|
-
managedTabSetForProfile(
|
|
3444
|
-
if (
|
|
3494
|
+
managedTabSetForProfile(record2, profileContextId) {
|
|
3495
|
+
if (record2.value.state !== "active") {
|
|
3445
3496
|
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3446
|
-
context: { workspaceId:
|
|
3497
|
+
context: { workspaceId: record2.value.id }
|
|
3447
3498
|
});
|
|
3448
3499
|
}
|
|
3449
|
-
if (
|
|
3450
|
-
return cloneManagedTabSet(
|
|
3500
|
+
if (record2.managedTabSet.profileContextId === profileContextId) {
|
|
3501
|
+
return cloneManagedTabSet(record2.managedTabSet);
|
|
3451
3502
|
}
|
|
3452
|
-
if (!
|
|
3453
|
-
|
|
3454
|
-
return cloneManagedTabSet(
|
|
3503
|
+
if (!record2.managedTabSet.profileContextId) {
|
|
3504
|
+
record2.managedTabSet.profileContextId = profileContextId;
|
|
3505
|
+
return cloneManagedTabSet(record2.managedTabSet);
|
|
3455
3506
|
}
|
|
3456
|
-
const existing =
|
|
3507
|
+
const existing = record2.additionalManagedTabSets.get(profileContextId);
|
|
3457
3508
|
if (existing) return cloneManagedTabSet(existing);
|
|
3458
|
-
if (
|
|
3509
|
+
if (record2.additionalManagedTabSets.size >= 127) {
|
|
3459
3510
|
throw new BrowserPilotError("result_too_large", "ManagedTabSet Profile context limit reached", {
|
|
3460
|
-
context: { workspaceId:
|
|
3511
|
+
context: { workspaceId: record2.value.id, maxManagedTabSets: 128 }
|
|
3461
3512
|
});
|
|
3462
3513
|
}
|
|
3463
3514
|
const managedTabSet = {
|
|
3464
3515
|
id: this.nextManagedTabSetId(),
|
|
3465
|
-
workspaceId:
|
|
3466
|
-
browserInstanceId:
|
|
3516
|
+
workspaceId: record2.value.id,
|
|
3517
|
+
browserInstanceId: record2.value.browserInstanceId,
|
|
3467
3518
|
profileContextId,
|
|
3468
3519
|
createdAt: this.now(),
|
|
3469
3520
|
state: "active"
|
|
3470
3521
|
};
|
|
3471
|
-
|
|
3522
|
+
record2.additionalManagedTabSets.set(profileContextId, managedTabSet);
|
|
3472
3523
|
return cloneManagedTabSet(managedTabSet);
|
|
3473
3524
|
}
|
|
3474
3525
|
requireLease(connection, leaseId, requireActive) {
|
|
@@ -3500,52 +3551,52 @@ var MemoryBrokerRuntime = class {
|
|
|
3500
3551
|
this.options.toolExecutor?.releaseLease?.(cloneLease(lease));
|
|
3501
3552
|
this.options.onLeaseReleased?.(cloneLease(lease));
|
|
3502
3553
|
}
|
|
3503
|
-
releaseWorkspaceRecord(
|
|
3504
|
-
|
|
3505
|
-
this.commands.releaseWorkspace(
|
|
3554
|
+
releaseWorkspaceRecord(record2) {
|
|
3555
|
+
record2.value.state = "releasing";
|
|
3556
|
+
this.commands.releaseWorkspace(record2.value.id);
|
|
3506
3557
|
for (const lease of this.leases.values()) {
|
|
3507
|
-
if (lease.workspaceId ===
|
|
3558
|
+
if (lease.workspaceId === record2.value.id && lease.state === "active") {
|
|
3508
3559
|
this.releaseLeaseRecord(lease, "released");
|
|
3509
3560
|
}
|
|
3510
3561
|
}
|
|
3511
|
-
const managedTabSets = runtimeManagedTabSets(
|
|
3562
|
+
const managedTabSets = runtimeManagedTabSets(record2);
|
|
3512
3563
|
for (const managedTabSet of managedTabSets) managedTabSet.state = "closed";
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
const principal = this.principals.get(
|
|
3564
|
+
record2.value.updatedAt = this.now();
|
|
3565
|
+
record2.value.state = "released";
|
|
3566
|
+
const principal = this.principals.get(record2.value.principalId);
|
|
3516
3567
|
if (principal) {
|
|
3517
3568
|
this.options.toolExecutor?.releaseWorkspace?.(
|
|
3518
3569
|
{ ...principal, capabilities: [...principal.capabilities] },
|
|
3519
|
-
cloneWorkspace(
|
|
3570
|
+
cloneWorkspace(record2.value),
|
|
3520
3571
|
managedTabSets.map(cloneManagedTabSet)
|
|
3521
3572
|
);
|
|
3522
3573
|
}
|
|
3523
3574
|
this.options.onWorkspaceReleased?.(
|
|
3524
|
-
cloneWorkspace(
|
|
3525
|
-
cloneManagedTabSet(
|
|
3575
|
+
cloneWorkspace(record2.value),
|
|
3576
|
+
cloneManagedTabSet(record2.managedTabSet)
|
|
3526
3577
|
);
|
|
3527
|
-
const cleanup2 = this.options.artifactStore?.releaseWorkspace(
|
|
3578
|
+
const cleanup2 = this.options.artifactStore?.releaseWorkspace(record2.value.id).catch(() => {
|
|
3528
3579
|
}) ?? Promise.resolve();
|
|
3529
|
-
this.workspaceCleanup.set(
|
|
3580
|
+
this.workspaceCleanup.set(record2.value.id, cleanup2);
|
|
3530
3581
|
void cleanup2.finally(() => {
|
|
3531
|
-
if (this.workspaceCleanup.get(
|
|
3532
|
-
this.workspaceCleanup.delete(
|
|
3582
|
+
if (this.workspaceCleanup.get(record2.value.id) === cleanup2) {
|
|
3583
|
+
this.workspaceCleanup.delete(record2.value.id);
|
|
3533
3584
|
}
|
|
3534
3585
|
});
|
|
3535
3586
|
}
|
|
3536
3587
|
pruneReleasedWorkspaces() {
|
|
3537
3588
|
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(
|
|
3589
|
+
const released = [...this.workspaces.values()].filter((record2) => record2.value.state === "released").sort((left, right) => left.value.updatedAt - right.value.updatedAt);
|
|
3590
|
+
for (const record2 of released) {
|
|
3591
|
+
this.workspaces.delete(record2.value.id);
|
|
3592
|
+
this.events.releaseWorkspace(record2.value.id);
|
|
3593
|
+
for (const managedTabSet of runtimeManagedTabSets(record2)) {
|
|
3543
3594
|
this.managedTabSetIds.delete(managedTabSet.id);
|
|
3544
3595
|
}
|
|
3545
3596
|
for (const [leaseId, lease] of this.leases) {
|
|
3546
|
-
if (lease.workspaceId ===
|
|
3597
|
+
if (lease.workspaceId === record2.value.id) this.leases.delete(leaseId);
|
|
3547
3598
|
}
|
|
3548
|
-
this.cleanupUnusedPrincipal(
|
|
3599
|
+
this.cleanupUnusedPrincipal(record2.value.principalId);
|
|
3549
3600
|
if (this.workspaces.size < this.maxWorkspaceRecords) return;
|
|
3550
3601
|
}
|
|
3551
3602
|
}
|
|
@@ -3558,8 +3609,8 @@ var MemoryBrokerRuntime = class {
|
|
|
3558
3609
|
}
|
|
3559
3610
|
}
|
|
3560
3611
|
cleanupUnusedPrincipal(principalId) {
|
|
3561
|
-
const hasConnection = [...this.
|
|
3562
|
-
const hasWorkspace = [...this.workspaces.values()].some((
|
|
3612
|
+
const hasConnection = [...this.connectionsByClientSession.values()].some((connection) => connection.value.principalId === principalId);
|
|
3613
|
+
const hasWorkspace = [...this.workspaces.values()].some((record2) => record2.value.principalId === principalId);
|
|
3563
3614
|
if (hasConnection || hasWorkspace) return;
|
|
3564
3615
|
const principal = this.principals.get(principalId);
|
|
3565
3616
|
if (!principal) return;
|
|
@@ -3582,8 +3633,10 @@ var MemoryBrokerRuntime = class {
|
|
|
3582
3633
|
const params = validateToolCallParams(value);
|
|
3583
3634
|
const definition = getToolDefinition(params.name);
|
|
3584
3635
|
if (!isToolAvailableInProtocol(definition.name, connection.value.protocol)) {
|
|
3585
|
-
|
|
3636
|
+
const requiredMinor = minimumProtocolMinorForTool(definition.name);
|
|
3637
|
+
throw protocolIncompatible(`${definition.name} requires protocol 1.${requiredMinor} or newer`, {
|
|
3586
3638
|
tool: definition.name,
|
|
3639
|
+
requiredProtocol: { major: 1, minor: requiredMinor },
|
|
3587
3640
|
selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
|
|
3588
3641
|
});
|
|
3589
3642
|
}
|
|
@@ -3734,8 +3787,8 @@ var MemoryBrokerRuntime = class {
|
|
|
3734
3787
|
}));
|
|
3735
3788
|
}
|
|
3736
3789
|
async callBrowserDiscover(connection, params, args, definition) {
|
|
3737
|
-
const
|
|
3738
|
-
const filter = typeof
|
|
3790
|
+
const record2 = args;
|
|
3791
|
+
const filter = typeof record2.browser === "string" ? record2.browser.toLowerCase() : void 0;
|
|
3739
3792
|
const browsers = this.browserBindings.map((binding) => ({ ...binding.candidate })).filter((candidate) => !filter || [candidate.id, candidate.product, candidate.channel].some((value) => value?.toLowerCase().includes(filter)));
|
|
3740
3793
|
return asJson(await this.commands.run({
|
|
3741
3794
|
principalId: connection.value.principalId,
|
|
@@ -3764,36 +3817,6 @@ var MemoryBrokerRuntime = class {
|
|
|
3764
3817
|
this.requireWorkspace(connection, params.workspaceId, true);
|
|
3765
3818
|
return asJson(this.events.poll(params.workspaceId, params.cursor, params.limit));
|
|
3766
3819
|
}
|
|
3767
|
-
deliverNotification(event) {
|
|
3768
|
-
const workspace = this.workspaces.get(event.workspaceId);
|
|
3769
|
-
if (!workspace) return;
|
|
3770
|
-
const notification = {
|
|
3771
|
-
jsonrpc: "2.0",
|
|
3772
|
-
method: "events/event",
|
|
3773
|
-
params: { event }
|
|
3774
|
-
};
|
|
3775
|
-
for (const connection of this.connectionsByBridge.values()) {
|
|
3776
|
-
if (connection.value.principalId !== workspace.value.principalId || !connection.grantedCapabilities.includes("event.read")) continue;
|
|
3777
|
-
if (connection.notificationWaiter) {
|
|
3778
|
-
this.resolveNotificationWaiter(connection, structuredClone(notification));
|
|
3779
|
-
continue;
|
|
3780
|
-
}
|
|
3781
|
-
connection.notifications.push(structuredClone(notification));
|
|
3782
|
-
if (connection.notifications.length > connection.limits.eventJournalSize) {
|
|
3783
|
-
connection.notifications.shift();
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3786
|
-
}
|
|
3787
|
-
resolveNotificationWaiter(connection, notification) {
|
|
3788
|
-
const waiter = connection.notificationWaiter;
|
|
3789
|
-
if (!waiter) return;
|
|
3790
|
-
connection.notificationWaiter = void 0;
|
|
3791
|
-
if (waiter.timer) clearTimeout(waiter.timer);
|
|
3792
|
-
if (waiter.signal && waiter.onAbort) {
|
|
3793
|
-
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
3794
|
-
}
|
|
3795
|
-
waiter.resolve(notification);
|
|
3796
|
-
}
|
|
3797
3820
|
getCommand(connection, value) {
|
|
3798
3821
|
const params = validateCommandAccessParams(value);
|
|
3799
3822
|
if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
|
|
@@ -3803,6 +3826,18 @@ var MemoryBrokerRuntime = class {
|
|
|
3803
3826
|
...params.workspaceId ? { workspaceId: params.workspaceId } : {}
|
|
3804
3827
|
}));
|
|
3805
3828
|
}
|
|
3829
|
+
listCommands(connection, value) {
|
|
3830
|
+
const params = validateCommandListParams(value);
|
|
3831
|
+
this.requireWorkspace(connection, params.workspaceId, true);
|
|
3832
|
+
return asJson({
|
|
3833
|
+
commands: this.commands.list({
|
|
3834
|
+
principalId: connection.value.principalId,
|
|
3835
|
+
workspaceId: params.workspaceId,
|
|
3836
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
3837
|
+
...params.statuses ? { statuses: params.statuses } : {}
|
|
3838
|
+
})
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3806
3841
|
cancelCommand(connection, value) {
|
|
3807
3842
|
const params = validateCommandAccessParams(value);
|
|
3808
3843
|
if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
|
|
@@ -3820,8 +3855,15 @@ var MemoryBrokerRuntime = class {
|
|
|
3820
3855
|
async getArtifact(connection, value) {
|
|
3821
3856
|
const params = validateArtifactAccessParams(value);
|
|
3822
3857
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3823
|
-
const
|
|
3824
|
-
return asJson({ artifact:
|
|
3858
|
+
const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
|
|
3859
|
+
return asJson({ artifact: record2.descriptor, path: record2.path });
|
|
3860
|
+
}
|
|
3861
|
+
async listArtifacts(connection, value) {
|
|
3862
|
+
const params = validateArtifactListParams(value);
|
|
3863
|
+
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3864
|
+
return asJson({
|
|
3865
|
+
artifacts: await this.requireArtifactStore().list(params.workspaceId, params.kinds)
|
|
3866
|
+
});
|
|
3825
3867
|
}
|
|
3826
3868
|
async exportArtifact(connection, value) {
|
|
3827
3869
|
const params = validateArtifactExportParams(value);
|
|
@@ -3836,18 +3878,18 @@ var MemoryBrokerRuntime = class {
|
|
|
3836
3878
|
async importArtifact(connection, value) {
|
|
3837
3879
|
const params = validateArtifactImportParams(value);
|
|
3838
3880
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3839
|
-
const
|
|
3881
|
+
const record2 = await this.requireArtifactStore().importFile(
|
|
3840
3882
|
params.workspaceId,
|
|
3841
3883
|
params.path,
|
|
3842
3884
|
params.mimeType
|
|
3843
3885
|
);
|
|
3844
|
-
return asJson({ artifact:
|
|
3886
|
+
return asJson({ artifact: record2.descriptor });
|
|
3845
3887
|
}
|
|
3846
3888
|
async retainArtifact(connection, value) {
|
|
3847
3889
|
const params = validateArtifactAccessParams(value);
|
|
3848
3890
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3849
|
-
const
|
|
3850
|
-
return asJson({ artifact:
|
|
3891
|
+
const record2 = await this.requireArtifactStore().retain(params.workspaceId, params.artifactId);
|
|
3892
|
+
return asJson({ artifact: record2.descriptor, path: record2.path });
|
|
3851
3893
|
}
|
|
3852
3894
|
async releaseArtifact(connection, value) {
|
|
3853
3895
|
const params = validateArtifactAccessParams(value);
|
|
@@ -3886,9 +3928,9 @@ var MemoryBrokerRuntime = class {
|
|
|
3886
3928
|
context: { leaseId: lease.id, missingCapabilities: missing }
|
|
3887
3929
|
});
|
|
3888
3930
|
}
|
|
3889
|
-
|
|
3931
|
+
assertClientSessionId(value) {
|
|
3890
3932
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(value)) {
|
|
3891
|
-
throw invalidArgument("Invalid internal
|
|
3933
|
+
throw invalidArgument("Invalid internal client session ID", "clientSessionId");
|
|
3892
3934
|
}
|
|
3893
3935
|
}
|
|
3894
3936
|
nextId(kind, records) {
|
|
@@ -4394,20 +4436,6 @@ var PAGE_DIMENSIONS = `JSON.stringify({
|
|
|
4394
4436
|
width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
|
|
4395
4437
|
height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
|
|
4396
4438
|
})`;
|
|
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
4439
|
function elementRect(selector) {
|
|
4412
4440
|
return `JSON.stringify((() => {
|
|
4413
4441
|
const el = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -5120,6 +5148,7 @@ import { spawn } from "child_process";
|
|
|
5120
5148
|
// src/client.ts
|
|
5121
5149
|
import http from "http";
|
|
5122
5150
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
5151
|
+
var BROKER_RPC_VERSION = 2;
|
|
5123
5152
|
|
|
5124
5153
|
// src/chrome.ts
|
|
5125
5154
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -5403,7 +5432,7 @@ async function discoverBrowserCandidates(options = {}) {
|
|
|
5403
5432
|
id: stableBrowserId(definition, os),
|
|
5404
5433
|
product: definition.product,
|
|
5405
5434
|
channel: definition.channel,
|
|
5406
|
-
|
|
5435
|
+
userDataRoot: definition.dataDir,
|
|
5407
5436
|
processState,
|
|
5408
5437
|
remoteDebuggingState,
|
|
5409
5438
|
authorizationState,
|
|
@@ -5458,15 +5487,9 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5458
5487
|
let interactiveSince = null;
|
|
5459
5488
|
const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
|
|
5460
5489
|
let deadlineTimer;
|
|
5461
|
-
const deadline = new Promise((
|
|
5462
|
-
deadlineTimer = setTimeout(() =>
|
|
5490
|
+
const deadline = new Promise((resolve3) => {
|
|
5491
|
+
deadlineTimer = setTimeout(() => resolve3(deadlineReached), timeout);
|
|
5463
5492
|
});
|
|
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
5493
|
try {
|
|
5471
5494
|
while (true) {
|
|
5472
5495
|
const evaluation = await Promise.race([
|
|
@@ -5480,18 +5503,16 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5480
5503
|
]);
|
|
5481
5504
|
if (evaluation === deadlineReached) throw new PageLoadTimeoutError(timeout);
|
|
5482
5505
|
if (evaluation.state === "complete") {
|
|
5483
|
-
await injectBorder();
|
|
5484
5506
|
return;
|
|
5485
5507
|
}
|
|
5486
5508
|
if (evaluation.state === "interactive") {
|
|
5487
5509
|
if (interactiveSince === null) interactiveSince = Date.now();
|
|
5488
5510
|
if (Date.now() - interactiveSince >= interactiveGrace) {
|
|
5489
|
-
await injectBorder();
|
|
5490
5511
|
return;
|
|
5491
5512
|
}
|
|
5492
5513
|
}
|
|
5493
5514
|
const sleep = await Promise.race([
|
|
5494
|
-
new Promise((
|
|
5515
|
+
new Promise((resolve3) => setTimeout(() => resolve3("poll"), 200)),
|
|
5495
5516
|
deadline
|
|
5496
5517
|
]);
|
|
5497
5518
|
if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
|
|
@@ -5675,7 +5696,7 @@ var InputDispatcher = class {
|
|
|
5675
5696
|
}
|
|
5676
5697
|
hooks.afterCharacter?.({ character: char, index });
|
|
5677
5698
|
index += 1;
|
|
5678
|
-
if (delayMs > 0) await new Promise((
|
|
5699
|
+
if (delayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
5679
5700
|
}
|
|
5680
5701
|
}
|
|
5681
5702
|
async insertText(text) {
|
|
@@ -5719,7 +5740,7 @@ var ObservationService = class {
|
|
|
5719
5740
|
}
|
|
5720
5741
|
async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
|
|
5721
5742
|
if (this.settleDelayMs > 0) {
|
|
5722
|
-
await new Promise((
|
|
5743
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.settleDelayMs));
|
|
5723
5744
|
}
|
|
5724
5745
|
await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
|
|
5725
5746
|
return this.observe(limit);
|
|
@@ -5778,24 +5799,24 @@ function parsePointerTargetState(value) {
|
|
|
5778
5799
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5779
5800
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5780
5801
|
}
|
|
5781
|
-
const
|
|
5782
|
-
if (
|
|
5802
|
+
const record2 = value;
|
|
5803
|
+
if (record2.status === "ready" && Number.isFinite(record2.x) && Number.isFinite(record2.y)) {
|
|
5783
5804
|
return {
|
|
5784
5805
|
status: "ready",
|
|
5785
|
-
x: Number(
|
|
5786
|
-
y: Number(
|
|
5787
|
-
targetState: parseClickTargetState(
|
|
5806
|
+
x: Number(record2.x),
|
|
5807
|
+
y: Number(record2.y),
|
|
5808
|
+
targetState: parseClickTargetState(record2.targetState)
|
|
5788
5809
|
};
|
|
5789
5810
|
}
|
|
5790
|
-
if (
|
|
5811
|
+
if (record2.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record2.reason)) {
|
|
5791
5812
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5792
5813
|
}
|
|
5793
5814
|
let obstruction;
|
|
5794
|
-
if (
|
|
5795
|
-
if (typeof
|
|
5815
|
+
if (record2.obstruction !== void 0) {
|
|
5816
|
+
if (typeof record2.obstruction !== "object" || record2.obstruction === null || Array.isArray(record2.obstruction)) {
|
|
5796
5817
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5797
5818
|
}
|
|
5798
|
-
const candidate =
|
|
5819
|
+
const candidate = record2.obstruction;
|
|
5799
5820
|
if (typeof candidate.tagName !== "string" || candidate.tagName.length > 40 || candidate.role !== void 0 && (typeof candidate.role !== "string" || candidate.role.length > 40)) {
|
|
5800
5821
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
|
|
5801
5822
|
}
|
|
@@ -5806,7 +5827,7 @@ function parsePointerTargetState(value) {
|
|
|
5806
5827
|
}
|
|
5807
5828
|
return {
|
|
5808
5829
|
status: "blocked",
|
|
5809
|
-
reason:
|
|
5830
|
+
reason: record2.reason,
|
|
5810
5831
|
...obstruction ? { obstruction } : {}
|
|
5811
5832
|
};
|
|
5812
5833
|
}
|
|
@@ -5823,18 +5844,18 @@ function parseClickTargetState(value) {
|
|
|
5823
5844
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5824
5845
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
|
|
5825
5846
|
}
|
|
5826
|
-
const
|
|
5827
|
-
if (typeof
|
|
5847
|
+
const record2 = value;
|
|
5848
|
+
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
5849
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
|
|
5829
5850
|
}
|
|
5830
5851
|
return {
|
|
5831
|
-
connected:
|
|
5832
|
-
kind:
|
|
5833
|
-
focused:
|
|
5834
|
-
...
|
|
5835
|
-
...
|
|
5836
|
-
...
|
|
5837
|
-
...
|
|
5852
|
+
connected: record2.connected,
|
|
5853
|
+
kind: record2.kind,
|
|
5854
|
+
focused: record2.focused,
|
|
5855
|
+
...record2.checked !== void 0 ? { checked: record2.checked } : {},
|
|
5856
|
+
...record2.selected !== void 0 ? { selected: record2.selected } : {},
|
|
5857
|
+
...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
|
|
5858
|
+
...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
|
|
5838
5859
|
};
|
|
5839
5860
|
}
|
|
5840
5861
|
function clickEvidence(before, after, button, clickCount) {
|
|
@@ -5880,20 +5901,20 @@ function parseEditableState(value) {
|
|
|
5880
5901
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5881
5902
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
|
|
5882
5903
|
}
|
|
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
|
|
5904
|
+
const record2 = value;
|
|
5905
|
+
const kind = record2.kind;
|
|
5906
|
+
const inputType = record2.inputType;
|
|
5907
|
+
const editMode = record2.editMode;
|
|
5908
|
+
const selectionMode = record2.selectionMode;
|
|
5909
|
+
const reason2 = record2.reason;
|
|
5910
|
+
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
5911
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
|
|
5891
5912
|
}
|
|
5892
5913
|
return {
|
|
5893
5914
|
kind,
|
|
5894
|
-
value:
|
|
5895
|
-
sensitive:
|
|
5896
|
-
editable:
|
|
5915
|
+
value: record2.value,
|
|
5916
|
+
sensitive: record2.sensitive,
|
|
5917
|
+
editable: record2.editable,
|
|
5897
5918
|
...inputType !== void 0 ? { inputType } : {},
|
|
5898
5919
|
...editMode !== void 0 ? { editMode } : {},
|
|
5899
5920
|
...selectionMode !== void 0 ? { selectionMode } : {},
|
|
@@ -5928,19 +5949,19 @@ function parsePressTargetState(value, focusToken) {
|
|
|
5928
5949
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5929
5950
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
|
|
5930
5951
|
}
|
|
5931
|
-
const
|
|
5932
|
-
if (!PRESS_TARGET_KINDS.has(
|
|
5952
|
+
const record2 = value;
|
|
5953
|
+
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
5954
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
|
|
5934
5955
|
}
|
|
5935
5956
|
return {
|
|
5936
5957
|
...focusToken ? { focusToken } : {},
|
|
5937
|
-
kind:
|
|
5938
|
-
sensitive:
|
|
5939
|
-
...
|
|
5940
|
-
...
|
|
5941
|
-
...
|
|
5942
|
-
...
|
|
5943
|
-
...
|
|
5958
|
+
kind: record2.kind,
|
|
5959
|
+
sensitive: record2.sensitive,
|
|
5960
|
+
...record2.valueToken !== void 0 ? { valueToken: record2.valueToken } : {},
|
|
5961
|
+
...record2.selectedToken !== void 0 ? { selectedToken: record2.selectedToken } : {},
|
|
5962
|
+
...record2.checked !== void 0 ? { checked: record2.checked } : {},
|
|
5963
|
+
...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
|
|
5964
|
+
...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
|
|
5944
5965
|
};
|
|
5945
5966
|
}
|
|
5946
5967
|
function pressEvidence(before, after) {
|
|
@@ -6076,7 +6097,7 @@ var ActionService = class {
|
|
|
6076
6097
|
await this.input.click(point.x, point.y, { button, clickCount });
|
|
6077
6098
|
this.markDispatched(run);
|
|
6078
6099
|
if (this.readbackDelayMs > 0) {
|
|
6079
|
-
await new Promise((
|
|
6100
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6080
6101
|
}
|
|
6081
6102
|
let after;
|
|
6082
6103
|
try {
|
|
@@ -6128,7 +6149,7 @@ var ActionService = class {
|
|
|
6128
6149
|
await this.input.press(key);
|
|
6129
6150
|
this.markDispatched(run);
|
|
6130
6151
|
if (this.readbackDelayMs > 0) {
|
|
6131
|
-
await new Promise((
|
|
6152
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6132
6153
|
}
|
|
6133
6154
|
let after;
|
|
6134
6155
|
try {
|
|
@@ -6194,7 +6215,7 @@ var ActionService = class {
|
|
|
6194
6215
|
this.markDispatched(run);
|
|
6195
6216
|
}
|
|
6196
6217
|
if (this.readbackDelayMs > 0) {
|
|
6197
|
-
await new Promise((
|
|
6218
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6198
6219
|
}
|
|
6199
6220
|
const after = await this.readElementState(objectId);
|
|
6200
6221
|
evidence = inputEvidence("type", before, after, text, !!options.clear);
|
|
@@ -6226,7 +6247,7 @@ var ActionService = class {
|
|
|
6226
6247
|
this.willDispatch();
|
|
6227
6248
|
await this.input.click(location.x, location.y);
|
|
6228
6249
|
this.markDispatched(run);
|
|
6229
|
-
if (this.focusDelayMs > 0) await new Promise((
|
|
6250
|
+
if (this.focusDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.focusDelayMs));
|
|
6230
6251
|
}
|
|
6231
6252
|
await this.captureFocus(run, "capture_input_focus");
|
|
6232
6253
|
const before = await this.readActiveState();
|
|
@@ -6248,7 +6269,7 @@ var ActionService = class {
|
|
|
6248
6269
|
},
|
|
6249
6270
|
afterCharacter: () => this.markDispatched(run)
|
|
6250
6271
|
});
|
|
6251
|
-
if (this.readbackDelayMs > 0) await new Promise((
|
|
6272
|
+
if (this.readbackDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
6252
6273
|
const after = await this.readActiveState();
|
|
6253
6274
|
const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
|
|
6254
6275
|
this.requireVerification(evidence, options.verification);
|
|
@@ -6964,13 +6985,13 @@ var DownloadController = class {
|
|
|
6964
6985
|
return;
|
|
6965
6986
|
}
|
|
6966
6987
|
if (!this.isTracked(session, download)) return;
|
|
6967
|
-
const
|
|
6988
|
+
const record2 = await this.artifacts.ingestDownloadCopy(
|
|
6968
6989
|
session.context.workspaceId,
|
|
6969
6990
|
completedFile.path,
|
|
6970
6991
|
download.fileName
|
|
6971
6992
|
);
|
|
6972
6993
|
if (!this.isTracked(session, download)) {
|
|
6973
|
-
await this.artifacts.release(session.context.workspaceId,
|
|
6994
|
+
await this.artifacts.release(session.context.workspaceId, record2.descriptor.id);
|
|
6974
6995
|
return;
|
|
6975
6996
|
}
|
|
6976
6997
|
download.state = "terminal";
|
|
@@ -6978,7 +6999,7 @@ var DownloadController = class {
|
|
|
6978
6999
|
this.publish(session, {
|
|
6979
7000
|
downloadId: download.id,
|
|
6980
7001
|
state: "completed",
|
|
6981
|
-
artifact:
|
|
7002
|
+
artifact: record2.descriptor
|
|
6982
7003
|
});
|
|
6983
7004
|
} catch (error) {
|
|
6984
7005
|
if (download.state === "terminal") return;
|
|
@@ -7069,8 +7090,8 @@ var MAX_PROFILE_CONTEXT_RECORDS = 512;
|
|
|
7069
7090
|
function cloneTarget(target) {
|
|
7070
7091
|
return { ...target };
|
|
7071
7092
|
}
|
|
7072
|
-
function cloneRecord(
|
|
7073
|
-
return { ...
|
|
7093
|
+
function cloneRecord(record2) {
|
|
7094
|
+
return { ...record2, targets: record2.targets.map(cloneTarget) };
|
|
7074
7095
|
}
|
|
7075
7096
|
function contextKey(cdpBrowserContextId) {
|
|
7076
7097
|
return cdpBrowserContextId ? `cdp:${cdpBrowserContextId}` : DEFAULT_CDP_CONTEXT_KEY;
|
|
@@ -7115,22 +7136,23 @@ var MemoryProfileContextRegistry = class {
|
|
|
7115
7136
|
});
|
|
7116
7137
|
}
|
|
7117
7138
|
for (const id of this.currentByKey.values()) {
|
|
7118
|
-
const
|
|
7119
|
-
if (
|
|
7139
|
+
const record2 = this.recordsById.get(id);
|
|
7140
|
+
if (record2) record2.active = false;
|
|
7120
7141
|
}
|
|
7121
7142
|
this.contextByTarget.clear();
|
|
7122
7143
|
const activeKeys = new Set(grouped.keys());
|
|
7123
7144
|
for (const [key, group] of grouped) {
|
|
7124
7145
|
let id = this.currentByKey.get(key);
|
|
7125
|
-
let
|
|
7126
|
-
if (!
|
|
7146
|
+
let record2 = id ? this.recordsById.get(id) : void 0;
|
|
7147
|
+
if (!record2) {
|
|
7127
7148
|
id = this.nextId();
|
|
7128
7149
|
const rawContextId = group[0]?.cdpBrowserContextId;
|
|
7129
|
-
|
|
7150
|
+
record2 = {
|
|
7130
7151
|
id,
|
|
7131
7152
|
browserInstanceId: this.browserInstanceId,
|
|
7132
7153
|
browserConnectionGeneration,
|
|
7133
7154
|
label: `Profile ${++this.nextSequence}`,
|
|
7155
|
+
identityStatus: "unidentified",
|
|
7134
7156
|
tabCount: 0,
|
|
7135
7157
|
eligibleTabCount: 0,
|
|
7136
7158
|
sequence: this.nextSequence,
|
|
@@ -7138,34 +7160,34 @@ var MemoryProfileContextRegistry = class {
|
|
|
7138
7160
|
targets: [],
|
|
7139
7161
|
...rawContextId ? { cdpBrowserContextId: rawContextId } : {}
|
|
7140
7162
|
};
|
|
7141
|
-
this.recordsById.set(id,
|
|
7163
|
+
this.recordsById.set(id, record2);
|
|
7142
7164
|
this.recordOrder.push(id);
|
|
7143
7165
|
this.currentByKey.set(key, id);
|
|
7144
7166
|
}
|
|
7145
7167
|
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,
|
|
7168
|
+
record2.active = true;
|
|
7169
|
+
record2.tabCount = sorted.length;
|
|
7170
|
+
record2.eligibleTabCount = sorted.filter((target) => target.eligible).length;
|
|
7171
|
+
record2.targets = sorted;
|
|
7172
|
+
record2.representativeCdpTargetId = sorted[0]?.cdpTargetId;
|
|
7173
|
+
for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record2.id);
|
|
7152
7174
|
}
|
|
7153
7175
|
for (const [key, id] of this.currentByKey) {
|
|
7154
7176
|
if (activeKeys.has(key)) continue;
|
|
7155
|
-
const
|
|
7156
|
-
if (
|
|
7177
|
+
const record2 = this.recordsById.get(id);
|
|
7178
|
+
if (record2) record2.active = false;
|
|
7157
7179
|
this.currentByKey.delete(key);
|
|
7158
7180
|
}
|
|
7159
7181
|
this.pruneStaleRecords();
|
|
7160
7182
|
return this.list(browserConnectionGeneration);
|
|
7161
7183
|
}
|
|
7162
7184
|
list(browserConnectionGeneration) {
|
|
7163
|
-
return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((
|
|
7185
|
+
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
7186
|
}
|
|
7165
7187
|
resolve(profileContextId, browserConnectionGeneration) {
|
|
7166
|
-
const
|
|
7167
|
-
if (!
|
|
7168
|
-
if (!
|
|
7188
|
+
const record2 = this.recordsById.get(profileContextId);
|
|
7189
|
+
if (!record2) throw invalidArgument("Unknown Profile context", "profileContextId");
|
|
7190
|
+
if (!record2.active || record2.browserInstanceId !== this.browserInstanceId || record2.browserConnectionGeneration !== browserConnectionGeneration) {
|
|
7169
7191
|
throw new BrowserPilotError("profile_context_stale", "Profile context belongs to a stale browser connection", {
|
|
7170
7192
|
retryable: true,
|
|
7171
7193
|
context: {
|
|
@@ -7179,7 +7201,31 @@ var MemoryProfileContextRegistry = class {
|
|
|
7179
7201
|
}
|
|
7180
7202
|
});
|
|
7181
7203
|
}
|
|
7182
|
-
return cloneRecord(
|
|
7204
|
+
return cloneRecord(record2);
|
|
7205
|
+
}
|
|
7206
|
+
setVerifiedIdentity(profileContextId, browserConnectionGeneration, identity) {
|
|
7207
|
+
const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
|
|
7208
|
+
record2.identityStatus = "verified";
|
|
7209
|
+
record2.profileName = identity.profileName;
|
|
7210
|
+
record2.displayName = identity.profileName;
|
|
7211
|
+
record2.profileDirectory = identity.profileDirectory;
|
|
7212
|
+
if (identity.accountName) record2.accountName = identity.accountName;
|
|
7213
|
+
else delete record2.accountName;
|
|
7214
|
+
if (identity.accountEmail) record2.accountEmail = identity.accountEmail;
|
|
7215
|
+
else delete record2.accountEmail;
|
|
7216
|
+
delete record2.identityErrorCode;
|
|
7217
|
+
return cloneRecord(record2);
|
|
7218
|
+
}
|
|
7219
|
+
setIdentityUnavailable(profileContextId, browserConnectionGeneration, identityErrorCode) {
|
|
7220
|
+
const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
|
|
7221
|
+
record2.identityStatus = "unavailable";
|
|
7222
|
+
record2.identityErrorCode = identityErrorCode;
|
|
7223
|
+
delete record2.profileName;
|
|
7224
|
+
delete record2.displayName;
|
|
7225
|
+
delete record2.profileDirectory;
|
|
7226
|
+
delete record2.accountName;
|
|
7227
|
+
delete record2.accountEmail;
|
|
7228
|
+
return cloneRecord(record2);
|
|
7183
7229
|
}
|
|
7184
7230
|
forTarget(cdpTargetId, browserConnectionGeneration) {
|
|
7185
7231
|
const id = this.contextByTarget.get(cdpTargetId);
|
|
@@ -7198,19 +7244,23 @@ var MemoryProfileContextRegistry = class {
|
|
|
7198
7244
|
}
|
|
7199
7245
|
invalidateCurrent() {
|
|
7200
7246
|
for (const id of this.currentByKey.values()) {
|
|
7201
|
-
const
|
|
7202
|
-
if (
|
|
7247
|
+
const record2 = this.recordsById.get(id);
|
|
7248
|
+
if (record2) record2.active = false;
|
|
7203
7249
|
}
|
|
7204
7250
|
this.currentByKey.clear();
|
|
7205
7251
|
this.contextByTarget.clear();
|
|
7206
7252
|
}
|
|
7253
|
+
currentRecord(profileContextId, browserConnectionGeneration) {
|
|
7254
|
+
this.resolve(profileContextId, browserConnectionGeneration);
|
|
7255
|
+
return this.recordsById.get(profileContextId);
|
|
7256
|
+
}
|
|
7207
7257
|
pruneStaleRecords() {
|
|
7208
7258
|
while (this.recordsById.size > MAX_PROFILE_CONTEXT_RECORDS) {
|
|
7209
7259
|
const id = this.recordOrder.shift();
|
|
7210
7260
|
if (!id) return;
|
|
7211
|
-
const
|
|
7212
|
-
if (!
|
|
7213
|
-
if (
|
|
7261
|
+
const record2 = this.recordsById.get(id);
|
|
7262
|
+
if (!record2 || record2.active) {
|
|
7263
|
+
if (record2?.active) this.recordOrder.push(id);
|
|
7214
7264
|
continue;
|
|
7215
7265
|
}
|
|
7216
7266
|
this.recordsById.delete(id);
|
|
@@ -7233,8 +7283,10 @@ function isEligibleUserPage(target) {
|
|
|
7233
7283
|
return !(/* @__PURE__ */ new Set([
|
|
7234
7284
|
"devtools:",
|
|
7235
7285
|
"chrome:",
|
|
7286
|
+
"chrome-extension:",
|
|
7236
7287
|
"chrome-untrusted:",
|
|
7237
7288
|
"edge:",
|
|
7289
|
+
"edge-extension:",
|
|
7238
7290
|
"brave:",
|
|
7239
7291
|
"vivaldi:"
|
|
7240
7292
|
])).has(url.protocol);
|
|
@@ -7242,6 +7294,14 @@ function isEligibleUserPage(target) {
|
|
|
7242
7294
|
return target.url === "about:blank";
|
|
7243
7295
|
}
|
|
7244
7296
|
}
|
|
7297
|
+
function isExtensionPage(target) {
|
|
7298
|
+
try {
|
|
7299
|
+
const protocol = new URL(target.url).protocol;
|
|
7300
|
+
return protocol === "chrome-extension:" || protocol === "edge-extension:";
|
|
7301
|
+
} catch {
|
|
7302
|
+
return false;
|
|
7303
|
+
}
|
|
7304
|
+
}
|
|
7245
7305
|
var CdpBrowserTargetCatalog = class {
|
|
7246
7306
|
constructor(transport, browserInstanceId, identity, options) {
|
|
7247
7307
|
this.transport = transport;
|
|
@@ -7272,6 +7332,7 @@ var CdpBrowserTargetCatalog = class {
|
|
|
7272
7332
|
type: typeof value.type === "string" ? value.type : "",
|
|
7273
7333
|
...typeof value.openerId === "string" ? { openerCdpTargetId: value.openerId } : {}
|
|
7274
7334
|
};
|
|
7335
|
+
if (isExtensionPage(candidate)) continue;
|
|
7275
7336
|
const eligible2 = isEligibleUserPage(candidate) && !this.options.isExcludedTarget(candidate);
|
|
7276
7337
|
snapshots.push({
|
|
7277
7338
|
...candidate,
|
|
@@ -7507,8 +7568,8 @@ var BrowserWatchdogService = class {
|
|
|
7507
7568
|
|
|
7508
7569
|
// src/services/controlled-target-registry.ts
|
|
7509
7570
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
7510
|
-
function cloneRecord2(
|
|
7511
|
-
return { ...
|
|
7571
|
+
function cloneRecord2(record2) {
|
|
7572
|
+
return { ...record2 };
|
|
7512
7573
|
}
|
|
7513
7574
|
function targetKey2(workspaceId, browserInstanceId, cdpTargetId) {
|
|
7514
7575
|
return `${workspaceId}\0${browserInstanceId}\0${cdpTargetId}`;
|
|
@@ -7542,7 +7603,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7542
7603
|
existing.openerCdpTargetId = input.openerCdpTargetId;
|
|
7543
7604
|
return cloneRecord2(existing);
|
|
7544
7605
|
}
|
|
7545
|
-
const
|
|
7606
|
+
const record2 = {
|
|
7546
7607
|
id: this.nextId(),
|
|
7547
7608
|
principalId: input.principalId,
|
|
7548
7609
|
workspaceId: input.workspaceId,
|
|
@@ -7558,9 +7619,9 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7558
7619
|
createdAt: this.now(),
|
|
7559
7620
|
state: "active"
|
|
7560
7621
|
};
|
|
7561
|
-
this.records.set(
|
|
7562
|
-
this.activeKeys.set(key,
|
|
7563
|
-
return cloneRecord2(
|
|
7622
|
+
this.records.set(record2.id, record2);
|
|
7623
|
+
this.activeKeys.set(key, record2.id);
|
|
7624
|
+
return cloneRecord2(record2);
|
|
7564
7625
|
}
|
|
7565
7626
|
syncUserTargets(context, browserInstanceId, userTargets) {
|
|
7566
7627
|
const targetsByKey = /* @__PURE__ */ new Map();
|
|
@@ -7572,11 +7633,11 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7572
7633
|
);
|
|
7573
7634
|
}
|
|
7574
7635
|
const invalidated = [];
|
|
7575
|
-
for (const
|
|
7576
|
-
if (
|
|
7577
|
-
const key = targetKey2(
|
|
7636
|
+
for (const record2 of this.records.values()) {
|
|
7637
|
+
if (record2.state !== "active" || record2.origin !== "user_tab" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
|
|
7638
|
+
const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
|
|
7578
7639
|
if (!targetsByKey.has(key)) {
|
|
7579
|
-
invalidated.push(this.invalidate(
|
|
7640
|
+
invalidated.push(this.invalidate(record2, "target_ineligible"));
|
|
7580
7641
|
}
|
|
7581
7642
|
}
|
|
7582
7643
|
const targets = [];
|
|
@@ -7594,7 +7655,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7594
7655
|
targets.push(cloneRecord2(existing));
|
|
7595
7656
|
continue;
|
|
7596
7657
|
}
|
|
7597
|
-
const
|
|
7658
|
+
const record2 = {
|
|
7598
7659
|
id: this.nextId(),
|
|
7599
7660
|
principalId: context.principalId,
|
|
7600
7661
|
workspaceId: context.workspaceId,
|
|
@@ -7608,27 +7669,27 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7608
7669
|
createdAt: this.now(),
|
|
7609
7670
|
state: "active"
|
|
7610
7671
|
};
|
|
7611
|
-
this.records.set(
|
|
7612
|
-
this.activeKeys.set(key,
|
|
7613
|
-
targets.push(cloneRecord2(
|
|
7614
|
-
created.push(cloneRecord2(
|
|
7672
|
+
this.records.set(record2.id, record2);
|
|
7673
|
+
this.activeKeys.set(key, record2.id);
|
|
7674
|
+
targets.push(cloneRecord2(record2));
|
|
7675
|
+
created.push(cloneRecord2(record2));
|
|
7615
7676
|
}
|
|
7616
7677
|
return { targets, created, invalidated };
|
|
7617
7678
|
}
|
|
7618
7679
|
reconcileBrowserTargets(context, browserInstanceId, liveTargets) {
|
|
7619
7680
|
const invalidated = [];
|
|
7620
|
-
for (const
|
|
7621
|
-
if (
|
|
7622
|
-
const live = liveTargets.get(
|
|
7681
|
+
for (const record2 of this.records.values()) {
|
|
7682
|
+
if (record2.state !== "active" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
|
|
7683
|
+
const live = liveTargets.get(record2.cdpTargetId);
|
|
7623
7684
|
if (!live) {
|
|
7624
|
-
invalidated.push(this.invalidate(
|
|
7685
|
+
invalidated.push(this.invalidate(record2, "target_detached"));
|
|
7625
7686
|
continue;
|
|
7626
7687
|
}
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
if (
|
|
7631
|
-
invalidated.push(this.invalidate(
|
|
7688
|
+
record2.title = live.title;
|
|
7689
|
+
record2.url = live.url;
|
|
7690
|
+
record2.openerCdpTargetId = live.openerCdpTargetId;
|
|
7691
|
+
if (record2.profileContextId && live.profileContextId && record2.profileContextId !== live.profileContextId) {
|
|
7692
|
+
invalidated.push(this.invalidate(record2, "target_ineligible"));
|
|
7632
7693
|
}
|
|
7633
7694
|
}
|
|
7634
7695
|
return invalidated;
|
|
@@ -7636,38 +7697,38 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7636
7697
|
list(context, leaseId, scope = "all") {
|
|
7637
7698
|
const activeTargetId = this.activeByLease.get(leaseId);
|
|
7638
7699
|
const records = [...this.records.values()].filter(
|
|
7639
|
-
(
|
|
7700
|
+
(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
7701
|
).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,
|
|
7702
|
+
return records.map((record2) => ({
|
|
7703
|
+
targetId: record2.id,
|
|
7704
|
+
profileContextId: record2.profileContextId,
|
|
7705
|
+
title: record2.title,
|
|
7706
|
+
url: record2.url,
|
|
7707
|
+
active: record2.id === activeTargetId,
|
|
7708
|
+
origin: record2.origin,
|
|
7709
|
+
...record2.managedTabSetId ? { managedTabSetId: record2.managedTabSetId } : {},
|
|
7710
|
+
controlState: this.controlState(context, leaseId, record2)
|
|
7650
7711
|
}));
|
|
7651
7712
|
}
|
|
7652
7713
|
get(context, targetId) {
|
|
7653
|
-
const
|
|
7654
|
-
if (!
|
|
7714
|
+
const record2 = this.records.get(targetId);
|
|
7715
|
+
if (!record2 || record2.state !== "active") {
|
|
7655
7716
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7656
7717
|
context: { targetId }
|
|
7657
7718
|
});
|
|
7658
7719
|
}
|
|
7659
|
-
this.assertCallerOwns(context,
|
|
7660
|
-
return cloneRecord2(
|
|
7720
|
+
this.assertCallerOwns(context, record2);
|
|
7721
|
+
return cloneRecord2(record2);
|
|
7661
7722
|
}
|
|
7662
7723
|
acquire(context, leaseId, targetId) {
|
|
7663
|
-
const
|
|
7664
|
-
if (!
|
|
7724
|
+
const record2 = this.records.get(targetId);
|
|
7725
|
+
if (!record2 || record2.state !== "active") {
|
|
7665
7726
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7666
7727
|
context: { targetId }
|
|
7667
7728
|
});
|
|
7668
7729
|
}
|
|
7669
|
-
this.assertCallerOwns(context,
|
|
7670
|
-
const physicalKey = physicalTargetKey(
|
|
7730
|
+
this.assertCallerOwns(context, record2);
|
|
7731
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7671
7732
|
const controller = this.controllers.get(physicalKey);
|
|
7672
7733
|
if (controller && (controller.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId)) {
|
|
7673
7734
|
throw new BrowserPilotError("target_busy", "Target is controlled by another Lease", {
|
|
@@ -7682,18 +7743,18 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7682
7743
|
workspaceId: context.workspaceId,
|
|
7683
7744
|
targetId
|
|
7684
7745
|
});
|
|
7685
|
-
|
|
7686
|
-
return { target: cloneRecord2(
|
|
7746
|
+
record2.controllerLeaseId = leaseId;
|
|
7747
|
+
return { target: cloneRecord2(record2), newlyAcquired };
|
|
7687
7748
|
}
|
|
7688
7749
|
release(context, leaseId, targetId) {
|
|
7689
|
-
const
|
|
7690
|
-
if (!
|
|
7691
|
-
this.assertCallerOwns(context,
|
|
7692
|
-
const physicalKey = physicalTargetKey(
|
|
7750
|
+
const record2 = this.records.get(targetId);
|
|
7751
|
+
if (!record2 || record2.state !== "active") return false;
|
|
7752
|
+
this.assertCallerOwns(context, record2);
|
|
7753
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7693
7754
|
const controller = this.controllers.get(physicalKey);
|
|
7694
7755
|
if (controller?.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === targetId) {
|
|
7695
7756
|
this.controllers.delete(physicalKey);
|
|
7696
|
-
|
|
7757
|
+
record2.controllerLeaseId = void 0;
|
|
7697
7758
|
if (this.activeByLease.get(leaseId) === targetId) this.activeByLease.delete(leaseId);
|
|
7698
7759
|
return true;
|
|
7699
7760
|
}
|
|
@@ -7701,128 +7762,128 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7701
7762
|
return false;
|
|
7702
7763
|
}
|
|
7703
7764
|
controlledByLease(leaseId) {
|
|
7704
|
-
return [...this.records.values()].filter((
|
|
7765
|
+
return [...this.records.values()].filter((record2) => record2.state === "active" && record2.controllerLeaseId === leaseId).map(cloneRecord2);
|
|
7705
7766
|
}
|
|
7706
7767
|
releaseLease(leaseId) {
|
|
7707
7768
|
for (const [key, controller] of this.controllers) {
|
|
7708
7769
|
if (controller.leaseId === leaseId) this.controllers.delete(key);
|
|
7709
7770
|
}
|
|
7710
|
-
for (const
|
|
7711
|
-
if (
|
|
7771
|
+
for (const record2 of this.records.values()) {
|
|
7772
|
+
if (record2.controllerLeaseId === leaseId) record2.controllerLeaseId = void 0;
|
|
7712
7773
|
}
|
|
7713
7774
|
this.activeByLease.delete(leaseId);
|
|
7714
7775
|
}
|
|
7715
7776
|
activeTarget(context, leaseId) {
|
|
7716
7777
|
const targetId = this.activeByLease.get(leaseId);
|
|
7717
7778
|
if (!targetId) return void 0;
|
|
7718
|
-
const
|
|
7719
|
-
if (!
|
|
7779
|
+
const record2 = this.records.get(targetId);
|
|
7780
|
+
if (!record2 || record2.state !== "active") {
|
|
7720
7781
|
this.activeByLease.delete(leaseId);
|
|
7721
7782
|
return void 0;
|
|
7722
7783
|
}
|
|
7723
|
-
this.assertCallerOwns(context,
|
|
7724
|
-
return cloneRecord2(
|
|
7784
|
+
this.assertCallerOwns(context, record2);
|
|
7785
|
+
return cloneRecord2(record2);
|
|
7725
7786
|
}
|
|
7726
7787
|
clearActive(context, leaseId) {
|
|
7727
7788
|
const targetId = this.activeByLease.get(leaseId);
|
|
7728
7789
|
if (!targetId) return;
|
|
7729
|
-
const
|
|
7730
|
-
if (
|
|
7790
|
+
const record2 = this.records.get(targetId);
|
|
7791
|
+
if (record2?.state === "active") this.assertCallerOwns(context, record2);
|
|
7731
7792
|
this.activeByLease.delete(leaseId);
|
|
7732
7793
|
}
|
|
7733
7794
|
releaseWorkspace(context) {
|
|
7734
7795
|
const invalidated = [];
|
|
7735
|
-
for (const
|
|
7736
|
-
if (
|
|
7737
|
-
invalidated.push(this.invalidate(
|
|
7796
|
+
for (const record2 of [...this.records.values()]) {
|
|
7797
|
+
if (record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) {
|
|
7798
|
+
invalidated.push(this.invalidate(record2, "target_detached"));
|
|
7738
7799
|
}
|
|
7739
7800
|
}
|
|
7740
7801
|
return invalidated;
|
|
7741
7802
|
}
|
|
7742
7803
|
invalidateBrowserConnection(browserInstanceId) {
|
|
7743
7804
|
const invalidated = [];
|
|
7744
|
-
for (const
|
|
7745
|
-
if (
|
|
7746
|
-
invalidated.push(this.invalidate(
|
|
7805
|
+
for (const record2 of this.records.values()) {
|
|
7806
|
+
if (record2.state === "active" && record2.browserInstanceId === browserInstanceId) {
|
|
7807
|
+
invalidated.push(this.invalidate(record2, "browser_reconnected"));
|
|
7747
7808
|
}
|
|
7748
7809
|
}
|
|
7749
7810
|
return invalidated;
|
|
7750
7811
|
}
|
|
7751
7812
|
setActive(context, leaseId, targetId) {
|
|
7752
|
-
const
|
|
7753
|
-
const controller =
|
|
7754
|
-
if (!
|
|
7813
|
+
const record2 = this.records.get(targetId);
|
|
7814
|
+
const controller = record2 ? this.controllers.get(physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)) : void 0;
|
|
7815
|
+
if (!record2 || record2.state !== "active" || controller?.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId || controller.targetId !== targetId) {
|
|
7755
7816
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Lease", {
|
|
7756
7817
|
context: { targetId, leaseId }
|
|
7757
7818
|
});
|
|
7758
7819
|
}
|
|
7759
|
-
this.assertCallerOwns(context,
|
|
7820
|
+
this.assertCallerOwns(context, record2);
|
|
7760
7821
|
this.activeByLease.set(leaseId, targetId);
|
|
7761
7822
|
}
|
|
7762
7823
|
managedTargets(context, managedTabSetId) {
|
|
7763
7824
|
return [...this.records.values()].filter(
|
|
7764
|
-
(
|
|
7825
|
+
(record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && record2.origin !== "user_tab" && record2.managedTabSetId === managedTabSetId
|
|
7765
7826
|
).map(cloneRecord2);
|
|
7766
7827
|
}
|
|
7767
7828
|
activeRecords(context) {
|
|
7768
7829
|
return [...this.records.values()].filter(
|
|
7769
|
-
(
|
|
7830
|
+
(record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId
|
|
7770
7831
|
).map(cloneRecord2);
|
|
7771
7832
|
}
|
|
7772
7833
|
isManagedCdpTarget(browserInstanceId, cdpTargetId, browserConnectionGeneration) {
|
|
7773
|
-
for (const
|
|
7774
|
-
if (
|
|
7834
|
+
for (const record2 of this.records.values()) {
|
|
7835
|
+
if (record2.browserInstanceId === browserInstanceId && record2.cdpTargetId === cdpTargetId && record2.browserConnectionGeneration === browserConnectionGeneration && record2.origin !== "user_tab") return true;
|
|
7775
7836
|
}
|
|
7776
7837
|
return false;
|
|
7777
7838
|
}
|
|
7778
7839
|
markClosed(context, targetId) {
|
|
7779
|
-
const
|
|
7780
|
-
if (!
|
|
7840
|
+
const record2 = this.records.get(targetId);
|
|
7841
|
+
if (!record2 || record2.state !== "active") {
|
|
7781
7842
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7782
7843
|
context: { targetId }
|
|
7783
7844
|
});
|
|
7784
7845
|
}
|
|
7785
|
-
this.assertCallerOwns(context,
|
|
7846
|
+
this.assertCallerOwns(context, record2);
|
|
7786
7847
|
const invalidated = [];
|
|
7787
7848
|
for (const candidate of this.records.values()) {
|
|
7788
|
-
if (candidate.state === "active" && candidate.browserInstanceId ===
|
|
7849
|
+
if (candidate.state === "active" && candidate.browserInstanceId === record2.browserInstanceId && candidate.cdpTargetId === record2.cdpTargetId) {
|
|
7789
7850
|
invalidated.push(this.invalidate(candidate, "target_closed"));
|
|
7790
7851
|
}
|
|
7791
7852
|
}
|
|
7792
7853
|
return invalidated;
|
|
7793
7854
|
}
|
|
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 ===
|
|
7855
|
+
invalidate(record2, reason2) {
|
|
7856
|
+
record2.state = reason2 === "target_closed" ? "closed" : "detached";
|
|
7857
|
+
record2.invalidatedBy = reason2;
|
|
7858
|
+
record2.controllerLeaseId = void 0;
|
|
7859
|
+
const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
|
|
7860
|
+
if (this.activeKeys.get(key) === record2.id) this.activeKeys.delete(key);
|
|
7861
|
+
const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
|
|
7862
|
+
if (this.controllers.get(physicalKey)?.targetId === record2.id) {
|
|
7802
7863
|
this.controllers.delete(physicalKey);
|
|
7803
7864
|
}
|
|
7804
7865
|
for (const [leaseId, targetId] of this.activeByLease) {
|
|
7805
|
-
if (targetId ===
|
|
7866
|
+
if (targetId === record2.id) this.activeByLease.delete(leaseId);
|
|
7806
7867
|
}
|
|
7807
7868
|
return {
|
|
7808
|
-
targetId:
|
|
7809
|
-
workspaceId:
|
|
7810
|
-
browserConnectionGeneration:
|
|
7869
|
+
targetId: record2.id,
|
|
7870
|
+
workspaceId: record2.workspaceId,
|
|
7871
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
7811
7872
|
reason: reason2
|
|
7812
7873
|
};
|
|
7813
7874
|
}
|
|
7814
|
-
assertCallerOwns(context,
|
|
7815
|
-
if (
|
|
7875
|
+
assertCallerOwns(context, record2) {
|
|
7876
|
+
if (record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) return;
|
|
7816
7877
|
throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
|
|
7817
|
-
context: { targetId:
|
|
7878
|
+
context: { targetId: record2.id }
|
|
7818
7879
|
});
|
|
7819
7880
|
}
|
|
7820
|
-
controlState(context, leaseId,
|
|
7881
|
+
controlState(context, leaseId, record2) {
|
|
7821
7882
|
const controller = this.controllers.get(
|
|
7822
|
-
physicalTargetKey(
|
|
7883
|
+
physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)
|
|
7823
7884
|
);
|
|
7824
7885
|
if (!controller) return "available";
|
|
7825
|
-
return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId ===
|
|
7886
|
+
return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record2.id ? "controlled" : "busy";
|
|
7826
7887
|
}
|
|
7827
7888
|
nextId() {
|
|
7828
7889
|
const id = this.idFactory();
|
|
@@ -8098,17 +8159,17 @@ function parseObject(value, label) {
|
|
|
8098
8159
|
return value;
|
|
8099
8160
|
}
|
|
8100
8161
|
function parseDropdown(value) {
|
|
8101
|
-
const
|
|
8102
|
-
if (
|
|
8103
|
-
if (!["native", "aria"].includes(String(
|
|
8104
|
-
const { ok: _ok, ...info } =
|
|
8162
|
+
const record2 = parseObject(value, "dropdown information");
|
|
8163
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown inspection failed"), "target");
|
|
8164
|
+
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");
|
|
8165
|
+
const { ok: _ok, ...info } = record2;
|
|
8105
8166
|
return info;
|
|
8106
8167
|
}
|
|
8107
8168
|
function parseEvidence(value) {
|
|
8108
|
-
const
|
|
8109
|
-
if (
|
|
8110
|
-
if (
|
|
8111
|
-
const { ok: _ok, ...evidence } =
|
|
8169
|
+
const record2 = parseObject(value, "dropdown selection evidence");
|
|
8170
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown selection failed"), "target");
|
|
8171
|
+
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");
|
|
8172
|
+
const { ok: _ok, ...evidence } = record2;
|
|
8112
8173
|
return evidence;
|
|
8113
8174
|
}
|
|
8114
8175
|
function selectorExpression(selector, functionDeclaration, argument) {
|
|
@@ -8323,11 +8384,11 @@ var FrameService = class _FrameService {
|
|
|
8323
8384
|
|
|
8324
8385
|
// src/services/observation-store.ts
|
|
8325
8386
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
8326
|
-
function clone(
|
|
8387
|
+
function clone(record2) {
|
|
8327
8388
|
return {
|
|
8328
|
-
...
|
|
8329
|
-
truncationReasons: [...
|
|
8330
|
-
refs:
|
|
8389
|
+
...record2,
|
|
8390
|
+
truncationReasons: [...record2.truncationReasons],
|
|
8391
|
+
refs: record2.refs.map((ref) => ({ ...ref }))
|
|
8331
8392
|
};
|
|
8332
8393
|
}
|
|
8333
8394
|
var MemoryObservationStore = class {
|
|
@@ -8368,7 +8429,7 @@ var MemoryObservationStore = class {
|
|
|
8368
8429
|
throw new BrowserPilotError("internal_error", "Invalid or duplicate Observation ID");
|
|
8369
8430
|
}
|
|
8370
8431
|
const createdAt = this.now();
|
|
8371
|
-
const
|
|
8432
|
+
const record2 = {
|
|
8372
8433
|
id,
|
|
8373
8434
|
workspaceId: input.workspaceId,
|
|
8374
8435
|
leaseId: input.leaseId,
|
|
@@ -8388,58 +8449,58 @@ var MemoryObservationStore = class {
|
|
|
8388
8449
|
truncationReasons: [...input.truncationReasons],
|
|
8389
8450
|
refs: input.refs.map((ref) => ({ ...ref }))
|
|
8390
8451
|
};
|
|
8391
|
-
this.records.set(id,
|
|
8392
|
-
return clone(
|
|
8452
|
+
this.records.set(id, record2);
|
|
8453
|
+
return clone(record2);
|
|
8393
8454
|
}
|
|
8394
8455
|
resolve(input) {
|
|
8395
8456
|
const now = this.now();
|
|
8396
|
-
const
|
|
8397
|
-
if (!
|
|
8457
|
+
const record2 = this.records.get(input.observationId);
|
|
8458
|
+
if (!record2 || record2.workspaceId !== input.workspaceId || record2.leaseId !== input.leaseId || record2.targetId !== input.targetId) {
|
|
8398
8459
|
this.sweepAt(now);
|
|
8399
8460
|
throw this.stale(input);
|
|
8400
8461
|
}
|
|
8401
|
-
if (
|
|
8402
|
-
const reason2 =
|
|
8462
|
+
if (record2.invalidatedBy) {
|
|
8463
|
+
const reason2 = record2.invalidatedBy;
|
|
8403
8464
|
this.sweepAt(now);
|
|
8404
8465
|
throw this.stale(input, reason2);
|
|
8405
8466
|
}
|
|
8406
|
-
if (
|
|
8407
|
-
this.invalidateRecord(
|
|
8408
|
-
this.records.delete(
|
|
8467
|
+
if (record2.expiresAt <= now) {
|
|
8468
|
+
this.invalidateRecord(record2, "expired");
|
|
8469
|
+
this.records.delete(record2.id);
|
|
8409
8470
|
this.sweepAt(now);
|
|
8410
8471
|
throw this.stale(input, "expired");
|
|
8411
8472
|
}
|
|
8412
8473
|
this.sweepAt(now);
|
|
8413
|
-
if (
|
|
8414
|
-
this.invalidateRecord(
|
|
8474
|
+
if (record2.browserProcessIdentity !== input.browserProcessIdentity || record2.browserConnectionGeneration !== input.browserConnectionGeneration) {
|
|
8475
|
+
this.invalidateRecord(record2, "browser_reconnected");
|
|
8415
8476
|
throw this.stale(input, "browser_reconnected");
|
|
8416
8477
|
}
|
|
8417
|
-
if (
|
|
8418
|
-
this.invalidateRecord(
|
|
8478
|
+
if (record2.sessionId !== input.sessionId) {
|
|
8479
|
+
this.invalidateRecord(record2, "session_replaced");
|
|
8419
8480
|
throw this.stale(input, "session_replaced");
|
|
8420
8481
|
}
|
|
8421
|
-
if (
|
|
8422
|
-
this.invalidateRecord(
|
|
8482
|
+
if (record2.frameId !== input.frameId) {
|
|
8483
|
+
this.invalidateRecord(record2, "frame_changed");
|
|
8423
8484
|
throw this.stale(input, "frame_changed");
|
|
8424
8485
|
}
|
|
8425
|
-
if (
|
|
8426
|
-
this.invalidateRecord(
|
|
8486
|
+
if (record2.loaderId !== input.loaderId) {
|
|
8487
|
+
this.invalidateRecord(record2, "loader_replaced");
|
|
8427
8488
|
throw this.stale(input, "loader_replaced");
|
|
8428
8489
|
}
|
|
8429
|
-
if (
|
|
8430
|
-
this.invalidateRecord(
|
|
8490
|
+
if (record2.documentGeneration !== input.documentGeneration) {
|
|
8491
|
+
this.invalidateRecord(record2, "document_replaced");
|
|
8431
8492
|
throw this.stale(input, "document_replaced");
|
|
8432
8493
|
}
|
|
8433
|
-
if (input.ref !== void 0 && (input.ref < 1 || input.ref >
|
|
8494
|
+
if (input.ref !== void 0 && (input.ref < 1 || input.ref > record2.refs.length)) {
|
|
8434
8495
|
throw this.stale(input);
|
|
8435
8496
|
}
|
|
8436
|
-
return clone(
|
|
8497
|
+
return clone(record2);
|
|
8437
8498
|
}
|
|
8438
8499
|
latest(workspaceId, leaseId, targetId) {
|
|
8439
8500
|
this.sweep();
|
|
8440
8501
|
let latest;
|
|
8441
|
-
for (const
|
|
8442
|
-
if (
|
|
8502
|
+
for (const record2 of this.records.values()) {
|
|
8503
|
+
if (record2.workspaceId === workspaceId && record2.leaseId === leaseId && record2.targetId === targetId && !record2.invalidatedBy && (!latest || record2.createdAt >= latest.createdAt)) latest = record2;
|
|
8443
8504
|
}
|
|
8444
8505
|
if (latest) return clone(latest);
|
|
8445
8506
|
throw new BrowserPilotError("stale_ref", "No current Observation exists for this target", {
|
|
@@ -8447,25 +8508,25 @@ var MemoryObservationStore = class {
|
|
|
8447
8508
|
});
|
|
8448
8509
|
}
|
|
8449
8510
|
invalidateTarget(targetId, reason2) {
|
|
8450
|
-
for (const
|
|
8451
|
-
if (
|
|
8511
|
+
for (const record2 of this.records.values()) {
|
|
8512
|
+
if (record2.targetId === targetId && !record2.invalidatedBy) this.invalidateRecord(record2, reason2);
|
|
8452
8513
|
}
|
|
8453
8514
|
}
|
|
8454
8515
|
invalidateSession(sessionId, reason2 = "session_replaced") {
|
|
8455
|
-
for (const
|
|
8456
|
-
if (
|
|
8457
|
-
this.invalidateRecord(
|
|
8516
|
+
for (const record2 of this.records.values()) {
|
|
8517
|
+
if (record2.sessionId === sessionId && !record2.invalidatedBy) {
|
|
8518
|
+
this.invalidateRecord(record2, reason2);
|
|
8458
8519
|
}
|
|
8459
8520
|
}
|
|
8460
8521
|
}
|
|
8461
8522
|
releaseLease(leaseId) {
|
|
8462
|
-
for (const [id,
|
|
8463
|
-
if (
|
|
8523
|
+
for (const [id, record2] of this.records) {
|
|
8524
|
+
if (record2.leaseId === leaseId) this.records.delete(id);
|
|
8464
8525
|
}
|
|
8465
8526
|
}
|
|
8466
8527
|
releaseWorkspace(workspaceId) {
|
|
8467
|
-
for (const [id,
|
|
8468
|
-
if (
|
|
8528
|
+
for (const [id, record2] of this.records) {
|
|
8529
|
+
if (record2.workspaceId === workspaceId) this.records.delete(id);
|
|
8469
8530
|
}
|
|
8470
8531
|
}
|
|
8471
8532
|
sweep() {
|
|
@@ -8473,9 +8534,9 @@ var MemoryObservationStore = class {
|
|
|
8473
8534
|
}
|
|
8474
8535
|
sweepAt(now) {
|
|
8475
8536
|
let removed = 0;
|
|
8476
|
-
for (const [id,
|
|
8477
|
-
if (
|
|
8478
|
-
this.invalidateRecord(
|
|
8537
|
+
for (const [id, record2] of this.records) {
|
|
8538
|
+
if (record2.expiresAt > now) continue;
|
|
8539
|
+
this.invalidateRecord(record2, "expired");
|
|
8479
8540
|
this.records.delete(id);
|
|
8480
8541
|
removed += 1;
|
|
8481
8542
|
}
|
|
@@ -8484,9 +8545,9 @@ var MemoryObservationStore = class {
|
|
|
8484
8545
|
size() {
|
|
8485
8546
|
return this.records.size;
|
|
8486
8547
|
}
|
|
8487
|
-
invalidateRecord(
|
|
8488
|
-
|
|
8489
|
-
|
|
8548
|
+
invalidateRecord(record2, reason2) {
|
|
8549
|
+
record2.invalidatedBy = reason2;
|
|
8550
|
+
record2.refs = [];
|
|
8490
8551
|
}
|
|
8491
8552
|
stale(input, reason2) {
|
|
8492
8553
|
return new BrowserPilotError("stale_ref", "Observation ref is stale or does not belong to this context", {
|
|
@@ -8557,22 +8618,22 @@ var PageContentService = class {
|
|
|
8557
8618
|
if (typeof data !== "object" || data === null) {
|
|
8558
8619
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
|
|
8559
8620
|
}
|
|
8560
|
-
const
|
|
8561
|
-
if (
|
|
8621
|
+
const record2 = data;
|
|
8622
|
+
if (record2.ok !== true) {
|
|
8562
8623
|
throw invalidArgument(
|
|
8563
|
-
typeof
|
|
8624
|
+
typeof record2.error === "string" ? record2.error : "Failed to read page content",
|
|
8564
8625
|
selector ? "selector" : void 0
|
|
8565
8626
|
);
|
|
8566
8627
|
}
|
|
8567
|
-
if (typeof
|
|
8628
|
+
if (typeof record2.title !== "string" || typeof record2.url !== "string" || typeof record2.text !== "string" || !Number.isSafeInteger(record2.length) || typeof record2.truncated !== "boolean") {
|
|
8568
8629
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
|
|
8569
8630
|
}
|
|
8570
8631
|
return {
|
|
8571
|
-
title:
|
|
8572
|
-
url:
|
|
8573
|
-
text:
|
|
8574
|
-
length:
|
|
8575
|
-
truncated:
|
|
8632
|
+
title: record2.title,
|
|
8633
|
+
url: record2.url,
|
|
8634
|
+
text: record2.text,
|
|
8635
|
+
length: record2.length,
|
|
8636
|
+
truncated: record2.truncated
|
|
8576
8637
|
};
|
|
8577
8638
|
}
|
|
8578
8639
|
};
|
|
@@ -8951,13 +9012,13 @@ function parseEvidence2(value) {
|
|
|
8951
9012
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8952
9013
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
|
|
8953
9014
|
}
|
|
8954
|
-
const
|
|
8955
|
-
if (
|
|
9015
|
+
const record2 = value;
|
|
9016
|
+
if (record2.ok !== true) throw invalidArgument(String(record2.error || "Scroll failed"), "target");
|
|
8956
9017
|
const numeric = ["deltaX", "deltaY", "beforeX", "beforeY", "afterX", "afterY"];
|
|
8957
|
-
if (
|
|
9018
|
+
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
9019
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
|
|
8959
9020
|
}
|
|
8960
|
-
const { ok: _ok, ...evidence } =
|
|
9021
|
+
const { ok: _ok, ...evidence } = record2;
|
|
8961
9022
|
return evidence;
|
|
8962
9023
|
}
|
|
8963
9024
|
function validateInput(input) {
|
|
@@ -9459,7 +9520,7 @@ var TargetInventoryService = class {
|
|
|
9459
9520
|
}
|
|
9460
9521
|
adoptManagedPopups(context, liveTargets) {
|
|
9461
9522
|
const active = new Map(
|
|
9462
|
-
this.registry.activeRecords(context).map((
|
|
9523
|
+
this.registry.activeRecords(context).map((record2) => [record2.cdpTargetId, record2])
|
|
9463
9524
|
);
|
|
9464
9525
|
let adopted = true;
|
|
9465
9526
|
while (adopted) {
|
|
@@ -9468,7 +9529,7 @@ var TargetInventoryService = class {
|
|
|
9468
9529
|
if (active.has(target.cdpTargetId) || target.type && target.type !== "page") continue;
|
|
9469
9530
|
const ancestor = this.findManagedAncestor(target, active, liveTargets);
|
|
9470
9531
|
if (!ancestor?.managedTabSetId || ancestor.profileContextId && target.profileContextId && ancestor.profileContextId !== target.profileContextId) continue;
|
|
9471
|
-
const
|
|
9532
|
+
const record2 = this.registry.registerManaged({
|
|
9472
9533
|
principalId: context.principalId,
|
|
9473
9534
|
workspaceId: context.workspaceId,
|
|
9474
9535
|
browserInstanceId: this.browserInstanceId,
|
|
@@ -9481,9 +9542,9 @@ var TargetInventoryService = class {
|
|
|
9481
9542
|
title: target.title,
|
|
9482
9543
|
url: target.url
|
|
9483
9544
|
});
|
|
9484
|
-
active.set(
|
|
9485
|
-
this.notify(this.onTargetAttached,
|
|
9486
|
-
this.notify(this.onPopup,
|
|
9545
|
+
active.set(record2.cdpTargetId, record2);
|
|
9546
|
+
this.notify(this.onTargetAttached, record2);
|
|
9547
|
+
this.notify(this.onPopup, record2);
|
|
9487
9548
|
adopted = true;
|
|
9488
9549
|
}
|
|
9489
9550
|
}
|
|
@@ -9520,8 +9581,91 @@ var TargetInventoryService = class {
|
|
|
9520
9581
|
}
|
|
9521
9582
|
};
|
|
9522
9583
|
|
|
9584
|
+
// src/services/chrome-profile-identity.ts
|
|
9585
|
+
import { open } from "fs/promises";
|
|
9586
|
+
import { basename as basename2, dirname as dirname2, join as join4, resolve } from "path";
|
|
9587
|
+
var MAX_LOCAL_STATE_BYTES = 16 * 1024 * 1024;
|
|
9588
|
+
var MAX_IDENTITY_TEXT_LENGTH = 4096;
|
|
9589
|
+
var ProfileIdentityError = class extends Error {
|
|
9590
|
+
constructor(code, message) {
|
|
9591
|
+
super(message);
|
|
9592
|
+
this.code = code;
|
|
9593
|
+
this.name = "ProfileIdentityError";
|
|
9594
|
+
}
|
|
9595
|
+
};
|
|
9596
|
+
function record(value) {
|
|
9597
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
9598
|
+
}
|
|
9599
|
+
function identityText(value) {
|
|
9600
|
+
if (typeof value !== "string") return void 0;
|
|
9601
|
+
const normalized = value.trim().slice(0, MAX_IDENTITY_TEXT_LENGTH);
|
|
9602
|
+
return normalized || void 0;
|
|
9603
|
+
}
|
|
9604
|
+
async function readVerifiedChromeProfileIdentity(userDataRoot, reportedProfilePath) {
|
|
9605
|
+
const root = resolve(userDataRoot);
|
|
9606
|
+
const profilePath = resolve(reportedProfilePath);
|
|
9607
|
+
if (dirname2(profilePath) !== root) {
|
|
9608
|
+
throw new ProfileIdentityError(
|
|
9609
|
+
"profile_path_unverified",
|
|
9610
|
+
"Chrome reported a Profile path outside the connected browser user-data root"
|
|
9611
|
+
);
|
|
9612
|
+
}
|
|
9613
|
+
const profileDirectory = basename2(profilePath);
|
|
9614
|
+
if (!profileDirectory || profileDirectory === "." || profileDirectory === "..") {
|
|
9615
|
+
throw new ProfileIdentityError("profile_path_unverified", "Chrome reported an invalid Profile directory");
|
|
9616
|
+
}
|
|
9617
|
+
const localStatePath = join4(root, "Local State");
|
|
9618
|
+
let bytes;
|
|
9619
|
+
let handle;
|
|
9620
|
+
try {
|
|
9621
|
+
handle = await open(localStatePath, "r");
|
|
9622
|
+
const metadata = await handle.stat();
|
|
9623
|
+
if (!metadata.isFile() || metadata.size > MAX_LOCAL_STATE_BYTES) {
|
|
9624
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State is missing or too large");
|
|
9625
|
+
}
|
|
9626
|
+
bytes = Buffer.alloc(metadata.size);
|
|
9627
|
+
let offset = 0;
|
|
9628
|
+
while (offset < bytes.length) {
|
|
9629
|
+
const result = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
9630
|
+
if (result.bytesRead === 0) break;
|
|
9631
|
+
offset += result.bytesRead;
|
|
9632
|
+
}
|
|
9633
|
+
if (offset !== bytes.length) bytes = bytes.subarray(0, offset);
|
|
9634
|
+
} catch (error) {
|
|
9635
|
+
if (error instanceof ProfileIdentityError) throw error;
|
|
9636
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State could not be read");
|
|
9637
|
+
} finally {
|
|
9638
|
+
await handle?.close().catch(() => {
|
|
9639
|
+
});
|
|
9640
|
+
}
|
|
9641
|
+
let rootState;
|
|
9642
|
+
try {
|
|
9643
|
+
rootState = record(JSON.parse(bytes.toString("utf8")));
|
|
9644
|
+
} catch {
|
|
9645
|
+
throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State contains invalid JSON");
|
|
9646
|
+
}
|
|
9647
|
+
const profileState = record(rootState?.profile);
|
|
9648
|
+
const infoCache = record(profileState?.info_cache);
|
|
9649
|
+
const entry = record(infoCache?.[profileDirectory]);
|
|
9650
|
+
const profileName = identityText(entry?.name);
|
|
9651
|
+
if (!profileName) {
|
|
9652
|
+
throw new ProfileIdentityError(
|
|
9653
|
+
"profile_metadata_missing",
|
|
9654
|
+
"Chrome Local State has no verified metadata for the reported Profile directory"
|
|
9655
|
+
);
|
|
9656
|
+
}
|
|
9657
|
+
const accountName = identityText(entry?.gaia_name) ?? identityText(entry?.gaia_given_name);
|
|
9658
|
+
const accountEmail = identityText(entry?.user_name);
|
|
9659
|
+
return {
|
|
9660
|
+
profileName,
|
|
9661
|
+
profileDirectory,
|
|
9662
|
+
...accountName ? { accountName } : {},
|
|
9663
|
+
...accountEmail ? { accountEmail } : {}
|
|
9664
|
+
};
|
|
9665
|
+
}
|
|
9666
|
+
|
|
9523
9667
|
// src/services/upload-service.ts
|
|
9524
|
-
import { basename as
|
|
9668
|
+
import { basename as basename3 } from "path";
|
|
9525
9669
|
var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
|
|
9526
9670
|
"detached",
|
|
9527
9671
|
"disabled",
|
|
@@ -9547,17 +9691,17 @@ function parseFileInputState(value) {
|
|
|
9547
9691
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9548
9692
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
|
|
9549
9693
|
}
|
|
9550
|
-
const
|
|
9551
|
-
if (
|
|
9552
|
-
return { status: "blocked", reason:
|
|
9694
|
+
const record2 = value;
|
|
9695
|
+
if (record2.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record2.reason)) {
|
|
9696
|
+
return { status: "blocked", reason: record2.reason };
|
|
9553
9697
|
}
|
|
9554
|
-
if (
|
|
9698
|
+
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
9699
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
|
|
9556
9700
|
}
|
|
9557
9701
|
return {
|
|
9558
9702
|
status: "ready",
|
|
9559
|
-
fileCount: Number(
|
|
9560
|
-
...
|
|
9703
|
+
fileCount: Number(record2.fileCount),
|
|
9704
|
+
...record2.firstFileName !== void 0 ? { firstFileName: record2.firstFileName } : {}
|
|
9561
9705
|
};
|
|
9562
9706
|
}
|
|
9563
9707
|
var UploadService = class {
|
|
@@ -9644,7 +9788,7 @@ var UploadService = class {
|
|
|
9644
9788
|
backendNodeId
|
|
9645
9789
|
}, this.sessionId);
|
|
9646
9790
|
if (this.readbackDelayMs > 0) {
|
|
9647
|
-
await new Promise((
|
|
9791
|
+
await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
|
|
9648
9792
|
}
|
|
9649
9793
|
let after;
|
|
9650
9794
|
try {
|
|
@@ -9666,7 +9810,7 @@ var UploadService = class {
|
|
|
9666
9810
|
reason: "target_unavailable"
|
|
9667
9811
|
};
|
|
9668
9812
|
}
|
|
9669
|
-
const nameMatched = after.firstFileName ===
|
|
9813
|
+
const nameMatched = after.firstFileName === basename3(filePath);
|
|
9670
9814
|
const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
|
|
9671
9815
|
const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
|
|
9672
9816
|
return {
|
|
@@ -9798,39 +9942,39 @@ function publicRule(rule) {
|
|
|
9798
9942
|
...rule.type === "headers" ? { headers: rule.headers } : {}
|
|
9799
9943
|
};
|
|
9800
9944
|
}
|
|
9801
|
-
function publicRequest(
|
|
9802
|
-
const durationMs =
|
|
9945
|
+
function publicRequest(record2) {
|
|
9946
|
+
const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
|
|
9803
9947
|
return {
|
|
9804
|
-
requestId:
|
|
9805
|
-
sequence:
|
|
9806
|
-
method:
|
|
9807
|
-
url:
|
|
9808
|
-
type:
|
|
9809
|
-
requestHeaders:
|
|
9810
|
-
...
|
|
9811
|
-
postDataTruncated:
|
|
9812
|
-
...
|
|
9813
|
-
...
|
|
9814
|
-
...
|
|
9815
|
-
...
|
|
9816
|
-
...
|
|
9948
|
+
requestId: record2.id,
|
|
9949
|
+
sequence: record2.sequence,
|
|
9950
|
+
method: record2.method,
|
|
9951
|
+
url: record2.url,
|
|
9952
|
+
type: record2.type,
|
|
9953
|
+
requestHeaders: record2.requestHeaders,
|
|
9954
|
+
...record2.postData !== void 0 ? { postData: record2.postData } : {},
|
|
9955
|
+
postDataTruncated: record2.postDataTruncated,
|
|
9956
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
9957
|
+
...record2.statusText !== void 0 ? { statusText: record2.statusText } : {},
|
|
9958
|
+
...record2.responseHeaders !== void 0 ? { responseHeaders: record2.responseHeaders } : {},
|
|
9959
|
+
...record2.mimeType !== void 0 ? { mimeType: record2.mimeType } : {},
|
|
9960
|
+
...record2.size !== void 0 ? { size: record2.size } : {},
|
|
9817
9961
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
9818
|
-
...
|
|
9819
|
-
bodyAvailable:
|
|
9962
|
+
...record2.error !== void 0 ? { error: record2.error } : {},
|
|
9963
|
+
bodyAvailable: record2.bodyAvailable
|
|
9820
9964
|
};
|
|
9821
9965
|
}
|
|
9822
|
-
function publicRequestSummary(
|
|
9823
|
-
const durationMs =
|
|
9966
|
+
function publicRequestSummary(record2) {
|
|
9967
|
+
const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
|
|
9824
9968
|
return {
|
|
9825
|
-
requestId:
|
|
9826
|
-
sequence:
|
|
9827
|
-
method:
|
|
9828
|
-
url:
|
|
9829
|
-
type:
|
|
9830
|
-
...
|
|
9831
|
-
...
|
|
9969
|
+
requestId: record2.id,
|
|
9970
|
+
sequence: record2.sequence,
|
|
9971
|
+
method: record2.method,
|
|
9972
|
+
url: record2.url,
|
|
9973
|
+
type: record2.type,
|
|
9974
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
9975
|
+
...record2.size !== void 0 ? { size: record2.size } : {},
|
|
9832
9976
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
9833
|
-
...
|
|
9977
|
+
...record2.error !== void 0 ? { error: record2.error } : {}
|
|
9834
9978
|
};
|
|
9835
9979
|
}
|
|
9836
9980
|
var WorkspaceNetworkController = class {
|
|
@@ -9992,15 +10136,15 @@ var WorkspaceNetworkController = class {
|
|
|
9992
10136
|
};
|
|
9993
10137
|
}
|
|
9994
10138
|
async request(workspaceId, requestId, includeBody) {
|
|
9995
|
-
const
|
|
9996
|
-
if (!
|
|
9997
|
-
const base = { request: publicRequest(
|
|
9998
|
-
if (!includeBody || !
|
|
10139
|
+
const record2 = this.state(workspaceId).requestsById.get(requestId);
|
|
10140
|
+
if (!record2) throw invalidArgument("Network request was not found in this Workspace", "requestId");
|
|
10141
|
+
const base = { request: publicRequest(record2), bodyTruncated: false };
|
|
10142
|
+
if (!includeBody || !record2.bodyAvailable) return base;
|
|
9999
10143
|
try {
|
|
10000
10144
|
const result = await this.transport.send(
|
|
10001
10145
|
"Network.getResponseBody",
|
|
10002
|
-
{ requestId:
|
|
10003
|
-
|
|
10146
|
+
{ requestId: record2.networkId },
|
|
10147
|
+
record2.sessionId
|
|
10004
10148
|
);
|
|
10005
10149
|
if (typeof result?.body !== "string") return base;
|
|
10006
10150
|
const truncated = result.body.length > MAX_BODY_CHARACTERS;
|
|
@@ -10008,7 +10152,7 @@ var WorkspaceNetworkController = class {
|
|
|
10008
10152
|
...base,
|
|
10009
10153
|
body: result.body.slice(0, MAX_BODY_CHARACTERS),
|
|
10010
10154
|
bodyEncoding: result.base64Encoded === true ? "base64" : "utf8",
|
|
10011
|
-
...
|
|
10155
|
+
...record2.mimeType ? { mimeType: record2.mimeType } : {},
|
|
10012
10156
|
bodyTruncated: truncated
|
|
10013
10157
|
};
|
|
10014
10158
|
} catch {
|
|
@@ -10098,20 +10242,20 @@ var WorkspaceNetworkController = class {
|
|
|
10098
10242
|
});
|
|
10099
10243
|
this.transport.on?.("Network.loadingFinished", (params, sessionId) => {
|
|
10100
10244
|
if (!sessionId) return;
|
|
10101
|
-
const
|
|
10102
|
-
if (!
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
this.refreshRequestSize(
|
|
10245
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10246
|
+
if (!record2) return;
|
|
10247
|
+
record2.size = Math.max(0, Math.trunc(Number(params?.encodedDataLength) || 0));
|
|
10248
|
+
record2.endedAt = this.now();
|
|
10249
|
+
record2.bodyAvailable = true;
|
|
10250
|
+
this.refreshRequestSize(record2);
|
|
10107
10251
|
});
|
|
10108
10252
|
this.transport.on?.("Network.loadingFailed", (params, sessionId) => {
|
|
10109
10253
|
if (!sessionId) return;
|
|
10110
|
-
const
|
|
10111
|
-
if (!
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
this.refreshRequestSize(
|
|
10254
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10255
|
+
if (!record2) return;
|
|
10256
|
+
record2.error = boundedString2(params?.errorText, 4096);
|
|
10257
|
+
record2.endedAt = this.now();
|
|
10258
|
+
this.refreshRequestSize(record2);
|
|
10115
10259
|
});
|
|
10116
10260
|
}
|
|
10117
10261
|
async handleAuthRequired(params, sessionId) {
|
|
@@ -10179,7 +10323,7 @@ var WorkspaceNetworkController = class {
|
|
|
10179
10323
|
this.publishResponse(redirected);
|
|
10180
10324
|
}
|
|
10181
10325
|
const rawPostData = typeof params?.request?.postData === "string" ? params.request.postData : void 0;
|
|
10182
|
-
const
|
|
10326
|
+
const record2 = {
|
|
10183
10327
|
id: this.requestIdFactory(),
|
|
10184
10328
|
sequence: state.nextSequence++,
|
|
10185
10329
|
workspaceId: session.workspaceId,
|
|
@@ -10198,55 +10342,55 @@ var WorkspaceNetworkController = class {
|
|
|
10198
10342
|
bodyAvailable: false,
|
|
10199
10343
|
retainedByteSize: 0
|
|
10200
10344
|
};
|
|
10201
|
-
|
|
10202
|
-
state.requests.push(
|
|
10203
|
-
state.requestBytes +=
|
|
10204
|
-
state.requestsById.set(
|
|
10205
|
-
state.requestsByNetworkKey.set(key,
|
|
10345
|
+
record2.retainedByteSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
|
|
10346
|
+
state.requests.push(record2);
|
|
10347
|
+
state.requestBytes += record2.retainedByteSize;
|
|
10348
|
+
state.requestsById.set(record2.id, record2);
|
|
10349
|
+
state.requestsByNetworkKey.set(key, record2);
|
|
10206
10350
|
this.compactRequests(state);
|
|
10207
10351
|
this.safePublish({
|
|
10208
|
-
workspaceId:
|
|
10209
|
-
leaseId:
|
|
10210
|
-
targetId:
|
|
10211
|
-
browserConnectionGeneration:
|
|
10352
|
+
workspaceId: record2.workspaceId,
|
|
10353
|
+
leaseId: record2.leaseId,
|
|
10354
|
+
targetId: record2.targetId,
|
|
10355
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
10212
10356
|
type: "network.request",
|
|
10213
10357
|
sensitivity: "browser_data",
|
|
10214
10358
|
payload: {
|
|
10215
|
-
requestId:
|
|
10216
|
-
method:
|
|
10217
|
-
url: sanitizedEventUrl(
|
|
10218
|
-
type:
|
|
10359
|
+
requestId: record2.id,
|
|
10360
|
+
method: record2.method,
|
|
10361
|
+
url: sanitizedEventUrl(record2.url),
|
|
10362
|
+
type: record2.type
|
|
10219
10363
|
}
|
|
10220
10364
|
});
|
|
10221
10365
|
}
|
|
10222
10366
|
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(
|
|
10367
|
+
const record2 = this.findNetworkRequest(sessionId, params?.requestId);
|
|
10368
|
+
if (!record2) return;
|
|
10369
|
+
this.applyResponse(record2, params?.response);
|
|
10370
|
+
this.refreshRequestSize(record2);
|
|
10371
|
+
this.publishResponse(record2);
|
|
10372
|
+
}
|
|
10373
|
+
applyResponse(record2, response) {
|
|
10374
|
+
record2.status = validStatus(response?.status);
|
|
10375
|
+
record2.statusText = boundedString2(response?.statusText, 4096);
|
|
10376
|
+
record2.responseHeaders = headersFromCdp(response?.headers);
|
|
10377
|
+
record2.mimeType = boundedString2(response?.mimeType, 256);
|
|
10378
|
+
}
|
|
10379
|
+
publishResponse(record2) {
|
|
10380
|
+
const hint = accessBlockedAgentHint(record2.type, record2.status);
|
|
10237
10381
|
this.safePublish({
|
|
10238
|
-
workspaceId:
|
|
10239
|
-
leaseId:
|
|
10240
|
-
targetId:
|
|
10241
|
-
browserConnectionGeneration:
|
|
10382
|
+
workspaceId: record2.workspaceId,
|
|
10383
|
+
leaseId: record2.leaseId,
|
|
10384
|
+
targetId: record2.targetId,
|
|
10385
|
+
browserConnectionGeneration: record2.browserConnectionGeneration,
|
|
10242
10386
|
type: "network.response",
|
|
10243
10387
|
sensitivity: "browser_data",
|
|
10244
10388
|
payload: {
|
|
10245
|
-
requestId:
|
|
10246
|
-
method:
|
|
10247
|
-
url: sanitizedEventUrl(
|
|
10248
|
-
type:
|
|
10249
|
-
...
|
|
10389
|
+
requestId: record2.id,
|
|
10390
|
+
method: record2.method,
|
|
10391
|
+
url: sanitizedEventUrl(record2.url),
|
|
10392
|
+
type: record2.type,
|
|
10393
|
+
...record2.status !== void 0 ? { status: record2.status } : {},
|
|
10250
10394
|
...hint ? { hints: [hint] } : {}
|
|
10251
10395
|
}
|
|
10252
10396
|
});
|
|
@@ -10256,12 +10400,12 @@ var WorkspaceNetworkController = class {
|
|
|
10256
10400
|
if (!session || typeof requestId !== "string") return void 0;
|
|
10257
10401
|
return this.workspaces.get(session.workspaceId)?.requestsByNetworkKey.get(networkKey(sessionId, requestId));
|
|
10258
10402
|
}
|
|
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
|
-
|
|
10403
|
+
refreshRequestSize(record2) {
|
|
10404
|
+
const state = this.workspaces.get(record2.workspaceId);
|
|
10405
|
+
if (!state || state.requestsById.get(record2.id) !== record2) return;
|
|
10406
|
+
const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
|
|
10407
|
+
state.requestBytes += nextSize - record2.retainedByteSize;
|
|
10408
|
+
record2.retainedByteSize = nextSize;
|
|
10265
10409
|
this.compactRequests(state);
|
|
10266
10410
|
}
|
|
10267
10411
|
compactRequests(state) {
|
|
@@ -10301,6 +10445,7 @@ var BASE_BROWSER_TOOL_NAMES = [
|
|
|
10301
10445
|
"browser.discover",
|
|
10302
10446
|
"browser.connect",
|
|
10303
10447
|
"browser.profiles.list",
|
|
10448
|
+
"browser.profiles.identify",
|
|
10304
10449
|
"browser.profiles.select",
|
|
10305
10450
|
"browser.open",
|
|
10306
10451
|
"browser.observe",
|
|
@@ -10365,20 +10510,20 @@ function stateFingerprint(value) {
|
|
|
10365
10510
|
return createHash4("sha256").update(JSON.stringify(value) ?? "undefined").digest("hex");
|
|
10366
10511
|
}
|
|
10367
10512
|
function elementAddress(value) {
|
|
10368
|
-
const
|
|
10369
|
-
if (typeof
|
|
10513
|
+
const record2 = asRecord(value);
|
|
10514
|
+
if (typeof record2.selector === "string") return { selector: record2.selector };
|
|
10370
10515
|
return {
|
|
10371
|
-
observationId:
|
|
10372
|
-
ref: Number(
|
|
10516
|
+
observationId: record2.observationId,
|
|
10517
|
+
ref: Number(record2.ref)
|
|
10373
10518
|
};
|
|
10374
10519
|
}
|
|
10375
10520
|
function dropdownChoice(value) {
|
|
10376
|
-
const
|
|
10377
|
-
if (
|
|
10378
|
-
if (
|
|
10379
|
-
return { by: "label", label: String(
|
|
10521
|
+
const record2 = asRecord(value);
|
|
10522
|
+
if (record2.by === "index") return { by: "index", index: Number(record2.index) };
|
|
10523
|
+
if (record2.by === "label") {
|
|
10524
|
+
return { by: "label", label: String(record2.label), exact: record2.exact !== false };
|
|
10380
10525
|
}
|
|
10381
|
-
return { by: "value", value: String(
|
|
10526
|
+
return { by: "value", value: String(record2.value), exact: record2.exact !== false };
|
|
10382
10527
|
}
|
|
10383
10528
|
function optionMatches(option, choice) {
|
|
10384
10529
|
if (choice.by === "index") return option.index === choice.index;
|
|
@@ -10417,6 +10562,7 @@ var BrowserToolService = class {
|
|
|
10417
10562
|
}
|
|
10418
10563
|
this.loadWaiter = options.loadWaiter ?? waitForLoad;
|
|
10419
10564
|
this.connectBrowser = options.connectBrowser;
|
|
10565
|
+
this.readProfileIdentity = options.readProfileIdentity ?? readVerifiedChromeProfileIdentity;
|
|
10420
10566
|
this.watchdogs = new BrowserWatchdogService((event) => this.publishEvent(event), {
|
|
10421
10567
|
dialogTimeoutMs: options.dialogTimeoutMs,
|
|
10422
10568
|
noProgressThreshold: options.noProgressThreshold
|
|
@@ -10433,7 +10579,7 @@ var BrowserToolService = class {
|
|
|
10433
10579
|
transport,
|
|
10434
10580
|
binding.instance.id,
|
|
10435
10581
|
() => ({
|
|
10436
|
-
profileIdentity: binding.instance.
|
|
10582
|
+
profileIdentity: binding.instance.userDataRoot,
|
|
10437
10583
|
connectionGeneration: binding.instance.connectionGeneration
|
|
10438
10584
|
}),
|
|
10439
10585
|
{
|
|
@@ -10524,6 +10670,7 @@ var BrowserToolService = class {
|
|
|
10524
10670
|
navigationTimeoutMs;
|
|
10525
10671
|
loadWaiter;
|
|
10526
10672
|
connectBrowser;
|
|
10673
|
+
readProfileIdentity;
|
|
10527
10674
|
eventPublisher;
|
|
10528
10675
|
setEventPublisher(publisher) {
|
|
10529
10676
|
this.eventPublisher = publisher;
|
|
@@ -10590,11 +10737,13 @@ var BrowserToolService = class {
|
|
|
10590
10737
|
const args = asRecord(argsValue);
|
|
10591
10738
|
switch (definition.name) {
|
|
10592
10739
|
case "browser.discover":
|
|
10593
|
-
return this.discover();
|
|
10740
|
+
return this.discover(context);
|
|
10594
10741
|
case "browser.connect":
|
|
10595
10742
|
return this.connect(context, args);
|
|
10596
10743
|
case "browser.profiles.list":
|
|
10597
10744
|
return this.listProfiles(context);
|
|
10745
|
+
case "browser.profiles.identify":
|
|
10746
|
+
return this.identifyProfiles(context, args);
|
|
10598
10747
|
case "browser.profiles.select":
|
|
10599
10748
|
return this.selectProfile(context, args);
|
|
10600
10749
|
case "browser.tabs.list":
|
|
@@ -10673,6 +10822,9 @@ var BrowserToolService = class {
|
|
|
10673
10822
|
}
|
|
10674
10823
|
actorKey(context, definition, argsValue) {
|
|
10675
10824
|
const browserId = context.browser.instance.id;
|
|
10825
|
+
if (definition.name === "browser.profiles.identify") {
|
|
10826
|
+
return `${browserId}\0profile-identify`;
|
|
10827
|
+
}
|
|
10676
10828
|
if (definition.name.startsWith("browser.dialogs.")) {
|
|
10677
10829
|
return `${browserId}\0dialogs\0${context.workspace?.id ?? context.connection.id}`;
|
|
10678
10830
|
}
|
|
@@ -10735,14 +10887,15 @@ var BrowserToolService = class {
|
|
|
10735
10887
|
}
|
|
10736
10888
|
this.deleteDialogs((dialog) => dialog.workspaceId === workspace.id);
|
|
10737
10889
|
}
|
|
10738
|
-
discover() {
|
|
10890
|
+
discover(context) {
|
|
10739
10891
|
const candidate = this.binding.candidate;
|
|
10892
|
+
const userDataRoot = candidate.userDataRoot ?? candidate.profile;
|
|
10740
10893
|
return asJson2({
|
|
10741
10894
|
browsers: [{
|
|
10742
10895
|
id: candidate.id,
|
|
10743
10896
|
product: candidate.product,
|
|
10744
10897
|
...candidate.channel !== void 0 ? { channel: candidate.channel } : {},
|
|
10745
|
-
...
|
|
10898
|
+
...userDataRoot ? context.connection.protocol.minor >= 3 ? { userDataRoot } : { profile: userDataRoot } : {},
|
|
10746
10899
|
processState: candidate.processState ?? (candidate.state === "ready" ? "running" : "unknown"),
|
|
10747
10900
|
remoteDebuggingState: candidate.remoteDebuggingState ?? (candidate.state === "ready" ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
|
|
10748
10901
|
authorizationState: candidate.authorizationState ?? (candidate.state === "ready" ? "authorized" : candidate.state === "authorization_required" ? "required" : "not_applicable"),
|
|
@@ -10765,7 +10918,10 @@ var BrowserToolService = class {
|
|
|
10765
10918
|
context: { workspaceId: workspace.id }
|
|
10766
10919
|
});
|
|
10767
10920
|
}
|
|
10921
|
+
this.markDispatched(context);
|
|
10768
10922
|
await this.connectBrowser();
|
|
10923
|
+
} else {
|
|
10924
|
+
this.markDispatched(context);
|
|
10769
10925
|
}
|
|
10770
10926
|
if (this.binding.instance.state !== "connected") {
|
|
10771
10927
|
throw new BrowserPilotError("browser_disconnected", "Browser connection did not become ready", {
|
|
@@ -10773,7 +10929,6 @@ var BrowserToolService = class {
|
|
|
10773
10929
|
context: { workspaceId: workspace.id }
|
|
10774
10930
|
});
|
|
10775
10931
|
}
|
|
10776
|
-
this.markDispatched(context);
|
|
10777
10932
|
return asJson2({
|
|
10778
10933
|
workspaceId: workspace.id,
|
|
10779
10934
|
leaseId: lease.id,
|
|
@@ -10789,7 +10944,7 @@ var BrowserToolService = class {
|
|
|
10789
10944
|
return asJson2({
|
|
10790
10945
|
workspaceId: inventoryContext.workspaceId,
|
|
10791
10946
|
leaseId: inventoryContext.leaseId,
|
|
10792
|
-
targets
|
|
10947
|
+
targets: context.connection.protocol.minor >= 3 ? targets.map(({ active, ...target }) => ({ ...target, selected: active })) : targets
|
|
10793
10948
|
});
|
|
10794
10949
|
}
|
|
10795
10950
|
async listProfiles(context) {
|
|
@@ -10799,21 +10954,67 @@ var BrowserToolService = class {
|
|
|
10799
10954
|
return asJson2({
|
|
10800
10955
|
workspaceId: inventoryContext.workspaceId,
|
|
10801
10956
|
leaseId: inventoryContext.leaseId,
|
|
10802
|
-
profiles: profiles.map((profile) => (
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10957
|
+
profiles: profiles.map((profile) => this.profileResult(context, profile, targets))
|
|
10958
|
+
});
|
|
10959
|
+
}
|
|
10960
|
+
async identifyProfiles(context, args) {
|
|
10961
|
+
const inventoryContext = this.inventoryContext(context);
|
|
10962
|
+
const targets = await this.inventory.list(inventoryContext, "all");
|
|
10963
|
+
const requestedId = args.profileContextId;
|
|
10964
|
+
const profiles = requestedId ? [this.profileContexts.resolve(requestedId, inventoryContext.browserConnectionGeneration)] : this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
10965
|
+
const refresh = args.refresh === true;
|
|
10966
|
+
for (const profile of profiles) {
|
|
10967
|
+
if (!refresh && profile.identityStatus !== "unidentified") continue;
|
|
10968
|
+
this.markDispatched(context);
|
|
10969
|
+
try {
|
|
10970
|
+
const identity = await this.probeProfileIdentity(profile);
|
|
10971
|
+
this.profileContexts.setVerifiedIdentity(
|
|
10972
|
+
profile.id,
|
|
10973
|
+
inventoryContext.browserConnectionGeneration,
|
|
10974
|
+
identity
|
|
10975
|
+
);
|
|
10976
|
+
} catch (error) {
|
|
10977
|
+
if (!(error instanceof ProfileIdentityError)) throw error;
|
|
10978
|
+
this.profileContexts.setIdentityUnavailable(
|
|
10979
|
+
profile.id,
|
|
10980
|
+
inventoryContext.browserConnectionGeneration,
|
|
10981
|
+
error.code
|
|
10982
|
+
);
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10985
|
+
const current = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
10986
|
+
return asJson2({
|
|
10987
|
+
workspaceId: inventoryContext.workspaceId,
|
|
10988
|
+
leaseId: inventoryContext.leaseId,
|
|
10989
|
+
profiles: current.filter((profile) => !requestedId || profile.id === requestedId).map((profile) => this.profileResult(context, profile, targets))
|
|
10815
10990
|
});
|
|
10816
10991
|
}
|
|
10992
|
+
profileResult(context, profile, targets) {
|
|
10993
|
+
const identity = {};
|
|
10994
|
+
if (context.connection.protocol.minor >= 3) {
|
|
10995
|
+
identity.identityStatus = profile.identityStatus;
|
|
10996
|
+
if (profile.profileName) identity.profileName = profile.profileName;
|
|
10997
|
+
if (profile.accountName) identity.accountName = profile.accountName;
|
|
10998
|
+
if (profile.accountEmail) identity.accountEmail = profile.accountEmail;
|
|
10999
|
+
if (profile.profileDirectory) identity.profileDirectory = profile.profileDirectory;
|
|
11000
|
+
if (profile.identityErrorCode) identity.identityErrorCode = profile.identityErrorCode;
|
|
11001
|
+
} else if (profile.displayName) {
|
|
11002
|
+
identity.displayName = profile.displayName;
|
|
11003
|
+
}
|
|
11004
|
+
return {
|
|
11005
|
+
profileContextId: profile.id,
|
|
11006
|
+
label: profile.label,
|
|
11007
|
+
...identity,
|
|
11008
|
+
tabCount: profile.tabCount,
|
|
11009
|
+
eligibleTabCount: profile.eligibleTabCount,
|
|
11010
|
+
selected: context.workspace?.selectedProfileContextId === profile.id,
|
|
11011
|
+
representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
|
|
11012
|
+
targetId: target.targetId,
|
|
11013
|
+
title: target.title,
|
|
11014
|
+
url: target.url
|
|
11015
|
+
}))
|
|
11016
|
+
};
|
|
11017
|
+
}
|
|
10817
11018
|
async selectProfile(context, args) {
|
|
10818
11019
|
const inventoryContext = this.inventoryContext(context);
|
|
10819
11020
|
await this.inventory.refresh(inventoryContext);
|
|
@@ -10829,7 +11030,14 @@ var BrowserToolService = class {
|
|
|
10829
11030
|
leaseId: inventoryContext.leaseId,
|
|
10830
11031
|
profileContextId: profile.id,
|
|
10831
11032
|
label: profile.label,
|
|
10832
|
-
...
|
|
11033
|
+
...context.connection.protocol.minor >= 3 ? {
|
|
11034
|
+
identityStatus: profile.identityStatus,
|
|
11035
|
+
...profile.profileName ? { profileName: profile.profileName } : {},
|
|
11036
|
+
...profile.accountName ? { accountName: profile.accountName } : {},
|
|
11037
|
+
...profile.accountEmail ? { accountEmail: profile.accountEmail } : {},
|
|
11038
|
+
...profile.profileDirectory ? { profileDirectory: profile.profileDirectory } : {},
|
|
11039
|
+
...profile.identityErrorCode ? { identityErrorCode: profile.identityErrorCode } : {}
|
|
11040
|
+
} : profile.displayName ? { displayName: profile.displayName } : {}
|
|
10833
11041
|
});
|
|
10834
11042
|
}
|
|
10835
11043
|
async switchTab(context, args) {
|
|
@@ -10838,10 +11046,10 @@ var BrowserToolService = class {
|
|
|
10838
11046
|
await this.inventory.refresh(inventoryContext);
|
|
10839
11047
|
this.markDispatched(context);
|
|
10840
11048
|
const resolved = await this.inventory.activate(inventoryContext, targetId);
|
|
10841
|
-
const
|
|
11049
|
+
const record2 = this.registry.get(inventoryContext, targetId);
|
|
10842
11050
|
await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
|
|
10843
|
-
this.selectWorkspaceProfile(context,
|
|
10844
|
-
return asJson2(this.targetResult(context, targetId,
|
|
11051
|
+
this.selectWorkspaceProfile(context, record2.profileContextId);
|
|
11052
|
+
return asJson2(this.targetResult(context, targetId, record2.url));
|
|
10845
11053
|
}
|
|
10846
11054
|
async closeTab(context) {
|
|
10847
11055
|
const inventoryContext = this.inventoryContext(context);
|
|
@@ -11079,8 +11287,8 @@ var BrowserToolService = class {
|
|
|
11079
11287
|
...session.activeFrame ? { frameId: session.activeFrame.cdpFrameId } : {}
|
|
11080
11288
|
}
|
|
11081
11289
|
).locate(args.selector);
|
|
11082
|
-
const
|
|
11083
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11290
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11291
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), ...location });
|
|
11084
11292
|
}
|
|
11085
11293
|
async read(context, args) {
|
|
11086
11294
|
const targetId = this.requireTargetId(context);
|
|
@@ -11210,8 +11418,8 @@ var BrowserToolService = class {
|
|
|
11210
11418
|
address,
|
|
11211
11419
|
(objectId) => service.inspectObject(objectId)
|
|
11212
11420
|
);
|
|
11213
|
-
const
|
|
11214
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11421
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11422
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), ...result });
|
|
11215
11423
|
}
|
|
11216
11424
|
async selectDropdown(context, args) {
|
|
11217
11425
|
const targetId = this.requireTargetId(context);
|
|
@@ -11457,7 +11665,7 @@ var BrowserToolService = class {
|
|
|
11457
11665
|
);
|
|
11458
11666
|
}
|
|
11459
11667
|
const scale = this.previewScale(media.width, media.height);
|
|
11460
|
-
const
|
|
11668
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11461
11669
|
if (scale === void 0) {
|
|
11462
11670
|
const artifact2 = await this.createArtifact(context, {
|
|
11463
11671
|
kind: "screenshot",
|
|
@@ -11467,7 +11675,7 @@ var BrowserToolService = class {
|
|
|
11467
11675
|
...media.height !== void 0 ? { height: media.height } : {}
|
|
11468
11676
|
});
|
|
11469
11677
|
return asJson2({
|
|
11470
|
-
...this.targetResult(context, targetId,
|
|
11678
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11471
11679
|
artifact: artifact2,
|
|
11472
11680
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
11473
11681
|
});
|
|
@@ -11489,7 +11697,7 @@ var BrowserToolService = class {
|
|
|
11489
11697
|
...previewMedia.height !== void 0 ? { height: previewMedia.height } : {}
|
|
11490
11698
|
});
|
|
11491
11699
|
return asJson2({
|
|
11492
|
-
...this.targetResult(context, targetId,
|
|
11700
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11493
11701
|
artifact: artifact2,
|
|
11494
11702
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
11495
11703
|
});
|
|
@@ -11511,7 +11719,7 @@ var BrowserToolService = class {
|
|
|
11511
11719
|
previewOf: artifact.id
|
|
11512
11720
|
});
|
|
11513
11721
|
return asJson2({
|
|
11514
|
-
...this.targetResult(context, targetId,
|
|
11722
|
+
...this.targetResult(context, targetId, record2.url),
|
|
11515
11723
|
artifact,
|
|
11516
11724
|
preview,
|
|
11517
11725
|
...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
|
|
@@ -11532,15 +11740,15 @@ var BrowserToolService = class {
|
|
|
11532
11740
|
mimeType: media.mimeType,
|
|
11533
11741
|
bytes: media.bytes
|
|
11534
11742
|
});
|
|
11535
|
-
const
|
|
11536
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11743
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11744
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), artifact });
|
|
11537
11745
|
}
|
|
11538
11746
|
async cookies(context, args) {
|
|
11539
11747
|
const targetId = this.requireTargetId(context);
|
|
11540
11748
|
const session = await this.resolveTargetSession(context, targetId, "cookies.read");
|
|
11541
11749
|
const cookies = await new CookieService(this.transport, session.sessionId).list(args.domain);
|
|
11542
|
-
const
|
|
11543
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11750
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11751
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), cookies });
|
|
11544
11752
|
}
|
|
11545
11753
|
async setAuth(context, args) {
|
|
11546
11754
|
const { workspace, lease } = this.requireWorkspaceContext(context);
|
|
@@ -11620,7 +11828,7 @@ var BrowserToolService = class {
|
|
|
11620
11828
|
"expression"
|
|
11621
11829
|
);
|
|
11622
11830
|
}
|
|
11623
|
-
const
|
|
11831
|
+
const record2 = this.registry.get(this.inventoryContext(context), targetId);
|
|
11624
11832
|
let value = result?.value ?? result?.unserializableValue ?? null;
|
|
11625
11833
|
let truncated = false;
|
|
11626
11834
|
const serialized = JSON.stringify(value);
|
|
@@ -11628,7 +11836,7 @@ var BrowserToolService = class {
|
|
|
11628
11836
|
value = serialized.slice(0, 1e6);
|
|
11629
11837
|
truncated = true;
|
|
11630
11838
|
}
|
|
11631
|
-
return asJson2({ ...this.targetResult(context, targetId,
|
|
11839
|
+
return asJson2({ ...this.targetResult(context, targetId, record2.url), value, truncated });
|
|
11632
11840
|
}
|
|
11633
11841
|
listDialogs(context) {
|
|
11634
11842
|
const { workspace, lease } = this.requireWorkspaceContext(context);
|
|
@@ -11736,6 +11944,89 @@ var BrowserToolService = class {
|
|
|
11736
11944
|
throw error;
|
|
11737
11945
|
}
|
|
11738
11946
|
}
|
|
11947
|
+
async probeProfileIdentity(profile) {
|
|
11948
|
+
let targetId;
|
|
11949
|
+
let sessionId;
|
|
11950
|
+
let failure;
|
|
11951
|
+
try {
|
|
11952
|
+
try {
|
|
11953
|
+
targetId = await this.tryCreateManagedTarget({
|
|
11954
|
+
url: "about:blank",
|
|
11955
|
+
newWindow: true,
|
|
11956
|
+
...profile.cdpBrowserContextId ? { browserContextId: profile.cdpBrowserContextId } : {}
|
|
11957
|
+
}, profile);
|
|
11958
|
+
} catch (error) {
|
|
11959
|
+
if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
|
|
11960
|
+
}
|
|
11961
|
+
if (!targetId) targetId = await this.createManagedProfileWindow(profile);
|
|
11962
|
+
const attached = await this.transport.send("Target.attachToTarget", {
|
|
11963
|
+
targetId,
|
|
11964
|
+
flatten: true
|
|
11965
|
+
});
|
|
11966
|
+
if (typeof attached?.sessionId !== "string") {
|
|
11967
|
+
throw new ProfileIdentityError(
|
|
11968
|
+
"profile_path_unavailable",
|
|
11969
|
+
"Chrome could not attach to the temporary Profile identity target"
|
|
11970
|
+
);
|
|
11971
|
+
}
|
|
11972
|
+
const activeSessionId = attached.sessionId;
|
|
11973
|
+
sessionId = activeSessionId;
|
|
11974
|
+
await this.transport.send("Page.enable", {}, activeSessionId);
|
|
11975
|
+
const navigation = await this.transport.send("Page.navigate", {
|
|
11976
|
+
url: "chrome://version/"
|
|
11977
|
+
}, activeSessionId);
|
|
11978
|
+
if (navigation?.errorText) {
|
|
11979
|
+
throw new ProfileIdentityError(
|
|
11980
|
+
"profile_path_unavailable",
|
|
11981
|
+
"Chrome could not open its version page for Profile identification"
|
|
11982
|
+
);
|
|
11983
|
+
}
|
|
11984
|
+
await this.loadWaiter(this.transport, activeSessionId, this.navigationTimeoutMs);
|
|
11985
|
+
const evaluated = await this.transport.send("Runtime.evaluate", {
|
|
11986
|
+
expression: `(() => {
|
|
11987
|
+
/* browser-pilot.profile-path.v1 */
|
|
11988
|
+
const value = document.querySelector('#profile_path')?.textContent;
|
|
11989
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
11990
|
+
})()`,
|
|
11991
|
+
returnByValue: true
|
|
11992
|
+
}, activeSessionId);
|
|
11993
|
+
const reportedProfilePath = evaluated?.result?.value;
|
|
11994
|
+
if (typeof reportedProfilePath !== "string" || !reportedProfilePath.trim()) {
|
|
11995
|
+
throw new ProfileIdentityError(
|
|
11996
|
+
"profile_path_unavailable",
|
|
11997
|
+
"Chrome did not expose a Profile path on its version page"
|
|
11998
|
+
);
|
|
11999
|
+
}
|
|
12000
|
+
return await this.readProfileIdentity(
|
|
12001
|
+
this.binding.instance.userDataRoot,
|
|
12002
|
+
reportedProfilePath
|
|
12003
|
+
);
|
|
12004
|
+
} catch (error) {
|
|
12005
|
+
failure = error;
|
|
12006
|
+
throw error;
|
|
12007
|
+
} finally {
|
|
12008
|
+
if (sessionId) {
|
|
12009
|
+
await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
|
|
12010
|
+
});
|
|
12011
|
+
}
|
|
12012
|
+
if (targetId && !await this.closeTargetAndVerify(targetId)) {
|
|
12013
|
+
throw new BrowserPilotError(
|
|
12014
|
+
"unknown_outcome",
|
|
12015
|
+
"Temporary Profile identity target could not be safely closed",
|
|
12016
|
+
{
|
|
12017
|
+
retryable: true,
|
|
12018
|
+
context: { profileContextId: profile.id },
|
|
12019
|
+
remediation: {
|
|
12020
|
+
code: "inspect_profile_tabs",
|
|
12021
|
+
message: "List tabs and close any leftover chrome://version window before retrying.",
|
|
12022
|
+
actionRequired: true
|
|
12023
|
+
},
|
|
12024
|
+
...failure !== void 0 ? { cause: failure } : {}
|
|
12025
|
+
}
|
|
12026
|
+
);
|
|
12027
|
+
}
|
|
12028
|
+
}
|
|
12029
|
+
}
|
|
11739
12030
|
async tryCreateManagedTarget(params, profile) {
|
|
11740
12031
|
let targetId;
|
|
11741
12032
|
try {
|
|
@@ -11770,7 +12061,7 @@ var BrowserToolService = class {
|
|
|
11770
12061
|
if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
|
|
11771
12062
|
} catch {
|
|
11772
12063
|
}
|
|
11773
|
-
if (attempt < 19) await new Promise((
|
|
12064
|
+
if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11774
12065
|
}
|
|
11775
12066
|
return false;
|
|
11776
12067
|
}
|
|
@@ -11918,7 +12209,7 @@ var BrowserToolService = class {
|
|
|
11918
12209
|
candidateTargetId = candidate.targetId;
|
|
11919
12210
|
break;
|
|
11920
12211
|
}
|
|
11921
|
-
await new Promise((
|
|
12212
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11922
12213
|
}
|
|
11923
12214
|
if (!candidateTargetId) {
|
|
11924
12215
|
throw this.managedTargetUnknownOutcome(profile);
|
|
@@ -11957,7 +12248,7 @@ var BrowserToolService = class {
|
|
|
11957
12248
|
if (result?.result?.value !== void 0) return false;
|
|
11958
12249
|
} catch {
|
|
11959
12250
|
}
|
|
11960
|
-
if (attempt < 9) await new Promise((
|
|
12251
|
+
if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
11961
12252
|
}
|
|
11962
12253
|
return false;
|
|
11963
12254
|
} catch {
|
|
@@ -12022,8 +12313,6 @@ var BrowserToolService = class {
|
|
|
12022
12313
|
await this.transport.send("Page.enable", {}, attached.sessionId).catch(() => {
|
|
12023
12314
|
});
|
|
12024
12315
|
await this.downloads?.attachSession(session);
|
|
12025
|
-
await this.transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, attached.sessionId).catch(() => {
|
|
12026
|
-
});
|
|
12027
12316
|
return session;
|
|
12028
12317
|
} catch (error) {
|
|
12029
12318
|
this.retireSession(key, session);
|
|
@@ -12040,7 +12329,7 @@ var BrowserToolService = class {
|
|
|
12040
12329
|
});
|
|
12041
12330
|
const snapshot = afterAction ? await service.observeAfterAction(limit) : await service.observe(limit);
|
|
12042
12331
|
const identity = await this.observationContextIdentity(session);
|
|
12043
|
-
const
|
|
12332
|
+
const record2 = this.observations.create({
|
|
12044
12333
|
workspaceId: context.workspace.id,
|
|
12045
12334
|
leaseId: context.lease.id,
|
|
12046
12335
|
targetId,
|
|
@@ -12063,7 +12352,7 @@ var BrowserToolService = class {
|
|
|
12063
12352
|
previous && previous.frameId === frameId ? previous.guidance : void 0
|
|
12064
12353
|
);
|
|
12065
12354
|
this.guidanceBySession.set(session.sessionId, { frameId, guidance: snapshot.guidance });
|
|
12066
|
-
return { snapshot, record, hints };
|
|
12355
|
+
return { snapshot, record: record2, hints };
|
|
12067
12356
|
}
|
|
12068
12357
|
async resolveObservation(context, targetId, session, observationId, ref) {
|
|
12069
12358
|
const cdpSessionId = this.activeCdpSessionId(session);
|
|
@@ -12559,12 +12848,12 @@ var BrowserToolService = class {
|
|
|
12559
12848
|
if (!this.artifactStore || !context.workspace) {
|
|
12560
12849
|
throw new BrowserPilotError("internal_error", "Browser Artifact storage is unavailable");
|
|
12561
12850
|
}
|
|
12562
|
-
const
|
|
12851
|
+
const record2 = await this.artifactStore.create({
|
|
12563
12852
|
workspaceId: context.workspace.id,
|
|
12564
12853
|
sensitivity: "browser_data",
|
|
12565
12854
|
...input
|
|
12566
12855
|
});
|
|
12567
|
-
return
|
|
12856
|
+
return record2.descriptor;
|
|
12568
12857
|
}
|
|
12569
12858
|
previewScale(width, height) {
|
|
12570
12859
|
if (!width || !height) return void 0;
|
|
@@ -13208,10 +13497,10 @@ import {
|
|
|
13208
13497
|
writeFile
|
|
13209
13498
|
} from "fs/promises";
|
|
13210
13499
|
import { constants as fsConstants } from "fs";
|
|
13211
|
-
import { basename as
|
|
13500
|
+
import { basename as basename4, dirname as dirname3, extname, isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
13212
13501
|
var ARTIFACT_ID_PATTERN2 = /^artifact:[A-Za-z0-9][A-Za-z0-9._:-]{0,118}$/;
|
|
13213
|
-
function clone2(
|
|
13214
|
-
return { descriptor: { ...
|
|
13502
|
+
function clone2(record2) {
|
|
13503
|
+
return { descriptor: { ...record2.descriptor }, path: record2.path };
|
|
13215
13504
|
}
|
|
13216
13505
|
function inferredMimeType(path) {
|
|
13217
13506
|
const known = {
|
|
@@ -13246,7 +13535,7 @@ var ArtifactStore = class {
|
|
|
13246
13535
|
initialized = false;
|
|
13247
13536
|
mutationTail = Promise.resolve();
|
|
13248
13537
|
constructor(options = {}) {
|
|
13249
|
-
this.directory =
|
|
13538
|
+
this.directory = resolve2(options.directory ?? ARTIFACT_DIR);
|
|
13250
13539
|
this.ttlMs = options.ttlMs ?? 15 * 6e4;
|
|
13251
13540
|
this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
|
|
13252
13541
|
this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
|
|
@@ -13302,17 +13591,24 @@ var ArtifactStore = class {
|
|
|
13302
13591
|
async get(workspaceId, artifactId) {
|
|
13303
13592
|
return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
|
|
13304
13593
|
}
|
|
13594
|
+
async list(workspaceId, kinds) {
|
|
13595
|
+
return this.withLock(async () => {
|
|
13596
|
+
await this.sweepUnlocked();
|
|
13597
|
+
const selectedKinds = kinds ? new Set(kinds) : void 0;
|
|
13598
|
+
return [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId && (!selectedKinds || selectedKinds.has(record2.descriptor.kind))).sort((left, right) => right.descriptor.createdAt - left.descriptor.createdAt).map((record2) => ({ ...record2.descriptor }));
|
|
13599
|
+
});
|
|
13600
|
+
}
|
|
13305
13601
|
async export(workspaceId, artifactId, destination, overwrite = false) {
|
|
13306
13602
|
if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
|
|
13307
|
-
const target =
|
|
13603
|
+
const target = resolve2(destination);
|
|
13308
13604
|
if (this.isInsideStore(target)) {
|
|
13309
13605
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13310
13606
|
}
|
|
13311
13607
|
return this.withLock(async () => {
|
|
13312
|
-
const
|
|
13608
|
+
const record2 = await this.requireRecordUnlocked(workspaceId, artifactId);
|
|
13313
13609
|
let canonicalParent;
|
|
13314
13610
|
try {
|
|
13315
|
-
canonicalParent = await realpath(
|
|
13611
|
+
canonicalParent = await realpath(dirname3(target));
|
|
13316
13612
|
} catch (cause) {
|
|
13317
13613
|
throw new BrowserPilotError("invalid_argument", "Artifact export directory is not accessible", {
|
|
13318
13614
|
context: { field: "path" },
|
|
@@ -13321,7 +13617,7 @@ var ArtifactStore = class {
|
|
|
13321
13617
|
});
|
|
13322
13618
|
}
|
|
13323
13619
|
const canonicalStore = await realpath(this.directory);
|
|
13324
|
-
if (this.isWithin(canonicalStore,
|
|
13620
|
+
if (this.isWithin(canonicalStore, join5(canonicalParent, basename4(target)))) {
|
|
13325
13621
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13326
13622
|
}
|
|
13327
13623
|
if (overwrite) {
|
|
@@ -13331,7 +13627,7 @@ var ArtifactStore = class {
|
|
|
13331
13627
|
}
|
|
13332
13628
|
}
|
|
13333
13629
|
await copyFile(
|
|
13334
|
-
|
|
13630
|
+
record2.path,
|
|
13335
13631
|
target,
|
|
13336
13632
|
overwrite ? 0 : fsConstants.COPYFILE_EXCL
|
|
13337
13633
|
).catch((cause) => {
|
|
@@ -13343,7 +13639,7 @@ var ArtifactStore = class {
|
|
|
13343
13639
|
});
|
|
13344
13640
|
await chmod(target, 384).catch(() => {
|
|
13345
13641
|
});
|
|
13346
|
-
return { artifact: { ...
|
|
13642
|
+
return { artifact: { ...record2.descriptor }, path: target };
|
|
13347
13643
|
});
|
|
13348
13644
|
}
|
|
13349
13645
|
async retain(workspaceId, artifactId) {
|
|
@@ -13357,21 +13653,21 @@ var ArtifactStore = class {
|
|
|
13357
13653
|
}
|
|
13358
13654
|
async release(workspaceId, artifactId) {
|
|
13359
13655
|
await this.withLock(async () => {
|
|
13360
|
-
const
|
|
13361
|
-
if (!
|
|
13656
|
+
const record2 = this.records.get(artifactId);
|
|
13657
|
+
if (!record2 || record2.descriptor.workspaceId !== workspaceId) return;
|
|
13362
13658
|
this.records.delete(artifactId);
|
|
13363
13659
|
this.expired.delete(artifactId);
|
|
13364
|
-
await this.removeStoredFile(
|
|
13660
|
+
await this.removeStoredFile(record2.path);
|
|
13365
13661
|
});
|
|
13366
13662
|
}
|
|
13367
13663
|
async releaseWorkspace(workspaceId) {
|
|
13368
13664
|
await this.withLock(async () => {
|
|
13369
|
-
const records = [...this.records.values()].filter((
|
|
13370
|
-
for (const
|
|
13665
|
+
const records = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId);
|
|
13666
|
+
for (const record2 of records) this.records.delete(record2.descriptor.id);
|
|
13371
13667
|
for (const [artifactId, tombstone] of this.expired) {
|
|
13372
13668
|
if (tombstone.workspaceId === workspaceId) this.expired.delete(artifactId);
|
|
13373
13669
|
}
|
|
13374
|
-
await Promise.all(records.map((
|
|
13670
|
+
await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
|
|
13375
13671
|
});
|
|
13376
13672
|
}
|
|
13377
13673
|
async sweep() {
|
|
@@ -13382,7 +13678,7 @@ var ArtifactStore = class {
|
|
|
13382
13678
|
const records = [...this.records.values()];
|
|
13383
13679
|
this.records.clear();
|
|
13384
13680
|
this.expired.clear();
|
|
13385
|
-
await Promise.all(records.map((
|
|
13681
|
+
await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
|
|
13386
13682
|
});
|
|
13387
13683
|
}
|
|
13388
13684
|
size() {
|
|
@@ -13399,7 +13695,7 @@ var ArtifactStore = class {
|
|
|
13399
13695
|
}
|
|
13400
13696
|
async ingestFile(input) {
|
|
13401
13697
|
if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
|
|
13402
|
-
const source =
|
|
13698
|
+
const source = resolve2(input.sourcePath);
|
|
13403
13699
|
if (this.isInsideStore(source)) {
|
|
13404
13700
|
throw invalidArgument("Artifact import path must be outside Broker storage", "path");
|
|
13405
13701
|
}
|
|
@@ -13447,8 +13743,8 @@ var ArtifactStore = class {
|
|
|
13447
13743
|
}
|
|
13448
13744
|
if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
|
|
13449
13745
|
this.assertQuota(input.workspaceId, sourceInfo.size);
|
|
13450
|
-
const fileName = input.fileName ??
|
|
13451
|
-
if (!fileName || fileName === "." || fileName === ".." ||
|
|
13746
|
+
const fileName = input.fileName ?? basename4(source);
|
|
13747
|
+
if (!fileName || fileName === "." || fileName === ".." || basename4(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
|
|
13452
13748
|
throw invalidArgument("Artifact import filename is invalid or too long", "path");
|
|
13453
13749
|
}
|
|
13454
13750
|
const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
|
|
@@ -13456,8 +13752,8 @@ var ArtifactStore = class {
|
|
|
13456
13752
|
throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
|
|
13457
13753
|
}
|
|
13458
13754
|
const id = this.nextArtifactId();
|
|
13459
|
-
const storageDirectory =
|
|
13460
|
-
const destination =
|
|
13755
|
+
const storageDirectory = join5(this.directory, randomUUID12());
|
|
13756
|
+
const destination = join5(storageDirectory, fileName);
|
|
13461
13757
|
await mkdir(storageDirectory, { mode: 448 });
|
|
13462
13758
|
try {
|
|
13463
13759
|
await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
|
|
@@ -13483,9 +13779,9 @@ var ArtifactStore = class {
|
|
|
13483
13779
|
expiresAt: createdAt + this.ttlMs,
|
|
13484
13780
|
retained: false
|
|
13485
13781
|
};
|
|
13486
|
-
const
|
|
13487
|
-
this.records.set(id,
|
|
13488
|
-
return clone2(
|
|
13782
|
+
const record2 = { descriptor: descriptor2, path: destination };
|
|
13783
|
+
this.records.set(id, record2);
|
|
13784
|
+
return clone2(record2);
|
|
13489
13785
|
} catch (error) {
|
|
13490
13786
|
await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
|
|
13491
13787
|
});
|
|
@@ -13499,7 +13795,7 @@ var ArtifactStore = class {
|
|
|
13499
13795
|
this.assertQuota(input.workspaceId, byteSize);
|
|
13500
13796
|
await this.ensureDirectory();
|
|
13501
13797
|
const id = this.nextArtifactId();
|
|
13502
|
-
const file =
|
|
13798
|
+
const file = join5(this.directory, `${randomUUID12()}.bin`);
|
|
13503
13799
|
const createdAt = this.now();
|
|
13504
13800
|
await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
|
|
13505
13801
|
await chmod(file, 384).catch(() => {
|
|
@@ -13519,39 +13815,39 @@ var ArtifactStore = class {
|
|
|
13519
13815
|
retained: false,
|
|
13520
13816
|
...input.previewOf ? { previewOf: input.previewOf } : {}
|
|
13521
13817
|
};
|
|
13522
|
-
const
|
|
13523
|
-
this.records.set(id,
|
|
13524
|
-
return clone2(
|
|
13818
|
+
const record2 = { descriptor: descriptor2, path: file };
|
|
13819
|
+
this.records.set(id, record2);
|
|
13820
|
+
return clone2(record2);
|
|
13525
13821
|
}
|
|
13526
13822
|
async requireRecordUnlocked(workspaceId, artifactId) {
|
|
13527
13823
|
await this.sweepUnlocked();
|
|
13528
|
-
const
|
|
13529
|
-
if (!
|
|
13824
|
+
const record2 = this.records.get(artifactId);
|
|
13825
|
+
if (!record2 || record2.descriptor.workspaceId !== workspaceId) {
|
|
13530
13826
|
const tombstone = this.expired.get(artifactId);
|
|
13531
13827
|
if (tombstone?.workspaceId === workspaceId) throw this.artifactExpired(artifactId);
|
|
13532
13828
|
throw this.notFound(artifactId);
|
|
13533
13829
|
}
|
|
13534
13830
|
try {
|
|
13535
|
-
const info = await stat(
|
|
13536
|
-
if (!info.isFile() || info.size !==
|
|
13831
|
+
const info = await stat(record2.path);
|
|
13832
|
+
if (!info.isFile() || info.size !== record2.descriptor.byteSize) throw new Error("Artifact file mismatch");
|
|
13537
13833
|
} catch {
|
|
13538
13834
|
this.records.delete(artifactId);
|
|
13539
13835
|
throw this.notFound(artifactId);
|
|
13540
13836
|
}
|
|
13541
|
-
return
|
|
13837
|
+
return record2;
|
|
13542
13838
|
}
|
|
13543
13839
|
async sweepUnlocked() {
|
|
13544
13840
|
const now = this.now();
|
|
13545
|
-
const expiredRecords = [...this.records.values()].filter((
|
|
13546
|
-
for (const
|
|
13547
|
-
this.records.delete(
|
|
13548
|
-
this.expired.set(
|
|
13549
|
-
workspaceId:
|
|
13841
|
+
const expiredRecords = [...this.records.values()].filter((record2) => record2.descriptor.expiresAt <= now);
|
|
13842
|
+
for (const record2 of expiredRecords) {
|
|
13843
|
+
this.records.delete(record2.descriptor.id);
|
|
13844
|
+
this.expired.set(record2.descriptor.id, {
|
|
13845
|
+
workspaceId: record2.descriptor.workspaceId,
|
|
13550
13846
|
expiredAt: now
|
|
13551
13847
|
});
|
|
13552
13848
|
}
|
|
13553
13849
|
this.pruneExpiredTombstones(now);
|
|
13554
|
-
await Promise.all(expiredRecords.map((
|
|
13850
|
+
await Promise.all(expiredRecords.map((record2) => this.removeStoredFile(record2.path)));
|
|
13555
13851
|
return expiredRecords.length;
|
|
13556
13852
|
}
|
|
13557
13853
|
async ensureDirectory() {
|
|
@@ -13559,7 +13855,7 @@ var ArtifactStore = class {
|
|
|
13559
13855
|
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
13560
13856
|
if (this.purgeOrphansOnInitialize) {
|
|
13561
13857
|
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(
|
|
13858
|
+
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
13859
|
})));
|
|
13564
13860
|
}
|
|
13565
13861
|
this.initialized = true;
|
|
@@ -13598,8 +13894,8 @@ var ArtifactStore = class {
|
|
|
13598
13894
|
context: { maxArtifactBytes: this.maxArtifactBytes, byteSize }
|
|
13599
13895
|
});
|
|
13600
13896
|
}
|
|
13601
|
-
const workspaceBytes = [...this.records.values()].filter((
|
|
13602
|
-
const totalBytes = [...this.records.values()].reduce((sum,
|
|
13897
|
+
const workspaceBytes = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId).reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
|
|
13898
|
+
const totalBytes = [...this.records.values()].reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
|
|
13603
13899
|
if (workspaceBytes + byteSize > this.maxWorkspaceBytes || totalBytes + byteSize > this.maxTotalBytes) {
|
|
13604
13900
|
throw new BrowserPilotError("result_too_large", "Artifact Store quota exceeded", {
|
|
13605
13901
|
context: {
|
|
@@ -13613,7 +13909,7 @@ var ArtifactStore = class {
|
|
|
13613
13909
|
async removeStoredFile(path) {
|
|
13614
13910
|
await unlink(path).catch(() => {
|
|
13615
13911
|
});
|
|
13616
|
-
const parent =
|
|
13912
|
+
const parent = dirname3(path);
|
|
13617
13913
|
if (parent !== this.directory && this.isInsideStore(parent)) {
|
|
13618
13914
|
await rm(parent, { recursive: true, force: true }).catch(() => {
|
|
13619
13915
|
});
|
|
@@ -13827,8 +14123,8 @@ var ManagedTargetJanitorClient = class {
|
|
|
13827
14123
|
});
|
|
13828
14124
|
this.worker = worker;
|
|
13829
14125
|
this.ready = false;
|
|
13830
|
-
const ready = new Promise((
|
|
13831
|
-
this.readyResolve =
|
|
14126
|
+
const ready = new Promise((resolve3, reject) => {
|
|
14127
|
+
this.readyResolve = resolve3;
|
|
13832
14128
|
this.readyReject = reject;
|
|
13833
14129
|
});
|
|
13834
14130
|
worker.on("message", (value) => this.handleWorkerMessage(worker, value));
|
|
@@ -13933,7 +14229,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13933
14229
|
}
|
|
13934
14230
|
const id = this.nextRequestId++;
|
|
13935
14231
|
const request = { id, method, params };
|
|
13936
|
-
return new Promise((
|
|
14232
|
+
return new Promise((resolve3, reject) => {
|
|
13937
14233
|
const timer = setTimeout(() => {
|
|
13938
14234
|
this.pendingRequests.delete(id);
|
|
13939
14235
|
reject(method === "create" ? new BrowserPilotError(
|
|
@@ -13943,7 +14239,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13943
14239
|
) : new Error(`Managed browser connection timeout: ${method}`));
|
|
13944
14240
|
}, REQUEST_TIMEOUT_MS);
|
|
13945
14241
|
timer.unref();
|
|
13946
|
-
this.pendingRequests.set(id, { resolve:
|
|
14242
|
+
this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
|
|
13947
14243
|
worker.send?.(request, (error) => {
|
|
13948
14244
|
if (!error) return;
|
|
13949
14245
|
const pending = this.pendingRequests.get(id);
|
|
@@ -13973,19 +14269,19 @@ var ManagedTargetJanitorClient = class {
|
|
|
13973
14269
|
this.expectedExits.add(worker);
|
|
13974
14270
|
if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
|
|
13975
14271
|
else worker.kill("SIGTERM");
|
|
13976
|
-
await new Promise((
|
|
14272
|
+
await new Promise((resolve3) => {
|
|
13977
14273
|
if (worker.exitCode !== null || worker.signalCode !== null) {
|
|
13978
|
-
|
|
14274
|
+
resolve3();
|
|
13979
14275
|
return;
|
|
13980
14276
|
}
|
|
13981
14277
|
const timer = setTimeout(() => {
|
|
13982
14278
|
worker.kill("SIGKILL");
|
|
13983
|
-
|
|
14279
|
+
resolve3();
|
|
13984
14280
|
}, STOP_TIMEOUT_MS);
|
|
13985
14281
|
timer.unref();
|
|
13986
14282
|
worker.once("exit", () => {
|
|
13987
14283
|
clearTimeout(timer);
|
|
13988
|
-
|
|
14284
|
+
resolve3();
|
|
13989
14285
|
});
|
|
13990
14286
|
});
|
|
13991
14287
|
if (this.worker === worker) this.failWorker(worker, this.unavailableError());
|
|
@@ -14015,7 +14311,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
14015
14311
|
};
|
|
14016
14312
|
|
|
14017
14313
|
// src/version.ts
|
|
14018
|
-
var BROWSER_PILOT_VERSION = "0.
|
|
14314
|
+
var BROWSER_PILOT_VERSION = "0.5.0";
|
|
14019
14315
|
|
|
14020
14316
|
// src/daemon.ts
|
|
14021
14317
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -14029,7 +14325,7 @@ var browserProfile = process.argv[4] || "";
|
|
|
14029
14325
|
ensureBrokerDirectoriesSync();
|
|
14030
14326
|
for (const legacyFile of ["state.json", "refs.json"]) {
|
|
14031
14327
|
try {
|
|
14032
|
-
unlinkSync2(
|
|
14328
|
+
unlinkSync2(join6(STATE_DIR, legacyFile));
|
|
14033
14329
|
} catch {
|
|
14034
14330
|
}
|
|
14035
14331
|
}
|
|
@@ -14049,7 +14345,7 @@ function cleanup(brokerProcessIdentity) {
|
|
|
14049
14345
|
removeBrokerLocatorSync(brokerProcessIdentity);
|
|
14050
14346
|
}
|
|
14051
14347
|
function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
14052
|
-
return new Promise((
|
|
14348
|
+
return new Promise((resolve3, reject) => {
|
|
14053
14349
|
const chunks = [];
|
|
14054
14350
|
let byteLength = 0;
|
|
14055
14351
|
req.on("data", (value) => {
|
|
@@ -14062,7 +14358,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
|
14062
14358
|
}
|
|
14063
14359
|
chunks.push(chunk);
|
|
14064
14360
|
});
|
|
14065
|
-
req.on("end", () =>
|
|
14361
|
+
req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
14066
14362
|
req.on("error", reject);
|
|
14067
14363
|
});
|
|
14068
14364
|
}
|
|
@@ -14161,7 +14457,7 @@ async function main() {
|
|
|
14161
14457
|
candidate: {
|
|
14162
14458
|
id: customBrowserId,
|
|
14163
14459
|
product: browserProduct || "Chromium",
|
|
14164
|
-
|
|
14460
|
+
userDataRoot: browserProfile,
|
|
14165
14461
|
processState: initialWsUrl ? "running" : "unknown",
|
|
14166
14462
|
remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
|
|
14167
14463
|
authorizationState: initialWsUrl ? "authorized" : "not_applicable",
|
|
@@ -14193,7 +14489,7 @@ async function main() {
|
|
|
14193
14489
|
} : {
|
|
14194
14490
|
id: browserId,
|
|
14195
14491
|
product: browserProduct || "Unavailable browser",
|
|
14196
|
-
|
|
14492
|
+
userDataRoot: browserProfile,
|
|
14197
14493
|
processState: initialWsUrl ? "running" : "unknown",
|
|
14198
14494
|
remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
|
|
14199
14495
|
authorizationState: initialWsUrl ? "authorized" : "not_applicable",
|
|
@@ -14202,7 +14498,7 @@ async function main() {
|
|
|
14202
14498
|
instance: {
|
|
14203
14499
|
id: browserInstanceId,
|
|
14204
14500
|
product: selectedProduct || "Unavailable browser",
|
|
14205
|
-
|
|
14501
|
+
userDataRoot: selectedProfile,
|
|
14206
14502
|
processIdentity: initialWsUrl ?? "",
|
|
14207
14503
|
connectionGeneration: initialWsUrl ? 1 : 0,
|
|
14208
14504
|
state: initialWsUrl ? "connected" : "disconnected"
|
|
@@ -14242,7 +14538,7 @@ async function main() {
|
|
|
14242
14538
|
instance: {
|
|
14243
14539
|
id: instanceId,
|
|
14244
14540
|
product: discovered.candidate.product,
|
|
14245
|
-
|
|
14541
|
+
userDataRoot: discovered.dataDir,
|
|
14246
14542
|
processIdentity: "",
|
|
14247
14543
|
connectionGeneration: 0,
|
|
14248
14544
|
state: "disconnected"
|
|
@@ -14298,7 +14594,7 @@ async function main() {
|
|
|
14298
14594
|
if (existing) return existing;
|
|
14299
14595
|
const attempt = (async () => {
|
|
14300
14596
|
const chrome = discoverChromeAtDataDir(
|
|
14301
|
-
controller.binding.instance.
|
|
14597
|
+
controller.binding.instance.userDataRoot,
|
|
14302
14598
|
controller.binding.instance.product
|
|
14303
14599
|
);
|
|
14304
14600
|
if (!chrome) {
|
|
@@ -14541,7 +14837,7 @@ async function main() {
|
|
|
14541
14837
|
res.end(JSON.stringify({
|
|
14542
14838
|
ok: true,
|
|
14543
14839
|
wsUrl: currentWsUrl,
|
|
14544
|
-
brokerProtocol:
|
|
14840
|
+
brokerProtocol: BROKER_RPC_VERSION,
|
|
14545
14841
|
brokerProcessIdentity,
|
|
14546
14842
|
serviceVersion: BROWSER_PILOT_VERSION,
|
|
14547
14843
|
executableVersion: executable.version,
|
|
@@ -14553,7 +14849,7 @@ async function main() {
|
|
|
14553
14849
|
id: browserBinding.candidate.id,
|
|
14554
14850
|
product: selectedProduct,
|
|
14555
14851
|
...browserBinding.candidate.channel ? { channel: browserBinding.candidate.channel } : {},
|
|
14556
|
-
|
|
14852
|
+
userDataRoot: selectedProfile,
|
|
14557
14853
|
state: browserBinding.instance.state,
|
|
14558
14854
|
connectionGeneration: browserBinding.instance.connectionGeneration
|
|
14559
14855
|
} } : {}
|
|
@@ -14569,14 +14865,14 @@ async function main() {
|
|
|
14569
14865
|
}
|
|
14570
14866
|
const body = JSON.parse(await readBody(req, DEFAULT_PROTOCOL_LIMITS.maxMessageBytes + 4096));
|
|
14571
14867
|
if (!isRecord3(body)) throw invalidArgument("Broker RPC body must be an object", "body");
|
|
14572
|
-
if (typeof body.
|
|
14573
|
-
throw invalidArgument("
|
|
14868
|
+
if (typeof body.clientSessionId !== "string") {
|
|
14869
|
+
throw invalidArgument("clientSessionId is required", "clientSessionId");
|
|
14574
14870
|
}
|
|
14575
14871
|
if (typeof body.method !== "string" || body.method.length === 0 || body.method.length > 256) {
|
|
14576
14872
|
throw invalidArgument("method is required", "method");
|
|
14577
14873
|
}
|
|
14578
14874
|
const result = await broker.call(
|
|
14579
|
-
body.
|
|
14875
|
+
body.clientSessionId,
|
|
14580
14876
|
body.method,
|
|
14581
14877
|
body.params
|
|
14582
14878
|
);
|
|
@@ -14589,47 +14885,6 @@ async function main() {
|
|
|
14589
14885
|
return;
|
|
14590
14886
|
}
|
|
14591
14887
|
}
|
|
14592
|
-
if (req.method === "POST" && url.pathname === "/broker/disconnect") {
|
|
14593
|
-
try {
|
|
14594
|
-
const body = JSON.parse(await readBody(req, 4096));
|
|
14595
|
-
if (!isRecord3(body) || typeof body.bridgeSessionId !== "string") {
|
|
14596
|
-
throw invalidArgument("bridgeSessionId is required", "bridgeSessionId");
|
|
14597
|
-
}
|
|
14598
|
-
broker.disconnect(body.bridgeSessionId);
|
|
14599
|
-
res.writeHead(200);
|
|
14600
|
-
res.end(JSON.stringify({ ok: true }));
|
|
14601
|
-
return;
|
|
14602
|
-
} catch (error) {
|
|
14603
|
-
res.writeHead(200);
|
|
14604
|
-
res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
|
|
14605
|
-
return;
|
|
14606
|
-
}
|
|
14607
|
-
}
|
|
14608
|
-
if (req.method === "POST" && url.pathname === "/broker/events/next") {
|
|
14609
|
-
const abort = new AbortController();
|
|
14610
|
-
res.once("close", () => abort.abort());
|
|
14611
|
-
try {
|
|
14612
|
-
const body = JSON.parse(await readBody(req, 4096));
|
|
14613
|
-
if (!isRecord3(body) || typeof body.bridgeSessionId !== "string" || !Number.isSafeInteger(body.waitMs)) {
|
|
14614
|
-
throw invalidArgument("bridgeSessionId and integer waitMs are required");
|
|
14615
|
-
}
|
|
14616
|
-
const notification = await broker.nextNotification(body.bridgeSessionId, {
|
|
14617
|
-
waitMs: Number(body.waitMs),
|
|
14618
|
-
signal: abort.signal
|
|
14619
|
-
});
|
|
14620
|
-
if (!res.destroyed) {
|
|
14621
|
-
res.writeHead(200);
|
|
14622
|
-
res.end(JSON.stringify({ notification }));
|
|
14623
|
-
}
|
|
14624
|
-
return;
|
|
14625
|
-
} catch (error) {
|
|
14626
|
-
if (!res.destroyed) {
|
|
14627
|
-
res.writeHead(200);
|
|
14628
|
-
res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
|
|
14629
|
-
}
|
|
14630
|
-
return;
|
|
14631
|
-
}
|
|
14632
|
-
}
|
|
14633
14888
|
if (req.method === "POST" && url.pathname === "/cdp") {
|
|
14634
14889
|
const body = await readBody(req);
|
|
14635
14890
|
const { method, params, sessionId } = JSON.parse(body);
|
|
@@ -14710,13 +14965,13 @@ async function main() {
|
|
|
14710
14965
|
});
|
|
14711
14966
|
}
|
|
14712
14967
|
const clients = broker.lifecycleSummary();
|
|
14713
|
-
if (clients.
|
|
14714
|
-
throw new BrowserPilotError("broker_in_use", "Browser Pilot has live
|
|
14968
|
+
if (clients.activeLeases > 0) {
|
|
14969
|
+
throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
|
|
14715
14970
|
retryable: true,
|
|
14716
14971
|
context: clients,
|
|
14717
14972
|
remediation: {
|
|
14718
|
-
code: "
|
|
14719
|
-
message: "Close the Agent
|
|
14973
|
+
code: "close_active_clients",
|
|
14974
|
+
message: "Close the other Agent clients using Browser Pilot, then retry disconnect.",
|
|
14720
14975
|
actionRequired: true
|
|
14721
14976
|
}
|
|
14722
14977
|
});
|