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