browser-pilot-cli 0.4.0 → 0.5.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
@@ -291,6 +291,7 @@ var ERROR_CODES = [
291
291
  "action_not_verified",
292
292
  "command_cancelled",
293
293
  "command_expired",
294
+ "wait_timeout",
294
295
  "unknown_outcome",
295
296
  "artifact_not_found",
296
297
  "artifact_expired",
@@ -1896,7 +1897,7 @@ function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
1896
1897
  }
1897
1898
  return result;
1898
1899
  }
1899
- function assertToolManifest(definitions = TOOL_DEFINITIONS) {
1900
+ function assertToolDefinitions(definitions = TOOL_DEFINITIONS) {
1900
1901
  const names = /* @__PURE__ */ new Set();
1901
1902
  const knownCapabilities = new Set(CAPABILITIES);
1902
1903
  for (const definition of definitions) {
@@ -1960,13 +1961,7 @@ function validateToolResult(name, value) {
1960
1961
  assertSchemaValue(definition.outputSchema, value, `${name} result`);
1961
1962
  return value;
1962
1963
  }
1963
- function getToolManifest(capabilities, availableTools, protocol) {
1964
- const granted = capabilities ? new Set(capabilities) : null;
1965
- const available = availableTools ? new Set(availableTools) : null;
1966
- const tools = TOOL_DEFINITIONS.filter((definition) => (!granted || definition.requiredCapabilities.every((capability) => granted.has(capability))) && (!available || available.has(definition.name)) && (!protocol || isToolAvailableInProtocol(definition.name, protocol)));
1967
- return { schemaVersion: 1, tools };
1968
- }
1969
- assertToolManifest();
1964
+ assertToolDefinitions();
1970
1965
 
1971
1966
  // src/protocol/validation.ts
1972
1967
  var CLIENT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{2,127}$/;
@@ -1978,6 +1973,21 @@ var TARGET_ID_PATTERN = /^target:[A-Za-z0-9._:-]+$/;
1978
1973
  var ARTIFACT_ID_PATTERN = /^artifact:[A-Za-z0-9._:-]+$/;
1979
1974
  var COMMAND_ID_PATTERN = /^command:[A-Za-z0-9._:-]+$/;
1980
1975
  var EVENT_CURSOR_PATTERN = /^cursor:(0|[1-9][0-9]{0,15})$/;
1976
+ var COMMAND_STATUSES = /* @__PURE__ */ new Set([
1977
+ "accepted",
1978
+ "dispatched",
1979
+ "completed",
1980
+ "unknown_outcome",
1981
+ "cancelled",
1982
+ "expired"
1983
+ ]);
1984
+ var ARTIFACT_KINDS = /* @__PURE__ */ new Set([
1985
+ "screenshot",
1986
+ "screenshot_preview",
1987
+ "pdf",
1988
+ "download",
1989
+ "upload_input"
1990
+ ]);
1981
1991
  var MIN_NEGOTIATED_TRANSPORT_BYTES = 64 * 1024;
1982
1992
  var MAX_DECLARED_TRANSPORT_BYTES = 1024 * 1024 * 1024;
1983
1993
  function isRecord2(value) {
@@ -2103,17 +2113,13 @@ function parseTransportLimitPreferences(value) {
2103
2113
  }
2104
2114
  function validateInitializeParams(value) {
2105
2115
  if (!isRecord2(value)) throw invalidArgument("initialize params must be an object", "params");
2106
- assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "launchMode", "limits"]);
2116
+ assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "limits"]);
2107
2117
  if (!isRecord2(value.client)) throw invalidArgument("client must be an object", "client");
2108
2118
  assertOnlyKeys(value.client, ["id", "name", "version", "instanceId"], "client");
2109
2119
  const requested = value.requestedCapabilities;
2110
2120
  if (!Array.isArray(requested) || requested.some((item) => typeof item !== "string" || item.length === 0 || item.length > 128)) {
2111
2121
  throw invalidArgument("requestedCapabilities must be an array of non-empty strings", "requestedCapabilities");
2112
2122
  }
2113
- const launchMode = value.launchMode;
2114
- if (launchMode !== "one-shot" && launchMode !== "embedded") {
2115
- throw invalidArgument("launchMode must be one-shot or embedded", "launchMode");
2116
- }
2117
2123
  return {
2118
2124
  client: {
2119
2125
  id: requireString(value.client, "id", { pattern: CLIENT_ID_PATTERN, maxLength: 128 }),
@@ -2123,7 +2129,6 @@ function validateInitializeParams(value) {
2123
2129
  },
2124
2130
  protocol: parseProtocolRange(value.protocol),
2125
2131
  requestedCapabilities: [...new Set(requested)],
2126
- launchMode,
2127
2132
  ...value.limits !== void 0 ? { limits: parseTransportLimitPreferences(value.limits) } : {}
2128
2133
  };
2129
2134
  }
@@ -2143,9 +2148,6 @@ function validateTtlMs(record2) {
2143
2148
  function validateOpaqueId(record2, key, pattern) {
2144
2149
  return requireString(record2, key, { pattern, maxLength: 128 });
2145
2150
  }
2146
- function validateToolsListParams(value) {
2147
- return validateOptionalEmptyParams(value);
2148
- }
2149
2151
  function validateShutdownParams(value) {
2150
2152
  return validateOptionalEmptyParams(value);
2151
2153
  }
@@ -2201,6 +2203,25 @@ function validateCommandAccessParams(value) {
2201
2203
  } : {}
2202
2204
  };
2203
2205
  }
2206
+ function validateCommandListParams(value) {
2207
+ if (!isRecord2(value)) throw invalidArgument("Command list params must be an object", "params");
2208
+ assertOnlyKeys(value, ["workspaceId", "limit", "statuses"]);
2209
+ if (value.limit !== void 0 && (!Number.isSafeInteger(value.limit) || Number(value.limit) < 1 || Number(value.limit) > 100)) {
2210
+ throw invalidArgument("limit must be an integer from 1 through 100", "limit");
2211
+ }
2212
+ if (value.statuses !== void 0 && (!Array.isArray(value.statuses) || value.statuses.length === 0 || value.statuses.some((status) => typeof status !== "string" || !COMMAND_STATUSES.has(status)))) {
2213
+ throw invalidArgument("statuses must contain valid Command statuses", "statuses");
2214
+ }
2215
+ return {
2216
+ workspaceId: validateOpaqueId(
2217
+ value,
2218
+ "workspaceId",
2219
+ WORKSPACE_ID_PATTERN
2220
+ ),
2221
+ ...value.limit !== void 0 ? { limit: Number(value.limit) } : {},
2222
+ ...value.statuses !== void 0 ? { statuses: value.statuses } : {}
2223
+ };
2224
+ }
2204
2225
  function validateEventsPollParams(value) {
2205
2226
  if (!isRecord2(value)) throw invalidArgument("events/poll params must be an object", "params");
2206
2227
  assertOnlyKeys(value, ["workspaceId", "cursor", "limit"]);
@@ -2226,6 +2247,18 @@ function validateArtifactAccessParams(value) {
2226
2247
  artifactId: validateOpaqueId(value, "artifactId", ARTIFACT_ID_PATTERN)
2227
2248
  };
2228
2249
  }
2250
+ function validateArtifactListParams(value) {
2251
+ if (!isRecord2(value)) throw invalidArgument("Artifact list params must be an object", "params");
2252
+ assertOnlyKeys(value, ["workspaceId", "leaseId", "kinds"]);
2253
+ if (value.kinds !== void 0 && (!Array.isArray(value.kinds) || value.kinds.length === 0 || value.kinds.some((kind) => typeof kind !== "string" || !ARTIFACT_KINDS.has(kind)))) {
2254
+ throw invalidArgument("kinds must contain valid Artifact kinds", "kinds");
2255
+ }
2256
+ return {
2257
+ workspaceId: validateOpaqueId(value, "workspaceId", WORKSPACE_ID_PATTERN),
2258
+ leaseId: validateOpaqueId(value, "leaseId", LEASE_ID_PATTERN),
2259
+ ...value.kinds !== void 0 ? { kinds: value.kinds } : {}
2260
+ };
2261
+ }
2229
2262
  function validateArtifactExportParams(value) {
2230
2263
  if (!isRecord2(value)) throw invalidArgument("artifacts/export params must be an object", "params");
2231
2264
  assertOnlyKeys(value, ["workspaceId", "leaseId", "artifactId", "path", "overwrite"]);
@@ -2437,6 +2470,11 @@ var MemoryCommandRuntime = class {
2437
2470
  this.sweep();
2438
2471
  return this.outcome(this.requireOwned(context));
2439
2472
  }
2473
+ list(context) {
2474
+ this.sweep();
2475
+ const statuses = context.statuses ? new Set(context.statuses) : void 0;
2476
+ return [...this.records.values()].filter((record2) => record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && (!statuses || statuses.has(record2.status))).sort((left, right) => right.acceptedAt - left.acceptedAt).slice(0, context.limit ?? 20).map(descriptor);
2477
+ }
2440
2478
  cancel(context) {
2441
2479
  this.sweep();
2442
2480
  const record2 = this.requireOwned(context);
@@ -2899,7 +2937,6 @@ var MemoryBrokerRuntime = class {
2899
2937
  preserveIfGenerationStale: true
2900
2938
  });
2901
2939
  }));
2902
- this.listenerCleanup.push(this.events.subscribe((event) => this.deliverNotification(event)));
2903
2940
  options.toolExecutor?.setEventPublisher?.((event) => {
2904
2941
  this.publishBrowserEvent(event);
2905
2942
  });
@@ -2936,7 +2973,7 @@ var MemoryBrokerRuntime = class {
2936
2973
  limits;
2937
2974
  principals = /* @__PURE__ */ new Map();
2938
2975
  principalIdsByKey = /* @__PURE__ */ new Map();
2939
- connectionsByBridge = /* @__PURE__ */ new Map();
2976
+ connectionsByClientSession = /* @__PURE__ */ new Map();
2940
2977
  connectionsById = /* @__PURE__ */ new Map();
2941
2978
  workspaces = /* @__PURE__ */ new Map();
2942
2979
  leases = /* @__PURE__ */ new Map();
@@ -2959,30 +2996,27 @@ var MemoryBrokerRuntime = class {
2959
2996
  events;
2960
2997
  listenerCleanup = [];
2961
2998
  workspaceCleanup = /* @__PURE__ */ new Map();
2962
- async call(bridgeSessionId, method, params) {
2963
- this.assertBridgeSessionId(bridgeSessionId);
2999
+ async call(clientSessionId, method, params) {
3000
+ this.assertClientSessionId(clientSessionId);
2964
3001
  this.sweepExpiredLeases();
2965
- if (method === "initialize") return this.initialize(bridgeSessionId, params);
2966
- const connection = this.requireConnection(bridgeSessionId);
3002
+ if (method === "initialize") return this.initialize(clientSessionId, params);
3003
+ const connection = this.requireConnection(clientSessionId);
2967
3004
  connection.value.lastSeenAt = this.now();
2968
3005
  switch (method) {
2969
- case "tools/list":
2970
- validateToolsListParams(params);
2971
- return asJson(getToolManifest(
2972
- connection.grantedCapabilities,
2973
- this.options.toolExecutor?.supportedTools,
2974
- connection.value.protocol
2975
- ));
2976
3006
  case "tools/call":
2977
3007
  return this.callTool(connection, params);
2978
3008
  case "commands/get":
2979
3009
  return this.getCommand(connection, params);
3010
+ case "commands/list":
3011
+ return this.listCommands(connection, params);
2980
3012
  case "commands/cancel":
2981
3013
  return this.cancelCommand(connection, params);
2982
3014
  case "events/poll":
2983
3015
  return this.pollEvents(connection, params);
2984
3016
  case "artifacts/get":
2985
3017
  return this.getArtifact(connection, params);
3018
+ case "artifacts/list":
3019
+ return this.listArtifacts(connection, params);
2986
3020
  case "artifacts/export":
2987
3021
  return this.exportArtifact(connection, params);
2988
3022
  case "artifacts/import":
@@ -3013,12 +3047,11 @@ var MemoryBrokerRuntime = class {
3013
3047
  });
3014
3048
  }
3015
3049
  }
3016
- disconnect(bridgeSessionId) {
3017
- const connection = this.connectionsByBridge.get(bridgeSessionId);
3050
+ disconnect(clientSessionId) {
3051
+ const connection = this.connectionsByClientSession.get(clientSessionId);
3018
3052
  if (!connection) return;
3019
3053
  this.commands.releaseConnection(connection.value.id);
3020
- this.resolveNotificationWaiter(connection, void 0);
3021
- this.connectionsByBridge.delete(bridgeSessionId);
3054
+ this.connectionsByClientSession.delete(clientSessionId);
3022
3055
  this.connectionsById.delete(connection.value.id);
3023
3056
  for (const [leaseId, lease] of this.leases) {
3024
3057
  if (lease.connectionId !== connection.value.id) continue;
@@ -3028,40 +3061,11 @@ var MemoryBrokerRuntime = class {
3028
3061
  this.cleanupUnusedPrincipal(connection.value.principalId);
3029
3062
  }
3030
3063
  close() {
3031
- for (const bridgeSessionId of [...this.connectionsByBridge.keys()]) {
3032
- this.disconnect(bridgeSessionId);
3064
+ for (const clientSessionId of [...this.connectionsByClientSession.keys()]) {
3065
+ this.disconnect(clientSessionId);
3033
3066
  }
3034
3067
  for (const cleanup2 of this.listenerCleanup.splice(0)) cleanup2();
3035
3068
  }
3036
- async nextNotification(bridgeSessionId, options = {}) {
3037
- const connection = this.requireConnection(bridgeSessionId);
3038
- const waitMs = options.waitMs ?? 25e3;
3039
- if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > 3e4) {
3040
- throw invalidArgument("Notification waitMs must be from 0 through 30000", "waitMs");
3041
- }
3042
- const queued = connection.notifications.shift();
3043
- if (queued || waitMs === 0 || options.signal?.aborted) return queued;
3044
- if (connection.notificationWaiter) {
3045
- throw invalidArgument("Only one notification poll may be active per bridge Connection");
3046
- }
3047
- return new Promise((resolve3) => {
3048
- const waiter = { resolve: resolve3, signal: options.signal };
3049
- const finish = () => this.resolveNotificationWaiter(connection, void 0);
3050
- waiter.timer = setTimeout(finish, waitMs);
3051
- waiter.timer.unref();
3052
- if (options.signal) {
3053
- waiter.onAbort = finish;
3054
- options.signal.addEventListener("abort", finish, { once: true });
3055
- }
3056
- connection.notificationWaiter = waiter;
3057
- });
3058
- }
3059
- async *notifications(bridgeSessionId, signal) {
3060
- while (!signal.aborted) {
3061
- const notification = await this.nextNotification(bridgeSessionId, { waitMs: 3e4, signal });
3062
- if (notification) yield notification;
3063
- }
3064
- }
3065
3069
  publishBrowserEvent(input) {
3066
3070
  const workspace = this.workspaces.get(input.workspaceId);
3067
3071
  if (!workspace || workspace.value.state !== "active") return void 0;
@@ -3159,9 +3163,9 @@ var MemoryBrokerRuntime = class {
3159
3163
  expired += 1;
3160
3164
  }
3161
3165
  }
3162
- for (const connection of [...this.connectionsByBridge.values()]) {
3166
+ for (const connection of [...this.connectionsByClientSession.values()]) {
3163
3167
  if (connection.value.lastSeenAt + this.connectionIdleTtlMs <= now) {
3164
- this.disconnect(connection.bridgeSessionId);
3168
+ this.disconnect(connection.clientSessionId);
3165
3169
  }
3166
3170
  }
3167
3171
  const leasedWorkspaceIds = /* @__PURE__ */ new Set();
@@ -3178,32 +3182,25 @@ var MemoryBrokerRuntime = class {
3178
3182
  stats() {
3179
3183
  return {
3180
3184
  principals: this.principals.size,
3181
- connections: this.connectionsByBridge.size,
3185
+ connections: this.connectionsByClientSession.size,
3182
3186
  activeWorkspaces: [...this.workspaces.values()].filter((record2) => record2.value.state === "active").length,
3183
3187
  activeLeases: [...this.leases.values()].filter((lease) => lease.state === "active").length
3184
3188
  };
3185
3189
  }
3186
3190
  lifecycleSummary() {
3187
- let embeddedConnections = 0;
3188
- let oneShotConnections = 0;
3189
- for (const connection of this.connectionsByBridge.values()) {
3190
- if (connection.value.launchMode === "embedded") embeddedConnections += 1;
3191
- else oneShotConnections += 1;
3192
- }
3193
3191
  const stats = this.stats();
3194
3192
  return {
3195
- embeddedConnections,
3196
- oneShotConnections,
3193
+ connections: stats.connections,
3197
3194
  activeWorkspaces: stats.activeWorkspaces,
3198
3195
  activeLeases: stats.activeLeases
3199
3196
  };
3200
3197
  }
3201
- initialize(bridgeSessionId, value) {
3198
+ initialize(clientSessionId, value) {
3202
3199
  const params = validateInitializeParams(value);
3203
- const existing = this.connectionsByBridge.get(bridgeSessionId);
3200
+ const existing = this.connectionsByClientSession.get(clientSessionId);
3204
3201
  if (existing) {
3205
- if (params.launchMode !== "one-shot" || existing.value.launchMode !== "one-shot" || existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
3206
- throw invalidArgument("This bridge connection is already initialized", "method");
3202
+ if (existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
3203
+ throw invalidArgument("This client session is already initialized", "method");
3207
3204
  }
3208
3205
  const protocol2 = negotiateProtocol(params.protocol);
3209
3206
  if (params.limits && (protocol2.major < 1 || protocol2.major === 1 && protocol2.minor < 1)) {
@@ -3214,10 +3211,10 @@ var MemoryBrokerRuntime = class {
3214
3211
  const capabilities2 = negotiateCapabilities(params.requestedCapabilities, this.allowedCapabilities);
3215
3212
  const limits2 = negotiateProtocolLimits(params.limits, this.limits);
3216
3213
  if (protocol2.major !== existing.value.protocol.major || protocol2.minor !== existing.value.protocol.minor || capabilities2.granted.length !== existing.grantedCapabilities.length || capabilities2.granted.some((capability, index) => capability !== existing.grantedCapabilities[index]) || limits2.maxMessageBytes !== existing.limits.maxMessageBytes || limits2.maxResultBytes !== existing.limits.maxResultBytes || limits2.maxArtifactBytes !== existing.limits.maxArtifactBytes || limits2.eventJournalSize !== existing.limits.eventJournalSize) {
3217
- throw new BrowserPilotError("protocol_incompatible", "One-shot connection cannot change its negotiated contract", {
3214
+ throw new BrowserPilotError("protocol_incompatible", "Client session cannot change its negotiated contract", {
3218
3215
  remediation: {
3219
3216
  code: "restart_browser_pilot",
3220
- message: "Restart Browser Pilot before using a different one-shot protocol contract.",
3217
+ message: "Restart Browser Pilot before using a different client protocol contract.",
3221
3218
  actionRequired: true
3222
3219
  }
3223
3220
  });
@@ -3235,7 +3232,7 @@ var MemoryBrokerRuntime = class {
3235
3232
  limits: { ...existing.limits }
3236
3233
  });
3237
3234
  }
3238
- if (this.connectionsByBridge.size >= this.maxConnections) {
3235
+ if (this.connectionsByClientSession.size >= this.maxConnections) {
3239
3236
  throw new BrowserPilotError("result_too_large", "Broker connection limit reached", {
3240
3237
  context: { maxConnections: this.maxConnections }
3241
3238
  });
@@ -3255,7 +3252,6 @@ var MemoryBrokerRuntime = class {
3255
3252
  id: connectionId,
3256
3253
  principalId: principal.id,
3257
3254
  clientInstanceId: params.client.instanceId,
3258
- launchMode: params.launchMode,
3259
3255
  protocol,
3260
3256
  connectedAt: now,
3261
3257
  lastSeenAt: now
@@ -3273,13 +3269,12 @@ var MemoryBrokerRuntime = class {
3273
3269
  };
3274
3270
  const connection = {
3275
3271
  value: connectionValue,
3276
- bridgeSessionId,
3272
+ clientSessionId,
3277
3273
  client: { ...params.client },
3278
3274
  grantedCapabilities: [...capabilities.granted],
3279
- limits,
3280
- notifications: []
3275
+ limits
3281
3276
  };
3282
- this.connectionsByBridge.set(bridgeSessionId, connection);
3277
+ this.connectionsByClientSession.set(clientSessionId, connection);
3283
3278
  this.connectionsById.set(connectionId, connection);
3284
3279
  return asJson(initializeResult);
3285
3280
  }
@@ -3471,10 +3466,10 @@ var MemoryBrokerRuntime = class {
3471
3466
  this.principals.set(id, principal);
3472
3467
  return principal;
3473
3468
  }
3474
- requireConnection(bridgeSessionId) {
3475
- const connection = this.connectionsByBridge.get(bridgeSessionId);
3469
+ requireConnection(clientSessionId) {
3470
+ const connection = this.connectionsByClientSession.get(clientSessionId);
3476
3471
  if (connection) return connection;
3477
- throw new BrowserPilotError("not_initialized", "Bridge connection is not initialized", {
3472
+ throw new BrowserPilotError("not_initialized", "Client session is not initialized", {
3478
3473
  rpcCode: -32002
3479
3474
  });
3480
3475
  }
@@ -3614,7 +3609,7 @@ var MemoryBrokerRuntime = class {
3614
3609
  }
3615
3610
  }
3616
3611
  cleanupUnusedPrincipal(principalId) {
3617
- const hasConnection = [...this.connectionsByBridge.values()].some((connection) => connection.value.principalId === principalId);
3612
+ const hasConnection = [...this.connectionsByClientSession.values()].some((connection) => connection.value.principalId === principalId);
3618
3613
  const hasWorkspace = [...this.workspaces.values()].some((record2) => record2.value.principalId === principalId);
3619
3614
  if (hasConnection || hasWorkspace) return;
3620
3615
  const principal = this.principals.get(principalId);
@@ -3685,7 +3680,12 @@ var MemoryBrokerRuntime = class {
3685
3680
  if (!binding || !isBrowserConnectTool && binding.instance.state !== "connected") {
3686
3681
  throw new BrowserPilotError("browser_disconnected", "Workspace browser is disconnected", {
3687
3682
  retryable: true,
3688
- context: workspaceRecord ? { workspaceId: workspaceRecord.value.id } : void 0
3683
+ context: workspaceRecord ? { workspaceId: workspaceRecord.value.id } : void 0,
3684
+ remediation: binding?.candidate.remediation ?? {
3685
+ code: "connect_browser",
3686
+ message: "Run 'bp browsers', follow its setup remediation, then run one explicit 'bp connect'.",
3687
+ actionRequired: true
3688
+ }
3689
3689
  });
3690
3690
  }
3691
3691
  const isBrowserConnectionAttempt = isBrowserConnectTool && binding.instance.state !== "connected";
@@ -3822,36 +3822,6 @@ var MemoryBrokerRuntime = class {
3822
3822
  this.requireWorkspace(connection, params.workspaceId, true);
3823
3823
  return asJson(this.events.poll(params.workspaceId, params.cursor, params.limit));
3824
3824
  }
3825
- deliverNotification(event) {
3826
- const workspace = this.workspaces.get(event.workspaceId);
3827
- if (!workspace) return;
3828
- const notification = {
3829
- jsonrpc: "2.0",
3830
- method: "events/event",
3831
- params: { event }
3832
- };
3833
- for (const connection of this.connectionsByBridge.values()) {
3834
- if (connection.value.principalId !== workspace.value.principalId || !connection.grantedCapabilities.includes("event.read")) continue;
3835
- if (connection.notificationWaiter) {
3836
- this.resolveNotificationWaiter(connection, structuredClone(notification));
3837
- continue;
3838
- }
3839
- connection.notifications.push(structuredClone(notification));
3840
- if (connection.notifications.length > connection.limits.eventJournalSize) {
3841
- connection.notifications.shift();
3842
- }
3843
- }
3844
- }
3845
- resolveNotificationWaiter(connection, notification) {
3846
- const waiter = connection.notificationWaiter;
3847
- if (!waiter) return;
3848
- connection.notificationWaiter = void 0;
3849
- if (waiter.timer) clearTimeout(waiter.timer);
3850
- if (waiter.signal && waiter.onAbort) {
3851
- waiter.signal.removeEventListener("abort", waiter.onAbort);
3852
- }
3853
- waiter.resolve(notification);
3854
- }
3855
3825
  getCommand(connection, value) {
3856
3826
  const params = validateCommandAccessParams(value);
3857
3827
  if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
@@ -3861,6 +3831,18 @@ var MemoryBrokerRuntime = class {
3861
3831
  ...params.workspaceId ? { workspaceId: params.workspaceId } : {}
3862
3832
  }));
3863
3833
  }
3834
+ listCommands(connection, value) {
3835
+ const params = validateCommandListParams(value);
3836
+ this.requireWorkspace(connection, params.workspaceId, true);
3837
+ return asJson({
3838
+ commands: this.commands.list({
3839
+ principalId: connection.value.principalId,
3840
+ workspaceId: params.workspaceId,
3841
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
3842
+ ...params.statuses ? { statuses: params.statuses } : {}
3843
+ })
3844
+ });
3845
+ }
3864
3846
  cancelCommand(connection, value) {
3865
3847
  const params = validateCommandAccessParams(value);
3866
3848
  if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
@@ -3881,6 +3863,13 @@ var MemoryBrokerRuntime = class {
3881
3863
  const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
3882
3864
  return asJson({ artifact: record2.descriptor, path: record2.path });
3883
3865
  }
3866
+ async listArtifacts(connection, value) {
3867
+ const params = validateArtifactListParams(value);
3868
+ this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3869
+ return asJson({
3870
+ artifacts: await this.requireArtifactStore().list(params.workspaceId, params.kinds)
3871
+ });
3872
+ }
3884
3873
  async exportArtifact(connection, value) {
3885
3874
  const params = validateArtifactExportParams(value);
3886
3875
  this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
@@ -3944,9 +3933,9 @@ var MemoryBrokerRuntime = class {
3944
3933
  context: { leaseId: lease.id, missingCapabilities: missing }
3945
3934
  });
3946
3935
  }
3947
- assertBridgeSessionId(value) {
3936
+ assertClientSessionId(value) {
3948
3937
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(value)) {
3949
- throw invalidArgument("Invalid internal bridge session ID", "bridgeSessionId");
3938
+ throw invalidArgument("Invalid internal client session ID", "clientSessionId");
3950
3939
  }
3951
3940
  }
3952
3941
  nextId(kind, records) {
@@ -5164,6 +5153,7 @@ import { spawn } from "child_process";
5164
5153
  // src/client.ts
5165
5154
  import http from "http";
5166
5155
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5156
+ var BROKER_RPC_VERSION = 2;
5167
5157
 
5168
5158
  // src/chrome.ts
5169
5159
  import { createHash as createHash3 } from "crypto";
@@ -10933,7 +10923,10 @@ var BrowserToolService = class {
10933
10923
  context: { workspaceId: workspace.id }
10934
10924
  });
10935
10925
  }
10926
+ this.markDispatched(context);
10936
10927
  await this.connectBrowser();
10928
+ } else {
10929
+ this.markDispatched(context);
10937
10930
  }
10938
10931
  if (this.binding.instance.state !== "connected") {
10939
10932
  throw new BrowserPilotError("browser_disconnected", "Browser connection did not become ready", {
@@ -10941,7 +10934,6 @@ var BrowserToolService = class {
10941
10934
  context: { workspaceId: workspace.id }
10942
10935
  });
10943
10936
  }
10944
- this.markDispatched(context);
10945
10937
  return asJson2({
10946
10938
  workspaceId: workspace.id,
10947
10939
  leaseId: lease.id,
@@ -13604,6 +13596,13 @@ var ArtifactStore = class {
13604
13596
  async get(workspaceId, artifactId) {
13605
13597
  return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
13606
13598
  }
13599
+ async list(workspaceId, kinds) {
13600
+ return this.withLock(async () => {
13601
+ await this.sweepUnlocked();
13602
+ const selectedKinds = kinds ? new Set(kinds) : void 0;
13603
+ return [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId && (!selectedKinds || selectedKinds.has(record2.descriptor.kind))).sort((left, right) => right.descriptor.createdAt - left.descriptor.createdAt).map((record2) => ({ ...record2.descriptor }));
13604
+ });
13605
+ }
13607
13606
  async export(workspaceId, artifactId, destination, overwrite = false) {
13608
13607
  if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
13609
13608
  const target = resolve2(destination);
@@ -14317,7 +14316,7 @@ var ManagedTargetJanitorClient = class {
14317
14316
  };
14318
14317
 
14319
14318
  // src/version.ts
14320
- var BROWSER_PILOT_VERSION = "0.4.0";
14319
+ var BROWSER_PILOT_VERSION = "0.5.1";
14321
14320
 
14322
14321
  // src/daemon.ts
14323
14322
  var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
@@ -14843,7 +14842,7 @@ async function main() {
14843
14842
  res.end(JSON.stringify({
14844
14843
  ok: true,
14845
14844
  wsUrl: currentWsUrl,
14846
- brokerProtocol: 1,
14845
+ brokerProtocol: BROKER_RPC_VERSION,
14847
14846
  brokerProcessIdentity,
14848
14847
  serviceVersion: BROWSER_PILOT_VERSION,
14849
14848
  executableVersion: executable.version,
@@ -14871,14 +14870,14 @@ async function main() {
14871
14870
  }
14872
14871
  const body = JSON.parse(await readBody(req, DEFAULT_PROTOCOL_LIMITS.maxMessageBytes + 4096));
14873
14872
  if (!isRecord3(body)) throw invalidArgument("Broker RPC body must be an object", "body");
14874
- if (typeof body.bridgeSessionId !== "string") {
14875
- throw invalidArgument("bridgeSessionId is required", "bridgeSessionId");
14873
+ if (typeof body.clientSessionId !== "string") {
14874
+ throw invalidArgument("clientSessionId is required", "clientSessionId");
14876
14875
  }
14877
14876
  if (typeof body.method !== "string" || body.method.length === 0 || body.method.length > 256) {
14878
14877
  throw invalidArgument("method is required", "method");
14879
14878
  }
14880
14879
  const result = await broker.call(
14881
- body.bridgeSessionId,
14880
+ body.clientSessionId,
14882
14881
  body.method,
14883
14882
  body.params
14884
14883
  );
@@ -14891,47 +14890,6 @@ async function main() {
14891
14890
  return;
14892
14891
  }
14893
14892
  }
14894
- if (req.method === "POST" && url.pathname === "/broker/disconnect") {
14895
- try {
14896
- const body = JSON.parse(await readBody(req, 4096));
14897
- if (!isRecord3(body) || typeof body.bridgeSessionId !== "string") {
14898
- throw invalidArgument("bridgeSessionId is required", "bridgeSessionId");
14899
- }
14900
- broker.disconnect(body.bridgeSessionId);
14901
- res.writeHead(200);
14902
- res.end(JSON.stringify({ ok: true }));
14903
- return;
14904
- } catch (error) {
14905
- res.writeHead(200);
14906
- res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
14907
- return;
14908
- }
14909
- }
14910
- if (req.method === "POST" && url.pathname === "/broker/events/next") {
14911
- const abort = new AbortController();
14912
- res.once("close", () => abort.abort());
14913
- try {
14914
- const body = JSON.parse(await readBody(req, 4096));
14915
- if (!isRecord3(body) || typeof body.bridgeSessionId !== "string" || !Number.isSafeInteger(body.waitMs)) {
14916
- throw invalidArgument("bridgeSessionId and integer waitMs are required");
14917
- }
14918
- const notification = await broker.nextNotification(body.bridgeSessionId, {
14919
- waitMs: Number(body.waitMs),
14920
- signal: abort.signal
14921
- });
14922
- if (!res.destroyed) {
14923
- res.writeHead(200);
14924
- res.end(JSON.stringify({ notification }));
14925
- }
14926
- return;
14927
- } catch (error) {
14928
- if (!res.destroyed) {
14929
- res.writeHead(200);
14930
- res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
14931
- }
14932
- return;
14933
- }
14934
- }
14935
14893
  if (req.method === "POST" && url.pathname === "/cdp") {
14936
14894
  const body = await readBody(req);
14937
14895
  const { method, params, sessionId } = JSON.parse(body);
@@ -15012,7 +14970,7 @@ async function main() {
15012
14970
  });
15013
14971
  }
15014
14972
  const clients = broker.lifecycleSummary();
15015
- if (clients.embeddedConnections > 0 || clients.activeLeases > 0) {
14973
+ if (clients.activeLeases > 0) {
15016
14974
  throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
15017
14975
  retryable: true,
15018
14976
  context: clients,