browser-pilot-cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -406
- package/dist/cli.js +1089 -2550
- package/dist/daemon.js +125 -172
- 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/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);
|
|
@@ -3822,36 +3817,6 @@ var MemoryBrokerRuntime = class {
|
|
|
3822
3817
|
this.requireWorkspace(connection, params.workspaceId, true);
|
|
3823
3818
|
return asJson(this.events.poll(params.workspaceId, params.cursor, params.limit));
|
|
3824
3819
|
}
|
|
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
3820
|
getCommand(connection, value) {
|
|
3856
3821
|
const params = validateCommandAccessParams(value);
|
|
3857
3822
|
if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
|
|
@@ -3861,6 +3826,18 @@ var MemoryBrokerRuntime = class {
|
|
|
3861
3826
|
...params.workspaceId ? { workspaceId: params.workspaceId } : {}
|
|
3862
3827
|
}));
|
|
3863
3828
|
}
|
|
3829
|
+
listCommands(connection, value) {
|
|
3830
|
+
const params = validateCommandListParams(value);
|
|
3831
|
+
this.requireWorkspace(connection, params.workspaceId, true);
|
|
3832
|
+
return asJson({
|
|
3833
|
+
commands: this.commands.list({
|
|
3834
|
+
principalId: connection.value.principalId,
|
|
3835
|
+
workspaceId: params.workspaceId,
|
|
3836
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
3837
|
+
...params.statuses ? { statuses: params.statuses } : {}
|
|
3838
|
+
})
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3864
3841
|
cancelCommand(connection, value) {
|
|
3865
3842
|
const params = validateCommandAccessParams(value);
|
|
3866
3843
|
if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
|
|
@@ -3881,6 +3858,13 @@ var MemoryBrokerRuntime = class {
|
|
|
3881
3858
|
const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
|
|
3882
3859
|
return asJson({ artifact: record2.descriptor, path: record2.path });
|
|
3883
3860
|
}
|
|
3861
|
+
async listArtifacts(connection, value) {
|
|
3862
|
+
const params = validateArtifactListParams(value);
|
|
3863
|
+
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
3864
|
+
return asJson({
|
|
3865
|
+
artifacts: await this.requireArtifactStore().list(params.workspaceId, params.kinds)
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3884
3868
|
async exportArtifact(connection, value) {
|
|
3885
3869
|
const params = validateArtifactExportParams(value);
|
|
3886
3870
|
this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
|
|
@@ -3944,9 +3928,9 @@ var MemoryBrokerRuntime = class {
|
|
|
3944
3928
|
context: { leaseId: lease.id, missingCapabilities: missing }
|
|
3945
3929
|
});
|
|
3946
3930
|
}
|
|
3947
|
-
|
|
3931
|
+
assertClientSessionId(value) {
|
|
3948
3932
|
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(value)) {
|
|
3949
|
-
throw invalidArgument("Invalid internal
|
|
3933
|
+
throw invalidArgument("Invalid internal client session ID", "clientSessionId");
|
|
3950
3934
|
}
|
|
3951
3935
|
}
|
|
3952
3936
|
nextId(kind, records) {
|
|
@@ -5164,6 +5148,7 @@ import { spawn } from "child_process";
|
|
|
5164
5148
|
// src/client.ts
|
|
5165
5149
|
import http from "http";
|
|
5166
5150
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
5151
|
+
var BROKER_RPC_VERSION = 2;
|
|
5167
5152
|
|
|
5168
5153
|
// src/chrome.ts
|
|
5169
5154
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -10933,7 +10918,10 @@ var BrowserToolService = class {
|
|
|
10933
10918
|
context: { workspaceId: workspace.id }
|
|
10934
10919
|
});
|
|
10935
10920
|
}
|
|
10921
|
+
this.markDispatched(context);
|
|
10936
10922
|
await this.connectBrowser();
|
|
10923
|
+
} else {
|
|
10924
|
+
this.markDispatched(context);
|
|
10937
10925
|
}
|
|
10938
10926
|
if (this.binding.instance.state !== "connected") {
|
|
10939
10927
|
throw new BrowserPilotError("browser_disconnected", "Browser connection did not become ready", {
|
|
@@ -10941,7 +10929,6 @@ var BrowserToolService = class {
|
|
|
10941
10929
|
context: { workspaceId: workspace.id }
|
|
10942
10930
|
});
|
|
10943
10931
|
}
|
|
10944
|
-
this.markDispatched(context);
|
|
10945
10932
|
return asJson2({
|
|
10946
10933
|
workspaceId: workspace.id,
|
|
10947
10934
|
leaseId: lease.id,
|
|
@@ -13604,6 +13591,13 @@ var ArtifactStore = class {
|
|
|
13604
13591
|
async get(workspaceId, artifactId) {
|
|
13605
13592
|
return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
|
|
13606
13593
|
}
|
|
13594
|
+
async list(workspaceId, kinds) {
|
|
13595
|
+
return this.withLock(async () => {
|
|
13596
|
+
await this.sweepUnlocked();
|
|
13597
|
+
const selectedKinds = kinds ? new Set(kinds) : void 0;
|
|
13598
|
+
return [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId && (!selectedKinds || selectedKinds.has(record2.descriptor.kind))).sort((left, right) => right.descriptor.createdAt - left.descriptor.createdAt).map((record2) => ({ ...record2.descriptor }));
|
|
13599
|
+
});
|
|
13600
|
+
}
|
|
13607
13601
|
async export(workspaceId, artifactId, destination, overwrite = false) {
|
|
13608
13602
|
if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
|
|
13609
13603
|
const target = resolve2(destination);
|
|
@@ -14317,7 +14311,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
14317
14311
|
};
|
|
14318
14312
|
|
|
14319
14313
|
// src/version.ts
|
|
14320
|
-
var BROWSER_PILOT_VERSION = "0.
|
|
14314
|
+
var BROWSER_PILOT_VERSION = "0.5.0";
|
|
14321
14315
|
|
|
14322
14316
|
// src/daemon.ts
|
|
14323
14317
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -14843,7 +14837,7 @@ async function main() {
|
|
|
14843
14837
|
res.end(JSON.stringify({
|
|
14844
14838
|
ok: true,
|
|
14845
14839
|
wsUrl: currentWsUrl,
|
|
14846
|
-
brokerProtocol:
|
|
14840
|
+
brokerProtocol: BROKER_RPC_VERSION,
|
|
14847
14841
|
brokerProcessIdentity,
|
|
14848
14842
|
serviceVersion: BROWSER_PILOT_VERSION,
|
|
14849
14843
|
executableVersion: executable.version,
|
|
@@ -14871,14 +14865,14 @@ async function main() {
|
|
|
14871
14865
|
}
|
|
14872
14866
|
const body = JSON.parse(await readBody(req, DEFAULT_PROTOCOL_LIMITS.maxMessageBytes + 4096));
|
|
14873
14867
|
if (!isRecord3(body)) throw invalidArgument("Broker RPC body must be an object", "body");
|
|
14874
|
-
if (typeof body.
|
|
14875
|
-
throw invalidArgument("
|
|
14868
|
+
if (typeof body.clientSessionId !== "string") {
|
|
14869
|
+
throw invalidArgument("clientSessionId is required", "clientSessionId");
|
|
14876
14870
|
}
|
|
14877
14871
|
if (typeof body.method !== "string" || body.method.length === 0 || body.method.length > 256) {
|
|
14878
14872
|
throw invalidArgument("method is required", "method");
|
|
14879
14873
|
}
|
|
14880
14874
|
const result = await broker.call(
|
|
14881
|
-
body.
|
|
14875
|
+
body.clientSessionId,
|
|
14882
14876
|
body.method,
|
|
14883
14877
|
body.params
|
|
14884
14878
|
);
|
|
@@ -14891,47 +14885,6 @@ async function main() {
|
|
|
14891
14885
|
return;
|
|
14892
14886
|
}
|
|
14893
14887
|
}
|
|
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
14888
|
if (req.method === "POST" && url.pathname === "/cdp") {
|
|
14936
14889
|
const body = await readBody(req);
|
|
14937
14890
|
const { method, params, sessionId } = JSON.parse(body);
|
|
@@ -15012,7 +14965,7 @@ async function main() {
|
|
|
15012
14965
|
});
|
|
15013
14966
|
}
|
|
15014
14967
|
const clients = broker.lifecycleSummary();
|
|
15015
|
-
if (clients.
|
|
14968
|
+
if (clients.activeLeases > 0) {
|
|
15016
14969
|
throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
|
|
15017
14970
|
retryable: true,
|
|
15018
14971
|
context: clients,
|