browser-pilot-cli 0.2.2 → 0.3.0-rc.1

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/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 randomUUID13 } from "crypto";
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, { value: workspace, managedTabSet });
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
- record.managedTabSet.state = "closed";
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(record.managedTabSet)
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
- this.managedTabSetIds.delete(record.managedTabSet.id);
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 randomUUID10 } from "crypto";
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
- if (browserInstanceId !== this.browserInstanceId || !this.identity()) return [];
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 eligible = [];
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
- if (!isEligibleUserPage(candidate) || this.options.isExcludedTarget(candidate)) continue;
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 randomUUID7 } from "crypto";
7212
- function cloneRecord(record) {
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:${randomUUID7()}`);
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 cloneRecord(existing);
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 cloneRecord(record);
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
- targets.push(cloneRecord(existing));
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(cloneRecord(record));
7312
- created.push(cloneRecord(record));
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 cloneRecord(record);
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: cloneRecord(record), newlyAcquired };
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(cloneRecord);
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 cloneRecord(record);
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(cloneRecord);
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(cloneRecord);
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 randomUUID8 } from "crypto";
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:${randomUUID8()}`);
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 targets = /* @__PURE__ */ new Map();
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
- targets.set(targetId, {
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.type) ? { type: readString(value.type) } : {}
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 randomUUID9 } from "crypto";
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:${randomUUID9()}`);
9454
- this.ruleIdFactory = options.ruleIdFactory ?? (() => `rule:${randomUUID9()}`);
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, managedTabSet) {
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
- const { managedTabSet } = this.requireWorkspaceContext(context);
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
- const created = await this.managedTargets.createTarget({
11232
- url: "about:blank",
11233
- ...windowId !== void 0 ? { windowId } : { newWindow: true }
11234
- });
11235
- if (typeof created?.targetId !== "string") {
11236
- throw new BrowserPilotError("internal_error", "Chrome returned an invalid target ID");
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 (windowId === void 0) {
11240
- const window = await this.transport.send("Browser.getWindowForTarget", { targetId: created.targetId });
11241
- if (Number.isSafeInteger(window?.windowId)) this.managedWindowIds.set(managedTabSet.id, 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
- cdpTargetId: created.targetId,
11714
+ profileContextId: profile.id,
11715
+ cdpTargetId: createdTargetId,
11247
11716
  title: "",
11248
11717
  url: "about:blank"
11249
11718
  });
@@ -11251,11 +11720,198 @@ 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: created.targetId }).catch(() => {
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 < 5; 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 < 4) 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 < 40; 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.openerId === representative.cdpTargetId && 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
+ candidateTargetId = candidate.targetId;
11893
+ break;
11894
+ }
11895
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
11896
+ }
11897
+ if (!candidateTargetId) {
11898
+ throw this.managedTargetUnknownOutcome(profile);
11899
+ }
11900
+ try {
11901
+ await this.managedTargets.adoptTarget(candidateTargetId);
11902
+ adoptedTargetId = candidateTargetId;
11903
+ } catch (error) {
11904
+ throw error;
11905
+ }
11906
+ return candidateTargetId;
11907
+ } finally {
11908
+ const unadopted = [...spawnedTargetIds].filter((targetId) => targetId !== adoptedTargetId);
11909
+ const cleanup2 = await Promise.all(unadopted.map((targetId) => this.closeTargetAndVerify(targetId)));
11910
+ await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
11911
+ });
11912
+ if (cleanup2.some((closed) => !closed)) throw this.managedTargetUnknownOutcome(profile);
11913
+ }
11914
+ }
11259
11915
  async resolveTargetSession(context, targetId, operation) {
11260
11916
  const inventoryContext = this.inventoryContext(context);
11261
11917
  await this.inventory.refresh(inventoryContext);
@@ -11745,13 +12401,72 @@ var BrowserToolService = class {
11745
12401
  });
11746
12402
  }
11747
12403
  targetResult(context, targetId, url) {
12404
+ const target = this.registry.get({
12405
+ principalId: context.principal.id,
12406
+ workspaceId: context.workspace.id
12407
+ }, targetId);
11748
12408
  return {
11749
12409
  workspaceId: context.workspace.id,
11750
12410
  leaseId: context.lease.id,
11751
12411
  targetId,
12412
+ profileContextId: target.profileContextId,
11752
12413
  url
11753
12414
  };
11754
12415
  }
12416
+ resolveNewTargetProfile(context, args, inventoryContext) {
12417
+ const explicit = args.profileContextId;
12418
+ if (explicit) {
12419
+ return this.profileContexts.resolve(explicit, inventoryContext.browserConnectionGeneration);
12420
+ }
12421
+ const active = this.inventory.activeTarget(inventoryContext);
12422
+ if (active) {
12423
+ return this.profileContexts.resolve(
12424
+ active.profileContextId,
12425
+ inventoryContext.browserConnectionGeneration
12426
+ );
12427
+ }
12428
+ if (context.workspace?.selectedProfileContextId) {
12429
+ return this.profileContexts.resolve(
12430
+ context.workspace.selectedProfileContextId,
12431
+ inventoryContext.browserConnectionGeneration
12432
+ );
12433
+ }
12434
+ const profiles = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
12435
+ if (profiles.length === 1) return profiles[0];
12436
+ if (profiles.length === 0) {
12437
+ throw new BrowserPilotError("profile_context_unavailable", "Chrome exposes no live Profile context", {
12438
+ retryable: true,
12439
+ context: { workspaceId: inventoryContext.workspaceId },
12440
+ remediation: {
12441
+ code: "open_profile_window",
12442
+ message: "Open a Chrome window in the intended Profile, then list Profile contexts again.",
12443
+ actionRequired: true
12444
+ }
12445
+ });
12446
+ }
12447
+ throw new BrowserPilotError("profile_selection_required", "Select a Chrome Profile before opening a new target", {
12448
+ context: {
12449
+ workspaceId: inventoryContext.workspaceId,
12450
+ profiles: profiles.map((profile) => ({
12451
+ profileContextId: profile.id,
12452
+ label: profile.label,
12453
+ tabCount: profile.tabCount,
12454
+ eligibleTabCount: profile.eligibleTabCount
12455
+ }))
12456
+ },
12457
+ remediation: {
12458
+ code: "select_profile_context",
12459
+ message: "List Profile contexts, ask the user which one to use, and select that context.",
12460
+ actionRequired: true
12461
+ }
12462
+ });
12463
+ }
12464
+ selectWorkspaceProfile(context, profileContextId) {
12465
+ if (!context.selectProfileContext) {
12466
+ throw new BrowserPilotError("internal_error", "Broker cannot update Workspace Profile selection");
12467
+ }
12468
+ context.selectProfileContext(profileContextId);
12469
+ }
11755
12470
  requireWorkspaceContext(context) {
11756
12471
  if (!context.workspace || !context.managedTabSet || !context.lease) {
11757
12472
  throw new BrowserPilotError("internal_error", "Browser tool is missing Workspace context");
@@ -11932,7 +12647,7 @@ var BrowserToolService = class {
11932
12647
  if (existing) this.removeDialog(existing);
11933
12648
  }
11934
12649
  const dialog = {
11935
- id: `dialog:${randomUUID10()}`,
12650
+ id: `dialog:${randomUUID11()}`,
11936
12651
  workspaceId: session.workspaceId,
11937
12652
  leaseId: session.leaseId,
11938
12653
  targetId: session.targetId,
@@ -12117,7 +12832,7 @@ var BrowserToolService = class {
12117
12832
  let frames = this.framesBySession.get(session.sessionId);
12118
12833
  if (!frames) {
12119
12834
  const top2 = {
12120
- id: `frame:${randomUUID10()}`,
12835
+ id: `frame:${randomUUID11()}`,
12121
12836
  cdpFrameId: pageFrames[0].id,
12122
12837
  sessionId: pageFrames[0].sessionId,
12123
12838
  cdpTargetId: pageFrames[0].cdpTargetId,
@@ -12137,7 +12852,7 @@ var BrowserToolService = class {
12137
12852
  let frame = frames.byCdpId.get(pageFrame.id);
12138
12853
  if (!frame) {
12139
12854
  frame = {
12140
- id: `frame:${randomUUID10()}`,
12855
+ id: `frame:${randomUUID11()}`,
12141
12856
  cdpFrameId: pageFrame.id,
12142
12857
  sessionId: pageFrame.sessionId,
12143
12858
  cdpTargetId: pageFrame.cdpTargetId,
@@ -12405,11 +13120,11 @@ var BrowserToolRouter = class {
12405
13120
  releaseLease(lease) {
12406
13121
  for (const service of this.services.values()) service.releaseLease(lease);
12407
13122
  }
12408
- releaseWorkspace(principal, workspace, managedTabSet) {
13123
+ releaseWorkspace(principal, workspace, managedTabSets) {
12409
13124
  this.services.get(workspace.browserInstanceId)?.releaseWorkspace(
12410
13125
  principal,
12411
13126
  workspace,
12412
- managedTabSet
13127
+ managedTabSets
12413
13128
  );
12414
13129
  }
12415
13130
  service(browserInstanceId) {
@@ -12420,7 +13135,7 @@ var BrowserToolRouter = class {
12420
13135
  };
12421
13136
 
12422
13137
  // src/services/artifact-store.ts
12423
- import { randomUUID as randomUUID11 } from "crypto";
13138
+ import { randomUUID as randomUUID12 } from "crypto";
12424
13139
  import {
12425
13140
  chmod as chmod2,
12426
13141
  copyFile,
@@ -12482,7 +13197,7 @@ var ArtifactStore = class {
12482
13197
  this.maxExpiredTombstones = options.maxExpiredTombstones ?? 4096;
12483
13198
  this.purgeOrphansOnInitialize = options.purgeOrphansOnInitialize ?? true;
12484
13199
  this.now = options.now ?? Date.now;
12485
- this.idFactory = options.idFactory ?? (() => `artifact:${randomUUID11()}`);
13200
+ this.idFactory = options.idFactory ?? (() => `artifact:${randomUUID12()}`);
12486
13201
  for (const value of [
12487
13202
  this.ttlMs,
12488
13203
  this.retainedTtlMs,
@@ -12667,7 +13382,7 @@ var ArtifactStore = class {
12667
13382
  throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
12668
13383
  }
12669
13384
  const id = this.nextArtifactId();
12670
- const storageDirectory = join5(this.directory, randomUUID11());
13385
+ const storageDirectory = join5(this.directory, randomUUID12());
12671
13386
  const destination = join5(storageDirectory, fileName);
12672
13387
  await mkdir2(storageDirectory, { mode: 448 });
12673
13388
  try {
@@ -12712,7 +13427,7 @@ var ArtifactStore = class {
12712
13427
  this.assertQuota(input.workspaceId, byteSize);
12713
13428
  await this.ensureDirectory();
12714
13429
  const id = this.nextArtifactId();
12715
- const file = join5(this.directory, `${randomUUID11()}.bin`);
13430
+ const file = join5(this.directory, `${randomUUID12()}.bin`);
12716
13431
  const createdAt = this.now();
12717
13432
  await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
12718
13433
  await chmod2(file, 384).catch(() => {
@@ -12852,7 +13567,7 @@ var ArtifactStore = class {
12852
13567
  };
12853
13568
 
12854
13569
  // src/services/compatibility-dialog-service.ts
12855
- import { randomUUID as randomUUID12 } from "crypto";
13570
+ import { randomUUID as randomUUID13 } from "crypto";
12856
13571
  var DIALOG_TYPES2 = /* @__PURE__ */ new Set([
12857
13572
  "alert",
12858
13573
  "confirm",
@@ -12869,7 +13584,7 @@ var CompatibilityDialogService = class {
12869
13584
  if (!type || !DIALOG_TYPES2.has(type)) return;
12870
13585
  this.removeBySession(sessionId);
12871
13586
  const dialog = {
12872
- dialogId: `dialog:${randomUUID12()}`,
13587
+ dialogId: `dialog:${randomUUID13()}`,
12873
13588
  sessionId,
12874
13589
  type,
12875
13590
  message: typeof params.message === "string" ? params.message : "",
@@ -13000,6 +13715,15 @@ var ManagedTargetJanitorClient = class {
13000
13715
  this.ownedTargetIds.add(result.targetId);
13001
13716
  return { targetId: result.targetId };
13002
13717
  }
13718
+ async adoptTarget(targetId) {
13719
+ await this.ensureWorker();
13720
+ if (this.ownedTargetIds.has(targetId)) return;
13721
+ const result = await this.request("adopt", { targetIds: [targetId] });
13722
+ if (result?.adopted !== 1) {
13723
+ throw new BrowserPilotError("internal_error", "Managed browser connection could not adopt the target");
13724
+ }
13725
+ this.ownedTargetIds.add(targetId);
13726
+ }
13003
13727
  async close() {
13004
13728
  if (this.closed) return;
13005
13729
  this.closed = true;
@@ -13096,7 +13820,13 @@ var ManagedTargetJanitorClient = class {
13096
13820
  if (message.error && typeof message.error === "object" && !Array.isArray(message.error)) {
13097
13821
  const error = message.error;
13098
13822
  const description = typeof error.message === "string" ? error.message.slice(0, 1024) : "Managed browser connection failed";
13099
- pending.reject(error.code === "cdp_error" ? new Error(description) : new BrowserPilotError("browser_disconnected", description, { retryable: true }));
13823
+ if (error.code === "cdp_error") {
13824
+ pending.reject(new Error(description));
13825
+ } else if (error.code === "janitor_error" && pending.method === "create") {
13826
+ pending.reject(new ManagedTargetCreationRejectedError(description));
13827
+ } else {
13828
+ pending.reject(new BrowserPilotError("internal_error", description, { retryable: true }));
13829
+ }
13100
13830
  return;
13101
13831
  }
13102
13832
  pending.resolve(message.result);
@@ -13134,10 +13864,14 @@ var ManagedTargetJanitorClient = class {
13134
13864
  return new Promise((resolve3, reject) => {
13135
13865
  const timer = setTimeout(() => {
13136
13866
  this.pendingRequests.delete(id);
13137
- reject(new Error(`Managed browser connection timeout: ${method}`));
13867
+ reject(method === "create" ? new BrowserPilotError(
13868
+ "unknown_outcome",
13869
+ "Managed target creation timed out before its outcome was received",
13870
+ { retryable: true }
13871
+ ) : new Error(`Managed browser connection timeout: ${method}`));
13138
13872
  }, REQUEST_TIMEOUT_MS);
13139
13873
  timer.unref();
13140
- this.pendingRequests.set(id, { resolve: resolve3, reject, timer });
13874
+ this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
13141
13875
  worker.send?.(request, (error) => {
13142
13876
  if (!error) return;
13143
13877
  const pending = this.pendingRequests.get(id);
@@ -13209,7 +13943,7 @@ var ManagedTargetJanitorClient = class {
13209
13943
  };
13210
13944
 
13211
13945
  // src/version.ts
13212
- var BROWSER_PILOT_VERSION = "0.2.2";
13946
+ var BROWSER_PILOT_VERSION = "0.3.0-rc.1";
13213
13947
 
13214
13948
  // src/daemon.ts
13215
13949
  var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
@@ -13372,7 +14106,7 @@ async function main() {
13372
14106
  } : discoveredBrowsers[0]);
13373
14107
  const selectedProduct = selectedBrowser?.candidate.product ?? browserProduct;
13374
14108
  const selectedProfile = selectedBrowser?.dataDir ?? browserProfile;
13375
- const browserId = selectedBrowser?.candidate.id ?? `browser:custom:${randomUUID13()}`;
14109
+ const browserId = selectedBrowser?.candidate.id ?? `browser:custom:${randomUUID14()}`;
13376
14110
  const browserInstanceId = `browser-instance:${browserId.slice("browser:".length)}`;
13377
14111
  const browserBinding = {
13378
14112
  candidate: selectedBrowser ? {