@earendil-works/gondolin 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,729 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SandboxWsServer = void 0;
7
- exports.resolveSandboxWsServerOptions = resolveSandboxWsServerOptions;
8
- const fs_1 = __importDefault(require("fs"));
9
- const net_1 = __importDefault(require("net"));
10
- const path_1 = __importDefault(require("path"));
11
- const child_process_1 = require("child_process");
12
- const events_1 = require("events");
13
- const ws_1 = require("ws");
14
- const virtio_protocol_1 = require("./virtio-protocol");
15
- const ws_protocol_1 = require("./ws-protocol");
16
- const sandbox_controller_1 = require("./sandbox-controller");
17
- const qemu_net_1 = require("./qemu-net");
18
- const MAX_REQUEST_ID = 0xffffffff;
19
- const DEFAULT_MAX_JSON_BYTES = 256 * 1024;
20
- const DEFAULT_MAX_STDIN_BYTES = 64 * 1024;
21
- function resolveSandboxWsServerOptions(options = {}) {
22
- const repoRoot = path_1.default.resolve(__dirname, "../..");
23
- const defaultKernel = path_1.default.resolve(repoRoot, "guest/image/out/vmlinuz-virt");
24
- const defaultInitrd = path_1.default.resolve(repoRoot, "guest/image/out/initramfs.cpio.gz");
25
- const defaultVirtio = path_1.default.resolve(repoRoot, "tmp/virtio.sock");
26
- const defaultNetSock = path_1.default.resolve(repoRoot, "tmp/net.sock");
27
- const defaultNetMac = "02:00:00:00:00:01";
28
- const hostArch = detectHostArch();
29
- const defaultQemu = hostArch === "arm64" ? "qemu-system-aarch64" : "qemu-system-x86_64";
30
- const defaultMemory = hostArch === "arm64" ? "512M" : "256M";
31
- return {
32
- host: options.host ?? "127.0.0.1",
33
- port: options.port ?? 8080,
34
- qemuPath: options.qemuPath ?? defaultQemu,
35
- kernelPath: options.kernelPath ?? defaultKernel,
36
- initrdPath: options.initrdPath ?? defaultInitrd,
37
- memory: options.memory ?? defaultMemory,
38
- cpus: options.cpus ?? 1,
39
- virtioSocketPath: options.virtioSocketPath ?? defaultVirtio,
40
- netSocketPath: options.netSocketPath ?? defaultNetSock,
41
- netMac: options.netMac ?? defaultNetMac,
42
- netEnabled: options.netEnabled ?? true,
43
- netDebug: options.netDebug ?? false,
44
- machineType: options.machineType,
45
- accel: options.accel,
46
- cpu: options.cpu,
47
- console: options.console,
48
- token: options.token ?? process.env.ELWING_TOKEN ?? process.env.SANDBOX_WS_TOKEN,
49
- autoRestart: options.autoRestart ?? true,
50
- append: options.append,
51
- maxJsonBytes: options.maxJsonBytes ?? DEFAULT_MAX_JSON_BYTES,
52
- maxStdinBytes: options.maxStdinBytes ?? DEFAULT_MAX_STDIN_BYTES,
53
- policy: options.policy ?? null,
54
- };
55
- }
56
- function detectHostArch() {
57
- if (process.arch === "arm64")
58
- return "arm64";
59
- if (process.platform === "darwin" && process.arch === "x64") {
60
- try {
61
- const result = (0, child_process_1.execSync)("sysctl -n hw.optional.arm64", {
62
- stdio: ["ignore", "pipe", "ignore"],
63
- })
64
- .toString()
65
- .trim();
66
- if (result === "1")
67
- return "arm64";
68
- }
69
- catch {
70
- // ignore
71
- }
72
- }
73
- return process.arch;
74
- }
75
- class VirtioBridge {
76
- constructor(socketPath, maxPendingBytes = 8 * 1024 * 1024) {
77
- this.socketPath = socketPath;
78
- this.maxPendingBytes = maxPendingBytes;
79
- this.socket = null;
80
- this.server = null;
81
- this.reader = new virtio_protocol_1.FrameReader();
82
- this.reconnectTimer = null;
83
- this.pending = [];
84
- this.pendingBytes = 0;
85
- this.waitingDrain = false;
86
- this.allowReconnect = true;
87
- }
88
- connect() {
89
- if (this.server)
90
- return;
91
- this.allowReconnect = true;
92
- if (!fs_1.default.existsSync(path_1.default.dirname(this.socketPath))) {
93
- fs_1.default.mkdirSync(path_1.default.dirname(this.socketPath), { recursive: true });
94
- }
95
- fs_1.default.rmSync(this.socketPath, { force: true });
96
- const server = net_1.default.createServer((socket) => {
97
- this.attachSocket(socket);
98
- });
99
- this.server = server;
100
- server.on("error", (err) => {
101
- this.onError?.(err);
102
- server.close();
103
- });
104
- server.on("close", () => {
105
- this.server = null;
106
- this.scheduleReconnect();
107
- });
108
- server.listen(this.socketPath);
109
- }
110
- disconnect() {
111
- this.allowReconnect = false;
112
- if (this.reconnectTimer) {
113
- clearTimeout(this.reconnectTimer);
114
- this.reconnectTimer = null;
115
- }
116
- if (this.socket) {
117
- this.socket.end();
118
- this.socket = null;
119
- }
120
- if (this.server) {
121
- this.server.close();
122
- this.server = null;
123
- }
124
- this.waitingDrain = false;
125
- }
126
- send(message) {
127
- if (!this.socket) {
128
- this.connect();
129
- }
130
- const frame = (0, virtio_protocol_1.encodeFrame)(message);
131
- if (this.pending.length === 0 && !this.waitingDrain) {
132
- return this.writeFrame(frame);
133
- }
134
- const queued = this.queueFrame(frame);
135
- if (queued && this.socket && this.socket.writable && !this.waitingDrain) {
136
- this.flushPending();
137
- }
138
- return queued;
139
- }
140
- writeFrame(frame) {
141
- if (!this.socket || !this.socket.writable) {
142
- return this.queueFrame(frame);
143
- }
144
- const ok = this.socket.write(frame);
145
- if (!ok) {
146
- this.waitingDrain = true;
147
- this.socket.once("drain", () => {
148
- this.waitingDrain = false;
149
- this.flushPending();
150
- });
151
- }
152
- return true;
153
- }
154
- queueFrame(frame) {
155
- if (this.pendingBytes + frame.length > this.maxPendingBytes) {
156
- return false;
157
- }
158
- this.pending.push(frame);
159
- this.pendingBytes += frame.length;
160
- return true;
161
- }
162
- flushPending() {
163
- if (!this.socket || this.waitingDrain || !this.socket.writable)
164
- return;
165
- while (this.pending.length > 0) {
166
- const frame = this.pending.shift();
167
- this.pendingBytes -= frame.length;
168
- const ok = this.writeFrame(frame);
169
- if (!ok || this.waitingDrain)
170
- return;
171
- }
172
- }
173
- attachSocket(socket) {
174
- if (this.socket) {
175
- this.socket.destroy();
176
- }
177
- this.socket = socket;
178
- this.waitingDrain = false;
179
- socket.on("data", (chunk) => {
180
- this.reader.push(chunk, (frame) => {
181
- try {
182
- const message = (0, virtio_protocol_1.decodeMessage)(frame);
183
- this.onMessage?.(message);
184
- }
185
- catch (err) {
186
- this.onError?.(err);
187
- this.handleDisconnect();
188
- }
189
- });
190
- });
191
- socket.on("error", (err) => {
192
- this.onError?.(err);
193
- this.handleDisconnect();
194
- });
195
- socket.on("end", () => {
196
- this.handleDisconnect();
197
- });
198
- socket.on("close", () => {
199
- this.handleDisconnect();
200
- });
201
- this.flushPending();
202
- }
203
- handleDisconnect() {
204
- if (this.socket) {
205
- this.socket.destroy();
206
- this.socket = null;
207
- }
208
- this.waitingDrain = false;
209
- }
210
- scheduleReconnect() {
211
- if (!this.allowReconnect || this.reconnectTimer)
212
- return;
213
- this.reconnectTimer = setTimeout(() => {
214
- this.reconnectTimer = null;
215
- if (this.allowReconnect) {
216
- this.connect();
217
- }
218
- }, 500);
219
- }
220
- }
221
- function parseMac(value) {
222
- const parts = value.split(":");
223
- if (parts.length !== 6)
224
- return null;
225
- const bytes = parts.map((part) => Number.parseInt(part, 16));
226
- if (bytes.some((byte) => !Number.isFinite(byte) || byte < 0 || byte > 255))
227
- return null;
228
- return Buffer.from(bytes);
229
- }
230
- function isValidRequestId(value) {
231
- return (typeof value === "number" &&
232
- Number.isInteger(value) &&
233
- value >= 0 &&
234
- value <= MAX_REQUEST_ID);
235
- }
236
- function estimateBase64Bytes(value) {
237
- const len = value.length;
238
- const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
239
- return Math.floor((len * 3) / 4) - padding;
240
- }
241
- function validateToken(headers, token) {
242
- if (!token)
243
- return true;
244
- const headerToken = headers["x-elwing-token"] ?? headers["x-sandbox-token"];
245
- if (typeof headerToken === "string" && headerToken === token)
246
- return true;
247
- if (Array.isArray(headerToken) && headerToken.includes(token))
248
- return true;
249
- const auth = headers.authorization;
250
- if (typeof auth === "string" && auth.startsWith("Bearer ")) {
251
- return auth.slice("Bearer ".length) === token;
252
- }
253
- return false;
254
- }
255
- function safeSend(ws, data, options) {
256
- if (ws.readyState !== ws_1.WebSocket.OPEN)
257
- return false;
258
- try {
259
- if (options) {
260
- ws.send(data, options);
261
- }
262
- else {
263
- ws.send(data);
264
- }
265
- return true;
266
- }
267
- catch {
268
- return false;
269
- }
270
- }
271
- function sendJson(ws, message) {
272
- return safeSend(ws, JSON.stringify(message));
273
- }
274
- function sendBinary(ws, data) {
275
- return safeSend(ws, data, { binary: true });
276
- }
277
- function sendError(ws, error) {
278
- return sendJson(ws, error);
279
- }
280
- function formatHost(host) {
281
- return host.includes(":") ? `[${host}]` : host;
282
- }
283
- function resolveAddress(host, address) {
284
- if (!address || typeof address === "string") {
285
- const formattedHost = formatHost(host);
286
- return {
287
- host,
288
- port: 0,
289
- url: `ws://${formattedHost}`,
290
- };
291
- }
292
- const formattedHost = formatHost(host);
293
- return {
294
- host,
295
- port: address.port,
296
- url: `ws://${formattedHost}:${address.port}`,
297
- };
298
- }
299
- class SandboxWsServer extends events_1.EventEmitter {
300
- constructor(options = {}) {
301
- super();
302
- this.wss = null;
303
- this.inflight = new Map();
304
- this.stdinAllowed = new Set();
305
- this.startPromise = null;
306
- this.stopPromise = null;
307
- this.address = null;
308
- this.qemuLogBuffer = "";
309
- this.status = "stopped";
310
- this.options = resolveSandboxWsServerOptions(options);
311
- this.policy = this.options.policy;
312
- const hostArch = detectHostArch();
313
- const consoleDevice = hostArch === "arm64" ? "ttyAMA0" : "ttyS0";
314
- const sandboxConfig = {
315
- qemuPath: this.options.qemuPath,
316
- kernelPath: this.options.kernelPath,
317
- initrdPath: this.options.initrdPath,
318
- memory: this.options.memory,
319
- cpus: this.options.cpus,
320
- virtioSocketPath: this.options.virtioSocketPath,
321
- netSocketPath: this.options.netEnabled ? this.options.netSocketPath : undefined,
322
- netMac: this.options.netMac,
323
- append: this.options.append ?? `console=${consoleDevice}`,
324
- machineType: this.options.machineType,
325
- accel: this.options.accel,
326
- cpu: this.options.cpu,
327
- console: this.options.console,
328
- autoRestart: this.options.autoRestart,
329
- };
330
- this.controller = new sandbox_controller_1.SandboxController(sandboxConfig);
331
- this.bridge = new VirtioBridge(this.options.virtioSocketPath);
332
- const mac = parseMac(this.options.netMac) ?? Buffer.from([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
333
- this.network = this.options.netEnabled
334
- ? new qemu_net_1.QemuNetworkBackend({
335
- socketPath: this.options.netSocketPath,
336
- vmMac: mac,
337
- debug: this.options.netDebug,
338
- policy: this.policy ?? undefined,
339
- })
340
- : null;
341
- if (this.network) {
342
- this.network.on("log", (message) => {
343
- this.emit("log", message);
344
- });
345
- this.network.on("error", (err) => {
346
- this.emit("error", err);
347
- });
348
- this.network.on("policy", (policy) => {
349
- this.emit("policy", policy);
350
- });
351
- }
352
- this.controller.on("state", (state) => {
353
- this.status = state;
354
- if (state === "running") {
355
- this.bridge.connect();
356
- }
357
- if (state === "stopped") {
358
- this.failInflight("sandbox_stopped", "sandbox is not running");
359
- }
360
- if (!this.wss)
361
- return;
362
- for (const client of this.wss.clients) {
363
- sendJson(client, { type: "status", state });
364
- }
365
- this.emit("state", state);
366
- });
367
- this.controller.on("exit", (info) => {
368
- if (this.qemuLogBuffer.length > 0) {
369
- this.emit("log", `[qemu] ${this.qemuLogBuffer}`);
370
- this.qemuLogBuffer = "";
371
- }
372
- this.failInflight("sandbox_stopped", "sandbox exited");
373
- this.emit("exit", info);
374
- });
375
- this.controller.on("log", (chunk) => {
376
- this.qemuLogBuffer += chunk;
377
- let newlineIndex = this.qemuLogBuffer.indexOf("\n");
378
- while (newlineIndex !== -1) {
379
- const line = this.qemuLogBuffer.slice(0, newlineIndex + 1);
380
- this.qemuLogBuffer = this.qemuLogBuffer.slice(newlineIndex + 1);
381
- this.emit("log", `[qemu] ${line}`);
382
- newlineIndex = this.qemuLogBuffer.indexOf("\n");
383
- }
384
- });
385
- this.bridge.onMessage = (message) => {
386
- if (!isValidRequestId(message.id)) {
387
- return;
388
- }
389
- if (message.t === "exec_output") {
390
- const client = this.inflight.get(message.id);
391
- if (!client)
392
- return;
393
- const data = message.p.data;
394
- try {
395
- if (!sendBinary(client, (0, ws_protocol_1.encodeOutputFrame)(message.id, message.p.stream, data))) {
396
- this.inflight.delete(message.id);
397
- this.stdinAllowed.delete(message.id);
398
- }
399
- }
400
- catch {
401
- this.inflight.delete(message.id);
402
- this.stdinAllowed.delete(message.id);
403
- }
404
- }
405
- else if (message.t === "exec_response") {
406
- const client = this.inflight.get(message.id);
407
- if (client) {
408
- sendJson(client, {
409
- type: "exec_response",
410
- id: message.id,
411
- exit_code: message.p.exit_code,
412
- signal: message.p.signal,
413
- });
414
- }
415
- this.inflight.delete(message.id);
416
- this.stdinAllowed.delete(message.id);
417
- }
418
- else if (message.t === "error") {
419
- const client = this.inflight.get(message.id);
420
- if (client) {
421
- sendError(client, {
422
- type: "error",
423
- id: message.id,
424
- code: message.p.code,
425
- message: message.p.message,
426
- });
427
- }
428
- this.inflight.delete(message.id);
429
- this.stdinAllowed.delete(message.id);
430
- }
431
- };
432
- this.bridge.onError = (err) => {
433
- const message = err instanceof Error ? err.message : "unknown error";
434
- this.emit("error", new Error(`[virtio] decode error: ${message}`));
435
- this.failInflight("protocol_error", "virtio decode error");
436
- };
437
- }
438
- getAddress() {
439
- return this.address;
440
- }
441
- getUrl() {
442
- return this.address?.url ?? null;
443
- }
444
- getState() {
445
- return this.status;
446
- }
447
- getPolicy() {
448
- return this.policy;
449
- }
450
- setPolicy(policy) {
451
- this.policy = policy;
452
- this.network?.setPolicy(policy);
453
- this.emit("policy", policy);
454
- }
455
- async start() {
456
- if (this.startPromise)
457
- return this.startPromise;
458
- this.startPromise = this.startInternal().finally(() => {
459
- this.startPromise = null;
460
- });
461
- return this.startPromise;
462
- }
463
- async stop() {
464
- if (this.stopPromise)
465
- return this.stopPromise;
466
- this.stopPromise = this.stopInternal().finally(() => {
467
- this.stopPromise = null;
468
- });
469
- return this.stopPromise;
470
- }
471
- async startInternal() {
472
- if (this.wss) {
473
- return this.address ?? resolveAddress(this.options.host, this.wss.address());
474
- }
475
- this.wss = new ws_1.WebSocketServer({
476
- host: this.options.host,
477
- port: this.options.port,
478
- maxPayload: this.options.maxJsonBytes,
479
- verifyClient: (info, done) => {
480
- if (!validateToken(info.req.headers, this.options.token)) {
481
- done(false, 401, "Unauthorized");
482
- return;
483
- }
484
- done(true);
485
- },
486
- });
487
- this.wss.on("connection", (ws) => this.handleConnection(ws));
488
- this.wss.on("close", () => {
489
- this.wss = null;
490
- this.address = null;
491
- });
492
- this.network?.start();
493
- this.bridge.connect();
494
- void this.controller.start();
495
- const address = await new Promise((resolve, reject) => {
496
- const handleError = (err) => {
497
- cleanup();
498
- reject(err);
499
- };
500
- const handleListening = () => {
501
- cleanup();
502
- const resolved = resolveAddress(this.options.host, this.wss?.address() ?? null);
503
- this.address = resolved;
504
- resolve(resolved);
505
- };
506
- const cleanup = () => {
507
- this.wss?.off("error", handleError);
508
- this.wss?.off("listening", handleListening);
509
- };
510
- this.wss?.once("error", handleError);
511
- this.wss?.once("listening", handleListening);
512
- });
513
- return address;
514
- }
515
- async stopInternal() {
516
- this.failInflight("server_shutdown", "server is shutting down");
517
- await this.controller.stop();
518
- this.bridge.disconnect();
519
- this.network?.stop();
520
- if (this.wss) {
521
- for (const client of this.wss.clients) {
522
- client.terminate();
523
- }
524
- await new Promise((resolve) => {
525
- let finished = false;
526
- const finish = () => {
527
- if (finished)
528
- return;
529
- finished = true;
530
- clearTimeout(timeout);
531
- resolve();
532
- };
533
- const timeout = setTimeout(() => {
534
- finish();
535
- }, 1000);
536
- this.wss?.close(() => finish());
537
- });
538
- }
539
- this.wss = null;
540
- this.address = null;
541
- }
542
- handleConnection(ws) {
543
- if (!sendJson(ws, { type: "status", state: this.controller.getState() })) {
544
- ws.close();
545
- return;
546
- }
547
- ws.on("message", (data, isBinary) => {
548
- if (isBinary) {
549
- sendError(ws, {
550
- type: "error",
551
- code: "invalid_message",
552
- message: "binary input frames are not supported",
553
- });
554
- return;
555
- }
556
- const size = typeof data === "string"
557
- ? Buffer.byteLength(data)
558
- : Buffer.isBuffer(data)
559
- ? data.length
560
- : Array.isArray(data)
561
- ? data.reduce((total, chunk) => total + chunk.length, 0)
562
- : data.byteLength;
563
- if (size > this.options.maxJsonBytes) {
564
- sendError(ws, {
565
- type: "error",
566
- code: "payload_too_large",
567
- message: "message exceeds size limit",
568
- });
569
- return;
570
- }
571
- let message;
572
- try {
573
- message = JSON.parse(data.toString());
574
- }
575
- catch {
576
- sendError(ws, {
577
- type: "error",
578
- code: "invalid_json",
579
- message: "failed to parse JSON",
580
- });
581
- return;
582
- }
583
- if (message.type === "exec") {
584
- this.handleExec(ws, message);
585
- }
586
- else if (message.type === "stdin") {
587
- this.handleStdin(ws, message);
588
- }
589
- else if (message.type === "lifecycle") {
590
- if (message.action === "restart") {
591
- void this.controller.restart();
592
- }
593
- else if (message.action === "shutdown") {
594
- void this.controller.stop();
595
- }
596
- }
597
- else if (message.type === "policy") {
598
- this.handlePolicy(ws, message);
599
- }
600
- else {
601
- sendError(ws, {
602
- type: "error",
603
- code: "unknown_type",
604
- message: "unsupported message type",
605
- });
606
- }
607
- });
608
- ws.on("close", () => {
609
- for (const [id, client] of this.inflight.entries()) {
610
- if (client === ws) {
611
- this.inflight.delete(id);
612
- this.stdinAllowed.delete(id);
613
- }
614
- }
615
- });
616
- }
617
- handleExec(ws, message) {
618
- if (!isValidRequestId(message.id) || !message.cmd) {
619
- sendError(ws, {
620
- type: "error",
621
- code: "invalid_request",
622
- message: "exec requires uint32 id and cmd",
623
- });
624
- return;
625
- }
626
- if (this.inflight.has(message.id)) {
627
- sendError(ws, {
628
- type: "error",
629
- id: message.id,
630
- code: "duplicate_id",
631
- message: "request id already in use",
632
- });
633
- return;
634
- }
635
- this.inflight.set(message.id, ws);
636
- if (message.stdin)
637
- this.stdinAllowed.add(message.id);
638
- const payload = {
639
- cmd: message.cmd,
640
- argv: message.argv ?? [],
641
- env: message.env ?? [],
642
- cwd: message.cwd,
643
- stdin: message.stdin ?? false,
644
- pty: message.pty ?? false,
645
- };
646
- if (!this.bridge.send((0, virtio_protocol_1.buildExecRequest)(message.id, payload))) {
647
- this.inflight.delete(message.id);
648
- this.stdinAllowed.delete(message.id);
649
- sendError(ws, {
650
- type: "error",
651
- id: message.id,
652
- code: "queue_full",
653
- message: "virtio bridge queue exceeded",
654
- });
655
- }
656
- }
657
- handleStdin(ws, message) {
658
- if (!isValidRequestId(message.id)) {
659
- sendError(ws, {
660
- type: "error",
661
- code: "invalid_request",
662
- message: "stdin requires a uint32 id",
663
- });
664
- return;
665
- }
666
- if (!this.inflight.has(message.id)) {
667
- sendError(ws, {
668
- type: "error",
669
- id: message.id,
670
- code: "unknown_id",
671
- message: "request id not found",
672
- });
673
- return;
674
- }
675
- if (!this.stdinAllowed.has(message.id)) {
676
- sendError(ws, {
677
- type: "error",
678
- id: message.id,
679
- code: "stdin_disabled",
680
- message: "stdin was not enabled for this request",
681
- });
682
- return;
683
- }
684
- const base64 = message.data ?? "";
685
- if (base64 && estimateBase64Bytes(base64) > this.options.maxStdinBytes) {
686
- sendError(ws, {
687
- type: "error",
688
- id: message.id,
689
- code: "payload_too_large",
690
- message: "stdin chunk exceeds size limit",
691
- });
692
- return;
693
- }
694
- const data = base64 ? Buffer.from(base64, "base64") : Buffer.alloc(0);
695
- if (data.length > this.options.maxStdinBytes) {
696
- sendError(ws, {
697
- type: "error",
698
- id: message.id,
699
- code: "payload_too_large",
700
- message: "stdin chunk exceeds size limit",
701
- });
702
- return;
703
- }
704
- if (!this.bridge.send((0, virtio_protocol_1.buildStdinData)(message.id, data, message.eof))) {
705
- sendError(ws, {
706
- type: "error",
707
- id: message.id,
708
- code: "queue_full",
709
- message: "virtio bridge queue exceeded",
710
- });
711
- }
712
- }
713
- handlePolicy(_ws, message) {
714
- this.setPolicy(message.policy);
715
- }
716
- failInflight(code, message) {
717
- for (const [id, client] of this.inflight.entries()) {
718
- sendError(client, {
719
- type: "error",
720
- id,
721
- code,
722
- message,
723
- });
724
- }
725
- this.inflight.clear();
726
- this.stdinAllowed.clear();
727
- }
728
- }
729
- exports.SandboxWsServer = SandboxWsServer;