@secure-exec/browser 0.0.0-agentos-dylib-base.edaa4a4

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.
Files changed (66) hide show
  1. package/README.md +6 -0
  2. package/dist/child-process-bridge.d.ts +25 -0
  3. package/dist/child-process-bridge.js +50 -0
  4. package/dist/converged-base64.d.ts +2 -0
  5. package/dist/converged-base64.js +41 -0
  6. package/dist/converged-dgram-bridge.d.ts +11 -0
  7. package/dist/converged-dgram-bridge.js +147 -0
  8. package/dist/converged-driver-setup.d.ts +22 -0
  9. package/dist/converged-driver-setup.js +72 -0
  10. package/dist/converged-execution-host-bridge.d.ts +7 -0
  11. package/dist/converged-execution-host-bridge.js +85 -0
  12. package/dist/converged-executor-session.d.ts +60 -0
  13. package/dist/converged-executor-session.js +127 -0
  14. package/dist/converged-fs-bridge.d.ts +42 -0
  15. package/dist/converged-fs-bridge.js +245 -0
  16. package/dist/converged-module-servicer.d.ts +8 -0
  17. package/dist/converged-module-servicer.js +79 -0
  18. package/dist/converged-net-bridge.d.ts +28 -0
  19. package/dist/converged-net-bridge.js +155 -0
  20. package/dist/converged-permissions.d.ts +9 -0
  21. package/dist/converged-permissions.js +46 -0
  22. package/dist/converged-sync-bridge-handler.d.ts +47 -0
  23. package/dist/converged-sync-bridge-handler.js +140 -0
  24. package/dist/converged-sync-bridge-router.d.ts +33 -0
  25. package/dist/converged-sync-bridge-router.js +41 -0
  26. package/dist/driver.d.ts +91 -0
  27. package/dist/driver.js +386 -0
  28. package/dist/encoding.d.ts +4 -0
  29. package/dist/encoding.js +102 -0
  30. package/dist/generated/util-polyfill.d.ts +1 -0
  31. package/dist/generated/util-polyfill.js +2 -0
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.js +5 -0
  34. package/dist/kernel-backed-filesystem.d.ts +33 -0
  35. package/dist/kernel-backed-filesystem.js +205 -0
  36. package/dist/os-filesystem.d.ts +47 -0
  37. package/dist/os-filesystem.js +409 -0
  38. package/dist/permission-validation.d.ts +15 -0
  39. package/dist/permission-validation.js +62 -0
  40. package/dist/root-filesystem-from-vfs.d.ts +13 -0
  41. package/dist/root-filesystem-from-vfs.js +95 -0
  42. package/dist/runtime-driver.d.ts +66 -0
  43. package/dist/runtime-driver.js +611 -0
  44. package/dist/runtime.d.ts +248 -0
  45. package/dist/runtime.js +2296 -0
  46. package/dist/sidecar-wasm-module.d.ts +62 -0
  47. package/dist/sidecar-wasm-module.js +28 -0
  48. package/dist/sidecar-worker-protocol.d.ts +14 -0
  49. package/dist/sidecar-worker-protocol.js +9 -0
  50. package/dist/sidecar-worker.d.ts +19 -0
  51. package/dist/sidecar-worker.js +63 -0
  52. package/dist/signals.d.ts +13 -0
  53. package/dist/signals.js +89 -0
  54. package/dist/sync-bridge.d.ts +50 -0
  55. package/dist/sync-bridge.js +93 -0
  56. package/dist/wasi-polyfill.d.ts +1 -0
  57. package/dist/wasi-polyfill.js +2154 -0
  58. package/dist/worker-adapter.d.ts +21 -0
  59. package/dist/worker-adapter.js +41 -0
  60. package/dist/worker-protocol.d.ts +104 -0
  61. package/dist/worker-protocol.js +1 -0
  62. package/dist/worker-sidecar-client.d.ts +71 -0
  63. package/dist/worker-sidecar-client.js +152 -0
  64. package/dist/worker.d.ts +1 -0
  65. package/dist/worker.js +2125 -0
  66. package/package.json +111 -0
@@ -0,0 +1,611 @@
1
+ import { decodeChildProcessInput, encodeChildProcessBytes, parseChildProcessSpawnRequest, } from "./child-process-bridge.js";
2
+ import { getBrowserSystemDriverOptions } from "./driver.js";
3
+ import { toUint8Array } from "./encoding.js";
4
+ import { createNetworkStub, wrapCommandExecutor } from "./runtime.js";
5
+ import { applyProcessSignalStateUpdate, parseProcessSignalStateArgs, } from "./signals.js";
6
+ import { assertBrowserSyncBridgeSupport, createBrowserSyncBridgePayload, isBrowserWorkerSyncOperation, SYNC_BRIDGE_KIND_BINARY, SYNC_BRIDGE_KIND_JSON, SYNC_BRIDGE_KIND_NONE, SYNC_BRIDGE_KIND_TEXT, SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_READY, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_ERROR, SYNC_BRIDGE_STATUS_OK, toBrowserSyncBridgeError, } from "./sync-bridge.js";
7
+ const DEFAULT_BROWSER_TIMING_MITIGATION = "freeze";
8
+ const BROWSER_OPTION_VALIDATORS = [
9
+ {
10
+ label: "memoryLimit",
11
+ hasValue: (options) => options.memoryLimit !== undefined,
12
+ },
13
+ {
14
+ label: "cpuTimeLimitMs",
15
+ hasValue: (options) => options.cpuTimeLimitMs !== undefined,
16
+ },
17
+ ];
18
+ function serializePermissions(permissions) {
19
+ if (!permissions) {
20
+ return undefined;
21
+ }
22
+ const serialize = (fn) => typeof fn === "function" ? fn.toString() : undefined;
23
+ return {
24
+ fs: serialize(permissions.fs),
25
+ network: serialize(permissions.network),
26
+ childProcess: serialize(permissions.childProcess),
27
+ env: serialize(permissions.env),
28
+ };
29
+ }
30
+ function resolveWorkerUrl(workerUrl) {
31
+ if (workerUrl instanceof URL) {
32
+ return workerUrl;
33
+ }
34
+ if (workerUrl) {
35
+ return new URL(workerUrl, import.meta.url);
36
+ }
37
+ return new URL("./worker.js", import.meta.url);
38
+ }
39
+ function createWorkerControlToken() {
40
+ if (typeof globalThis.crypto?.randomUUID === "function") {
41
+ return globalThis.crypto.randomUUID();
42
+ }
43
+ return `browser-runtime-${Date.now()}-${Math.random().toString(16).slice(2)}`;
44
+ }
45
+ function toBrowserWorkerExecOptions(options) {
46
+ if (!options) {
47
+ return undefined;
48
+ }
49
+ return {
50
+ filePath: options.filePath,
51
+ env: options.env,
52
+ cwd: options.cwd,
53
+ stdin: options.stdin,
54
+ timingMitigation: options.timingMitigation,
55
+ };
56
+ }
57
+ function validateBrowserRuntimeOptions(options) {
58
+ const unsupported = BROWSER_OPTION_VALIDATORS.filter((validator) => validator.hasValue(options)).map((validator) => validator.label);
59
+ if (unsupported.length === 0) {
60
+ return;
61
+ }
62
+ throw new Error(`Browser runtime does not support Node-only options: ${unsupported.join(", ")}`);
63
+ }
64
+ function validateBrowserExecOptions(options) {
65
+ const unsupported = [];
66
+ if (options?.cpuTimeLimitMs !== undefined) {
67
+ unsupported.push("cpuTimeLimitMs");
68
+ }
69
+ if (unsupported.length === 0) {
70
+ return;
71
+ }
72
+ throw new Error(`Browser runtime does not support Node-only exec options: ${unsupported.join(", ")}`);
73
+ }
74
+ function isStdioMessage(message) {
75
+ return message.type === "stdio";
76
+ }
77
+ function isResponseMessage(message) {
78
+ return message.type === "response";
79
+ }
80
+ function isSyncRequestMessage(message) {
81
+ return message.type === "sync-request";
82
+ }
83
+ function throwBridgePayloadTooLarge(label, actualBytes, maxBytes) {
84
+ const error = new Error(`[${SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE}] ${label}: payload is ${actualBytes} bytes, limit is ${maxBytes} bytes`);
85
+ error.code = SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE;
86
+ throw error;
87
+ }
88
+ async function waitForChildProcessEvent(session, waitMs) {
89
+ const deadline = Date.now() + Math.max(0, waitMs);
90
+ while (session.events.length === 0 &&
91
+ !session.exited &&
92
+ Date.now() < deadline) {
93
+ await new Promise((resolve) => setTimeout(resolve, 5));
94
+ }
95
+ return session.events.shift() ?? null;
96
+ }
97
+ function createSyncBridgeResponseBytes(response, encoder) {
98
+ switch (response.kind) {
99
+ case SYNC_BRIDGE_KIND_NONE:
100
+ return new Uint8Array(0);
101
+ case SYNC_BRIDGE_KIND_TEXT:
102
+ return encoder.encode(response.value);
103
+ case SYNC_BRIDGE_KIND_BINARY:
104
+ return response.value;
105
+ case SYNC_BRIDGE_KIND_JSON:
106
+ return encoder.encode(JSON.stringify(response.value));
107
+ default:
108
+ return new Uint8Array(0);
109
+ }
110
+ }
111
+ async function handleSyncBridgeOperation(host, message) {
112
+ switch (message.operation) {
113
+ case "child_process.spawn": {
114
+ const request = parseChildProcessSpawnRequest(message.args[0], "child_process.spawn request");
115
+ const { command, args } = request;
116
+ const options = request.options ?? {};
117
+ const sessionId = host.allocateChildProcessSessionId();
118
+ const events = [];
119
+ const process = host.commandExecutor.spawn(command, args, {
120
+ cwd: options.cwd,
121
+ env: options.env,
122
+ onStdout: (data) => {
123
+ events.push({
124
+ type: "stdout",
125
+ data: encodeChildProcessBytes(data),
126
+ });
127
+ },
128
+ onStderr: (data) => {
129
+ events.push({
130
+ type: "stderr",
131
+ data: encodeChildProcessBytes(data),
132
+ });
133
+ },
134
+ });
135
+ const session = {
136
+ executionId: message.executionId,
137
+ process,
138
+ events,
139
+ exited: false,
140
+ };
141
+ void process
142
+ .wait()
143
+ .then((code) => {
144
+ session.exited = true;
145
+ session.events.push({ type: "exit", exitCode: code, signal: null });
146
+ })
147
+ .catch(() => {
148
+ session.exited = true;
149
+ session.events.push({ type: "exit", exitCode: 1, signal: null });
150
+ });
151
+ host.childProcessSessions.set(sessionId, session);
152
+ return { kind: SYNC_BRIDGE_KIND_JSON, value: sessionId };
153
+ }
154
+ case "child_process.poll": {
155
+ const sessionId = Number(message.args[0]);
156
+ const waitMs = Number(message.args[1] ?? 0);
157
+ const session = host.childProcessSessions.get(sessionId);
158
+ if (!session || session.executionId !== message.executionId) {
159
+ return { kind: SYNC_BRIDGE_KIND_JSON, value: null };
160
+ }
161
+ const event = await waitForChildProcessEvent(session, waitMs);
162
+ if (event?.type === "exit" && session.events.length === 0) {
163
+ host.childProcessSessions.delete(sessionId);
164
+ }
165
+ return { kind: SYNC_BRIDGE_KIND_JSON, value: event };
166
+ }
167
+ case "child_process.write_stdin": {
168
+ const sessionId = Number(message.args[0]);
169
+ const session = host.childProcessSessions.get(sessionId);
170
+ if (!session || session.executionId !== message.executionId) {
171
+ throw new Error(`unknown child_process session ${sessionId}`);
172
+ }
173
+ session?.process.writeStdin(toUint8Array(message.args[1]));
174
+ return { kind: SYNC_BRIDGE_KIND_NONE };
175
+ }
176
+ case "child_process.close_stdin": {
177
+ const sessionId = Number(message.args[0]);
178
+ const session = host.childProcessSessions.get(sessionId);
179
+ if (!session || session.executionId !== message.executionId) {
180
+ throw new Error(`unknown child_process session ${sessionId}`);
181
+ }
182
+ session.process.closeStdin();
183
+ return { kind: SYNC_BRIDGE_KIND_NONE };
184
+ }
185
+ case "child_process.kill": {
186
+ const sessionId = Number(message.args[0]);
187
+ const signal = Number(message.args[1]);
188
+ const session = host.childProcessSessions.get(sessionId);
189
+ if (!session || session.executionId !== message.executionId) {
190
+ throw new Error(`unknown child_process session ${sessionId}`);
191
+ }
192
+ session.process.kill(signal);
193
+ return { kind: SYNC_BRIDGE_KIND_NONE };
194
+ }
195
+ case "child_process.spawn_sync": {
196
+ const request = parseChildProcessSpawnRequest(message.args[0], "child_process.spawn_sync request");
197
+ const { command, args } = request;
198
+ const options = request.options ?? {};
199
+ const stdoutChunks = [];
200
+ const stderrChunks = [];
201
+ const proc = host.commandExecutor.spawn(command, args, {
202
+ cwd: options.cwd,
203
+ env: options.env,
204
+ onStdout: (data) => stdoutChunks.push(data),
205
+ onStderr: (data) => stderrChunks.push(data),
206
+ });
207
+ const input = decodeChildProcessInput(options.input);
208
+ if (input !== undefined) {
209
+ proc.writeStdin(input);
210
+ }
211
+ proc.closeStdin();
212
+ const exitCode = await proc.wait();
213
+ const decoder = new TextDecoder();
214
+ return {
215
+ kind: SYNC_BRIDGE_KIND_TEXT,
216
+ value: JSON.stringify({
217
+ stdout: stdoutChunks.map((chunk) => decoder.decode(chunk)).join(""),
218
+ stderr: stderrChunks.map((chunk) => decoder.decode(chunk)).join(""),
219
+ code: exitCode,
220
+ }),
221
+ };
222
+ }
223
+ case "process.signal_state": {
224
+ const { signal, registration } = parseProcessSignalStateArgs(message.args);
225
+ applyProcessSignalStateUpdate(host.signalStates, message.executionId, signal, registration);
226
+ return { kind: SYNC_BRIDGE_KIND_NONE };
227
+ }
228
+ default:
229
+ throw new Error(`Unsupported browser sync bridge operation: ${String(message.operation)}`);
230
+ }
231
+ }
232
+ export class BrowserRuntimeDriver {
233
+ worker;
234
+ pending = new Map();
235
+ controlToken = createWorkerControlToken();
236
+ defaultOnStdio;
237
+ defaultTimingMitigation;
238
+ networkAdapter;
239
+ commandExecutor;
240
+ syncBridge;
241
+ childProcessSessions = new Map();
242
+ signalStates = new Map();
243
+ ready;
244
+ encoder = new TextEncoder();
245
+ nextId = 1;
246
+ nextExecutionId = 1;
247
+ nextChildProcessSessionId = 1;
248
+ disposed = false;
249
+ networkPermission;
250
+ convergedServicer;
251
+ convergedReady;
252
+ constructor(options, factoryOptions = {}) {
253
+ if (typeof Worker === "undefined") {
254
+ throw new Error("Browser runtime requires a global Worker implementation");
255
+ }
256
+ assertBrowserSyncBridgeSupport();
257
+ this.defaultOnStdio = options.onStdio;
258
+ this.defaultTimingMitigation =
259
+ options.timingMitigation ??
260
+ options.runtime.process.timingMitigation ??
261
+ DEFAULT_BROWSER_TIMING_MITIGATION;
262
+ this.networkAdapter = options.system.network ?? createNetworkStub();
263
+ this.networkPermission = options.system.permissions?.network;
264
+ this.commandExecutor = wrapCommandExecutor(options.system.commandExecutor ?? {
265
+ spawn() {
266
+ throw new Error("ENOSYS: child_process.spawn is not supported");
267
+ },
268
+ }, options.system.permissions);
269
+ this.syncBridge = createBrowserSyncBridgePayload(options.payloadLimits);
270
+ this.worker = new Worker(resolveWorkerUrl(factoryOptions.workerUrl), {
271
+ type: "module",
272
+ });
273
+ this.worker.onmessage = this.handleWorkerMessage;
274
+ this.worker.onerror = this.handleWorkerError;
275
+ const browserSystemOptions = getBrowserSystemDriverOptions(options.system);
276
+ const initPayload = {
277
+ processConfig: options.runtime.process,
278
+ osConfig: options.runtime.os,
279
+ permissions: serializePermissions(options.system.permissions),
280
+ filesystem: browserSystemOptions.filesystem,
281
+ networkEnabled: browserSystemOptions.networkEnabled,
282
+ timingMitigation: this.defaultTimingMitigation,
283
+ payloadLimits: options.payloadLimits,
284
+ syncBridge: this.syncBridge,
285
+ };
286
+ this.ready = this.callWorker("init", initPayload).then(() => undefined);
287
+ this.ready.catch(() => undefined);
288
+ if (factoryOptions.convergedSidecar) {
289
+ this.convergedReady = this.setupConvergedSidecar(factoryOptions.convergedSidecar);
290
+ this.convergedReady.catch(() => undefined);
291
+ }
292
+ }
293
+ async setupConvergedSidecar(options) {
294
+ const [{ createConvergedServicer }, sidecar] = await Promise.all([
295
+ import("./converged-driver-setup.js"),
296
+ options.loadSidecar(),
297
+ ]);
298
+ this.convergedServicer = createConvergedServicer({
299
+ pushFrame: sidecar.pushFrame,
300
+ config: options.config,
301
+ codec: options.codec,
302
+ setNextExecutionId: sidecar.setNextExecutionId?.bind(sidecar),
303
+ onFsReadDenied: options.onFsReadDenied,
304
+ });
305
+ }
306
+ get network() {
307
+ const adapter = this.networkAdapter;
308
+ return {
309
+ fetch: (url, options) => adapter.fetch(url, options),
310
+ dnsLookup: (hostname) => adapter.dnsLookup(hostname),
311
+ httpRequest: (url, options) => adapter.httpRequest(url, options),
312
+ };
313
+ }
314
+ handleWorkerError = (event) => {
315
+ if (this.disposed) {
316
+ return;
317
+ }
318
+ const error = event.error instanceof Error
319
+ ? event.error
320
+ : new Error(event.message
321
+ ? `Browser runtime worker error: ${event.message} (${event.filename}:${event.lineno}:${event.colno})`
322
+ : "Browser runtime worker error");
323
+ this.cleanup(error, { terminateWorker: true });
324
+ };
325
+ handleWorkerMessage = (event) => {
326
+ if (this.disposed) {
327
+ return;
328
+ }
329
+ const message = event.data;
330
+ if (message.controlToken !== this.controlToken) {
331
+ return;
332
+ }
333
+ if (isSyncRequestMessage(message)) {
334
+ void this.handleSyncRequest(message);
335
+ return;
336
+ }
337
+ if (isStdioMessage(message)) {
338
+ const pending = this.pending.get(message.requestId);
339
+ if (pending?.executionId !== message.executionId) {
340
+ return;
341
+ }
342
+ const hook = pending?.hook ?? this.defaultOnStdio;
343
+ if (!hook) {
344
+ return;
345
+ }
346
+ try {
347
+ hook({ channel: message.channel, message: message.message });
348
+ }
349
+ catch {
350
+ // Ignore host hook errors so sandbox execution can continue.
351
+ }
352
+ return;
353
+ }
354
+ if (!isResponseMessage(message)) {
355
+ return;
356
+ }
357
+ const pending = this.pending.get(message.id);
358
+ if (!pending) {
359
+ return;
360
+ }
361
+ this.pending.delete(message.id);
362
+ if (pending.executionId) {
363
+ this.cleanupExecutionState(pending.executionId);
364
+ }
365
+ if (message.ok) {
366
+ pending.resolve(message.result);
367
+ return;
368
+ }
369
+ const error = new Error(message.error.message);
370
+ if (message.error.stack) {
371
+ error.stack = message.error.stack;
372
+ }
373
+ error.code = message.error.code;
374
+ pending.reject(error);
375
+ };
376
+ async handleSyncRequest(message) {
377
+ const signal = new Int32Array(this.syncBridge.signalBuffer);
378
+ const data = new Uint8Array(this.syncBridge.dataBuffer);
379
+ try {
380
+ if (!this.hasPendingExecutionRequest(message.processRequestId, message.executionId)) {
381
+ throw new Error(`Browser runtime sync bridge request for unknown execution ${message.executionId}`);
382
+ }
383
+ if (!isBrowserWorkerSyncOperation(message.operation)) {
384
+ throw new Error(`Unsupported browser sync bridge operation: ${String(message.operation)}`);
385
+ }
386
+ const legacyServicer = (operation, args) => handleSyncBridgeOperation({
387
+ commandExecutor: this.commandExecutor,
388
+ childProcessSessions: this.childProcessSessions,
389
+ signalStates: this.signalStates,
390
+ allocateChildProcessSessionId: () => this.allocateChildProcessSessionId(),
391
+ networkPermission: this.networkPermission,
392
+ }, {
393
+ ...message,
394
+ operation: operation,
395
+ args: [...args],
396
+ });
397
+ // Converged-only: every guest syscall is serviced by the wasm kernel via
398
+ // the converged servicer. The legacy in-process TS kernel is gone; the
399
+ // `legacyServicer` survives ONLY as the converged router's fallback for
400
+ // host capabilities (child_process.* / process.signal_state), never as a
401
+ // standalone guest-syscall path.
402
+ if (this.convergedReady === undefined) {
403
+ throw new Error("Browser runtime requires a converged wasm sidecar; the legacy in-process kernel has been removed");
404
+ }
405
+ await this.convergedReady;
406
+ if (!this.convergedServicer) {
407
+ throw new Error("Converged sidecar servicer is unavailable after setup");
408
+ }
409
+ const response = await this.convergedServicer.route(message.executionId, message.operation, message.args, legacyServicer);
410
+ const bytes = createSyncBridgeResponseBytes(response, this.encoder);
411
+ if (bytes.byteLength > data.byteLength) {
412
+ const suffix = typeof message.args[0] === "string" ? ` ${message.args[0]}` : "";
413
+ throwBridgePayloadTooLarge(`${message.operation}${suffix}`, bytes.byteLength, data.byteLength);
414
+ }
415
+ data.set(bytes, 0);
416
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_OK);
417
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, response.kind);
418
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, bytes.byteLength);
419
+ }
420
+ catch (error) {
421
+ let bytes = this.encoder.encode(JSON.stringify(toBrowserSyncBridgeError(error)));
422
+ if (bytes.byteLength > data.byteLength) {
423
+ bytes = this.encoder.encode(JSON.stringify({
424
+ message: "Browser runtime sync bridge error exceeded shared buffer capacity",
425
+ code: SYNC_BRIDGE_PAYLOAD_LIMIT_ERROR_CODE,
426
+ }));
427
+ }
428
+ data.set(bytes, 0);
429
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATUS_INDEX, SYNC_BRIDGE_STATUS_ERROR);
430
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_KIND_INDEX, SYNC_BRIDGE_KIND_JSON);
431
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_LENGTH_INDEX, bytes.byteLength);
432
+ }
433
+ Atomics.store(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, SYNC_BRIDGE_SIGNAL_STATE_READY);
434
+ Atomics.notify(signal, SYNC_BRIDGE_SIGNAL_STATE_INDEX, 1);
435
+ }
436
+ rejectAllPending(error) {
437
+ const entries = Array.from(this.pending.values());
438
+ this.pending.clear();
439
+ for (const pending of entries) {
440
+ if (pending.executionId) {
441
+ this.cleanupExecutionState(pending.executionId);
442
+ }
443
+ pending.reject(error);
444
+ }
445
+ }
446
+ clearWorkerHandlers() {
447
+ try {
448
+ this.worker.onmessage = null;
449
+ }
450
+ catch {
451
+ // Ignore host Worker implementations with non-writable event hooks.
452
+ }
453
+ try {
454
+ this.worker.onerror = null;
455
+ }
456
+ catch {
457
+ // Ignore host Worker implementations with non-writable event hooks.
458
+ }
459
+ }
460
+ allocateExecutionId() {
461
+ return `exec-${this.nextExecutionId++}`;
462
+ }
463
+ allocateChildProcessSessionId() {
464
+ return this.nextChildProcessSessionId++;
465
+ }
466
+ hasPendingExecutionRequest(requestId, executionId) {
467
+ const pending = this.pending.get(requestId);
468
+ return pending?.executionId === executionId;
469
+ }
470
+ cleanupExecutionState(executionId) {
471
+ this.signalStates.delete(executionId);
472
+ for (const [sessionId, session] of this.childProcessSessions) {
473
+ if (session.executionId === executionId) {
474
+ this.childProcessSessions.delete(sessionId);
475
+ }
476
+ }
477
+ }
478
+ resetSyncBridgeState() {
479
+ new Int32Array(this.syncBridge.signalBuffer).fill(0);
480
+ new Uint8Array(this.syncBridge.dataBuffer).fill(0);
481
+ }
482
+ cleanup(error, options = {}) {
483
+ if (this.disposed) {
484
+ this.rejectAllPending(error);
485
+ return;
486
+ }
487
+ this.disposed = true;
488
+ this.clearWorkerHandlers();
489
+ if (options.terminateWorker) {
490
+ try {
491
+ this.worker.terminate();
492
+ }
493
+ catch {
494
+ // Ignore termination errors while tearing down a broken worker.
495
+ }
496
+ }
497
+ this.resetSyncBridgeState();
498
+ this.signalStates.clear();
499
+ this.rejectAllPending(error);
500
+ }
501
+ callWorker(type, payload, hook, executionId) {
502
+ if (this.disposed) {
503
+ return Promise.reject(new Error("Browser runtime has been disposed"));
504
+ }
505
+ const id = this.nextId++;
506
+ const message = payload === undefined
507
+ ? {
508
+ controlToken: this.controlToken,
509
+ id,
510
+ type,
511
+ }
512
+ : {
513
+ controlToken: this.controlToken,
514
+ id,
515
+ type,
516
+ payload,
517
+ };
518
+ return new Promise((resolve, reject) => {
519
+ this.pending.set(id, { resolve, reject, hook, executionId });
520
+ try {
521
+ this.worker.postMessage(message);
522
+ }
523
+ catch (error) {
524
+ this.pending.delete(id);
525
+ if (executionId) {
526
+ this.cleanupExecutionState(executionId);
527
+ }
528
+ reject(error);
529
+ }
530
+ });
531
+ }
532
+ async run(code, filePath) {
533
+ await this.ready;
534
+ const hook = this.defaultOnStdio;
535
+ const executionId = this.allocateExecutionId();
536
+ return this.callWorker("run", {
537
+ executionId,
538
+ code,
539
+ filePath,
540
+ captureStdio: Boolean(hook),
541
+ }, hook, executionId);
542
+ }
543
+ async exec(code, options) {
544
+ validateBrowserExecOptions(options);
545
+ await this.ready;
546
+ const hook = options?.onStdio ?? this.defaultOnStdio;
547
+ const executionId = this.allocateExecutionId();
548
+ return this.callWorker("exec", {
549
+ executionId,
550
+ code,
551
+ options: toBrowserWorkerExecOptions(options),
552
+ captureStdio: Boolean(hook),
553
+ }, hook, executionId);
554
+ }
555
+ async dispatchExtensionRequest(namespace, payload) {
556
+ await this.ready;
557
+ const response = await this.callWorker("extension", { namespace, payload });
558
+ return response.payload;
559
+ }
560
+ dispose() {
561
+ if (this.disposed) {
562
+ return;
563
+ }
564
+ this.cleanup(new Error("Browser runtime has been disposed"), {
565
+ terminateWorker: true,
566
+ });
567
+ }
568
+ /**
569
+ * Snapshot the converged VM root filesystem (writable changes) so callers can
570
+ * persist them to host storage across runtimes. Returns null in legacy mode.
571
+ */
572
+ async snapshotConvergedRootFilesystem() {
573
+ if (this.convergedReady === undefined) {
574
+ return null;
575
+ }
576
+ await this.convergedReady;
577
+ return this.convergedServicer?.snapshotRootFilesystem() ?? null;
578
+ }
579
+ async terminate() {
580
+ this.dispose();
581
+ }
582
+ signalPendingExecution(signal = 15) {
583
+ if (this.disposed) {
584
+ return false;
585
+ }
586
+ const pending = Array.from(this.pending.values()).find((entry) => entry.executionId);
587
+ if (!pending?.executionId) {
588
+ return false;
589
+ }
590
+ const id = this.nextId++;
591
+ const message = {
592
+ controlToken: this.controlToken,
593
+ id,
594
+ type: "signal",
595
+ payload: {
596
+ executionId: pending.executionId,
597
+ signal,
598
+ },
599
+ };
600
+ this.worker.postMessage(message);
601
+ return true;
602
+ }
603
+ }
604
+ export function createBrowserRuntimeDriverFactory(factoryOptions = {}) {
605
+ return {
606
+ createRuntimeDriver(options) {
607
+ validateBrowserRuntimeOptions(options);
608
+ return new BrowserRuntimeDriver(options, factoryOptions);
609
+ },
610
+ };
611
+ }