browser-pilot-cli 0.2.2 → 0.3.0-rc.2
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 +15 -6
- package/dist/cli.js +177 -8
- package/dist/daemon.js +852 -72
- package/dist/managed-target-janitor.js +6 -1
- package/docs/architecture/browser-pilot-platform-spec.md +85 -12
- package/docs/integration/stdio-bridge.md +53 -8
- package/docs/plans/profile-context-routing.md +199 -0
- package/docs/releases/v0.3.0-rc.1.md +49 -0
- package/docs/releases/v0.3.0-rc.2.md +43 -0
- package/package.json +4 -1
package/dist/daemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/daemon.ts
|
|
2
2
|
import http2 from "http";
|
|
3
3
|
import { unlinkSync as unlinkSync2, chmodSync as chmodSync2 } from "fs";
|
|
4
|
-
import { createHash as createHash5, randomUUID as
|
|
4
|
+
import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
|
|
5
5
|
import { join as join6 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/paths.ts
|
|
@@ -280,6 +280,9 @@ var ERROR_CODES = [
|
|
|
280
280
|
"browser_not_authorized",
|
|
281
281
|
"browser_disconnected",
|
|
282
282
|
"broker_in_use",
|
|
283
|
+
"profile_selection_required",
|
|
284
|
+
"profile_context_stale",
|
|
285
|
+
"profile_context_unavailable",
|
|
283
286
|
"workspace_not_found",
|
|
284
287
|
"lease_expired",
|
|
285
288
|
"target_not_owned",
|
|
@@ -360,7 +363,8 @@ function asBrowserPilotError(error) {
|
|
|
360
363
|
// src/protocol/model.ts
|
|
361
364
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
362
365
|
{ major: 1, minor: 0 },
|
|
363
|
-
{ major: 1, minor: 1 }
|
|
366
|
+
{ major: 1, minor: 1 },
|
|
367
|
+
{ major: 1, minor: 2 }
|
|
364
368
|
];
|
|
365
369
|
var CAPABILITIES = [
|
|
366
370
|
"browser.discovery",
|
|
@@ -871,6 +875,7 @@ function resultSchema(context, properties = {}, required = []) {
|
|
|
871
875
|
}
|
|
872
876
|
if (context === "target") {
|
|
873
877
|
contextProperties.targetId = opaqueIdSchema;
|
|
878
|
+
contextProperties.profileContextId = opaqueIdSchema;
|
|
874
879
|
contextProperties.url = boundedUrl;
|
|
875
880
|
contextRequired.push("targetId", "url");
|
|
876
881
|
}
|
|
@@ -950,6 +955,27 @@ var dropdownChoiceSchema = {
|
|
|
950
955
|
}, ["by", "value"])
|
|
951
956
|
]
|
|
952
957
|
};
|
|
958
|
+
var profileRepresentativeTabSchema = objectSchema({
|
|
959
|
+
targetId: opaqueIdSchema,
|
|
960
|
+
title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
961
|
+
url: boundedUrl
|
|
962
|
+
}, ["targetId", "title", "url"]);
|
|
963
|
+
var profileContextSchema = objectSchema({
|
|
964
|
+
profileContextId: opaqueIdSchema,
|
|
965
|
+
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
966
|
+
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
|
|
967
|
+
tabCount: integerSchema({ minimum: 0 }),
|
|
968
|
+
eligibleTabCount: integerSchema({ minimum: 0 }),
|
|
969
|
+
selected: booleanSchema(),
|
|
970
|
+
representativeTabs: arraySchema(profileRepresentativeTabSchema, { maxItems: 3 })
|
|
971
|
+
}, [
|
|
972
|
+
"profileContextId",
|
|
973
|
+
"label",
|
|
974
|
+
"tabCount",
|
|
975
|
+
"eligibleTabCount",
|
|
976
|
+
"selected",
|
|
977
|
+
"representativeTabs"
|
|
978
|
+
]);
|
|
953
979
|
function tool(definition) {
|
|
954
980
|
return definition;
|
|
955
981
|
}
|
|
@@ -1009,6 +1035,40 @@ var TOOL_DEFINITIONS = [
|
|
|
1009
1035
|
sensitivity: { input: [], output: ["browser_data"] },
|
|
1010
1036
|
artifactKinds: []
|
|
1011
1037
|
}),
|
|
1038
|
+
tool({
|
|
1039
|
+
name: "browser.profiles.list",
|
|
1040
|
+
title: "List browser Profiles",
|
|
1041
|
+
description: "Passively list live Profile contexts and bounded representative tabs.",
|
|
1042
|
+
context: "workspace",
|
|
1043
|
+
inputSchema: emptyInput,
|
|
1044
|
+
outputSchema: resultSchema("workspace", {
|
|
1045
|
+
profiles: arraySchema(profileContextSchema, { maxItems: 128 })
|
|
1046
|
+
}, ["profiles"]),
|
|
1047
|
+
requiredCapabilities: ["browser.control"],
|
|
1048
|
+
mutating: false,
|
|
1049
|
+
idempotency: "read_only",
|
|
1050
|
+
cancellation: "not_applicable",
|
|
1051
|
+
sensitivity: { input: [], output: ["browser_data"] },
|
|
1052
|
+
artifactKinds: []
|
|
1053
|
+
}),
|
|
1054
|
+
tool({
|
|
1055
|
+
name: "browser.profiles.select",
|
|
1056
|
+
title: "Select browser Profile",
|
|
1057
|
+
description: "Select one current Profile context for new managed targets in this Workspace.",
|
|
1058
|
+
context: "workspace",
|
|
1059
|
+
inputSchema: objectSchema({ profileContextId: opaqueIdSchema }, ["profileContextId"]),
|
|
1060
|
+
outputSchema: resultSchema("workspace", {
|
|
1061
|
+
profileContextId: opaqueIdSchema,
|
|
1062
|
+
label: stringSchema({ minLength: 1, maxLength: 128 }),
|
|
1063
|
+
displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data")
|
|
1064
|
+
}, ["profileContextId", "label"]),
|
|
1065
|
+
requiredCapabilities: ["browser.control"],
|
|
1066
|
+
mutating: true,
|
|
1067
|
+
idempotency: "idempotent",
|
|
1068
|
+
cancellation: "before_dispatch",
|
|
1069
|
+
sensitivity: { input: [], output: ["browser_data"] },
|
|
1070
|
+
artifactKinds: []
|
|
1071
|
+
}),
|
|
1012
1072
|
tool({
|
|
1013
1073
|
name: "browser.open",
|
|
1014
1074
|
title: "Open URL",
|
|
@@ -1018,6 +1078,7 @@ var TOOL_DEFINITIONS = [
|
|
|
1018
1078
|
url: boundedUrl,
|
|
1019
1079
|
targetId: opaqueIdSchema,
|
|
1020
1080
|
newTarget: booleanSchema(),
|
|
1081
|
+
profileContextId: opaqueIdSchema,
|
|
1021
1082
|
observationLimit: integerSchema({ minimum: 1, maximum: OBSERVATION_V1_LIMITS.maxElements })
|
|
1022
1083
|
}, ["url"]),
|
|
1023
1084
|
outputSchema: observationOutput,
|
|
@@ -1423,6 +1484,7 @@ var TOOL_DEFINITIONS = [
|
|
|
1423
1484
|
outputSchema: resultSchema("workspace", {
|
|
1424
1485
|
targets: arraySchema(objectSchema({
|
|
1425
1486
|
targetId: opaqueIdSchema,
|
|
1487
|
+
profileContextId: opaqueIdSchema,
|
|
1426
1488
|
title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
|
|
1427
1489
|
url: boundedUrl,
|
|
1428
1490
|
active: booleanSchema(),
|
|
@@ -1769,6 +1831,13 @@ var TOOL_DEFINITIONS = [
|
|
|
1769
1831
|
})
|
|
1770
1832
|
];
|
|
1771
1833
|
var toolsByName = new Map(TOOL_DEFINITIONS.map((definition) => [definition.name, definition]));
|
|
1834
|
+
var PROTOCOL_1_2_TOOLS = /* @__PURE__ */ new Set([
|
|
1835
|
+
"browser.profiles.list",
|
|
1836
|
+
"browser.profiles.select"
|
|
1837
|
+
]);
|
|
1838
|
+
function isToolAvailableInProtocol(name, protocol) {
|
|
1839
|
+
return protocol.major === 1 && (protocol.minor >= 2 || !PROTOCOL_1_2_TOOLS.has(name));
|
|
1840
|
+
}
|
|
1772
1841
|
function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
|
|
1773
1842
|
for (const value of schema["x-browser-pilot-sensitivity"] ?? []) result.add(value);
|
|
1774
1843
|
for (const property of Object.values(schema.properties ?? {})) schemaSensitivities(property, result);
|
|
@@ -1843,10 +1912,10 @@ function validateToolResult(name, value) {
|
|
|
1843
1912
|
assertSchemaValue(definition.outputSchema, value, `${name} result`);
|
|
1844
1913
|
return value;
|
|
1845
1914
|
}
|
|
1846
|
-
function getToolManifest(capabilities, availableTools) {
|
|
1915
|
+
function getToolManifest(capabilities, availableTools, protocol) {
|
|
1847
1916
|
const granted = capabilities ? new Set(capabilities) : null;
|
|
1848
1917
|
const available = availableTools ? new Set(availableTools) : null;
|
|
1849
|
-
const tools = TOOL_DEFINITIONS.filter((definition) => (!granted || definition.requiredCapabilities.every((capability) => granted.has(capability))) && (!available || available.has(definition.name)));
|
|
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)));
|
|
1850
1919
|
return { schemaVersion: 1, tools };
|
|
1851
1920
|
}
|
|
1852
1921
|
assertToolManifest();
|
|
@@ -2726,6 +2795,9 @@ function cloneWorkspace(value) {
|
|
|
2726
2795
|
function cloneManagedTabSet(value) {
|
|
2727
2796
|
return { ...value };
|
|
2728
2797
|
}
|
|
2798
|
+
function runtimeManagedTabSets(record) {
|
|
2799
|
+
return [record.managedTabSet, ...record.additionalManagedTabSets.values()];
|
|
2800
|
+
}
|
|
2729
2801
|
function cloneLease(value) {
|
|
2730
2802
|
return { ...value, capabilities: [...value.capabilities] };
|
|
2731
2803
|
}
|
|
@@ -2842,7 +2914,8 @@ var MemoryBrokerRuntime = class {
|
|
|
2842
2914
|
validateToolsListParams(params);
|
|
2843
2915
|
return asJson(getToolManifest(
|
|
2844
2916
|
connection.grantedCapabilities,
|
|
2845
|
-
this.options.toolExecutor?.supportedTools
|
|
2917
|
+
this.options.toolExecutor?.supportedTools,
|
|
2918
|
+
connection.value.protocol
|
|
2846
2919
|
));
|
|
2847
2920
|
case "tools/call":
|
|
2848
2921
|
return this.callTool(connection, params);
|
|
@@ -3178,6 +3251,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3178
3251
|
return asJson({
|
|
3179
3252
|
workspace: cloneWorkspace(existing.value),
|
|
3180
3253
|
managedTabSet: cloneManagedTabSet(existing.managedTabSet),
|
|
3254
|
+
managedTabSets: runtimeManagedTabSets(existing).map(cloneManagedTabSet),
|
|
3181
3255
|
eventCursor: this.events.currentCursor(existing.value.id)
|
|
3182
3256
|
});
|
|
3183
3257
|
}
|
|
@@ -3223,11 +3297,16 @@ var MemoryBrokerRuntime = class {
|
|
|
3223
3297
|
createdAt: now,
|
|
3224
3298
|
state: "active"
|
|
3225
3299
|
};
|
|
3226
|
-
this.workspaces.set(workspaceId, {
|
|
3300
|
+
this.workspaces.set(workspaceId, {
|
|
3301
|
+
value: workspace,
|
|
3302
|
+
managedTabSet,
|
|
3303
|
+
additionalManagedTabSets: /* @__PURE__ */ new Map()
|
|
3304
|
+
});
|
|
3227
3305
|
const eventCursor = this.events.createWorkspace(workspaceId);
|
|
3228
3306
|
return asJson({
|
|
3229
3307
|
workspace: cloneWorkspace(workspace),
|
|
3230
3308
|
managedTabSet: cloneManagedTabSet(managedTabSet),
|
|
3309
|
+
managedTabSets: [cloneManagedTabSet(managedTabSet)],
|
|
3231
3310
|
eventCursor
|
|
3232
3311
|
});
|
|
3233
3312
|
}
|
|
@@ -3238,6 +3317,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3238
3317
|
return asJson({
|
|
3239
3318
|
workspace: cloneWorkspace(record.value),
|
|
3240
3319
|
managedTabSet: cloneManagedTabSet(record.managedTabSet),
|
|
3320
|
+
managedTabSets: runtimeManagedTabSets(record).map(cloneManagedTabSet),
|
|
3241
3321
|
eventCursor: this.events.currentCursor(record.value.id)
|
|
3242
3322
|
});
|
|
3243
3323
|
}
|
|
@@ -3351,6 +3431,46 @@ var MemoryBrokerRuntime = class {
|
|
|
3351
3431
|
}
|
|
3352
3432
|
return record;
|
|
3353
3433
|
}
|
|
3434
|
+
selectWorkspaceProfileContext(record, profileContextId) {
|
|
3435
|
+
if (record.value.state !== "active") {
|
|
3436
|
+
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3437
|
+
context: { workspaceId: record.value.id }
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
record.value.selectedProfileContextId = profileContextId;
|
|
3441
|
+
record.value.updatedAt = this.now();
|
|
3442
|
+
}
|
|
3443
|
+
managedTabSetForProfile(record, profileContextId) {
|
|
3444
|
+
if (record.value.state !== "active") {
|
|
3445
|
+
throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
|
|
3446
|
+
context: { workspaceId: record.value.id }
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
if (record.managedTabSet.profileContextId === profileContextId) {
|
|
3450
|
+
return cloneManagedTabSet(record.managedTabSet);
|
|
3451
|
+
}
|
|
3452
|
+
if (!record.managedTabSet.profileContextId) {
|
|
3453
|
+
record.managedTabSet.profileContextId = profileContextId;
|
|
3454
|
+
return cloneManagedTabSet(record.managedTabSet);
|
|
3455
|
+
}
|
|
3456
|
+
const existing = record.additionalManagedTabSets.get(profileContextId);
|
|
3457
|
+
if (existing) return cloneManagedTabSet(existing);
|
|
3458
|
+
if (record.additionalManagedTabSets.size >= 127) {
|
|
3459
|
+
throw new BrowserPilotError("result_too_large", "ManagedTabSet Profile context limit reached", {
|
|
3460
|
+
context: { workspaceId: record.value.id, maxManagedTabSets: 128 }
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
const managedTabSet = {
|
|
3464
|
+
id: this.nextManagedTabSetId(),
|
|
3465
|
+
workspaceId: record.value.id,
|
|
3466
|
+
browserInstanceId: record.value.browserInstanceId,
|
|
3467
|
+
profileContextId,
|
|
3468
|
+
createdAt: this.now(),
|
|
3469
|
+
state: "active"
|
|
3470
|
+
};
|
|
3471
|
+
record.additionalManagedTabSets.set(profileContextId, managedTabSet);
|
|
3472
|
+
return cloneManagedTabSet(managedTabSet);
|
|
3473
|
+
}
|
|
3354
3474
|
requireLease(connection, leaseId, requireActive) {
|
|
3355
3475
|
const lease = this.leases.get(leaseId);
|
|
3356
3476
|
if (!lease || lease.connectionId !== connection.value.id || requireActive && lease.state !== "active") {
|
|
@@ -3388,7 +3508,8 @@ var MemoryBrokerRuntime = class {
|
|
|
3388
3508
|
this.releaseLeaseRecord(lease, "released");
|
|
3389
3509
|
}
|
|
3390
3510
|
}
|
|
3391
|
-
|
|
3511
|
+
const managedTabSets = runtimeManagedTabSets(record);
|
|
3512
|
+
for (const managedTabSet of managedTabSets) managedTabSet.state = "closed";
|
|
3392
3513
|
record.value.updatedAt = this.now();
|
|
3393
3514
|
record.value.state = "released";
|
|
3394
3515
|
const principal = this.principals.get(record.value.principalId);
|
|
@@ -3396,7 +3517,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3396
3517
|
this.options.toolExecutor?.releaseWorkspace?.(
|
|
3397
3518
|
{ ...principal, capabilities: [...principal.capabilities] },
|
|
3398
3519
|
cloneWorkspace(record.value),
|
|
3399
|
-
cloneManagedTabSet
|
|
3520
|
+
managedTabSets.map(cloneManagedTabSet)
|
|
3400
3521
|
);
|
|
3401
3522
|
}
|
|
3402
3523
|
this.options.onWorkspaceReleased?.(
|
|
@@ -3418,7 +3539,9 @@ var MemoryBrokerRuntime = class {
|
|
|
3418
3539
|
for (const record of released) {
|
|
3419
3540
|
this.workspaces.delete(record.value.id);
|
|
3420
3541
|
this.events.releaseWorkspace(record.value.id);
|
|
3421
|
-
|
|
3542
|
+
for (const managedTabSet of runtimeManagedTabSets(record)) {
|
|
3543
|
+
this.managedTabSetIds.delete(managedTabSet.id);
|
|
3544
|
+
}
|
|
3422
3545
|
for (const [leaseId, lease] of this.leases) {
|
|
3423
3546
|
if (lease.workspaceId === record.value.id) this.leases.delete(leaseId);
|
|
3424
3547
|
}
|
|
@@ -3458,6 +3581,12 @@ var MemoryBrokerRuntime = class {
|
|
|
3458
3581
|
async callTool(connection, value) {
|
|
3459
3582
|
const params = validateToolCallParams(value);
|
|
3460
3583
|
const definition = getToolDefinition(params.name);
|
|
3584
|
+
if (!isToolAvailableInProtocol(definition.name, connection.value.protocol)) {
|
|
3585
|
+
throw protocolIncompatible(`${definition.name} requires protocol 1.2 or newer`, {
|
|
3586
|
+
tool: definition.name,
|
|
3587
|
+
selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3461
3590
|
const isBrowserConnectTool = definition.name === "browser.connect";
|
|
3462
3591
|
const executor = this.options.toolExecutor;
|
|
3463
3592
|
if (!executor || !executor.supportedTools.includes(definition.name)) {
|
|
@@ -3465,6 +3594,12 @@ var MemoryBrokerRuntime = class {
|
|
|
3465
3594
|
}
|
|
3466
3595
|
this.assertCapabilities(connection, definition.requiredCapabilities);
|
|
3467
3596
|
const args = validateToolArguments(definition.name, params.arguments);
|
|
3597
|
+
if (definition.name === "browser.open" && connection.value.protocol.minor < 2 && args && typeof args === "object" && !Array.isArray(args) && args.profileContextId !== void 0) {
|
|
3598
|
+
throw protocolIncompatible("browser.open profileContextId requires protocol 1.2 or newer", {
|
|
3599
|
+
tool: definition.name,
|
|
3600
|
+
selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3468
3603
|
if (definition.name === "browser.discover") {
|
|
3469
3604
|
return this.callBrowserDiscover(connection, params, args, definition);
|
|
3470
3605
|
}
|
|
@@ -3502,7 +3637,11 @@ var MemoryBrokerRuntime = class {
|
|
|
3502
3637
|
capabilities: [...connection.grantedCapabilities],
|
|
3503
3638
|
...workspaceRecord ? {
|
|
3504
3639
|
workspace: cloneWorkspace(workspaceRecord.value),
|
|
3505
|
-
managedTabSet: cloneManagedTabSet(workspaceRecord.managedTabSet)
|
|
3640
|
+
managedTabSet: cloneManagedTabSet(workspaceRecord.managedTabSet),
|
|
3641
|
+
selectProfileContext: (profileContextId) => {
|
|
3642
|
+
this.selectWorkspaceProfileContext(workspaceRecord, profileContextId);
|
|
3643
|
+
},
|
|
3644
|
+
managedTabSetForProfile: (profileContextId) => this.managedTabSetForProfile(workspaceRecord, profileContextId)
|
|
3506
3645
|
} : {},
|
|
3507
3646
|
...lease ? { lease: cloneLease(lease) } : {},
|
|
3508
3647
|
...params.targetId ? { targetId: params.targetId } : {},
|
|
@@ -3770,7 +3909,7 @@ var MemoryBrokerRuntime = class {
|
|
|
3770
3909
|
};
|
|
3771
3910
|
|
|
3772
3911
|
// src/services/browser-tool-service.ts
|
|
3773
|
-
import { createHash as createHash4, randomUUID as
|
|
3912
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
3774
3913
|
|
|
3775
3914
|
// src/page-scripts.ts
|
|
3776
3915
|
var CLICK_TARGET_STATE_FUNCTION = `function() {
|
|
@@ -6946,6 +7085,171 @@ var DownloadController = class {
|
|
|
6946
7085
|
}
|
|
6947
7086
|
};
|
|
6948
7087
|
|
|
7088
|
+
// src/services/profile-context-registry.ts
|
|
7089
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
7090
|
+
var DEFAULT_CDP_CONTEXT_KEY = "\0default";
|
|
7091
|
+
var MAX_PROFILE_CONTEXTS = 128;
|
|
7092
|
+
var MAX_PROFILE_TARGETS = 4096;
|
|
7093
|
+
var MAX_PROFILE_CONTEXT_RECORDS = 512;
|
|
7094
|
+
function cloneTarget(target) {
|
|
7095
|
+
return { ...target };
|
|
7096
|
+
}
|
|
7097
|
+
function cloneRecord(record) {
|
|
7098
|
+
return { ...record, targets: record.targets.map(cloneTarget) };
|
|
7099
|
+
}
|
|
7100
|
+
function contextKey(cdpBrowserContextId) {
|
|
7101
|
+
return cdpBrowserContextId ? `cdp:${cdpBrowserContextId}` : DEFAULT_CDP_CONTEXT_KEY;
|
|
7102
|
+
}
|
|
7103
|
+
var MemoryProfileContextRegistry = class {
|
|
7104
|
+
constructor(browserInstanceId, options = {}) {
|
|
7105
|
+
this.browserInstanceId = browserInstanceId;
|
|
7106
|
+
this.idFactory = options.idFactory ?? (() => `profile-context:${randomUUID7()}`);
|
|
7107
|
+
}
|
|
7108
|
+
recordsById = /* @__PURE__ */ new Map();
|
|
7109
|
+
currentByKey = /* @__PURE__ */ new Map();
|
|
7110
|
+
contextByTarget = /* @__PURE__ */ new Map();
|
|
7111
|
+
recordOrder = [];
|
|
7112
|
+
idFactory;
|
|
7113
|
+
generation;
|
|
7114
|
+
nextSequence = 0;
|
|
7115
|
+
reconcile(browserConnectionGeneration, targets) {
|
|
7116
|
+
if (!Number.isSafeInteger(browserConnectionGeneration) || browserConnectionGeneration < 1) {
|
|
7117
|
+
throw new BrowserPilotError("internal_error", "Invalid browser connection generation");
|
|
7118
|
+
}
|
|
7119
|
+
if (targets.length > MAX_PROFILE_TARGETS) {
|
|
7120
|
+
throw new BrowserPilotError("result_too_large", "Profile target inventory limit reached", {
|
|
7121
|
+
context: { maxProfileTargets: MAX_PROFILE_TARGETS }
|
|
7122
|
+
});
|
|
7123
|
+
}
|
|
7124
|
+
if (this.generation !== browserConnectionGeneration) {
|
|
7125
|
+
this.invalidateCurrent();
|
|
7126
|
+
this.generation = browserConnectionGeneration;
|
|
7127
|
+
this.nextSequence = 0;
|
|
7128
|
+
}
|
|
7129
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
7130
|
+
for (const target of targets) {
|
|
7131
|
+
if (target.type !== "page") continue;
|
|
7132
|
+
const key = contextKey(target.cdpBrowserContextId);
|
|
7133
|
+
const group = grouped.get(key) ?? [];
|
|
7134
|
+
group.push(cloneTarget(target));
|
|
7135
|
+
grouped.set(key, group);
|
|
7136
|
+
}
|
|
7137
|
+
if (grouped.size > MAX_PROFILE_CONTEXTS) {
|
|
7138
|
+
throw new BrowserPilotError("result_too_large", "Profile context inventory limit reached", {
|
|
7139
|
+
context: { maxProfileContexts: MAX_PROFILE_CONTEXTS }
|
|
7140
|
+
});
|
|
7141
|
+
}
|
|
7142
|
+
for (const id of this.currentByKey.values()) {
|
|
7143
|
+
const record = this.recordsById.get(id);
|
|
7144
|
+
if (record) record.active = false;
|
|
7145
|
+
}
|
|
7146
|
+
this.contextByTarget.clear();
|
|
7147
|
+
const activeKeys = new Set(grouped.keys());
|
|
7148
|
+
for (const [key, group] of grouped) {
|
|
7149
|
+
let id = this.currentByKey.get(key);
|
|
7150
|
+
let record = id ? this.recordsById.get(id) : void 0;
|
|
7151
|
+
if (!record) {
|
|
7152
|
+
id = this.nextId();
|
|
7153
|
+
const rawContextId = group[0]?.cdpBrowserContextId;
|
|
7154
|
+
record = {
|
|
7155
|
+
id,
|
|
7156
|
+
browserInstanceId: this.browserInstanceId,
|
|
7157
|
+
browserConnectionGeneration,
|
|
7158
|
+
label: `Profile ${++this.nextSequence}`,
|
|
7159
|
+
tabCount: 0,
|
|
7160
|
+
eligibleTabCount: 0,
|
|
7161
|
+
sequence: this.nextSequence,
|
|
7162
|
+
active: true,
|
|
7163
|
+
targets: [],
|
|
7164
|
+
...rawContextId ? { cdpBrowserContextId: rawContextId } : {}
|
|
7165
|
+
};
|
|
7166
|
+
this.recordsById.set(id, record);
|
|
7167
|
+
this.recordOrder.push(id);
|
|
7168
|
+
this.currentByKey.set(key, id);
|
|
7169
|
+
}
|
|
7170
|
+
const sorted = [...group].sort((left, right) => Number(right.eligible) - Number(left.eligible) || left.cdpTargetId.localeCompare(right.cdpTargetId));
|
|
7171
|
+
record.active = true;
|
|
7172
|
+
record.tabCount = sorted.length;
|
|
7173
|
+
record.eligibleTabCount = sorted.filter((target) => target.eligible).length;
|
|
7174
|
+
record.targets = sorted;
|
|
7175
|
+
record.representativeCdpTargetId = sorted[0]?.cdpTargetId;
|
|
7176
|
+
for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record.id);
|
|
7177
|
+
}
|
|
7178
|
+
for (const [key, id] of this.currentByKey) {
|
|
7179
|
+
if (activeKeys.has(key)) continue;
|
|
7180
|
+
const record = this.recordsById.get(id);
|
|
7181
|
+
if (record) record.active = false;
|
|
7182
|
+
this.currentByKey.delete(key);
|
|
7183
|
+
}
|
|
7184
|
+
this.pruneStaleRecords();
|
|
7185
|
+
return this.list(browserConnectionGeneration);
|
|
7186
|
+
}
|
|
7187
|
+
list(browserConnectionGeneration) {
|
|
7188
|
+
return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((record) => record !== void 0 && record.active && record.browserConnectionGeneration === browserConnectionGeneration).sort((left, right) => left.sequence - right.sequence).map(cloneRecord);
|
|
7189
|
+
}
|
|
7190
|
+
resolve(profileContextId, browserConnectionGeneration) {
|
|
7191
|
+
const record = this.recordsById.get(profileContextId);
|
|
7192
|
+
if (!record) throw invalidArgument("Unknown Profile context", "profileContextId");
|
|
7193
|
+
if (!record.active || record.browserInstanceId !== this.browserInstanceId || record.browserConnectionGeneration !== browserConnectionGeneration) {
|
|
7194
|
+
throw new BrowserPilotError("profile_context_stale", "Profile context belongs to a stale browser connection", {
|
|
7195
|
+
retryable: true,
|
|
7196
|
+
context: {
|
|
7197
|
+
profileContextId,
|
|
7198
|
+
expectedConnectionGeneration: browserConnectionGeneration
|
|
7199
|
+
},
|
|
7200
|
+
remediation: {
|
|
7201
|
+
code: "relist_profile_contexts",
|
|
7202
|
+
message: "List Profile contexts again and select one from the current browser connection.",
|
|
7203
|
+
actionRequired: true
|
|
7204
|
+
}
|
|
7205
|
+
});
|
|
7206
|
+
}
|
|
7207
|
+
return cloneRecord(record);
|
|
7208
|
+
}
|
|
7209
|
+
forTarget(cdpTargetId, browserConnectionGeneration) {
|
|
7210
|
+
const id = this.contextByTarget.get(cdpTargetId);
|
|
7211
|
+
if (!id) return void 0;
|
|
7212
|
+
return this.resolve(id, browserConnectionGeneration);
|
|
7213
|
+
}
|
|
7214
|
+
forRawContext(cdpBrowserContextId, browserConnectionGeneration) {
|
|
7215
|
+
const id = this.currentByKey.get(contextKey(cdpBrowserContextId));
|
|
7216
|
+
if (!id) return void 0;
|
|
7217
|
+
return this.resolve(id, browserConnectionGeneration);
|
|
7218
|
+
}
|
|
7219
|
+
invalidate() {
|
|
7220
|
+
this.invalidateCurrent();
|
|
7221
|
+
this.generation = void 0;
|
|
7222
|
+
this.nextSequence = 0;
|
|
7223
|
+
}
|
|
7224
|
+
invalidateCurrent() {
|
|
7225
|
+
for (const id of this.currentByKey.values()) {
|
|
7226
|
+
const record = this.recordsById.get(id);
|
|
7227
|
+
if (record) record.active = false;
|
|
7228
|
+
}
|
|
7229
|
+
this.currentByKey.clear();
|
|
7230
|
+
this.contextByTarget.clear();
|
|
7231
|
+
}
|
|
7232
|
+
pruneStaleRecords() {
|
|
7233
|
+
while (this.recordsById.size > MAX_PROFILE_CONTEXT_RECORDS) {
|
|
7234
|
+
const id = this.recordOrder.shift();
|
|
7235
|
+
if (!id) return;
|
|
7236
|
+
const record = this.recordsById.get(id);
|
|
7237
|
+
if (!record || record.active) {
|
|
7238
|
+
if (record?.active) this.recordOrder.push(id);
|
|
7239
|
+
continue;
|
|
7240
|
+
}
|
|
7241
|
+
this.recordsById.delete(id);
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
nextId() {
|
|
7245
|
+
const id = this.idFactory();
|
|
7246
|
+
if (!/^profile-context:[A-Za-z0-9._:-]+$/.test(id) || this.recordsById.has(id)) {
|
|
7247
|
+
throw new BrowserPilotError("internal_error", "Profile context ID factory returned an invalid or duplicate ID");
|
|
7248
|
+
}
|
|
7249
|
+
return id;
|
|
7250
|
+
}
|
|
7251
|
+
};
|
|
7252
|
+
|
|
6949
7253
|
// src/services/browser-target-catalog.ts
|
|
6950
7254
|
function isEligibleUserPage(target) {
|
|
6951
7255
|
if (target.type !== "page") return false;
|
|
@@ -6969,17 +7273,21 @@ var CdpBrowserTargetCatalog = class {
|
|
|
6969
7273
|
this.browserInstanceId = browserInstanceId;
|
|
6970
7274
|
this.identity = identity;
|
|
6971
7275
|
this.options = options;
|
|
7276
|
+
this.profileContexts = options.profileContexts ?? new MemoryProfileContextRegistry(browserInstanceId);
|
|
6972
7277
|
}
|
|
7278
|
+
profileContexts;
|
|
6973
7279
|
async getBrowserIdentity(browserInstanceId) {
|
|
6974
7280
|
return browserInstanceId === this.browserInstanceId ? this.identity() : void 0;
|
|
6975
7281
|
}
|
|
6976
7282
|
async listEligibleUserTargets(browserInstanceId) {
|
|
6977
|
-
|
|
7283
|
+
const identity = this.identity();
|
|
7284
|
+
if (browserInstanceId !== this.browserInstanceId || !identity) return [];
|
|
6978
7285
|
const result = await this.transport.send("Target.getTargets");
|
|
6979
7286
|
if (!Array.isArray(result?.targetInfos)) {
|
|
6980
7287
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid target metadata");
|
|
6981
7288
|
}
|
|
6982
|
-
const
|
|
7289
|
+
const candidates = [];
|
|
7290
|
+
const snapshots = [];
|
|
6983
7291
|
for (const value of result.targetInfos) {
|
|
6984
7292
|
if (!value || typeof value !== "object" || typeof value.targetId !== "string") continue;
|
|
6985
7293
|
const candidate = {
|
|
@@ -6989,9 +7297,24 @@ var CdpBrowserTargetCatalog = class {
|
|
|
6989
7297
|
type: typeof value.type === "string" ? value.type : "",
|
|
6990
7298
|
...typeof value.openerId === "string" ? { openerCdpTargetId: value.openerId } : {}
|
|
6991
7299
|
};
|
|
6992
|
-
|
|
7300
|
+
const eligible2 = isEligibleUserPage(candidate) && !this.options.isExcludedTarget(candidate);
|
|
7301
|
+
snapshots.push({
|
|
7302
|
+
...candidate,
|
|
7303
|
+
eligible: eligible2,
|
|
7304
|
+
...typeof value.browserContextId === "string" ? { cdpBrowserContextId: value.browserContextId } : {}
|
|
7305
|
+
});
|
|
7306
|
+
if (eligible2) candidates.push(candidate);
|
|
7307
|
+
}
|
|
7308
|
+
this.profileContexts.reconcile(identity.connectionGeneration, snapshots);
|
|
7309
|
+
const eligible = [];
|
|
7310
|
+
for (const candidate of candidates) {
|
|
7311
|
+
const profile = this.profileContexts.forTarget(
|
|
7312
|
+
candidate.cdpTargetId,
|
|
7313
|
+
identity.connectionGeneration
|
|
7314
|
+
);
|
|
7315
|
+
if (!profile) continue;
|
|
6993
7316
|
const { type: _type, ...target } = candidate;
|
|
6994
|
-
eligible.push(target);
|
|
7317
|
+
eligible.push({ ...target, profileContextId: profile.id });
|
|
6995
7318
|
}
|
|
6996
7319
|
return eligible;
|
|
6997
7320
|
}
|
|
@@ -7208,8 +7531,8 @@ var BrowserWatchdogService = class {
|
|
|
7208
7531
|
};
|
|
7209
7532
|
|
|
7210
7533
|
// src/services/controlled-target-registry.ts
|
|
7211
|
-
import { randomUUID as
|
|
7212
|
-
function
|
|
7534
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
7535
|
+
function cloneRecord2(record) {
|
|
7213
7536
|
return { ...record };
|
|
7214
7537
|
}
|
|
7215
7538
|
function targetKey2(workspaceId, browserInstanceId, cdpTargetId) {
|
|
@@ -7227,7 +7550,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7227
7550
|
idFactory;
|
|
7228
7551
|
constructor(options = {}) {
|
|
7229
7552
|
this.now = options.now ?? Date.now;
|
|
7230
|
-
this.idFactory = options.idFactory ?? (() => `target:${
|
|
7553
|
+
this.idFactory = options.idFactory ?? (() => `target:${randomUUID8()}`);
|
|
7231
7554
|
}
|
|
7232
7555
|
registerManaged(input) {
|
|
7233
7556
|
const key = targetKey2(input.workspaceId, input.browserInstanceId, input.cdpTargetId);
|
|
@@ -7240,8 +7563,9 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7240
7563
|
}
|
|
7241
7564
|
existing.title = input.title;
|
|
7242
7565
|
existing.url = input.url;
|
|
7566
|
+
existing.profileContextId = input.profileContextId;
|
|
7243
7567
|
existing.openerCdpTargetId = input.openerCdpTargetId;
|
|
7244
|
-
return
|
|
7568
|
+
return cloneRecord2(existing);
|
|
7245
7569
|
}
|
|
7246
7570
|
const record = {
|
|
7247
7571
|
id: this.nextId(),
|
|
@@ -7250,6 +7574,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7250
7574
|
browserInstanceId: input.browserInstanceId,
|
|
7251
7575
|
browserConnectionGeneration: input.browserConnectionGeneration,
|
|
7252
7576
|
cdpTargetId: input.cdpTargetId,
|
|
7577
|
+
profileContextId: input.profileContextId,
|
|
7253
7578
|
...input.openerCdpTargetId ? { openerCdpTargetId: input.openerCdpTargetId } : {},
|
|
7254
7579
|
origin: input.origin,
|
|
7255
7580
|
managedTabSetId: input.managedTabSetId,
|
|
@@ -7260,7 +7585,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7260
7585
|
};
|
|
7261
7586
|
this.records.set(record.id, record);
|
|
7262
7587
|
this.activeKeys.set(key, record.id);
|
|
7263
|
-
return
|
|
7588
|
+
return cloneRecord2(record);
|
|
7264
7589
|
}
|
|
7265
7590
|
syncUserTargets(context, browserInstanceId, userTargets) {
|
|
7266
7591
|
const targetsByKey = /* @__PURE__ */ new Map();
|
|
@@ -7290,7 +7615,8 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7290
7615
|
if (existing.origin !== "user_tab") continue;
|
|
7291
7616
|
existing.title = target.title;
|
|
7292
7617
|
existing.url = target.url;
|
|
7293
|
-
|
|
7618
|
+
existing.profileContextId = target.profileContextId;
|
|
7619
|
+
targets.push(cloneRecord2(existing));
|
|
7294
7620
|
continue;
|
|
7295
7621
|
}
|
|
7296
7622
|
const record = {
|
|
@@ -7300,6 +7626,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7300
7626
|
browserInstanceId: target.browserInstanceId,
|
|
7301
7627
|
browserConnectionGeneration: context.browserConnectionGeneration,
|
|
7302
7628
|
cdpTargetId: target.cdpTargetId,
|
|
7629
|
+
profileContextId: target.profileContextId,
|
|
7303
7630
|
origin: "user_tab",
|
|
7304
7631
|
title: target.title,
|
|
7305
7632
|
url: target.url,
|
|
@@ -7308,8 +7635,8 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7308
7635
|
};
|
|
7309
7636
|
this.records.set(record.id, record);
|
|
7310
7637
|
this.activeKeys.set(key, record.id);
|
|
7311
|
-
targets.push(
|
|
7312
|
-
created.push(
|
|
7638
|
+
targets.push(cloneRecord2(record));
|
|
7639
|
+
created.push(cloneRecord2(record));
|
|
7313
7640
|
}
|
|
7314
7641
|
return { targets, created, invalidated };
|
|
7315
7642
|
}
|
|
@@ -7325,6 +7652,9 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7325
7652
|
record.title = live.title;
|
|
7326
7653
|
record.url = live.url;
|
|
7327
7654
|
record.openerCdpTargetId = live.openerCdpTargetId;
|
|
7655
|
+
if (record.profileContextId && live.profileContextId && record.profileContextId !== live.profileContextId) {
|
|
7656
|
+
invalidated.push(this.invalidate(record, "target_ineligible"));
|
|
7657
|
+
}
|
|
7328
7658
|
}
|
|
7329
7659
|
return invalidated;
|
|
7330
7660
|
}
|
|
@@ -7335,6 +7665,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7335
7665
|
).sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
|
|
7336
7666
|
return records.map((record) => ({
|
|
7337
7667
|
targetId: record.id,
|
|
7668
|
+
profileContextId: record.profileContextId,
|
|
7338
7669
|
title: record.title,
|
|
7339
7670
|
url: record.url,
|
|
7340
7671
|
active: record.id === activeTargetId,
|
|
@@ -7351,7 +7682,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7351
7682
|
});
|
|
7352
7683
|
}
|
|
7353
7684
|
this.assertCallerOwns(context, record);
|
|
7354
|
-
return
|
|
7685
|
+
return cloneRecord2(record);
|
|
7355
7686
|
}
|
|
7356
7687
|
acquire(context, leaseId, targetId) {
|
|
7357
7688
|
const record = this.records.get(targetId);
|
|
@@ -7377,7 +7708,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7377
7708
|
targetId
|
|
7378
7709
|
});
|
|
7379
7710
|
record.controllerLeaseId = leaseId;
|
|
7380
|
-
return { target:
|
|
7711
|
+
return { target: cloneRecord2(record), newlyAcquired };
|
|
7381
7712
|
}
|
|
7382
7713
|
release(context, leaseId, targetId) {
|
|
7383
7714
|
const record = this.records.get(targetId);
|
|
@@ -7395,7 +7726,7 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7395
7726
|
return false;
|
|
7396
7727
|
}
|
|
7397
7728
|
controlledByLease(leaseId) {
|
|
7398
|
-
return [...this.records.values()].filter((record) => record.state === "active" && record.controllerLeaseId === leaseId).map(
|
|
7729
|
+
return [...this.records.values()].filter((record) => record.state === "active" && record.controllerLeaseId === leaseId).map(cloneRecord2);
|
|
7399
7730
|
}
|
|
7400
7731
|
releaseLease(leaseId) {
|
|
7401
7732
|
for (const [key, controller] of this.controllers) {
|
|
@@ -7415,7 +7746,14 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7415
7746
|
return void 0;
|
|
7416
7747
|
}
|
|
7417
7748
|
this.assertCallerOwns(context, record);
|
|
7418
|
-
return
|
|
7749
|
+
return cloneRecord2(record);
|
|
7750
|
+
}
|
|
7751
|
+
clearActive(context, leaseId) {
|
|
7752
|
+
const targetId = this.activeByLease.get(leaseId);
|
|
7753
|
+
if (!targetId) return;
|
|
7754
|
+
const record = this.records.get(targetId);
|
|
7755
|
+
if (record?.state === "active") this.assertCallerOwns(context, record);
|
|
7756
|
+
this.activeByLease.delete(leaseId);
|
|
7419
7757
|
}
|
|
7420
7758
|
releaseWorkspace(context) {
|
|
7421
7759
|
const invalidated = [];
|
|
@@ -7449,12 +7787,12 @@ var MemoryControlledTargetRegistry = class {
|
|
|
7449
7787
|
managedTargets(context, managedTabSetId) {
|
|
7450
7788
|
return [...this.records.values()].filter(
|
|
7451
7789
|
(record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId && record.origin !== "user_tab" && record.managedTabSetId === managedTabSetId
|
|
7452
|
-
).map(
|
|
7790
|
+
).map(cloneRecord2);
|
|
7453
7791
|
}
|
|
7454
7792
|
activeRecords(context) {
|
|
7455
7793
|
return [...this.records.values()].filter(
|
|
7456
7794
|
(record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId
|
|
7457
|
-
).map(
|
|
7795
|
+
).map(cloneRecord2);
|
|
7458
7796
|
}
|
|
7459
7797
|
isManagedCdpTarget(browserInstanceId, cdpTargetId) {
|
|
7460
7798
|
for (const record of this.records.values()) {
|
|
@@ -8009,7 +8347,7 @@ var FrameService = class _FrameService {
|
|
|
8009
8347
|
};
|
|
8010
8348
|
|
|
8011
8349
|
// src/services/observation-store.ts
|
|
8012
|
-
import { randomUUID as
|
|
8350
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
8013
8351
|
function clone(record) {
|
|
8014
8352
|
return {
|
|
8015
8353
|
...record,
|
|
@@ -8027,7 +8365,7 @@ var MemoryObservationStore = class {
|
|
|
8027
8365
|
this.ttlMs = options.ttlMs ?? OBSERVATION_V1_LIMITS.ttlMs;
|
|
8028
8366
|
this.maxObservations = options.maxObservations ?? OBSERVATION_V1_LIMITS.maxStoredObservations;
|
|
8029
8367
|
this.now = options.now ?? Date.now;
|
|
8030
|
-
this.idFactory = options.idFactory ?? (() => `observation:${
|
|
8368
|
+
this.idFactory = options.idFactory ?? (() => `observation:${randomUUID9()}`);
|
|
8031
8369
|
if (!Number.isSafeInteger(this.ttlMs) || this.ttlMs <= 0) throw new Error("Invalid Observation TTL");
|
|
8032
8370
|
if (!Number.isSafeInteger(this.maxObservations) || this.maxObservations <= 0) {
|
|
8033
8371
|
throw new Error("Invalid Observation capacity");
|
|
@@ -8844,6 +9182,12 @@ var ScreenshotAnnotationService = class {
|
|
|
8844
9182
|
};
|
|
8845
9183
|
|
|
8846
9184
|
// src/services/managed-target-lifecycle.ts
|
|
9185
|
+
var ManagedTargetCreationRejectedError = class extends Error {
|
|
9186
|
+
constructor(message) {
|
|
9187
|
+
super(message);
|
|
9188
|
+
this.name = "ManagedTargetCreationRejectedError";
|
|
9189
|
+
}
|
|
9190
|
+
};
|
|
8847
9191
|
var TransportManagedTargetLifecycle = class {
|
|
8848
9192
|
constructor(transport) {
|
|
8849
9193
|
this.transport = transport;
|
|
@@ -8851,6 +9195,12 @@ var TransportManagedTargetLifecycle = class {
|
|
|
8851
9195
|
async createTarget(params) {
|
|
8852
9196
|
return this.transport.send("Target.createTarget", params);
|
|
8853
9197
|
}
|
|
9198
|
+
async adoptTarget(targetId) {
|
|
9199
|
+
const result = await this.transport.send("Target.getTargetInfo", { targetId });
|
|
9200
|
+
if (result?.targetInfo?.targetId !== targetId) {
|
|
9201
|
+
throw new Error("Chrome returned invalid managed target metadata");
|
|
9202
|
+
}
|
|
9203
|
+
}
|
|
8854
9204
|
};
|
|
8855
9205
|
|
|
8856
9206
|
// src/services/target-inventory-service.ts
|
|
@@ -8869,6 +9219,7 @@ var TargetInventoryService = class {
|
|
|
8869
9219
|
this.onControlAcquired = options.onControlAcquired;
|
|
8870
9220
|
this.onControlReleased = options.onControlReleased;
|
|
8871
9221
|
this.isCurrentContext = options.isCurrentContext;
|
|
9222
|
+
this.profileContexts = options.profileContexts ?? new MemoryProfileContextRegistry(browserInstanceId);
|
|
8872
9223
|
}
|
|
8873
9224
|
onInvalidated;
|
|
8874
9225
|
onTargetAttached;
|
|
@@ -8876,6 +9227,7 @@ var TargetInventoryService = class {
|
|
|
8876
9227
|
onControlAcquired;
|
|
8877
9228
|
onControlReleased;
|
|
8878
9229
|
isCurrentContext;
|
|
9230
|
+
profileContexts;
|
|
8879
9231
|
refreshTails = /* @__PURE__ */ new Map();
|
|
8880
9232
|
registerManagedTarget(input) {
|
|
8881
9233
|
return this.registry.registerManaged({
|
|
@@ -8884,6 +9236,7 @@ var TargetInventoryService = class {
|
|
|
8884
9236
|
browserInstanceId: this.browserInstanceId,
|
|
8885
9237
|
browserConnectionGeneration: input.browserConnectionGeneration,
|
|
8886
9238
|
managedTabSetId: input.managedTabSetId,
|
|
9239
|
+
profileContextId: input.profileContextId,
|
|
8887
9240
|
cdpTargetId: input.cdpTargetId,
|
|
8888
9241
|
...input.openerCdpTargetId ? { openerCdpTargetId: input.openerCdpTargetId } : {},
|
|
8889
9242
|
origin: input.origin ?? "managed",
|
|
@@ -8916,7 +9269,9 @@ var TargetInventoryService = class {
|
|
|
8916
9269
|
const controlledBefore = new Map(
|
|
8917
9270
|
this.registry.activeRecords(context).filter((target) => target.controllerLeaseId).map((target) => [target.id, target])
|
|
8918
9271
|
);
|
|
8919
|
-
const liveTargets = await this.readLiveTargets(
|
|
9272
|
+
const liveTargets = await this.readLiveTargets(
|
|
9273
|
+
Number.isSafeInteger(context.browserConnectionGeneration) ? context.browserConnectionGeneration : 1
|
|
9274
|
+
);
|
|
8920
9275
|
this.assertCurrent(context);
|
|
8921
9276
|
const userTargets = (await this.policy.listUserTargets(this.browserInstanceId)).filter((target) => target.browserInstanceId === this.browserInstanceId);
|
|
8922
9277
|
this.assertCurrent(context);
|
|
@@ -8981,6 +9336,7 @@ var TargetInventoryService = class {
|
|
|
8981
9336
|
cdpTargetId: acquisition.target.cdpTargetId,
|
|
8982
9337
|
origin: acquisition.target.origin,
|
|
8983
9338
|
...acquisition.target.managedTabSetId ? { managedTabSetId: acquisition.target.managedTabSetId } : {},
|
|
9339
|
+
profileContextId: acquisition.target.profileContextId,
|
|
8984
9340
|
newlyAcquired: acquisition.newlyAcquired
|
|
8985
9341
|
};
|
|
8986
9342
|
}
|
|
@@ -9039,6 +9395,9 @@ var TargetInventoryService = class {
|
|
|
9039
9395
|
activeTarget(context) {
|
|
9040
9396
|
return this.registry.activeTarget(context, context.leaseId);
|
|
9041
9397
|
}
|
|
9398
|
+
clearActive(context) {
|
|
9399
|
+
this.registry.clearActive(context, context.leaseId);
|
|
9400
|
+
}
|
|
9042
9401
|
releaseWorkspace(context) {
|
|
9043
9402
|
const controlled = this.registry.activeRecords(context).filter((target) => target.controllerLeaseId);
|
|
9044
9403
|
const invalidated = this.registry.releaseWorkspace(context);
|
|
@@ -9048,23 +9407,42 @@ var TargetInventoryService = class {
|
|
|
9048
9407
|
this.emitInvalidations(invalidated);
|
|
9049
9408
|
return invalidated;
|
|
9050
9409
|
}
|
|
9051
|
-
async readLiveTargets() {
|
|
9410
|
+
async readLiveTargets(browserConnectionGeneration) {
|
|
9052
9411
|
const result = await this.transport.send("Target.getTargets");
|
|
9053
9412
|
if (!Array.isArray(result?.targetInfos)) {
|
|
9054
9413
|
throw new BrowserPilotError("internal_error", "Chrome returned invalid target metadata");
|
|
9055
9414
|
}
|
|
9056
|
-
const
|
|
9415
|
+
const snapshots = [];
|
|
9057
9416
|
for (const value of result.targetInfos) {
|
|
9058
9417
|
if (!value || typeof value !== "object") continue;
|
|
9059
9418
|
const targetId = readString(value.targetId);
|
|
9060
9419
|
if (!targetId) continue;
|
|
9061
9420
|
const url = readString(value.url);
|
|
9062
|
-
|
|
9421
|
+
snapshots.push({
|
|
9063
9422
|
cdpTargetId: targetId,
|
|
9064
9423
|
title: readString(value.title) ?? "",
|
|
9065
9424
|
url: url || "about:blank",
|
|
9425
|
+
type: readString(value.type) ?? "",
|
|
9426
|
+
eligible: readString(value.type) === "page",
|
|
9066
9427
|
...readString(value.openerId) ? { openerCdpTargetId: readString(value.openerId) } : {},
|
|
9067
|
-
...readString(value.
|
|
9428
|
+
...readString(value.browserContextId) ? { cdpBrowserContextId: readString(value.browserContextId) } : {}
|
|
9429
|
+
});
|
|
9430
|
+
}
|
|
9431
|
+
this.profileContexts.reconcile(browserConnectionGeneration, snapshots);
|
|
9432
|
+
const targets = /* @__PURE__ */ new Map();
|
|
9433
|
+
for (const snapshot of snapshots) {
|
|
9434
|
+
const profile = this.profileContexts.forTarget(
|
|
9435
|
+
snapshot.cdpTargetId,
|
|
9436
|
+
browserConnectionGeneration
|
|
9437
|
+
);
|
|
9438
|
+
if (!profile) continue;
|
|
9439
|
+
targets.set(snapshot.cdpTargetId, {
|
|
9440
|
+
cdpTargetId: snapshot.cdpTargetId,
|
|
9441
|
+
profileContextId: profile.id,
|
|
9442
|
+
title: snapshot.title,
|
|
9443
|
+
url: snapshot.url,
|
|
9444
|
+
...snapshot.openerCdpTargetId ? { openerCdpTargetId: snapshot.openerCdpTargetId } : {},
|
|
9445
|
+
...snapshot.type ? { type: snapshot.type } : {}
|
|
9068
9446
|
});
|
|
9069
9447
|
}
|
|
9070
9448
|
return targets;
|
|
@@ -9079,13 +9457,14 @@ var TargetInventoryService = class {
|
|
|
9079
9457
|
for (const target of liveTargets.values()) {
|
|
9080
9458
|
if (active.has(target.cdpTargetId) || target.type && target.type !== "page") continue;
|
|
9081
9459
|
const ancestor = this.findManagedAncestor(target, active, liveTargets);
|
|
9082
|
-
if (!ancestor?.managedTabSetId) continue;
|
|
9460
|
+
if (!ancestor?.managedTabSetId || ancestor.profileContextId && target.profileContextId && ancestor.profileContextId !== target.profileContextId) continue;
|
|
9083
9461
|
const record = this.registry.registerManaged({
|
|
9084
9462
|
principalId: context.principalId,
|
|
9085
9463
|
workspaceId: context.workspaceId,
|
|
9086
9464
|
browserInstanceId: this.browserInstanceId,
|
|
9087
9465
|
browserConnectionGeneration: context.browserConnectionGeneration,
|
|
9088
9466
|
managedTabSetId: ancestor.managedTabSetId,
|
|
9467
|
+
profileContextId: target.profileContextId,
|
|
9089
9468
|
cdpTargetId: target.cdpTargetId,
|
|
9090
9469
|
...target.openerCdpTargetId ? { openerCdpTargetId: target.openerCdpTargetId } : {},
|
|
9091
9470
|
origin: "managed_popup",
|
|
@@ -9322,7 +9701,7 @@ var UploadService = class {
|
|
|
9322
9701
|
};
|
|
9323
9702
|
|
|
9324
9703
|
// src/services/workspace-network-controller.ts
|
|
9325
|
-
import { randomUUID as
|
|
9704
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
9326
9705
|
var DEFAULT_MAX_REQUESTS = 1e3;
|
|
9327
9706
|
var DEFAULT_MAX_JOURNAL_BYTES = 16 * 1024 * 1024;
|
|
9328
9707
|
var MAX_RULES_PER_WORKSPACE = 256;
|
|
@@ -9450,8 +9829,8 @@ var WorkspaceNetworkController = class {
|
|
|
9450
9829
|
this.maxRequestsPerWorkspace = options.maxRequestsPerWorkspace ?? DEFAULT_MAX_REQUESTS;
|
|
9451
9830
|
this.maxJournalBytesPerWorkspace = options.maxJournalBytesPerWorkspace ?? DEFAULT_MAX_JOURNAL_BYTES;
|
|
9452
9831
|
this.now = options.now ?? Date.now;
|
|
9453
|
-
this.requestIdFactory = options.requestIdFactory ?? (() => `request:${
|
|
9454
|
-
this.ruleIdFactory = options.ruleIdFactory ?? (() => `rule:${
|
|
9832
|
+
this.requestIdFactory = options.requestIdFactory ?? (() => `request:${randomUUID10()}`);
|
|
9833
|
+
this.ruleIdFactory = options.ruleIdFactory ?? (() => `rule:${randomUUID10()}`);
|
|
9455
9834
|
this.publish = options.publishEvent;
|
|
9456
9835
|
if (!Number.isSafeInteger(this.maxRequestsPerWorkspace) || this.maxRequestsPerWorkspace < 1 || !Number.isSafeInteger(this.maxJournalBytesPerWorkspace) || this.maxJournalBytesPerWorkspace < 1) {
|
|
9457
9836
|
throw new Error("Invalid Workspace network journal capacity");
|
|
@@ -9911,6 +10290,8 @@ var WorkspaceNetworkController = class {
|
|
|
9911
10290
|
var BASE_BROWSER_TOOL_NAMES = [
|
|
9912
10291
|
"browser.discover",
|
|
9913
10292
|
"browser.connect",
|
|
10293
|
+
"browser.profiles.list",
|
|
10294
|
+
"browser.profiles.select",
|
|
9914
10295
|
"browser.open",
|
|
9915
10296
|
"browser.observe",
|
|
9916
10297
|
"browser.observation.latest",
|
|
@@ -10037,6 +10418,7 @@ var BrowserToolService = class {
|
|
|
10037
10418
|
publishEvent: (event) => this.publishEvent(event)
|
|
10038
10419
|
});
|
|
10039
10420
|
this.supportedTools = this.artifactStore ? ALL_BROWSER_TOOL_NAMES : BASE_BROWSER_TOOL_NAMES;
|
|
10421
|
+
this.profileContexts = new MemoryProfileContextRegistry(binding.instance.id);
|
|
10040
10422
|
const catalog = new CdpBrowserTargetCatalog(
|
|
10041
10423
|
transport,
|
|
10042
10424
|
binding.instance.id,
|
|
@@ -10045,6 +10427,7 @@ var BrowserToolService = class {
|
|
|
10045
10427
|
connectionGeneration: binding.instance.connectionGeneration
|
|
10046
10428
|
}),
|
|
10047
10429
|
{
|
|
10430
|
+
profileContexts: this.profileContexts,
|
|
10048
10431
|
isExcludedTarget: (target) => this.registry.isManagedCdpTarget(
|
|
10049
10432
|
binding.instance.id,
|
|
10050
10433
|
target.cdpTargetId
|
|
@@ -10058,6 +10441,7 @@ var BrowserToolService = class {
|
|
|
10058
10441
|
policy,
|
|
10059
10442
|
this.registry,
|
|
10060
10443
|
{
|
|
10444
|
+
profileContexts: this.profileContexts,
|
|
10061
10445
|
onInvalidated: (invalidation) => {
|
|
10062
10446
|
this.observations.invalidateTarget(invalidation.targetId, invalidation.reason);
|
|
10063
10447
|
this.publishEvent({
|
|
@@ -10107,6 +10491,7 @@ var BrowserToolService = class {
|
|
|
10107
10491
|
registry = new MemoryControlledTargetRegistry();
|
|
10108
10492
|
observations;
|
|
10109
10493
|
inventory;
|
|
10494
|
+
profileContexts;
|
|
10110
10495
|
sessions = /* @__PURE__ */ new Map();
|
|
10111
10496
|
guidanceBySession = /* @__PURE__ */ new Map();
|
|
10112
10497
|
managedWindowIds = /* @__PURE__ */ new Map();
|
|
@@ -10133,6 +10518,9 @@ var BrowserToolService = class {
|
|
|
10133
10518
|
this.eventPublisher = publisher;
|
|
10134
10519
|
}
|
|
10135
10520
|
browserConnectionChanged(previous, current) {
|
|
10521
|
+
if (previous.connectionGeneration !== current.connectionGeneration || current.state !== "connected") {
|
|
10522
|
+
this.profileContexts.invalidate();
|
|
10523
|
+
}
|
|
10136
10524
|
this.binding.instance = { ...current };
|
|
10137
10525
|
this.binding.candidate.state = current.state === "connected" ? "ready" : "disconnected";
|
|
10138
10526
|
if (current.state === "connected") {
|
|
@@ -10194,6 +10582,10 @@ var BrowserToolService = class {
|
|
|
10194
10582
|
return this.discover();
|
|
10195
10583
|
case "browser.connect":
|
|
10196
10584
|
return this.connect(context, args);
|
|
10585
|
+
case "browser.profiles.list":
|
|
10586
|
+
return this.listProfiles(context);
|
|
10587
|
+
case "browser.profiles.select":
|
|
10588
|
+
return this.selectProfile(context, args);
|
|
10197
10589
|
case "browser.tabs.list":
|
|
10198
10590
|
return this.listTabs(context, args);
|
|
10199
10591
|
case "browser.tabs.switch":
|
|
@@ -10312,14 +10704,14 @@ var BrowserToolService = class {
|
|
|
10312
10704
|
}
|
|
10313
10705
|
this.deleteDialogs((dialog) => dialog.leaseId === lease.id);
|
|
10314
10706
|
}
|
|
10315
|
-
releaseWorkspace(principal, workspace,
|
|
10707
|
+
releaseWorkspace(principal, workspace, managedTabSets) {
|
|
10316
10708
|
const caller = { principalId: principal.id, workspaceId: workspace.id };
|
|
10317
10709
|
const records = this.registry.activeRecords(caller);
|
|
10318
10710
|
this.watchdogs.releaseWorkspace(workspace.id);
|
|
10319
10711
|
this.downloads?.releaseWorkspace(workspace.id);
|
|
10320
10712
|
this.inventory.releaseWorkspace(caller);
|
|
10321
10713
|
this.observations.releaseWorkspace(workspace.id);
|
|
10322
|
-
this.managedWindowIds.delete(managedTabSet.id);
|
|
10714
|
+
for (const managedTabSet of managedTabSets) this.managedWindowIds.delete(managedTabSet.id);
|
|
10323
10715
|
this.network.releaseWorkspace(workspace.id);
|
|
10324
10716
|
for (const [key, session] of this.sessions) {
|
|
10325
10717
|
if (session.workspaceId !== workspace.id) continue;
|
|
@@ -10389,6 +10781,46 @@ var BrowserToolService = class {
|
|
|
10389
10781
|
targets
|
|
10390
10782
|
});
|
|
10391
10783
|
}
|
|
10784
|
+
async listProfiles(context) {
|
|
10785
|
+
const inventoryContext = this.inventoryContext(context);
|
|
10786
|
+
const targets = await this.inventory.list(inventoryContext, "all");
|
|
10787
|
+
const profiles = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
10788
|
+
return asJson2({
|
|
10789
|
+
workspaceId: inventoryContext.workspaceId,
|
|
10790
|
+
leaseId: inventoryContext.leaseId,
|
|
10791
|
+
profiles: profiles.map((profile) => ({
|
|
10792
|
+
profileContextId: profile.id,
|
|
10793
|
+
label: profile.label,
|
|
10794
|
+
...profile.displayName ? { displayName: profile.displayName } : {},
|
|
10795
|
+
tabCount: profile.tabCount,
|
|
10796
|
+
eligibleTabCount: profile.eligibleTabCount,
|
|
10797
|
+
selected: context.workspace?.selectedProfileContextId === profile.id,
|
|
10798
|
+
representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
|
|
10799
|
+
targetId: target.targetId,
|
|
10800
|
+
title: target.title,
|
|
10801
|
+
url: target.url
|
|
10802
|
+
}))
|
|
10803
|
+
}))
|
|
10804
|
+
});
|
|
10805
|
+
}
|
|
10806
|
+
async selectProfile(context, args) {
|
|
10807
|
+
const inventoryContext = this.inventoryContext(context);
|
|
10808
|
+
await this.inventory.refresh(inventoryContext);
|
|
10809
|
+
const profile = this.profileContexts.resolve(
|
|
10810
|
+
args.profileContextId,
|
|
10811
|
+
inventoryContext.browserConnectionGeneration
|
|
10812
|
+
);
|
|
10813
|
+
this.markDispatched(context);
|
|
10814
|
+
this.inventory.clearActive(inventoryContext);
|
|
10815
|
+
this.selectWorkspaceProfile(context, profile.id);
|
|
10816
|
+
return asJson2({
|
|
10817
|
+
workspaceId: inventoryContext.workspaceId,
|
|
10818
|
+
leaseId: inventoryContext.leaseId,
|
|
10819
|
+
profileContextId: profile.id,
|
|
10820
|
+
label: profile.label,
|
|
10821
|
+
...profile.displayName ? { displayName: profile.displayName } : {}
|
|
10822
|
+
});
|
|
10823
|
+
}
|
|
10392
10824
|
async switchTab(context, args) {
|
|
10393
10825
|
const inventoryContext = this.inventoryContext(context);
|
|
10394
10826
|
const targetId = args.targetId;
|
|
@@ -10397,6 +10829,7 @@ var BrowserToolService = class {
|
|
|
10397
10829
|
const resolved = await this.inventory.activate(inventoryContext, targetId);
|
|
10398
10830
|
const record = this.registry.get(inventoryContext, targetId);
|
|
10399
10831
|
await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
|
|
10832
|
+
this.selectWorkspaceProfile(context, record.profileContextId);
|
|
10400
10833
|
return asJson2(this.targetResult(context, targetId, record.url));
|
|
10401
10834
|
}
|
|
10402
10835
|
async closeTab(context) {
|
|
@@ -10498,12 +10931,25 @@ var BrowserToolService = class {
|
|
|
10498
10931
|
let targetId = requestedTargetId;
|
|
10499
10932
|
if (!targetId && !newTarget) targetId = this.inventory.activeTarget(inventoryContext)?.id;
|
|
10500
10933
|
if (!targetId) {
|
|
10934
|
+
const profile = this.resolveNewTargetProfile(context, args, inventoryContext);
|
|
10501
10935
|
this.markDispatched(context);
|
|
10502
|
-
targetId = await this.createManagedTarget(context, inventoryContext);
|
|
10936
|
+
targetId = await this.createManagedTarget(context, inventoryContext, profile);
|
|
10937
|
+
this.selectWorkspaceProfile(context, profile.id);
|
|
10938
|
+
} else if (args.profileContextId) {
|
|
10939
|
+
const target = this.registry.get(inventoryContext, targetId);
|
|
10940
|
+
const requestedProfile = this.profileContexts.resolve(
|
|
10941
|
+
args.profileContextId,
|
|
10942
|
+
inventoryContext.browserConnectionGeneration
|
|
10943
|
+
);
|
|
10944
|
+
if (requestedProfile.id !== target.profileContextId) {
|
|
10945
|
+
throw invalidArgument("profileContextId does not match the existing target", "profileContextId");
|
|
10946
|
+
}
|
|
10503
10947
|
}
|
|
10504
10948
|
const resolved = await this.inventory.resolveForOperation(inventoryContext, targetId, "page.navigate");
|
|
10505
10949
|
this.markDispatched(context);
|
|
10506
10950
|
this.registry.setActive(inventoryContext, inventoryContext.leaseId, targetId);
|
|
10951
|
+
const activeTarget = this.registry.get(inventoryContext, targetId);
|
|
10952
|
+
this.selectWorkspaceProfile(context, activeTarget.profileContextId);
|
|
10507
10953
|
const session = await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
|
|
10508
10954
|
session.activeFrame = void 0;
|
|
10509
10955
|
await Promise.all(
|
|
@@ -11225,25 +11671,48 @@ var BrowserToolService = class {
|
|
|
11225
11671
|
action: accept ? "accept" : "dismiss"
|
|
11226
11672
|
});
|
|
11227
11673
|
}
|
|
11228
|
-
async createManagedTarget(context, inventoryContext) {
|
|
11229
|
-
|
|
11674
|
+
async createManagedTarget(context, inventoryContext, profile) {
|
|
11675
|
+
if (!context.managedTabSetForProfile) {
|
|
11676
|
+
throw new BrowserPilotError("internal_error", "Broker cannot bind a ManagedTabSet to a Profile context");
|
|
11677
|
+
}
|
|
11678
|
+
const managedTabSet = context.managedTabSetForProfile(profile.id);
|
|
11230
11679
|
const windowId = this.managedWindowIds.get(managedTabSet.id);
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11680
|
+
let createdTargetId;
|
|
11681
|
+
if (windowId !== void 0) {
|
|
11682
|
+
try {
|
|
11683
|
+
createdTargetId = await this.tryCreateManagedTarget({ url: "about:blank", windowId }, profile);
|
|
11684
|
+
} catch (error) {
|
|
11685
|
+
this.managedWindowIds.delete(managedTabSet.id);
|
|
11686
|
+
if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
|
|
11687
|
+
}
|
|
11688
|
+
}
|
|
11689
|
+
if (!createdTargetId) {
|
|
11690
|
+
try {
|
|
11691
|
+
createdTargetId = await this.tryCreateManagedTarget({
|
|
11692
|
+
url: "about:blank",
|
|
11693
|
+
newWindow: true,
|
|
11694
|
+
...profile.cdpBrowserContextId ? { browserContextId: profile.cdpBrowserContextId } : {}
|
|
11695
|
+
}, profile);
|
|
11696
|
+
} catch (error) {
|
|
11697
|
+
if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
|
|
11698
|
+
}
|
|
11699
|
+
}
|
|
11700
|
+
if (!createdTargetId) {
|
|
11701
|
+
createdTargetId = await this.createManagedProfileWindow(profile);
|
|
11237
11702
|
}
|
|
11238
11703
|
try {
|
|
11239
|
-
if (
|
|
11240
|
-
const window = await this.transport.send("Browser.getWindowForTarget", { targetId:
|
|
11241
|
-
if (Number.isSafeInteger(window?.windowId))
|
|
11704
|
+
if (!this.managedWindowIds.has(managedTabSet.id)) {
|
|
11705
|
+
const window = await this.transport.send("Browser.getWindowForTarget", { targetId: createdTargetId });
|
|
11706
|
+
if (!Number.isSafeInteger(window?.windowId)) {
|
|
11707
|
+
throw new BrowserPilotError("internal_error", "Chrome returned an invalid managed window ID");
|
|
11708
|
+
}
|
|
11709
|
+
this.managedWindowIds.set(managedTabSet.id, window.windowId);
|
|
11242
11710
|
}
|
|
11243
11711
|
const registered = this.inventory.registerManagedTarget({
|
|
11244
11712
|
...inventoryContext,
|
|
11245
11713
|
managedTabSetId: managedTabSet.id,
|
|
11246
|
-
|
|
11714
|
+
profileContextId: profile.id,
|
|
11715
|
+
cdpTargetId: createdTargetId,
|
|
11247
11716
|
title: "",
|
|
11248
11717
|
url: "about:blank"
|
|
11249
11718
|
});
|
|
@@ -11251,11 +11720,244 @@ var BrowserToolService = class {
|
|
|
11251
11720
|
await this.inventory.activate(inventoryContext, registered.id);
|
|
11252
11721
|
return registered.id;
|
|
11253
11722
|
} catch (error) {
|
|
11254
|
-
await this.transport.send("Target.closeTarget", { targetId:
|
|
11723
|
+
await this.transport.send("Target.closeTarget", { targetId: createdTargetId }).catch(() => {
|
|
11255
11724
|
});
|
|
11256
11725
|
throw error;
|
|
11257
11726
|
}
|
|
11258
11727
|
}
|
|
11728
|
+
async tryCreateManagedTarget(params, profile) {
|
|
11729
|
+
let targetId;
|
|
11730
|
+
try {
|
|
11731
|
+
targetId = (await this.managedTargets.createTarget(params)).targetId;
|
|
11732
|
+
if (await this.targetMatchesProfile(targetId, profile)) return targetId;
|
|
11733
|
+
if (!await this.closeTargetAndVerify(targetId)) {
|
|
11734
|
+
throw this.managedTargetUnknownOutcome(profile);
|
|
11735
|
+
}
|
|
11736
|
+
return void 0;
|
|
11737
|
+
} catch (error) {
|
|
11738
|
+
if (!targetId) {
|
|
11739
|
+
if (error instanceof BrowserPilotError) throw error;
|
|
11740
|
+
throw error instanceof ManagedTargetCreationRejectedError ? error : new ManagedTargetCreationRejectedError(
|
|
11741
|
+
error instanceof Error ? error.message : String(error)
|
|
11742
|
+
);
|
|
11743
|
+
}
|
|
11744
|
+
if (!await this.closeTargetAndVerify(targetId)) {
|
|
11745
|
+
throw this.managedTargetUnknownOutcome(profile, error);
|
|
11746
|
+
}
|
|
11747
|
+
if (error instanceof BrowserPilotError && error.code === "unknown_outcome") throw error;
|
|
11748
|
+
return void 0;
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
async closeTargetAndVerify(targetId) {
|
|
11752
|
+
try {
|
|
11753
|
+
await this.transport.send("Target.closeTarget", { targetId });
|
|
11754
|
+
} catch {
|
|
11755
|
+
}
|
|
11756
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
11757
|
+
try {
|
|
11758
|
+
const current = await this.transport.send("Target.getTargets");
|
|
11759
|
+
if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
|
|
11760
|
+
} catch {
|
|
11761
|
+
}
|
|
11762
|
+
if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11763
|
+
}
|
|
11764
|
+
return false;
|
|
11765
|
+
}
|
|
11766
|
+
managedTargetUnknownOutcome(profile, cause) {
|
|
11767
|
+
return new BrowserPilotError(
|
|
11768
|
+
"unknown_outcome",
|
|
11769
|
+
"Managed Profile window creation could not be safely verified",
|
|
11770
|
+
{
|
|
11771
|
+
retryable: true,
|
|
11772
|
+
context: { profileContextId: profile.id },
|
|
11773
|
+
remediation: {
|
|
11774
|
+
code: "inspect_profile_tabs",
|
|
11775
|
+
message: "List tabs and Profile contexts before deciding whether to retry target creation.",
|
|
11776
|
+
actionRequired: true
|
|
11777
|
+
},
|
|
11778
|
+
...cause !== void 0 ? { cause } : {}
|
|
11779
|
+
}
|
|
11780
|
+
);
|
|
11781
|
+
}
|
|
11782
|
+
async targetMatchesProfile(targetId, profile) {
|
|
11783
|
+
let target;
|
|
11784
|
+
try {
|
|
11785
|
+
const result = await this.transport.send("Target.getTargetInfo", { targetId });
|
|
11786
|
+
target = result?.targetInfo;
|
|
11787
|
+
} catch {
|
|
11788
|
+
}
|
|
11789
|
+
if (!target || target.targetId !== targetId) {
|
|
11790
|
+
const result = await this.transport.send("Target.getTargets");
|
|
11791
|
+
target = Array.isArray(result?.targetInfos) ? result.targetInfos.find((candidate) => candidate?.targetId === targetId) : void 0;
|
|
11792
|
+
}
|
|
11793
|
+
if (!target || target.targetId !== targetId || target.type !== "page") return false;
|
|
11794
|
+
const rawContextId = typeof target.browserContextId === "string" ? target.browserContextId : void 0;
|
|
11795
|
+
return rawContextId === profile.cdpBrowserContextId;
|
|
11796
|
+
}
|
|
11797
|
+
async createManagedProfileWindow(profile) {
|
|
11798
|
+
const representative = profile.targets.find((target) => target.type === "page");
|
|
11799
|
+
if (!representative) {
|
|
11800
|
+
throw new BrowserPilotError("profile_context_unavailable", "Profile has no representative page target", {
|
|
11801
|
+
retryable: true,
|
|
11802
|
+
context: { profileContextId: profile.id },
|
|
11803
|
+
remediation: {
|
|
11804
|
+
code: "open_profile_tab",
|
|
11805
|
+
message: "Open a neutral tab in the intended Chrome Profile, then retry.",
|
|
11806
|
+
actionRequired: true
|
|
11807
|
+
}
|
|
11808
|
+
});
|
|
11809
|
+
}
|
|
11810
|
+
const attached = await this.transport.send("Target.attachToTarget", {
|
|
11811
|
+
targetId: representative.cdpTargetId,
|
|
11812
|
+
flatten: true
|
|
11813
|
+
});
|
|
11814
|
+
if (typeof attached?.sessionId !== "string") {
|
|
11815
|
+
throw new BrowserPilotError("profile_context_unavailable", "Chrome could not attach to a Profile representative", {
|
|
11816
|
+
retryable: true,
|
|
11817
|
+
context: { profileContextId: profile.id }
|
|
11818
|
+
});
|
|
11819
|
+
}
|
|
11820
|
+
const sessionId = attached.sessionId;
|
|
11821
|
+
const marker = `about:blank#browser-pilot-${randomUUID11()}`;
|
|
11822
|
+
const windowName = `browser-pilot-${randomUUID11()}`;
|
|
11823
|
+
let candidateTargetId;
|
|
11824
|
+
let adoptedTargetId;
|
|
11825
|
+
const spawnedTargetIds = /* @__PURE__ */ new Set();
|
|
11826
|
+
try {
|
|
11827
|
+
await this.transport.send("Page.enable", {}, sessionId);
|
|
11828
|
+
const frameTree = await this.transport.send("Page.getFrameTree", {}, sessionId);
|
|
11829
|
+
const frameId = frameTree?.frameTree?.frame?.id;
|
|
11830
|
+
if (typeof frameId !== "string") {
|
|
11831
|
+
throw new BrowserPilotError("profile_context_unavailable", "Profile representative has no attachable top frame", {
|
|
11832
|
+
retryable: true,
|
|
11833
|
+
context: { profileContextId: profile.id }
|
|
11834
|
+
});
|
|
11835
|
+
}
|
|
11836
|
+
const isolated = await this.transport.send("Page.createIsolatedWorld", {
|
|
11837
|
+
frameId,
|
|
11838
|
+
worldName: "browser-pilot-managed-window",
|
|
11839
|
+
grantUniveralAccess: false
|
|
11840
|
+
}, sessionId);
|
|
11841
|
+
if (!Number.isSafeInteger(isolated?.executionContextId)) {
|
|
11842
|
+
throw new BrowserPilotError("profile_context_unavailable", "Chrome could not create an isolated Profile world", {
|
|
11843
|
+
retryable: true,
|
|
11844
|
+
context: { profileContextId: profile.id }
|
|
11845
|
+
});
|
|
11846
|
+
}
|
|
11847
|
+
const before = await this.transport.send("Target.getTargets");
|
|
11848
|
+
const existing = new Set(
|
|
11849
|
+
Array.isArray(before?.targetInfos) ? before.targetInfos.filter((target) => target && typeof target === "object" && typeof target.targetId === "string").map((target) => target.targetId) : []
|
|
11850
|
+
);
|
|
11851
|
+
const expression = `window.open(${JSON.stringify(marker)}, ${JSON.stringify(windowName)}, ${JSON.stringify("popup=yes,width=1280,height=900")}) !== null`;
|
|
11852
|
+
const opened = await this.transport.send("Runtime.evaluate", {
|
|
11853
|
+
expression,
|
|
11854
|
+
contextId: isolated.executionContextId,
|
|
11855
|
+
userGesture: true,
|
|
11856
|
+
returnByValue: true
|
|
11857
|
+
}, sessionId);
|
|
11858
|
+
if (opened?.result?.value !== true) {
|
|
11859
|
+
throw new BrowserPilotError("profile_context_unavailable", "Chrome blocked managed window creation", {
|
|
11860
|
+
retryable: true,
|
|
11861
|
+
context: { profileContextId: profile.id }
|
|
11862
|
+
});
|
|
11863
|
+
}
|
|
11864
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
11865
|
+
const current = await this.transport.send("Target.getTargets");
|
|
11866
|
+
const markerTargets = Array.isArray(current?.targetInfos) ? current.targetInfos.filter((target) => target && typeof target.targetId === "string" && !existing.has(target.targetId) && target.type === "page" && target.url === marker) : [];
|
|
11867
|
+
for (const target of markerTargets) spawnedTargetIds.add(target.targetId);
|
|
11868
|
+
if (markerTargets.length > 1) {
|
|
11869
|
+
throw new BrowserPilotError("profile_context_unavailable", "Chrome created ambiguous managed window targets", {
|
|
11870
|
+
retryable: true,
|
|
11871
|
+
context: { profileContextId: profile.id }
|
|
11872
|
+
});
|
|
11873
|
+
}
|
|
11874
|
+
if (markerTargets.length === 1) {
|
|
11875
|
+
const candidate = markerTargets[0];
|
|
11876
|
+
const candidateContextId = typeof candidate.browserContextId === "string" ? candidate.browserContextId : void 0;
|
|
11877
|
+
if (candidateContextId !== profile.cdpBrowserContextId) {
|
|
11878
|
+
throw new BrowserPilotError(
|
|
11879
|
+
"profile_context_unavailable",
|
|
11880
|
+
"Chrome created the managed window in a different Profile context",
|
|
11881
|
+
{
|
|
11882
|
+
retryable: true,
|
|
11883
|
+
context: { profileContextId: profile.id },
|
|
11884
|
+
remediation: {
|
|
11885
|
+
code: "relist_profile_contexts",
|
|
11886
|
+
message: "List Profile contexts again and retry with a current Profile context.",
|
|
11887
|
+
actionRequired: true
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11890
|
+
);
|
|
11891
|
+
}
|
|
11892
|
+
if (candidate.openerId !== representative.cdpTargetId && !await this.targetWindowNameMatches(candidate.targetId, windowName)) {
|
|
11893
|
+
throw new BrowserPilotError(
|
|
11894
|
+
"profile_context_unavailable",
|
|
11895
|
+
"Chrome could not prove ownership of the managed Profile window",
|
|
11896
|
+
{
|
|
11897
|
+
retryable: true,
|
|
11898
|
+
context: { profileContextId: profile.id },
|
|
11899
|
+
remediation: {
|
|
11900
|
+
code: "relist_profile_contexts",
|
|
11901
|
+
message: "List Profile contexts again and retry with a current Profile context.",
|
|
11902
|
+
actionRequired: true
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
);
|
|
11906
|
+
}
|
|
11907
|
+
candidateTargetId = candidate.targetId;
|
|
11908
|
+
break;
|
|
11909
|
+
}
|
|
11910
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
11911
|
+
}
|
|
11912
|
+
if (!candidateTargetId) {
|
|
11913
|
+
throw this.managedTargetUnknownOutcome(profile);
|
|
11914
|
+
}
|
|
11915
|
+
try {
|
|
11916
|
+
await this.managedTargets.adoptTarget(candidateTargetId);
|
|
11917
|
+
adoptedTargetId = candidateTargetId;
|
|
11918
|
+
} catch (error) {
|
|
11919
|
+
throw error;
|
|
11920
|
+
}
|
|
11921
|
+
return candidateTargetId;
|
|
11922
|
+
} finally {
|
|
11923
|
+
const unadopted = [...spawnedTargetIds].filter((targetId) => targetId !== adoptedTargetId);
|
|
11924
|
+
const cleanup2 = await Promise.all(unadopted.map((targetId) => this.closeTargetAndVerify(targetId)));
|
|
11925
|
+
await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
|
|
11926
|
+
});
|
|
11927
|
+
if (cleanup2.some((closed) => !closed)) throw this.managedTargetUnknownOutcome(profile);
|
|
11928
|
+
}
|
|
11929
|
+
}
|
|
11930
|
+
async targetWindowNameMatches(targetId, expected) {
|
|
11931
|
+
let sessionId;
|
|
11932
|
+
try {
|
|
11933
|
+
const attached = await this.transport.send("Target.attachToTarget", {
|
|
11934
|
+
targetId,
|
|
11935
|
+
flatten: true
|
|
11936
|
+
});
|
|
11937
|
+
if (typeof attached?.sessionId !== "string") return false;
|
|
11938
|
+
sessionId = attached.sessionId;
|
|
11939
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
11940
|
+
try {
|
|
11941
|
+
const result = await this.transport.send("Runtime.evaluate", {
|
|
11942
|
+
expression: "window.name",
|
|
11943
|
+
returnByValue: true
|
|
11944
|
+
}, sessionId);
|
|
11945
|
+
if (result?.result?.value === expected) return true;
|
|
11946
|
+
if (result?.result?.value !== void 0) return false;
|
|
11947
|
+
} catch {
|
|
11948
|
+
}
|
|
11949
|
+
if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
|
|
11950
|
+
}
|
|
11951
|
+
return false;
|
|
11952
|
+
} catch {
|
|
11953
|
+
return false;
|
|
11954
|
+
} finally {
|
|
11955
|
+
if (sessionId) {
|
|
11956
|
+
await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
|
|
11957
|
+
});
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11259
11961
|
async resolveTargetSession(context, targetId, operation) {
|
|
11260
11962
|
const inventoryContext = this.inventoryContext(context);
|
|
11261
11963
|
await this.inventory.refresh(inventoryContext);
|
|
@@ -11745,13 +12447,72 @@ var BrowserToolService = class {
|
|
|
11745
12447
|
});
|
|
11746
12448
|
}
|
|
11747
12449
|
targetResult(context, targetId, url) {
|
|
12450
|
+
const target = this.registry.get({
|
|
12451
|
+
principalId: context.principal.id,
|
|
12452
|
+
workspaceId: context.workspace.id
|
|
12453
|
+
}, targetId);
|
|
11748
12454
|
return {
|
|
11749
12455
|
workspaceId: context.workspace.id,
|
|
11750
12456
|
leaseId: context.lease.id,
|
|
11751
12457
|
targetId,
|
|
12458
|
+
profileContextId: target.profileContextId,
|
|
11752
12459
|
url
|
|
11753
12460
|
};
|
|
11754
12461
|
}
|
|
12462
|
+
resolveNewTargetProfile(context, args, inventoryContext) {
|
|
12463
|
+
const explicit = args.profileContextId;
|
|
12464
|
+
if (explicit) {
|
|
12465
|
+
return this.profileContexts.resolve(explicit, inventoryContext.browserConnectionGeneration);
|
|
12466
|
+
}
|
|
12467
|
+
const active = this.inventory.activeTarget(inventoryContext);
|
|
12468
|
+
if (active) {
|
|
12469
|
+
return this.profileContexts.resolve(
|
|
12470
|
+
active.profileContextId,
|
|
12471
|
+
inventoryContext.browserConnectionGeneration
|
|
12472
|
+
);
|
|
12473
|
+
}
|
|
12474
|
+
if (context.workspace?.selectedProfileContextId) {
|
|
12475
|
+
return this.profileContexts.resolve(
|
|
12476
|
+
context.workspace.selectedProfileContextId,
|
|
12477
|
+
inventoryContext.browserConnectionGeneration
|
|
12478
|
+
);
|
|
12479
|
+
}
|
|
12480
|
+
const profiles = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
|
|
12481
|
+
if (profiles.length === 1) return profiles[0];
|
|
12482
|
+
if (profiles.length === 0) {
|
|
12483
|
+
throw new BrowserPilotError("profile_context_unavailable", "Chrome exposes no live Profile context", {
|
|
12484
|
+
retryable: true,
|
|
12485
|
+
context: { workspaceId: inventoryContext.workspaceId },
|
|
12486
|
+
remediation: {
|
|
12487
|
+
code: "open_profile_window",
|
|
12488
|
+
message: "Open a Chrome window in the intended Profile, then list Profile contexts again.",
|
|
12489
|
+
actionRequired: true
|
|
12490
|
+
}
|
|
12491
|
+
});
|
|
12492
|
+
}
|
|
12493
|
+
throw new BrowserPilotError("profile_selection_required", "Select a Chrome Profile before opening a new target", {
|
|
12494
|
+
context: {
|
|
12495
|
+
workspaceId: inventoryContext.workspaceId,
|
|
12496
|
+
profiles: profiles.map((profile) => ({
|
|
12497
|
+
profileContextId: profile.id,
|
|
12498
|
+
label: profile.label,
|
|
12499
|
+
tabCount: profile.tabCount,
|
|
12500
|
+
eligibleTabCount: profile.eligibleTabCount
|
|
12501
|
+
}))
|
|
12502
|
+
},
|
|
12503
|
+
remediation: {
|
|
12504
|
+
code: "select_profile_context",
|
|
12505
|
+
message: "List Profile contexts, ask the user which one to use, and select that context.",
|
|
12506
|
+
actionRequired: true
|
|
12507
|
+
}
|
|
12508
|
+
});
|
|
12509
|
+
}
|
|
12510
|
+
selectWorkspaceProfile(context, profileContextId) {
|
|
12511
|
+
if (!context.selectProfileContext) {
|
|
12512
|
+
throw new BrowserPilotError("internal_error", "Broker cannot update Workspace Profile selection");
|
|
12513
|
+
}
|
|
12514
|
+
context.selectProfileContext(profileContextId);
|
|
12515
|
+
}
|
|
11755
12516
|
requireWorkspaceContext(context) {
|
|
11756
12517
|
if (!context.workspace || !context.managedTabSet || !context.lease) {
|
|
11757
12518
|
throw new BrowserPilotError("internal_error", "Browser tool is missing Workspace context");
|
|
@@ -11932,7 +12693,7 @@ var BrowserToolService = class {
|
|
|
11932
12693
|
if (existing) this.removeDialog(existing);
|
|
11933
12694
|
}
|
|
11934
12695
|
const dialog = {
|
|
11935
|
-
id: `dialog:${
|
|
12696
|
+
id: `dialog:${randomUUID11()}`,
|
|
11936
12697
|
workspaceId: session.workspaceId,
|
|
11937
12698
|
leaseId: session.leaseId,
|
|
11938
12699
|
targetId: session.targetId,
|
|
@@ -12117,7 +12878,7 @@ var BrowserToolService = class {
|
|
|
12117
12878
|
let frames = this.framesBySession.get(session.sessionId);
|
|
12118
12879
|
if (!frames) {
|
|
12119
12880
|
const top2 = {
|
|
12120
|
-
id: `frame:${
|
|
12881
|
+
id: `frame:${randomUUID11()}`,
|
|
12121
12882
|
cdpFrameId: pageFrames[0].id,
|
|
12122
12883
|
sessionId: pageFrames[0].sessionId,
|
|
12123
12884
|
cdpTargetId: pageFrames[0].cdpTargetId,
|
|
@@ -12137,7 +12898,7 @@ var BrowserToolService = class {
|
|
|
12137
12898
|
let frame = frames.byCdpId.get(pageFrame.id);
|
|
12138
12899
|
if (!frame) {
|
|
12139
12900
|
frame = {
|
|
12140
|
-
id: `frame:${
|
|
12901
|
+
id: `frame:${randomUUID11()}`,
|
|
12141
12902
|
cdpFrameId: pageFrame.id,
|
|
12142
12903
|
sessionId: pageFrame.sessionId,
|
|
12143
12904
|
cdpTargetId: pageFrame.cdpTargetId,
|
|
@@ -12405,11 +13166,11 @@ var BrowserToolRouter = class {
|
|
|
12405
13166
|
releaseLease(lease) {
|
|
12406
13167
|
for (const service of this.services.values()) service.releaseLease(lease);
|
|
12407
13168
|
}
|
|
12408
|
-
releaseWorkspace(principal, workspace,
|
|
13169
|
+
releaseWorkspace(principal, workspace, managedTabSets) {
|
|
12409
13170
|
this.services.get(workspace.browserInstanceId)?.releaseWorkspace(
|
|
12410
13171
|
principal,
|
|
12411
13172
|
workspace,
|
|
12412
|
-
|
|
13173
|
+
managedTabSets
|
|
12413
13174
|
);
|
|
12414
13175
|
}
|
|
12415
13176
|
service(browserInstanceId) {
|
|
@@ -12420,7 +13181,7 @@ var BrowserToolRouter = class {
|
|
|
12420
13181
|
};
|
|
12421
13182
|
|
|
12422
13183
|
// src/services/artifact-store.ts
|
|
12423
|
-
import { randomUUID as
|
|
13184
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
12424
13185
|
import {
|
|
12425
13186
|
chmod as chmod2,
|
|
12426
13187
|
copyFile,
|
|
@@ -12482,7 +13243,7 @@ var ArtifactStore = class {
|
|
|
12482
13243
|
this.maxExpiredTombstones = options.maxExpiredTombstones ?? 4096;
|
|
12483
13244
|
this.purgeOrphansOnInitialize = options.purgeOrphansOnInitialize ?? true;
|
|
12484
13245
|
this.now = options.now ?? Date.now;
|
|
12485
|
-
this.idFactory = options.idFactory ?? (() => `artifact:${
|
|
13246
|
+
this.idFactory = options.idFactory ?? (() => `artifact:${randomUUID12()}`);
|
|
12486
13247
|
for (const value of [
|
|
12487
13248
|
this.ttlMs,
|
|
12488
13249
|
this.retainedTtlMs,
|
|
@@ -12667,7 +13428,7 @@ var ArtifactStore = class {
|
|
|
12667
13428
|
throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
|
|
12668
13429
|
}
|
|
12669
13430
|
const id = this.nextArtifactId();
|
|
12670
|
-
const storageDirectory = join5(this.directory,
|
|
13431
|
+
const storageDirectory = join5(this.directory, randomUUID12());
|
|
12671
13432
|
const destination = join5(storageDirectory, fileName);
|
|
12672
13433
|
await mkdir2(storageDirectory, { mode: 448 });
|
|
12673
13434
|
try {
|
|
@@ -12712,7 +13473,7 @@ var ArtifactStore = class {
|
|
|
12712
13473
|
this.assertQuota(input.workspaceId, byteSize);
|
|
12713
13474
|
await this.ensureDirectory();
|
|
12714
13475
|
const id = this.nextArtifactId();
|
|
12715
|
-
const file = join5(this.directory, `${
|
|
13476
|
+
const file = join5(this.directory, `${randomUUID12()}.bin`);
|
|
12716
13477
|
const createdAt = this.now();
|
|
12717
13478
|
await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
|
|
12718
13479
|
await chmod2(file, 384).catch(() => {
|
|
@@ -12852,7 +13613,7 @@ var ArtifactStore = class {
|
|
|
12852
13613
|
};
|
|
12853
13614
|
|
|
12854
13615
|
// src/services/compatibility-dialog-service.ts
|
|
12855
|
-
import { randomUUID as
|
|
13616
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
12856
13617
|
var DIALOG_TYPES2 = /* @__PURE__ */ new Set([
|
|
12857
13618
|
"alert",
|
|
12858
13619
|
"confirm",
|
|
@@ -12869,7 +13630,7 @@ var CompatibilityDialogService = class {
|
|
|
12869
13630
|
if (!type || !DIALOG_TYPES2.has(type)) return;
|
|
12870
13631
|
this.removeBySession(sessionId);
|
|
12871
13632
|
const dialog = {
|
|
12872
|
-
dialogId: `dialog:${
|
|
13633
|
+
dialogId: `dialog:${randomUUID13()}`,
|
|
12873
13634
|
sessionId,
|
|
12874
13635
|
type,
|
|
12875
13636
|
message: typeof params.message === "string" ? params.message : "",
|
|
@@ -13000,6 +13761,15 @@ var ManagedTargetJanitorClient = class {
|
|
|
13000
13761
|
this.ownedTargetIds.add(result.targetId);
|
|
13001
13762
|
return { targetId: result.targetId };
|
|
13002
13763
|
}
|
|
13764
|
+
async adoptTarget(targetId) {
|
|
13765
|
+
await this.ensureWorker();
|
|
13766
|
+
if (this.ownedTargetIds.has(targetId)) return;
|
|
13767
|
+
const result = await this.request("adopt", { targetIds: [targetId] });
|
|
13768
|
+
if (result?.adopted !== 1) {
|
|
13769
|
+
throw new BrowserPilotError("internal_error", "Managed browser connection could not adopt the target");
|
|
13770
|
+
}
|
|
13771
|
+
this.ownedTargetIds.add(targetId);
|
|
13772
|
+
}
|
|
13003
13773
|
async close() {
|
|
13004
13774
|
if (this.closed) return;
|
|
13005
13775
|
this.closed = true;
|
|
@@ -13096,7 +13866,13 @@ var ManagedTargetJanitorClient = class {
|
|
|
13096
13866
|
if (message.error && typeof message.error === "object" && !Array.isArray(message.error)) {
|
|
13097
13867
|
const error = message.error;
|
|
13098
13868
|
const description = typeof error.message === "string" ? error.message.slice(0, 1024) : "Managed browser connection failed";
|
|
13099
|
-
|
|
13869
|
+
if (error.code === "cdp_error") {
|
|
13870
|
+
pending.reject(new Error(description));
|
|
13871
|
+
} else if (error.code === "janitor_error" && pending.method === "create") {
|
|
13872
|
+
pending.reject(new ManagedTargetCreationRejectedError(description));
|
|
13873
|
+
} else {
|
|
13874
|
+
pending.reject(new BrowserPilotError("internal_error", description, { retryable: true }));
|
|
13875
|
+
}
|
|
13100
13876
|
return;
|
|
13101
13877
|
}
|
|
13102
13878
|
pending.resolve(message.result);
|
|
@@ -13134,10 +13910,14 @@ var ManagedTargetJanitorClient = class {
|
|
|
13134
13910
|
return new Promise((resolve3, reject) => {
|
|
13135
13911
|
const timer = setTimeout(() => {
|
|
13136
13912
|
this.pendingRequests.delete(id);
|
|
13137
|
-
reject(
|
|
13913
|
+
reject(method === "create" ? new BrowserPilotError(
|
|
13914
|
+
"unknown_outcome",
|
|
13915
|
+
"Managed target creation timed out before its outcome was received",
|
|
13916
|
+
{ retryable: true }
|
|
13917
|
+
) : new Error(`Managed browser connection timeout: ${method}`));
|
|
13138
13918
|
}, REQUEST_TIMEOUT_MS);
|
|
13139
13919
|
timer.unref();
|
|
13140
|
-
this.pendingRequests.set(id, { resolve: resolve3, reject, timer });
|
|
13920
|
+
this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
|
|
13141
13921
|
worker.send?.(request, (error) => {
|
|
13142
13922
|
if (!error) return;
|
|
13143
13923
|
const pending = this.pendingRequests.get(id);
|
|
@@ -13209,7 +13989,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13209
13989
|
};
|
|
13210
13990
|
|
|
13211
13991
|
// src/version.ts
|
|
13212
|
-
var BROWSER_PILOT_VERSION = "0.
|
|
13992
|
+
var BROWSER_PILOT_VERSION = "0.3.0-rc.2";
|
|
13213
13993
|
|
|
13214
13994
|
// src/daemon.ts
|
|
13215
13995
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -13372,7 +14152,7 @@ async function main() {
|
|
|
13372
14152
|
} : discoveredBrowsers[0]);
|
|
13373
14153
|
const selectedProduct = selectedBrowser?.candidate.product ?? browserProduct;
|
|
13374
14154
|
const selectedProfile = selectedBrowser?.dataDir ?? browserProfile;
|
|
13375
|
-
const browserId = selectedBrowser?.candidate.id ?? `browser:custom:${
|
|
14155
|
+
const browserId = selectedBrowser?.candidate.id ?? `browser:custom:${randomUUID14()}`;
|
|
13376
14156
|
const browserInstanceId = `browser-instance:${browserId.slice("browser:".length)}`;
|
|
13377
14157
|
const browserBinding = {
|
|
13378
14158
|
candidate: selectedBrowser ? {
|