@orkestrel/browser 0.0.22 → 0.0.24
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 +1 -1
- package/dist/bin/main.js +19 -4
- package/dist/bin/main.js.map +1 -1
- package/dist/src/core/index.cjs +62 -15
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +20 -10
- package/dist/src/core/index.d.ts +20 -10
- package/dist/src/core/index.js +62 -15
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +831 -100
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +180 -17
- package/dist/src/server/index.d.ts +180 -17
- package/dist/src/server/index.js +816 -104
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -3
|
@@ -13,6 +13,7 @@ let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
|
13
13
|
let _orkestrel_websocket = require("@orkestrel/websocket");
|
|
14
14
|
let node_events = require("node:events");
|
|
15
15
|
let _orkestrel_mcp = require("@orkestrel/mcp");
|
|
16
|
+
let _orkestrel_pool = require("@orkestrel/pool");
|
|
16
17
|
let _orkestrel_mcp_server = require("@orkestrel/mcp/server");
|
|
17
18
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
18
19
|
//#region src/server/constants.ts
|
|
@@ -212,6 +213,108 @@ var BROWSER_RUN_FILE = "run.json";
|
|
|
212
213
|
var BROWSER_RUN_DIRECTORY = "runs";
|
|
213
214
|
/** Bounds a file-store listing page by default. */
|
|
214
215
|
var BROWSER_FILE_STORE_LIMIT = 100;
|
|
216
|
+
/** Names the refusal when no browser can serve a call. */
|
|
217
|
+
var BROWSER_SERVER_UNAVAILABLE = "BROWSER_SERVER_UNAVAILABLE";
|
|
218
|
+
/** Names a failed attempt to warm a browser. */
|
|
219
|
+
var BROWSER_SERVER_LAUNCH = "BROWSER_SERVER_LAUNCH";
|
|
220
|
+
/** Sets the default number of warm browsers to 1. */
|
|
221
|
+
var BROWSER_SERVER_POOL_SIZE = 1;
|
|
222
|
+
/** Limits the number of warm browsers to 3. */
|
|
223
|
+
var BROWSER_SERVER_POOL_LIMIT = 3;
|
|
224
|
+
/** Permits one failed refill before the next failure spends the bound. */
|
|
225
|
+
var BROWSER_SERVER_RESTARTS = 1;
|
|
226
|
+
/** Names the browser process record in each profile. */
|
|
227
|
+
var BROWSER_SERVER_RECORD = "browse.json";
|
|
228
|
+
/** Names the diagnostic when the warm floor spends its restart bound. */
|
|
229
|
+
var BROWSER_SERVER_EXHAUSTED = "BROWSER_SERVER_EXHAUSTED";
|
|
230
|
+
/** Names a failed browser server teardown. */
|
|
231
|
+
var BROWSER_SERVER_TEARDOWN = "BROWSER_SERVER_TEARDOWN";
|
|
232
|
+
/** Names a failed profile sweep. */
|
|
233
|
+
var BROWSER_SERVER_SWEEP = "BROWSER_SERVER_SWEEP";
|
|
234
|
+
/** Names a refused browser server option. */
|
|
235
|
+
var BROWSER_SERVER_OPTIONS = "BROWSER_SERVER_OPTIONS";
|
|
236
|
+
/** Names the notice that a browser and its session state were lost. */
|
|
237
|
+
var BROWSER_SERVER_CRASH = "BROWSER_SERVER_CRASH";
|
|
238
|
+
/** Names an interrupted call whose outcome is unknown. */
|
|
239
|
+
var BROWSER_SERVER_UNRESOLVED = "BROWSER_SERVER_UNRESOLVED";
|
|
240
|
+
/** Names the refusal when every browser is admitted to a holder. */
|
|
241
|
+
var BROWSER_SERVER_BUSY = "BROWSER_SERVER_BUSY";
|
|
242
|
+
/** Names the refusal for an unknown or ended holder. */
|
|
243
|
+
var BROWSER_SERVER_HOLDER = "BROWSER_SERVER_HOLDER";
|
|
244
|
+
/** Defines the holder tools independently of the shared browser's vocabulary. */
|
|
245
|
+
var BROWSER_SERVER_COPY = Object.freeze({
|
|
246
|
+
acquire: Object.freeze({
|
|
247
|
+
name: "acquire",
|
|
248
|
+
description: "Acquires an independent browser and returns its holder id and tool catalog. Destroy the holder when its work ends.",
|
|
249
|
+
parameters: Object.freeze({
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: Object.freeze({ purpose: Object.freeze({
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "The work this browser will serve."
|
|
254
|
+
}) }),
|
|
255
|
+
required: Object.freeze(["purpose"])
|
|
256
|
+
})
|
|
257
|
+
}),
|
|
258
|
+
execute: Object.freeze({
|
|
259
|
+
name: "execute",
|
|
260
|
+
description: "Runs a tool on the selected holder, with that tool’s arguments inside arguments. Get its available tools with tools.",
|
|
261
|
+
parameters: Object.freeze({
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: Object.freeze({
|
|
264
|
+
holder: Object.freeze({
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "The holder id returned by acquire."
|
|
267
|
+
}),
|
|
268
|
+
name: Object.freeze({
|
|
269
|
+
type: "string",
|
|
270
|
+
description: "The tool name from this holder’s catalog."
|
|
271
|
+
}),
|
|
272
|
+
arguments: Object.freeze({
|
|
273
|
+
type: "object",
|
|
274
|
+
description: "The arguments for the selected tool."
|
|
275
|
+
})
|
|
276
|
+
}),
|
|
277
|
+
required: Object.freeze([
|
|
278
|
+
"holder",
|
|
279
|
+
"name",
|
|
280
|
+
"arguments"
|
|
281
|
+
])
|
|
282
|
+
}),
|
|
283
|
+
annotations: Object.freeze({
|
|
284
|
+
pure: false,
|
|
285
|
+
untrusted: true
|
|
286
|
+
})
|
|
287
|
+
}),
|
|
288
|
+
tools: Object.freeze({
|
|
289
|
+
name: "tools",
|
|
290
|
+
description: "Lists the selected holder’s tools, including tools supplied by its page.",
|
|
291
|
+
parameters: Object.freeze({
|
|
292
|
+
type: "object",
|
|
293
|
+
properties: Object.freeze({ holder: Object.freeze({
|
|
294
|
+
type: "string",
|
|
295
|
+
description: "The holder id returned by acquire."
|
|
296
|
+
}) }),
|
|
297
|
+
required: Object.freeze(["holder"])
|
|
298
|
+
}),
|
|
299
|
+
annotations: Object.freeze({
|
|
300
|
+
pure: true,
|
|
301
|
+
untrusted: true
|
|
302
|
+
})
|
|
303
|
+
}),
|
|
304
|
+
destroy: Object.freeze({
|
|
305
|
+
name: "destroy",
|
|
306
|
+
description: "Ends the selected holder and destroys its browser. Calls still running on that holder report an unresolved outcome.",
|
|
307
|
+
parameters: Object.freeze({
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: Object.freeze({ holder: Object.freeze({
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "The holder id returned by acquire."
|
|
312
|
+
}) }),
|
|
313
|
+
required: Object.freeze(["holder"])
|
|
314
|
+
}),
|
|
315
|
+
annotations: Object.freeze({ pure: false })
|
|
316
|
+
})
|
|
317
|
+
});
|
|
215
318
|
//#endregion
|
|
216
319
|
//#region src/server/errors.ts
|
|
217
320
|
/**
|
|
@@ -255,6 +358,77 @@ function isBrowserDestroyedError(value) {
|
|
|
255
358
|
//#endregion
|
|
256
359
|
//#region src/server/helpers.ts
|
|
257
360
|
/**
|
|
361
|
+
* Parses positive integer viewport dimensions in WIDTHxHEIGHT form.
|
|
362
|
+
* @param value - The viewport environment variable, or undefined when unset
|
|
363
|
+
* @returns The viewport, or undefined when absent or malformed
|
|
364
|
+
* @example
|
|
365
|
+
* parseBrowserViewport('1280x720') // { width: 1280, height: 720 }
|
|
366
|
+
*/
|
|
367
|
+
function parseBrowserViewport(value) {
|
|
368
|
+
if (value === void 0) return void 0;
|
|
369
|
+
const match = /^([0-9]+)x([0-9]+)$/u.exec(value);
|
|
370
|
+
if (match === null || match[0] !== value) return void 0;
|
|
371
|
+
const width = (0, _orkestrel_contract.parseInteger)(match[1]);
|
|
372
|
+
const height = (0, _orkestrel_contract.parseInteger)(match[2]);
|
|
373
|
+
if (width === void 0 || height === void 0 || !Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) return void 0;
|
|
374
|
+
return {
|
|
375
|
+
width,
|
|
376
|
+
height
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Probes whether a process has not been confirmed absent.
|
|
381
|
+
* @param pid - Process identifier to probe with signal zero
|
|
382
|
+
* @returns True if the host has not confirmed absence with ESRCH; false otherwise
|
|
383
|
+
* @example
|
|
384
|
+
* probeProcess(process.pid) // true
|
|
385
|
+
*/
|
|
386
|
+
function probeProcess(pid) {
|
|
387
|
+
try {
|
|
388
|
+
process.kill(pid, 0);
|
|
389
|
+
return true;
|
|
390
|
+
} catch (error) {
|
|
391
|
+
return !((0, _orkestrel_contract.isError)(error) && "code" in error && error.code === "ESRCH");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Parses a profile record naming a positive process identifier and a loopback DevTools endpoint.
|
|
396
|
+
* @param text - JSON record read from a browse profile
|
|
397
|
+
* @returns The record, or undefined when its shape or endpoint is refused
|
|
398
|
+
* @example
|
|
399
|
+
* parseBrowserProfileRecord('{"pid":123,"endpoint":"ws://127.0.0.1:9222/devtools/browser/session"}')
|
|
400
|
+
*/
|
|
401
|
+
function parseBrowserProfileRecord(text) {
|
|
402
|
+
const record = (0, _orkestrel_contract.parseJSON)(text);
|
|
403
|
+
if (!(0, _orkestrel_contract.isRecord)(record)) return void 0;
|
|
404
|
+
const pid = record["pid"];
|
|
405
|
+
const endpoint = record["endpoint"];
|
|
406
|
+
if (!(0, _orkestrel_contract.isNumber)(pid) || !Number.isSafeInteger(pid) || pid <= 0 || !(0, _orkestrel_contract.isString)(endpoint)) return void 0;
|
|
407
|
+
const match = /^ws:\/\/127\.0\.0\.1:([1-9]\d*)\/devtools\/browser\/[^/?#\s\\]+$/.exec(endpoint);
|
|
408
|
+
if (match === null || Number(match[1]) > 65535) return void 0;
|
|
409
|
+
return {
|
|
410
|
+
pid,
|
|
411
|
+
endpoint
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Describes a browser loss with its code, cause, and the session state it invalidates.
|
|
416
|
+
* @param code - Browser server diagnostic code
|
|
417
|
+
* @param cause - The loss or refusal, including its process context when present
|
|
418
|
+
* @param url - The lost page's last URL, when known
|
|
419
|
+
* @returns The coded diagnostic and any operation-lifecycle guidance
|
|
420
|
+
* @example
|
|
421
|
+
* describeBrowserServerLoss('BROWSER_SERVER_UNAVAILABLE', new Error('Browser exited'))
|
|
422
|
+
*/
|
|
423
|
+
function describeBrowserServerLoss(code, cause, url) {
|
|
424
|
+
const pid = (0, _src_core.isBrowserError)(cause) ? cause.context?.["pid"] : void 0;
|
|
425
|
+
const message = `${code}: ${(cause === void 0 ? "Browser session lost" : (0, _orkestrel_contract.isError)(cause) ? cause.message : String(cause)).replace(/\.+$/u, "")}${(0, _orkestrel_contract.isNumber)(pid) ? ` (pid ${pid})` : ""}.`;
|
|
426
|
+
if (code !== "BROWSER_SERVER_CRASH" && code !== "BROWSER_SERVER_UNRESOLVED") return message;
|
|
427
|
+
const lost = `Lost the page${url === void 0 ? "" : ` at ${url}`}, its tabs, every element reference, the retained reading, dialogs, holds, an unsaved recording, the active replay, and the isolated context's cookies.`;
|
|
428
|
+
const next = `The next call acquires a browser that starts at about:blank, or answers ${BROWSER_SERVER_UNAVAILABLE} when none can serve.`;
|
|
429
|
+
return code === "BROWSER_SERVER_UNRESOLVED" ? `${message} The outcome is unknown. ${lost} Browse did not repeat the call. ${next}` : `${message} ${lost}`;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
258
432
|
* Formats a process identifier and UUID token as a lock entry name.
|
|
259
433
|
* @param pid - Positive safe integer identifying the holder process
|
|
260
434
|
* @param token - Lowercase UUID token identifying this acquisition
|
|
@@ -868,12 +1042,12 @@ var FileBrowserWriter = class {
|
|
|
868
1042
|
};
|
|
869
1043
|
//#endregion
|
|
870
1044
|
//#region package.json
|
|
871
|
-
var version = "0.0.
|
|
1045
|
+
var version = "0.0.24";
|
|
872
1046
|
//#endregion
|
|
873
1047
|
//#region src/server/BrowserMCPServer.ts
|
|
874
1048
|
/**
|
|
875
1049
|
* Implements `BrowserMCPServerInterface`: serves the browser vocabulary and the journey tools over
|
|
876
|
-
* the Model Context Protocol on stdio, and
|
|
1050
|
+
* the Model Context Protocol on stdio, and warms Chromium at server start.
|
|
877
1051
|
*
|
|
878
1052
|
* @remarks
|
|
879
1053
|
* The server's own manager holds one dispatcher per name of the vocabulary — `look`, `read`, `plain`,
|
|
@@ -883,18 +1057,27 @@ var version = "0.0.22";
|
|
|
883
1057
|
* absent. After the launch, a tool the toolset's manager adds under another name, such as a page
|
|
884
1058
|
* tool it adopts, is mirrored as a dispatcher with that tool's definition, and the mirror is
|
|
885
1059
|
* removed when the toolset withdraws the tool, so the server's tool list changes and a subscribed
|
|
886
|
-
* client is notified. The
|
|
887
|
-
*
|
|
888
|
-
*
|
|
1060
|
+
* client is notified. The server also advertises `acquire`, `execute`, `tools`, and `destroy` with
|
|
1061
|
+
* `BROWSER_SERVER_COPY`. Startup warms the pool and leases the shared browser. The legacy
|
|
1062
|
+
* handshake and browser calls await that setup. Named holders each own a lease, a pending grant,
|
|
1063
|
+
* and loss notices; concurrent calls on a holder share its replacement grant. Ending a holder
|
|
1064
|
+
* destroys its token, and admission counts that holder until disposal settles.
|
|
1065
|
+
* A launch creates `ROOT/.profiles/PID-UUID/` exclusively, connects a browser that never attaches to an
|
|
889
1066
|
* existing endpoint, opens one page in an isolated context, and constructs the page toolset with
|
|
890
1067
|
* that context and the file journey and run stores under the root, on a manager the toolset owns.
|
|
891
1068
|
* A dispatcher forwards its arguments and the call's context, signal included, to the toolset's
|
|
892
1069
|
* manager and returns that manager's value, or rejects with its failure message; a text value
|
|
893
1070
|
* reaches the client as one text block rather than as its JSON text.
|
|
1071
|
+
* A disconnect or current-page crash retires its slot. A failed call receives a liveness ping;
|
|
1072
|
+
* loss makes its outcome unresolved, while a known success remains successful. Pending losses
|
|
1073
|
+
* prefix the next outcome on a successor, or a refusal, and calls are never repeated.
|
|
1074
|
+
* Concurrent checks share one ping per slot. Cancelling a call ends only its wait; the ping keeps
|
|
1075
|
+
* its deadline and records loss for the next call.
|
|
894
1076
|
*
|
|
895
1077
|
* `start()` serves stdio and destroys the server at the end of its input, on `SIGINT`, and on
|
|
896
|
-
* `SIGTERM`. `destroy()` stops reading requests and removes every dispatcher, then destroys
|
|
897
|
-
* toolset, which aborts the active replay, then
|
|
1078
|
+
* `SIGTERM`. `destroy()` stops reading requests and removes every dispatcher, then destroys every
|
|
1079
|
+
* slot through the pool — its toolset, which aborts the active replay, then its browser, then its
|
|
1080
|
+
* profile — and rechecks the profile folders this server made or kept.
|
|
898
1081
|
*
|
|
899
1082
|
* Chromium on Linux refuses to start as the root user unless its sandbox is disabled, so a server
|
|
900
1083
|
* running as root on Linux launches Chromium with `--no-sandbox`; a launch that could run
|
|
@@ -913,6 +1096,7 @@ var BrowserMCPServer = class {
|
|
|
913
1096
|
#root;
|
|
914
1097
|
#headless;
|
|
915
1098
|
#executable;
|
|
1099
|
+
#viewport;
|
|
916
1100
|
#readonly;
|
|
917
1101
|
#launcher;
|
|
918
1102
|
#input;
|
|
@@ -921,14 +1105,34 @@ var BrowserMCPServer = class {
|
|
|
921
1105
|
#abort = new AbortController();
|
|
922
1106
|
#signal;
|
|
923
1107
|
#vocabulary;
|
|
924
|
-
#added;
|
|
925
|
-
#removed;
|
|
926
|
-
#cleared;
|
|
927
1108
|
#mirrored;
|
|
928
|
-
#
|
|
929
|
-
#
|
|
930
|
-
#
|
|
931
|
-
#
|
|
1109
|
+
#pool;
|
|
1110
|
+
#log;
|
|
1111
|
+
#folders = /* @__PURE__ */ new Set();
|
|
1112
|
+
#faults = /* @__PURE__ */ new Set();
|
|
1113
|
+
#losses = /* @__PURE__ */ new WeakMap();
|
|
1114
|
+
#pings = /* @__PURE__ */ new WeakMap();
|
|
1115
|
+
#watches = /* @__PURE__ */ new Map();
|
|
1116
|
+
#shared = {
|
|
1117
|
+
id: "shared",
|
|
1118
|
+
purpose: "Shared browser",
|
|
1119
|
+
abort: this.#abort
|
|
1120
|
+
};
|
|
1121
|
+
#holders = /* @__PURE__ */ new Map([[this.#shared.id, this.#shared]]);
|
|
1122
|
+
#leases = /* @__PURE__ */ new Map();
|
|
1123
|
+
#grants = /* @__PURE__ */ new Map();
|
|
1124
|
+
#notices = /* @__PURE__ */ new Map();
|
|
1125
|
+
#disposals = /* @__PURE__ */ new Map();
|
|
1126
|
+
#drains = /* @__PURE__ */ new Map();
|
|
1127
|
+
#readers = /* @__PURE__ */ new Map();
|
|
1128
|
+
#writers = /* @__PURE__ */ new Set();
|
|
1129
|
+
#size;
|
|
1130
|
+
#reference;
|
|
1131
|
+
#references = 0;
|
|
1132
|
+
#failure;
|
|
1133
|
+
#stranded;
|
|
1134
|
+
#starting;
|
|
1135
|
+
#sweeping;
|
|
932
1136
|
#closing;
|
|
933
1137
|
/**
|
|
934
1138
|
* Registers the dispatchers and binds the protocol to the stdio streams without reading them.
|
|
@@ -936,18 +1140,49 @@ var BrowserMCPServer = class {
|
|
|
936
1140
|
* @param options - The root, the launch, the journeys' read-only switch, and the streams
|
|
937
1141
|
*/
|
|
938
1142
|
constructor(options) {
|
|
1143
|
+
const size = options?.pool?.size ?? 1;
|
|
1144
|
+
if (!(0, _orkestrel_contract.isInteger)(size) || size < 1 || size > 3) throw new _src_core.BrowserError(`pool.size must be an integer from 1 through 3`, BROWSER_SERVER_OPTIONS);
|
|
1145
|
+
this.#size = size;
|
|
1146
|
+
this.#log = options?.log ?? process.stderr;
|
|
1147
|
+
this.#reference = this.#issue.bind(this);
|
|
1148
|
+
this.#pool = (0, _orkestrel_pool.createPool)({
|
|
1149
|
+
create: this.#warm.bind(this),
|
|
1150
|
+
destroy: (slot) => this.#destroySlot(slot.profile, slot.browser, slot.toolset, this.#losses.get(slot)?.cause),
|
|
1151
|
+
validate: this.#validate.bind(this),
|
|
1152
|
+
watch: this.#watch.bind(this),
|
|
1153
|
+
error: this.#fault.bind(this),
|
|
1154
|
+
min: size,
|
|
1155
|
+
restarts: 1
|
|
1156
|
+
});
|
|
939
1157
|
this.#root = (0, node_path.resolve)(options?.root ?? "tmp/browsers");
|
|
940
1158
|
this.#headless = options?.headless ?? true;
|
|
941
1159
|
this.#executable = options?.executable;
|
|
1160
|
+
this.#viewport = options?.viewport;
|
|
942
1161
|
this.#readonly = options?.readonly ?? false;
|
|
943
1162
|
this.#launcher = options?.launch ?? createBrowser;
|
|
944
1163
|
this.#input = options?.stdio?.input ?? process.stdin;
|
|
945
1164
|
this.#tools = (0, _orkestrel_tool.createToolManager)();
|
|
946
1165
|
const names = [..._src_core.BROWSER_TOOL_NAMES, ..._src_core.BROWSER_JOURNEY_TOOL_NAMES];
|
|
947
|
-
this.#vocabulary = new Set(names);
|
|
1166
|
+
this.#vocabulary = /* @__PURE__ */ new Set([...names, ...Object.keys(BROWSER_SERVER_COPY)]);
|
|
948
1167
|
for (const name of names) this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
949
1168
|
..._src_core.BROWSER_TOOL_COPY[name],
|
|
950
|
-
execute: this.#forward.bind(this, name)
|
|
1169
|
+
execute: this.#forward.bind(this, this.#shared, name)
|
|
1170
|
+
}));
|
|
1171
|
+
this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
1172
|
+
...BROWSER_SERVER_COPY.acquire,
|
|
1173
|
+
execute: this.#acquire.bind(this)
|
|
1174
|
+
}));
|
|
1175
|
+
this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
1176
|
+
...BROWSER_SERVER_COPY.execute,
|
|
1177
|
+
execute: this.#dispatch.bind(this)
|
|
1178
|
+
}));
|
|
1179
|
+
this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
1180
|
+
...BROWSER_SERVER_COPY.tools,
|
|
1181
|
+
execute: this.#catalog.bind(this)
|
|
1182
|
+
}));
|
|
1183
|
+
this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
1184
|
+
...BROWSER_SERVER_COPY.destroy,
|
|
1185
|
+
execute: this.#dispose.bind(this)
|
|
951
1186
|
}));
|
|
952
1187
|
const server = (0, _orkestrel_mcp.createMCPServer)({
|
|
953
1188
|
identity: {
|
|
@@ -955,30 +1190,43 @@ var BrowserMCPServer = class {
|
|
|
955
1190
|
version
|
|
956
1191
|
},
|
|
957
1192
|
tools: this.#tools,
|
|
958
|
-
execution: this.#execute.bind(this)
|
|
1193
|
+
execution: this.#execute.bind(this),
|
|
1194
|
+
handshake: this.#handshake.bind(this)
|
|
959
1195
|
});
|
|
960
1196
|
this.#transport = (0, _orkestrel_mcp_server.createStdioServer)((0, _orkestrel_mcp.createMCPLegacy)(server), {
|
|
961
1197
|
input: this.#input,
|
|
962
1198
|
...options?.stdio?.output === void 0 ? {} : { output: options.stdio.output }
|
|
963
1199
|
});
|
|
964
1200
|
this.#signal = this.#end.bind(this);
|
|
965
|
-
this.#added = this.#mirror.bind(this);
|
|
966
|
-
this.#removed = this.#withdraw.bind(this);
|
|
967
|
-
this.#cleared = this.#clear.bind(this);
|
|
968
1201
|
}
|
|
969
1202
|
async start() {
|
|
970
1203
|
if (this.#closing !== void 0) throw this.#ended();
|
|
971
|
-
|
|
972
|
-
this.#
|
|
973
|
-
this.#transport.start();
|
|
974
|
-
this.#input.on("end", this.#signal);
|
|
975
|
-
process.on("SIGINT", this.#signal);
|
|
976
|
-
process.on("SIGTERM", this.#signal);
|
|
1204
|
+
this.#starting ??= this.#setup();
|
|
1205
|
+
return this.#starting;
|
|
977
1206
|
}
|
|
978
1207
|
destroy() {
|
|
979
|
-
this.#closing ??= this.#destroy();
|
|
1208
|
+
this.#closing ??= Promise.resolve().then(this.#destroy.bind(this));
|
|
980
1209
|
return this.#closing;
|
|
981
1210
|
}
|
|
1211
|
+
async #setup() {
|
|
1212
|
+
this.#input.on("end", this.#signal);
|
|
1213
|
+
process.on("SIGINT", this.#signal);
|
|
1214
|
+
process.on("SIGTERM", this.#signal);
|
|
1215
|
+
this.#transport.start();
|
|
1216
|
+
try {
|
|
1217
|
+
await (0, node_fs_promises.mkdir)((0, node_path.join)(this.#root, ".profiles"), { recursive: true });
|
|
1218
|
+
if (this.#closing !== void 0) return;
|
|
1219
|
+
this.#sweeping = this.#sweep();
|
|
1220
|
+
const warming = this.#pool.start().then(void 0, (error) => error);
|
|
1221
|
+
await this.#grant(this.#shared, this.#abort.signal);
|
|
1222
|
+
if (this.#closing !== void 0) return;
|
|
1223
|
+
warming.then(this.#exhaust.bind(this));
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (this.#closing !== void 0) return;
|
|
1226
|
+
if ((0, _src_core.isBrowserError)(error) && error.code === "BROWSER_SERVER_UNAVAILABLE") throw error;
|
|
1227
|
+
throw this.#unavailable((0, _orkestrel_pool.isPoolError)(error) ? error.cause ?? this.#failure : error);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
982
1230
|
async #destroy() {
|
|
983
1231
|
process.removeListener("SIGINT", this.#signal);
|
|
984
1232
|
process.removeListener("SIGTERM", this.#signal);
|
|
@@ -986,19 +1234,37 @@ var BrowserMCPServer = class {
|
|
|
986
1234
|
this.#transport.stop();
|
|
987
1235
|
for (const tool of this.#tools.tools()) this.#tools.remove(tool.name);
|
|
988
1236
|
this.#unmirror();
|
|
1237
|
+
this.#leases.clear();
|
|
1238
|
+
for (const holder of this.#holders.values()) holder.abort.abort();
|
|
989
1239
|
this.#abort.abort();
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
await this.#browser?.destroy().catch((error) => failures.push(error));
|
|
993
|
-
this.#browser = void 0;
|
|
994
|
-
const profile = this.#profile;
|
|
995
|
-
this.#profile = void 0;
|
|
996
|
-
if (profile !== void 0) await (0, node_fs_promises.rm)(profile, {
|
|
997
|
-
recursive: true,
|
|
998
|
-
force: true,
|
|
999
|
-
maxRetries: 5
|
|
1240
|
+
const barrier = this.#pool.destroy().catch((error) => {
|
|
1241
|
+
for (const failure of (0, _orkestrel_pool.isPoolError)(error) ? error.context?.failures ?? [error] : [error]) this.#faults.add(failure);
|
|
1000
1242
|
});
|
|
1001
|
-
|
|
1243
|
+
await this.#starting?.catch(() => void 0);
|
|
1244
|
+
await barrier;
|
|
1245
|
+
await Promise.allSettled(this.#disposals.values());
|
|
1246
|
+
this.#holders.clear();
|
|
1247
|
+
await this.#sweeping;
|
|
1248
|
+
if (this.#stranded !== void 0) this.#faults.add(this.#stranded);
|
|
1249
|
+
for (const folder of this.#folders) {
|
|
1250
|
+
try {
|
|
1251
|
+
const record = parseBrowserProfileRecord(await (0, node_fs_promises.readFile)((0, node_path.join)(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1252
|
+
if (record === void 0 || probeProcess(record.pid)) continue;
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
if (!(0, _orkestrel_contract.isError)(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
1255
|
+
}
|
|
1256
|
+
try {
|
|
1257
|
+
await (0, node_fs_promises.rm)(folder, {
|
|
1258
|
+
recursive: true,
|
|
1259
|
+
force: true,
|
|
1260
|
+
maxRetries: 5
|
|
1261
|
+
});
|
|
1262
|
+
this.#folders.delete(folder);
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
this.#faults.add(error);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (this.#faults.size > 0) throw new AggregateError([...this.#faults], "The browse server teardown failed");
|
|
1002
1268
|
}
|
|
1003
1269
|
async #execute(context) {
|
|
1004
1270
|
const result = await context.tools.execute(context.call, {
|
|
@@ -1015,49 +1281,395 @@ var BrowserMCPServer = class {
|
|
|
1015
1281
|
};
|
|
1016
1282
|
}
|
|
1017
1283
|
#end() {
|
|
1018
|
-
this
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (this.#
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1284
|
+
if (this.#closing !== void 0) return;
|
|
1285
|
+
this.destroy().catch(this.#endFailure.bind(this));
|
|
1286
|
+
}
|
|
1287
|
+
#endFailure(error) {
|
|
1288
|
+
process.exitCode = 1;
|
|
1289
|
+
this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_TEARDOWN, error)}`);
|
|
1290
|
+
}
|
|
1291
|
+
async #acquire(args, context) {
|
|
1292
|
+
context.signal.throwIfAborted();
|
|
1293
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1294
|
+
const purpose = args["purpose"];
|
|
1295
|
+
if (!(0, _orkestrel_contract.isString)(purpose) || purpose.trim().length === 0) throw new _src_core.BrowserError("purpose must describe the work", "BROWSER_TOOLSET_ARGUMENT");
|
|
1296
|
+
if (this.#holders.size >= this.#size) throw new _src_core.BrowserError(`${BROWSER_SERVER_BUSY}: ${[...this.#holders.values()].map((holder) => `${holder.id} (${holder.purpose})`).join(", ")}. Call destroy for a holder no longer needed or call the named tools to share the shared browser.`, BROWSER_SERVER_BUSY);
|
|
1297
|
+
const holder = {
|
|
1298
|
+
id: (0, node_crypto.randomUUID)(),
|
|
1299
|
+
purpose,
|
|
1300
|
+
abort: new AbortController()
|
|
1301
|
+
};
|
|
1302
|
+
this.#holders.set(holder.id, holder);
|
|
1303
|
+
const listener = (0, node_events.addAbortListener)(context.signal, () => holder.abort.abort(context.signal.reason));
|
|
1304
|
+
try {
|
|
1305
|
+
const slot = await this.#serve(holder, context.signal);
|
|
1306
|
+
context.signal.throwIfAborted();
|
|
1307
|
+
if (holder.abort.signal.aborted || this.#holders.get(holder.id) !== holder) throw this.#ended();
|
|
1308
|
+
if (this.#leases.get(holder)?.value !== slot) throw this.#unavailable(this.#losses.get(slot)?.cause);
|
|
1309
|
+
const catalog = {
|
|
1310
|
+
holder: holder.id,
|
|
1311
|
+
tools: slot.toolset.tools.definitions()
|
|
1312
|
+
};
|
|
1313
|
+
return JSON.stringify(catalog);
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
await this.#retire(holder).catch(this.#fault.bind(this));
|
|
1316
|
+
throw error;
|
|
1317
|
+
} finally {
|
|
1318
|
+
listener[Symbol.dispose]();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
#holder(value) {
|
|
1322
|
+
const holder = (0, _orkestrel_contract.isString)(value) ? this.#holders.get(value) : void 0;
|
|
1323
|
+
if (holder === void 0 || holder === this.#shared || holder.abort.signal.aborted) throw new _src_core.BrowserError(`${BROWSER_SERVER_HOLDER}: unknown or ended holder`, BROWSER_SERVER_HOLDER);
|
|
1324
|
+
return holder;
|
|
1325
|
+
}
|
|
1326
|
+
async #dispatch(args, context) {
|
|
1327
|
+
const holder = this.#holder(args["holder"]);
|
|
1328
|
+
const name = args["name"];
|
|
1329
|
+
const parameters = args["arguments"];
|
|
1330
|
+
if (!(0, _orkestrel_contract.isString)(name) || !(0, _orkestrel_contract.isRecord)(parameters)) throw new _src_core.BrowserError("execute requires name and arguments", "BROWSER_TOOLSET_ARGUMENT");
|
|
1331
|
+
return this.#forward(holder, name, parameters, context);
|
|
1332
|
+
}
|
|
1333
|
+
async #catalog(args, context) {
|
|
1334
|
+
const holder = this.#holder(args["holder"]);
|
|
1335
|
+
const signal = AbortSignal.any([context.signal, holder.abort.signal]);
|
|
1336
|
+
try {
|
|
1337
|
+
const slot = await this.#serve(holder, signal);
|
|
1338
|
+
signal.throwIfAborted();
|
|
1339
|
+
if (this.#leases.get(holder)?.value !== slot) throw this.#unavailable(this.#losses.get(slot)?.cause);
|
|
1340
|
+
const catalog = {
|
|
1341
|
+
holder: holder.id,
|
|
1342
|
+
tools: slot.toolset.tools.definitions()
|
|
1343
|
+
};
|
|
1344
|
+
return this.#annotate(holder, JSON.stringify(catalog));
|
|
1345
|
+
} catch (error) {
|
|
1346
|
+
context.signal.throwIfAborted();
|
|
1347
|
+
if (holder.abort.signal.aborted) throw new _src_core.BrowserError(`${BROWSER_SERVER_UNRESOLVED}: the holder ended during the call`, BROWSER_SERVER_UNRESOLVED);
|
|
1348
|
+
if ((0, _src_core.isBrowserError)(error)) throw new _src_core.BrowserError(this.#annotate(holder, `${error.code}: ${error.message}`), error.code);
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
async #dispose(args) {
|
|
1353
|
+
const id = args["holder"];
|
|
1354
|
+
await (((0, _orkestrel_contract.isString)(id) ? this.#disposals.get(id) : void 0) ?? this.#retire(this.#holder(id)));
|
|
1355
|
+
return "Holder destroyed.";
|
|
1356
|
+
}
|
|
1357
|
+
#retire(holder) {
|
|
1358
|
+
const pending = this.#disposals.get(holder.id);
|
|
1359
|
+
if (pending !== void 0) return pending;
|
|
1360
|
+
holder.abort.abort();
|
|
1361
|
+
const token = this.#leases.get(holder);
|
|
1362
|
+
this.#leases.delete(holder);
|
|
1363
|
+
const disposal = Promise.resolve().then(async () => {
|
|
1364
|
+
try {
|
|
1365
|
+
await this.#grants.get(holder)?.catch(() => void 0);
|
|
1366
|
+
await token?.destroy();
|
|
1367
|
+
await Promise.all(this.#drains.get(holder) ?? []);
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
this.#faults.add(error);
|
|
1370
|
+
} finally {
|
|
1371
|
+
if (this.#holders.get(holder.id) === holder) this.#holders.delete(holder.id);
|
|
1372
|
+
this.#notices.delete(holder);
|
|
1373
|
+
this.#drains.delete(holder);
|
|
1374
|
+
}
|
|
1037
1375
|
});
|
|
1038
|
-
|
|
1376
|
+
this.#disposals.set(holder.id, disposal);
|
|
1377
|
+
return disposal;
|
|
1378
|
+
}
|
|
1379
|
+
async #forward(holder, name, args, context) {
|
|
1380
|
+
context.signal.throwIfAborted();
|
|
1381
|
+
const journey = args["journey"];
|
|
1382
|
+
if (name !== "replay" && name !== "forget" || !(0, _orkestrel_contract.isString)(journey)) return this.#perform(holder, name, args, context);
|
|
1383
|
+
if (this.#writers.has(journey) || name === "forget" && this.#readers.has(journey)) throw new _src_core.BrowserError(`BROWSER_JOURNEY_LOCKED: Journey ${journey} is locked; call ${name} again.`, "BROWSER_JOURNEY_LOCKED", { name: journey });
|
|
1384
|
+
const reader = Symbol(journey);
|
|
1385
|
+
if (name === "forget") this.#writers.add(journey);
|
|
1386
|
+
else {
|
|
1387
|
+
const readers = this.#readers.get(journey) ?? /* @__PURE__ */ new Set();
|
|
1388
|
+
readers.add(reader);
|
|
1389
|
+
this.#readers.set(journey, readers);
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
return await this.#perform(holder, name, args, context);
|
|
1393
|
+
} finally {
|
|
1394
|
+
if (name === "forget") this.#writers.delete(journey);
|
|
1395
|
+
else {
|
|
1396
|
+
const readers = this.#readers.get(journey);
|
|
1397
|
+
readers?.delete(reader);
|
|
1398
|
+
if (readers?.size === 0) this.#readers.delete(journey);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1039
1401
|
}
|
|
1040
|
-
async #
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1402
|
+
async #perform(holder, name, args, context) {
|
|
1403
|
+
const signal = holder === this.#shared ? context.signal : AbortSignal.any([context.signal, holder.abort.signal]);
|
|
1404
|
+
let slot;
|
|
1405
|
+
try {
|
|
1406
|
+
slot = await this.#serve(holder, signal);
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
context.signal.throwIfAborted();
|
|
1409
|
+
if (holder !== this.#shared && holder.abort.signal.aborted) throw new _src_core.BrowserError(`${BROWSER_SERVER_UNRESOLVED}: the holder ended during the call`, BROWSER_SERVER_UNRESOLVED);
|
|
1410
|
+
if ((0, _src_core.isBrowserError)(error)) throw new _src_core.BrowserError(this.#annotate(holder, `${error.code}: ${error.message}`), error.code);
|
|
1411
|
+
throw error;
|
|
1412
|
+
}
|
|
1413
|
+
let result;
|
|
1414
|
+
try {
|
|
1415
|
+
const execution = slot.toolset.tools.execute({
|
|
1416
|
+
id: name,
|
|
1417
|
+
name,
|
|
1418
|
+
arguments: args
|
|
1419
|
+
}, {
|
|
1420
|
+
...context,
|
|
1421
|
+
signal
|
|
1422
|
+
});
|
|
1423
|
+
const outcome = await (name === "replay" || name === "forget" ? execution : this.#race(execution, signal));
|
|
1424
|
+
if (outcome === void 0) throw this.#ended();
|
|
1425
|
+
result = outcome;
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
result = {
|
|
1428
|
+
id: name,
|
|
1429
|
+
name,
|
|
1430
|
+
success: false,
|
|
1431
|
+
error: (0, _orkestrel_contract.isError)(error) ? error.message : String(error)
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
context.signal.throwIfAborted();
|
|
1435
|
+
if (holder !== this.#shared && holder.abort.signal.aborted) throw new _src_core.BrowserError(BROWSER_SERVER_UNRESOLVED + ": the holder ended during the call", BROWSER_SERVER_UNRESOLVED);
|
|
1436
|
+
if (!result.success) {
|
|
1437
|
+
if (this.#leases.get(holder)?.value === slot && !context.signal.aborted) try {
|
|
1438
|
+
await this.#race(this.#ping(slot), context.signal);
|
|
1439
|
+
} catch {}
|
|
1440
|
+
context.signal.throwIfAborted();
|
|
1441
|
+
if (this.#leases.get(holder)?.value !== slot) {
|
|
1442
|
+
const loss = this.#losses.get(slot);
|
|
1443
|
+
throw new _src_core.BrowserError(this.#annotate(holder, describeBrowserServerLoss(BROWSER_SERVER_UNRESOLVED, loss?.cause, loss?.url), slot), BROWSER_SERVER_UNRESOLVED);
|
|
1444
|
+
}
|
|
1445
|
+
throw new _src_core.BrowserError(this.#annotate(holder, result.error, slot));
|
|
1446
|
+
}
|
|
1447
|
+
return typeof result.value === "string" ? this.#annotate(holder, result.value, slot) : result.value;
|
|
1448
|
+
}
|
|
1449
|
+
#annotate(holder, value, slot) {
|
|
1450
|
+
const notes = [];
|
|
1451
|
+
for (const lost of this.#notices.get(holder) ?? []) {
|
|
1452
|
+
if (lost === slot) continue;
|
|
1453
|
+
const loss = this.#losses.get(lost);
|
|
1454
|
+
notes.push(describeBrowserServerLoss(BROWSER_SERVER_CRASH, loss?.cause, loss?.url));
|
|
1455
|
+
this.#notices.get(holder)?.delete(lost);
|
|
1456
|
+
}
|
|
1457
|
+
return [...notes, value].join("\n");
|
|
1458
|
+
}
|
|
1459
|
+
async #handshake(options) {
|
|
1460
|
+
try {
|
|
1461
|
+
await this.#race(this.#starting, options.signal);
|
|
1462
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
options.signal.throwIfAborted();
|
|
1465
|
+
if ((0, _src_core.isBrowserError)(error)) throw new _orkestrel_mcp.MCPError(`${error.code}: ${error.message}`, _orkestrel_mcp.JSONRPC_SERVER_ERROR, { code: error.code });
|
|
1466
|
+
throw error;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
async #race(promise, signal) {
|
|
1470
|
+
signal.throwIfAborted();
|
|
1471
|
+
const aborted = Promise.withResolvers();
|
|
1472
|
+
const listener = (0, node_events.addAbortListener)(signal, () => aborted.reject(signal.reason));
|
|
1473
|
+
try {
|
|
1474
|
+
return await Promise.race([promise, aborted.promise]);
|
|
1475
|
+
} finally {
|
|
1476
|
+
listener[Symbol.dispose]();
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
async #grant(holder, signal) {
|
|
1480
|
+
const held = this.#leases.get(holder);
|
|
1481
|
+
if (held !== void 0) return held;
|
|
1482
|
+
let granting = this.#grants.get(holder);
|
|
1483
|
+
if (granting === void 0) {
|
|
1484
|
+
granting = this.#pool.acquire(holder.abort.signal).then(this.#hold.bind(this, holder));
|
|
1485
|
+
this.#grants.set(holder, granting);
|
|
1486
|
+
granting.finally(() => {
|
|
1487
|
+
if (this.#grants.get(holder) === granting) this.#grants.delete(holder);
|
|
1488
|
+
}).catch(() => void 0);
|
|
1489
|
+
}
|
|
1490
|
+
const token = await this.#race(granting, signal);
|
|
1491
|
+
if (token === void 0) throw this.#ended();
|
|
1492
|
+
return token;
|
|
1493
|
+
}
|
|
1494
|
+
async #hold(holder, token) {
|
|
1495
|
+
if (this.#closing !== void 0 || this.#holders.get(holder.id) !== holder || holder.abort.signal.aborted) {
|
|
1496
|
+
await token.destroy();
|
|
1497
|
+
throw this.#ended();
|
|
1498
|
+
}
|
|
1499
|
+
if (this.#losses.has(token.value)) {
|
|
1500
|
+
await token.destroy().catch(() => void 0);
|
|
1501
|
+
throw this.#unavailable(this.#losses.get(token.value)?.cause);
|
|
1502
|
+
}
|
|
1503
|
+
this.#leases.set(holder, token);
|
|
1504
|
+
if (holder === this.#shared) {
|
|
1505
|
+
const tools = token.value.toolset.tools;
|
|
1506
|
+
const mirrored = {
|
|
1507
|
+
token,
|
|
1508
|
+
added: this.#mirror.bind(this, token),
|
|
1509
|
+
removed: this.#withdraw.bind(this, token),
|
|
1510
|
+
cleared: this.#clear.bind(this, token)
|
|
1511
|
+
};
|
|
1512
|
+
this.#mirrored = mirrored;
|
|
1513
|
+
for (const tool of tools.tools()) this.#mirror(token, tool);
|
|
1514
|
+
tools.emitter.on("add", mirrored.added);
|
|
1515
|
+
tools.emitter.on("remove", mirrored.removed);
|
|
1516
|
+
tools.emitter.on("clear", mirrored.cleared);
|
|
1517
|
+
}
|
|
1518
|
+
return token;
|
|
1519
|
+
}
|
|
1520
|
+
async #serve(holder, signal) {
|
|
1521
|
+
if (this.#closing !== void 0 || holder.abort.signal.aborted) throw this.#ended();
|
|
1522
|
+
await this.#race(this.#starting, signal);
|
|
1523
|
+
if (this.#closing !== void 0 || holder.abort.signal.aborted) throw this.#ended();
|
|
1524
|
+
const held = this.#leases.get(holder);
|
|
1525
|
+
if (held !== void 0) try {
|
|
1526
|
+
await this.#race(this.#ping(held.value), signal);
|
|
1527
|
+
if (this.#leases.get(holder) === held) return held.value;
|
|
1528
|
+
} catch {
|
|
1529
|
+
signal.throwIfAborted();
|
|
1530
|
+
if (holder.abort.signal.aborted) throw this.#ended();
|
|
1531
|
+
}
|
|
1532
|
+
try {
|
|
1533
|
+
const token = await this.#grant(holder, signal);
|
|
1534
|
+
if (this.#leases.get(holder) !== token) throw this.#unavailable(this.#losses.get(token.value)?.cause);
|
|
1535
|
+
return token.value;
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
signal.throwIfAborted();
|
|
1538
|
+
if (holder.abort.signal.aborted || (0, _orkestrel_pool.isPoolError)(error) && error.code === "destroyed") throw this.#ended();
|
|
1539
|
+
if ((0, _orkestrel_pool.isPoolError)(error) && (error.code === "create" || error.code === "cleanup")) {
|
|
1540
|
+
this.#exhaust(error);
|
|
1541
|
+
throw this.#unavailable(error.cause ?? this.#failure);
|
|
1542
|
+
}
|
|
1543
|
+
throw error;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
#ping(slot) {
|
|
1547
|
+
const pending = this.#pings.get(slot);
|
|
1548
|
+
if (pending !== void 0) return pending;
|
|
1549
|
+
const ping = slot.browser.ping({ signal: this.#abort.signal }).catch((error) => {
|
|
1550
|
+
if (!this.#abort.signal.aborted) this.#lose(slot, error);
|
|
1551
|
+
}).finally(() => this.#pings.delete(slot));
|
|
1552
|
+
this.#pings.set(slot, ping);
|
|
1553
|
+
return ping;
|
|
1554
|
+
}
|
|
1555
|
+
async #validate(slot) {
|
|
1556
|
+
try {
|
|
1557
|
+
await slot.browser.ping({ signal: this.#abort.signal });
|
|
1558
|
+
return true;
|
|
1559
|
+
} catch (error) {
|
|
1560
|
+
if (!this.#abort.signal.aborted) this.#lose(slot, error);
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
#lose(slot, cause) {
|
|
1565
|
+
if (this.#closing !== void 0 || this.#losses.has(slot)) return;
|
|
1566
|
+
this.#losses.set(slot, {
|
|
1567
|
+
cause,
|
|
1568
|
+
url: slot.toolset.view.url
|
|
1569
|
+
});
|
|
1570
|
+
this.#failure = cause;
|
|
1571
|
+
for (const [holder, token] of this.#leases) {
|
|
1572
|
+
if (token.value !== slot || this.#holders.get(holder.id) !== holder) continue;
|
|
1573
|
+
if (holder === this.#shared) this.#unmirror();
|
|
1574
|
+
this.#leases.delete(holder);
|
|
1575
|
+
const notices = this.#notices.get(holder) ?? /* @__PURE__ */ new Set();
|
|
1576
|
+
notices.add(slot);
|
|
1577
|
+
this.#notices.set(holder, notices);
|
|
1578
|
+
const drain = token.destroy();
|
|
1579
|
+
const drains = this.#drains.get(holder) ?? /* @__PURE__ */ new Set();
|
|
1580
|
+
drains.add(drain);
|
|
1581
|
+
this.#drains.set(holder, drains);
|
|
1582
|
+
drain.finally(() => drains.delete(drain)).catch(() => void 0);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
#watch(slot, signal) {
|
|
1586
|
+
const loss = Promise.withResolvers();
|
|
1587
|
+
const disconnect = this.#disconnect.bind(this, slot);
|
|
1588
|
+
const page = this.#track.bind(this, slot);
|
|
1589
|
+
const subscription = (0, node_events.addAbortListener)(signal, this.#unwatch.bind(this, slot));
|
|
1590
|
+
this.#watches.set(slot, {
|
|
1591
|
+
resolve: loss.resolve,
|
|
1592
|
+
disconnect,
|
|
1593
|
+
page,
|
|
1594
|
+
subscription,
|
|
1595
|
+
crashes: /* @__PURE__ */ new Map()
|
|
1596
|
+
});
|
|
1597
|
+
slot.browser.emitter.on("disconnect", disconnect);
|
|
1598
|
+
slot.context.emitter.on("page", page);
|
|
1599
|
+
for (const current of slot.context.pages()) this.#track(slot, current);
|
|
1600
|
+
if (signal.aborted) this.#unwatch(slot);
|
|
1601
|
+
else if (slot.browser.status !== "connected") this.#disconnect(slot);
|
|
1602
|
+
return loss.promise;
|
|
1603
|
+
}
|
|
1604
|
+
#track(slot, page) {
|
|
1605
|
+
const watch = this.#watches.get(slot);
|
|
1606
|
+
if (watch === void 0 || watch.crashes.has(page)) return;
|
|
1607
|
+
const crash = this.#createCrash(slot, page);
|
|
1608
|
+
page.emitter.on("crash", crash);
|
|
1609
|
+
this.#watches.set(slot, {
|
|
1610
|
+
...watch,
|
|
1611
|
+
crashes: new Map([...watch.crashes, [page, crash]])
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
#createCrash(slot, page) {
|
|
1615
|
+
return () => {
|
|
1616
|
+
if (page === slot.toolset.view) this.#report(slot, /* @__PURE__ */ new Error("The current page renderer crashed"));
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
#disconnect(slot) {
|
|
1620
|
+
this.#report(slot, /* @__PURE__ */ new Error(slot.browser.pid === void 0 ? "The browser process exited" : "The browser transport disconnected"));
|
|
1621
|
+
}
|
|
1622
|
+
#report(slot, cause) {
|
|
1623
|
+
const watch = this.#watches.get(slot);
|
|
1624
|
+
if (watch === void 0) return;
|
|
1625
|
+
this.#unwatch(slot);
|
|
1626
|
+
this.#lose(slot, cause);
|
|
1627
|
+
watch.resolve(cause);
|
|
1628
|
+
}
|
|
1629
|
+
#unwatch(slot) {
|
|
1630
|
+
const watch = this.#watches.get(slot);
|
|
1631
|
+
if (watch === void 0) return;
|
|
1632
|
+
this.#watches.delete(slot);
|
|
1633
|
+
slot.browser.emitter.off("disconnect", watch.disconnect);
|
|
1634
|
+
slot.context.emitter.off("page", watch.page);
|
|
1635
|
+
for (const [page, crash] of watch.crashes) page.emitter.off("crash", crash);
|
|
1636
|
+
watch.subscription[Symbol.dispose]();
|
|
1637
|
+
}
|
|
1638
|
+
#fault(error) {
|
|
1639
|
+
this.#faults.add(error);
|
|
1640
|
+
}
|
|
1641
|
+
async #warm() {
|
|
1642
|
+
if (this.#closing !== void 0) throw this.#ended();
|
|
1643
|
+
if (this.#stranded !== void 0) throw new _src_core.BrowserError((0, _orkestrel_contract.isError)(this.#stranded) ? this.#stranded.message : String(this.#stranded), BROWSER_SERVER_TEARDOWN, (0, _src_core.isBrowserError)(this.#stranded) ? this.#stranded.context : void 0);
|
|
1644
|
+
const profile = (0, node_path.join)(this.#root, ".profiles", formatBrowserLockEntry(process.pid, (0, node_crypto.randomUUID)()));
|
|
1047
1645
|
let browser;
|
|
1048
1646
|
let toolset;
|
|
1049
1647
|
try {
|
|
1648
|
+
await (0, node_fs_promises.mkdir)(profile);
|
|
1649
|
+
this.#folders.add(profile);
|
|
1050
1650
|
browser = this.#launcher({
|
|
1051
1651
|
headless: this.#headless,
|
|
1052
1652
|
profile,
|
|
1053
1653
|
cdp: { discover: false },
|
|
1054
1654
|
signal: this.#abort.signal,
|
|
1055
1655
|
...this.#executable === void 0 ? {} : { executable: this.#executable },
|
|
1656
|
+
...this.#viewport === void 0 ? {} : { viewport: this.#viewport },
|
|
1056
1657
|
...process.platform === "linux" && process.getuid?.() === 0 ? { args: ["--no-sandbox"] } : {}
|
|
1057
1658
|
});
|
|
1058
|
-
this.#browser = browser;
|
|
1059
1659
|
await browser.connect();
|
|
1060
|
-
|
|
1660
|
+
if (browser.pid !== void 0 && browser.endpoint !== void 0) {
|
|
1661
|
+
const record = (0, node_path.join)(profile, BROWSER_SERVER_RECORD);
|
|
1662
|
+
await (0, node_fs_promises.writeFile)(`${record}.tmp`, JSON.stringify({
|
|
1663
|
+
pid: browser.pid,
|
|
1664
|
+
endpoint: browser.endpoint
|
|
1665
|
+
}));
|
|
1666
|
+
await (0, node_fs_promises.rename)(`${record}.tmp`, record);
|
|
1667
|
+
}
|
|
1668
|
+
const context = await browser.isolate({
|
|
1669
|
+
reference: this.#reference,
|
|
1670
|
+
downloads: { path: (0, node_path.join)(profile, "downloads") },
|
|
1671
|
+
...this.#viewport === void 0 ? {} : { emulation: { viewport: this.#viewport } }
|
|
1672
|
+
});
|
|
1061
1673
|
const page = await context.create();
|
|
1062
1674
|
toolset = (0, _src_core.createBrowserToolset)(page, {
|
|
1063
1675
|
context,
|
|
@@ -1067,49 +1679,128 @@ var BrowserMCPServer = class {
|
|
|
1067
1679
|
readonly: this.#readonly
|
|
1068
1680
|
}
|
|
1069
1681
|
});
|
|
1070
|
-
this.#mirrored = toolset.tools;
|
|
1071
|
-
toolset.tools.emitter.on("add", this.#added);
|
|
1072
|
-
toolset.tools.emitter.on("remove", this.#removed);
|
|
1073
|
-
toolset.tools.emitter.on("clear", this.#cleared);
|
|
1074
1682
|
await toolset.start();
|
|
1075
|
-
return
|
|
1683
|
+
return {
|
|
1684
|
+
browser,
|
|
1685
|
+
profile,
|
|
1686
|
+
context,
|
|
1687
|
+
toolset
|
|
1688
|
+
};
|
|
1076
1689
|
} catch (error) {
|
|
1077
|
-
this.#
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1690
|
+
if (this.#folders.has(profile)) await this.#destroySlot(profile, browser, toolset).catch((failure) => {
|
|
1691
|
+
this.#stranded = failure;
|
|
1692
|
+
});
|
|
1693
|
+
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_LAUNCH, error)}`);
|
|
1694
|
+
throw error;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
async #destroySlot(profile, browser, toolset, cause) {
|
|
1698
|
+
if ((0, _src_core.isCDPTimeoutError)(cause) && browser?.pid !== void 0) try {
|
|
1699
|
+
process.kill(browser.pid, "SIGKILL");
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
if (!(0, _orkestrel_contract.isError)(error) || !("code" in error) || error.code !== "ESRCH") this.#faults.add(error);
|
|
1702
|
+
}
|
|
1703
|
+
await toolset?.destroy().catch((error) => this.#faults.add(error));
|
|
1704
|
+
try {
|
|
1705
|
+
await browser?.destroy();
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
if (this.#closing === void 0) this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_TEARDOWN, error)}`);
|
|
1708
|
+
throw error;
|
|
1709
|
+
}
|
|
1710
|
+
try {
|
|
1082
1711
|
await (0, node_fs_promises.rm)(profile, {
|
|
1083
1712
|
recursive: true,
|
|
1084
1713
|
force: true,
|
|
1085
1714
|
maxRetries: 5
|
|
1086
1715
|
});
|
|
1087
|
-
|
|
1716
|
+
this.#folders.delete(profile);
|
|
1717
|
+
} catch {}
|
|
1718
|
+
}
|
|
1719
|
+
async #sweep() {
|
|
1720
|
+
const profiles = (0, node_path.join)(this.#root, ".profiles");
|
|
1721
|
+
try {
|
|
1722
|
+
for (const entry of await (0, node_fs_promises.readdir)(profiles)) {
|
|
1723
|
+
if (this.#closing !== void 0) return;
|
|
1724
|
+
const pid = parseBrowserLockEntry(entry);
|
|
1725
|
+
if (pid === void 0 || pid === process.pid || probeProcess(pid)) continue;
|
|
1726
|
+
const folder = (0, node_path.join)(profiles, entry);
|
|
1727
|
+
this.#folders.add(folder);
|
|
1728
|
+
try {
|
|
1729
|
+
const record = parseBrowserProfileRecord(await (0, node_fs_promises.readFile)((0, node_path.join)(folder, BROWSER_SERVER_RECORD), "utf8"));
|
|
1730
|
+
if (record === void 0) continue;
|
|
1731
|
+
if (probeProcess(record.pid)) {
|
|
1732
|
+
const transport = createCDPTransport({ url: record.endpoint });
|
|
1733
|
+
const client = (0, _src_core.createCDPClient)({ transport });
|
|
1734
|
+
try {
|
|
1735
|
+
this.#abort.signal.throwIfAborted();
|
|
1736
|
+
await this.#race(client.connect(), this.#abort.signal);
|
|
1737
|
+
await client.send("Browser.close", void 0, { signal: this.#abort.signal });
|
|
1738
|
+
} catch {} finally {
|
|
1739
|
+
await transport.close();
|
|
1740
|
+
await client.close();
|
|
1741
|
+
}
|
|
1742
|
+
if (probeProcess(record.pid)) continue;
|
|
1743
|
+
}
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
if (!(0, _orkestrel_contract.isError)(error) || !("code" in error) || error.code !== "ENOENT") continue;
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
await (0, node_fs_promises.rm)(folder, {
|
|
1749
|
+
recursive: true,
|
|
1750
|
+
force: true,
|
|
1751
|
+
maxRetries: 5
|
|
1752
|
+
});
|
|
1753
|
+
this.#folders.delete(folder);
|
|
1754
|
+
} catch {}
|
|
1755
|
+
}
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_SWEEP, error)}`);
|
|
1088
1758
|
}
|
|
1089
1759
|
}
|
|
1090
|
-
#
|
|
1091
|
-
|
|
1760
|
+
#write(line) {
|
|
1761
|
+
try {
|
|
1762
|
+
this.#log.write(`${line}\n`, (error) => {
|
|
1763
|
+
if (error !== void 0 && error !== null) this.#faults.add(error);
|
|
1764
|
+
});
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
this.#faults.add(error);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
#exhaust(error) {
|
|
1770
|
+
if ((0, _orkestrel_pool.isPoolError)(error) && error.code === "create") this.#write(`browse: ${describeBrowserServerLoss(BROWSER_SERVER_EXHAUSTED, error.cause ?? this.#failure)}`);
|
|
1771
|
+
}
|
|
1772
|
+
#unavailable(cause) {
|
|
1773
|
+
const message = describeBrowserServerLoss(BROWSER_SERVER_UNAVAILABLE, cause);
|
|
1774
|
+
return new _src_core.BrowserError(message.slice(BROWSER_SERVER_UNAVAILABLE.length + 2), BROWSER_SERVER_UNAVAILABLE);
|
|
1775
|
+
}
|
|
1776
|
+
#issue() {
|
|
1777
|
+
return `e${++this.#references}`;
|
|
1778
|
+
}
|
|
1779
|
+
#mirror(token, tool) {
|
|
1780
|
+
if (this.#leases.get(this.#shared) !== token || this.#vocabulary.has(tool.name)) return;
|
|
1092
1781
|
this.#tools.add((0, _orkestrel_tool.createTool)({
|
|
1093
1782
|
...(0, _orkestrel_tool.toolToDefinition)(tool),
|
|
1094
|
-
execute: this.#forward.bind(this, tool.name)
|
|
1783
|
+
execute: this.#forward.bind(this, this.#shared, tool.name)
|
|
1095
1784
|
}));
|
|
1096
1785
|
}
|
|
1097
|
-
#withdraw(tool) {
|
|
1098
|
-
if (this.#vocabulary.has(tool.name)) return;
|
|
1099
|
-
const current =
|
|
1786
|
+
#withdraw(token, tool) {
|
|
1787
|
+
if (this.#leases.get(this.#shared) !== token || this.#vocabulary.has(tool.name)) return;
|
|
1788
|
+
const current = token.value.toolset.tools.tool(tool.name);
|
|
1100
1789
|
if (current === void 0) this.#tools.remove(tool.name);
|
|
1101
|
-
else this.#mirror(current);
|
|
1790
|
+
else this.#mirror(token, current);
|
|
1102
1791
|
}
|
|
1103
|
-
#clear(tools) {
|
|
1104
|
-
for (const tool of tools) this.#withdraw(tool);
|
|
1792
|
+
#clear(token, tools) {
|
|
1793
|
+
for (const tool of tools) this.#withdraw(token, tool);
|
|
1105
1794
|
}
|
|
1106
1795
|
#unmirror() {
|
|
1107
1796
|
const mirrored = this.#mirrored;
|
|
1108
1797
|
if (mirrored === void 0) return;
|
|
1109
1798
|
this.#mirrored = void 0;
|
|
1110
|
-
mirrored.
|
|
1111
|
-
|
|
1112
|
-
|
|
1799
|
+
const tools = mirrored.token.value.toolset.tools;
|
|
1800
|
+
for (const tool of tools.tools()) if (!this.#vocabulary.has(tool.name)) this.#tools.remove(tool.name);
|
|
1801
|
+
tools.emitter.off("add", mirrored.added);
|
|
1802
|
+
tools.emitter.off("remove", mirrored.removed);
|
|
1803
|
+
tools.emitter.off("clear", mirrored.cleared);
|
|
1113
1804
|
}
|
|
1114
1805
|
#ended() {
|
|
1115
1806
|
return new _src_core.BrowserError("the browser session ended", "BROWSER_TOOLSET_ENDED");
|
|
@@ -1279,13 +1970,7 @@ var FileBrowserStore = class {
|
|
|
1279
1970
|
const holder = entries[0];
|
|
1280
1971
|
if (holder !== void 0) {
|
|
1281
1972
|
const pid = parseBrowserLockEntry(holder);
|
|
1282
|
-
if (pid === void 0) break;
|
|
1283
|
-
try {
|
|
1284
|
-
process.kill(pid, 0);
|
|
1285
|
-
break;
|
|
1286
|
-
} catch (cause) {
|
|
1287
|
-
if (!(cause instanceof Error && "code" in cause && cause.code === "ESRCH")) break;
|
|
1288
|
-
}
|
|
1973
|
+
if (pid === void 0 || probeProcess(pid)) break;
|
|
1289
1974
|
if (!await this.#unlinkLock(this.resolvePath(path, holder))) continue;
|
|
1290
1975
|
}
|
|
1291
1976
|
await this.#removeLock(path);
|
|
@@ -1553,6 +2238,24 @@ var FileBrowserRunStore = class {
|
|
|
1553
2238
|
constructor(options) {
|
|
1554
2239
|
this.#files = new FileBrowserStore(options);
|
|
1555
2240
|
}
|
|
2241
|
+
async snapshot(bytes, options) {
|
|
2242
|
+
options?.signal?.throwIfAborted();
|
|
2243
|
+
const owned = new Uint8Array(bytes);
|
|
2244
|
+
const directory = await this.#files.allocate(this.#files.resolvePath(BROWSER_RUN_DIRECTORY), _src_core.generateBrowserRunId, options);
|
|
2245
|
+
const path = this.#files.resolvePath(directory.path, "view.png");
|
|
2246
|
+
try {
|
|
2247
|
+
await this.#files.check(path, options);
|
|
2248
|
+
options?.signal?.throwIfAborted();
|
|
2249
|
+
await (0, node_fs_promises.writeFile)(path, owned, {
|
|
2250
|
+
flag: "wx",
|
|
2251
|
+
signal: options?.signal
|
|
2252
|
+
});
|
|
2253
|
+
return path;
|
|
2254
|
+
} catch (error) {
|
|
2255
|
+
options?.signal?.throwIfAborted();
|
|
2256
|
+
throw this.#files.translateError(path, error);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
1556
2259
|
async open(name, options) {
|
|
1557
2260
|
options?.signal?.throwIfAborted();
|
|
1558
2261
|
this.#files.validateName(name);
|
|
@@ -1727,7 +2430,7 @@ function createFileBrowserRunStore(options) {
|
|
|
1727
2430
|
}
|
|
1728
2431
|
/**
|
|
1729
2432
|
* Creates the browse server, which serves the browser vocabulary and the journey tools over the
|
|
1730
|
-
* Model Context Protocol on stdio and
|
|
2433
|
+
* Model Context Protocol on stdio and warms Chromium at server start.
|
|
1731
2434
|
*
|
|
1732
2435
|
* @param options - The root, the launch, the journeys' read-only switch, and the streams
|
|
1733
2436
|
* @returns A {@link BrowserMCPServerInterface} that reads nothing until `start()`
|
|
@@ -1737,7 +2440,7 @@ function createFileBrowserRunStore(options) {
|
|
|
1737
2440
|
* import { createBrowserMCPServer } from '@orkestrel/browser/server'
|
|
1738
2441
|
*
|
|
1739
2442
|
* const server = createBrowserMCPServer({ root: 'tmp/browsers', headless: true })
|
|
1740
|
-
* await server.start() //
|
|
2443
|
+
* await server.start() // resolves after leasing a connected browser
|
|
1741
2444
|
* await server.destroy()
|
|
1742
2445
|
* ```
|
|
1743
2446
|
*/
|
|
@@ -1813,6 +2516,15 @@ var Browser = class {
|
|
|
1813
2516
|
get pid() {
|
|
1814
2517
|
return this.#servingPid ?? this.#process?.pid;
|
|
1815
2518
|
}
|
|
2519
|
+
get endpoint() {
|
|
2520
|
+
return this.#endpoint;
|
|
2521
|
+
}
|
|
2522
|
+
async ping(options) {
|
|
2523
|
+
if (this.#destroyed) throw new BrowserDestroyedError();
|
|
2524
|
+
const client = this.#client;
|
|
2525
|
+
if (this.#status !== "connected" || client === void 0) throw new BrowserNotConnectedError();
|
|
2526
|
+
await client.send("Browser.getVersion", void 0, options);
|
|
2527
|
+
}
|
|
1816
2528
|
async discover() {
|
|
1817
2529
|
const result = await this.#discoverCDP();
|
|
1818
2530
|
this.#emitter.emit("discover", result);
|
|
@@ -1938,7 +2650,7 @@ var Browser = class {
|
|
|
1938
2650
|
await this.#connectCDP(discovery.endpoint);
|
|
1939
2651
|
return;
|
|
1940
2652
|
}
|
|
1941
|
-
} else await this.#raceAbort(this.#assertPortFree());
|
|
2653
|
+
} else if (this.#options.cdp?.port !== void 0) await this.#raceAbort(this.#assertPortFree());
|
|
1942
2654
|
this.#assertNotAborted();
|
|
1943
2655
|
await this.#launch();
|
|
1944
2656
|
} catch (error) {
|
|
@@ -2598,6 +3310,21 @@ exports.BROWSER_PROCESS_EXIT_CAUSE = BROWSER_PROCESS_EXIT_CAUSE;
|
|
|
2598
3310
|
exports.BROWSER_PROFILE_PREFIX = BROWSER_PROFILE_PREFIX;
|
|
2599
3311
|
exports.BROWSER_RUN_DIRECTORY = BROWSER_RUN_DIRECTORY;
|
|
2600
3312
|
exports.BROWSER_RUN_FILE = BROWSER_RUN_FILE;
|
|
3313
|
+
exports.BROWSER_SERVER_BUSY = BROWSER_SERVER_BUSY;
|
|
3314
|
+
exports.BROWSER_SERVER_COPY = BROWSER_SERVER_COPY;
|
|
3315
|
+
exports.BROWSER_SERVER_CRASH = BROWSER_SERVER_CRASH;
|
|
3316
|
+
exports.BROWSER_SERVER_EXHAUSTED = BROWSER_SERVER_EXHAUSTED;
|
|
3317
|
+
exports.BROWSER_SERVER_HOLDER = BROWSER_SERVER_HOLDER;
|
|
3318
|
+
exports.BROWSER_SERVER_LAUNCH = BROWSER_SERVER_LAUNCH;
|
|
3319
|
+
exports.BROWSER_SERVER_OPTIONS = BROWSER_SERVER_OPTIONS;
|
|
3320
|
+
exports.BROWSER_SERVER_POOL_LIMIT = BROWSER_SERVER_POOL_LIMIT;
|
|
3321
|
+
exports.BROWSER_SERVER_POOL_SIZE = BROWSER_SERVER_POOL_SIZE;
|
|
3322
|
+
exports.BROWSER_SERVER_RECORD = BROWSER_SERVER_RECORD;
|
|
3323
|
+
exports.BROWSER_SERVER_RESTARTS = BROWSER_SERVER_RESTARTS;
|
|
3324
|
+
exports.BROWSER_SERVER_SWEEP = BROWSER_SERVER_SWEEP;
|
|
3325
|
+
exports.BROWSER_SERVER_TEARDOWN = BROWSER_SERVER_TEARDOWN;
|
|
3326
|
+
exports.BROWSER_SERVER_UNAVAILABLE = BROWSER_SERVER_UNAVAILABLE;
|
|
3327
|
+
exports.BROWSER_SERVER_UNRESOLVED = BROWSER_SERVER_UNRESOLVED;
|
|
2601
3328
|
exports.BROWSER_STORE_CACHE_DIRS = BROWSER_STORE_CACHE_DIRS;
|
|
2602
3329
|
exports.BROWSER_STORE_DEFAULT_DIRS = BROWSER_STORE_DEFAULT_DIRS;
|
|
2603
3330
|
exports.BROWSER_STORE_ENV_KEY = BROWSER_STORE_ENV_KEY;
|
|
@@ -2626,6 +3353,7 @@ exports.createBrowserWriter = createBrowserWriter;
|
|
|
2626
3353
|
exports.createCDPTransport = createCDPTransport;
|
|
2627
3354
|
exports.createFileBrowserJourneyStore = createFileBrowserJourneyStore;
|
|
2628
3355
|
exports.createFileBrowserRunStore = createFileBrowserRunStore;
|
|
3356
|
+
exports.describeBrowserServerLoss = describeBrowserServerLoss;
|
|
2629
3357
|
exports.fetchCDPTargets = fetchCDPTargets;
|
|
2630
3358
|
exports.findEnvOverrides = findEnvOverrides;
|
|
2631
3359
|
exports.findInstallPaths = findInstallPaths;
|
|
@@ -2639,7 +3367,10 @@ exports.launchBrowserProcess = launchBrowserProcess;
|
|
|
2639
3367
|
exports.normalizeExecutablePath = normalizeExecutablePath;
|
|
2640
3368
|
exports.parseBrowserEngine = parseBrowserEngine;
|
|
2641
3369
|
exports.parseBrowserLockEntry = parseBrowserLockEntry;
|
|
3370
|
+
exports.parseBrowserProfileRecord = parseBrowserProfileRecord;
|
|
3371
|
+
exports.parseBrowserViewport = parseBrowserViewport;
|
|
2642
3372
|
exports.probePathNames = probePathNames;
|
|
3373
|
+
exports.probeProcess = probeProcess;
|
|
2643
3374
|
exports.readBrowserEndpoint = readBrowserEndpoint;
|
|
2644
3375
|
exports.readFirstLine = readFirstLine;
|
|
2645
3376
|
exports.removeBrowserProfile = removeBrowserProfile;
|