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/README.md +267 -406
- package/dist/cli.js +1136 -2564
- package/dist/daemon.js +131 -173
- package/docs/architecture/browser-pilot-platform-spec.md +443 -1047
- package/docs/plans/browser-capability-evolution.md +6 -6
- package/docs/plans/profile-context-routing.md +1 -1
- package/docs/plans/universal-agent-integration.md +121 -348
- package/docs/releases/v0.5.0.md +93 -0
- package/docs/releases/v0.5.1.md +30 -0
- package/package.json +2 -8
- package/docs/integration/stdio-bridge.md +0 -694
- package/docs/integration/stdio-conformance.md +0 -51
- package/scripts/run-stdio-conformance.mjs +0 -698
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
|
|
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
|
-
|
|
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", "
|
|
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
|
-
|
|
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(
|
|
2963
|
-
this.
|
|
2999
|
+
async call(clientSessionId, method, params) {
|
|
3000
|
+
this.assertClientSessionId(clientSessionId);
|
|
2964
3001
|
this.sweepExpiredLeases();
|
|
2965
|
-
if (method === "initialize") return this.initialize(
|
|
2966
|
-
const connection = this.requireConnection(
|
|
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(
|
|
3017
|
-
const connection = this.
|
|
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.
|
|
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
|
|
3032
|
-
this.disconnect(
|
|
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.
|
|
3166
|
+
for (const connection of [...this.connectionsByClientSession.values()]) {
|
|
3163
3167
|
if (connection.value.lastSeenAt + this.connectionIdleTtlMs <= now) {
|
|
3164
|
-
this.disconnect(connection.
|
|
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.
|
|
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
|
-
|
|
3196
|
-
oneShotConnections,
|
|
3193
|
+
connections: stats.connections,
|
|
3197
3194
|
activeWorkspaces: stats.activeWorkspaces,
|
|
3198
3195
|
activeLeases: stats.activeLeases
|
|
3199
3196
|
};
|
|
3200
3197
|
}
|
|
3201
|
-
initialize(
|
|
3198
|
+
initialize(clientSessionId, value) {
|
|
3202
3199
|
const params = validateInitializeParams(value);
|
|
3203
|
-
const existing = this.
|
|
3200
|
+
const existing = this.connectionsByClientSession.get(clientSessionId);
|
|
3204
3201
|
if (existing) {
|
|
3205
|
-
if (
|
|
3206
|
-
throw invalidArgument("This
|
|
3202
|
+
if (existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
|
|
3203
|
+
throw invalidArgument("This client session is already initialized", "method");
|
|
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", "
|
|
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
|
|
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.
|
|
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
|
-
|
|
3272
|
+
clientSessionId,
|
|
3277
3273
|
client: { ...params.client },
|
|
3278
3274
|
grantedCapabilities: [...capabilities.granted],
|
|
3279
|
-
limits
|
|
3280
|
-
notifications: []
|
|
3275
|
+
limits
|
|
3281
3276
|
};
|
|
3282
|
-
this.
|
|
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(
|
|
3475
|
-
const connection = this.
|
|
3469
|
+
requireConnection(clientSessionId) {
|
|
3470
|
+
const connection = this.connectionsByClientSession.get(clientSessionId);
|
|
3476
3471
|
if (connection) return connection;
|
|
3477
|
-
throw new BrowserPilotError("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.
|
|
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
|
-
|
|
3936
|
+
assertClientSessionId(value) {
|
|
3948
3937
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(value)) {
|
|
3949
|
-
throw invalidArgument("Invalid internal
|
|
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.
|
|
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:
|
|
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.
|
|
14875
|
-
throw invalidArgument("
|
|
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.
|
|
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.
|
|
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,
|