@ai-ecoverse/slicc-kernel 1.8.11 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/index.js +391 -2
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +486 -66
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +382 -28
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +945 -179
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +382 -28
- package/dist/process-worker.js.map +4 -4
- package/package.json +2 -2
package/dist/node.js
CHANGED
|
@@ -1,5 +1,496 @@
|
|
|
1
1
|
// src/node.ts
|
|
2
|
-
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
|
|
3
|
+
|
|
4
|
+
// src/fs/types.ts
|
|
5
|
+
function fsError(code, path) {
|
|
6
|
+
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
7
|
+
}
|
|
8
|
+
function normalizePath(path) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
for (const part of path.split("/")) {
|
|
11
|
+
if (part === "" || part === ".") continue;
|
|
12
|
+
if (part === "..") parts.pop();
|
|
13
|
+
else parts.push(part);
|
|
14
|
+
}
|
|
15
|
+
return `/${parts.join("/")}`;
|
|
16
|
+
}
|
|
17
|
+
function resolvePath(base, path) {
|
|
18
|
+
return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
|
|
19
|
+
}
|
|
20
|
+
function inodeOf(path) {
|
|
21
|
+
let h1 = 3735928559;
|
|
22
|
+
let h2 = 1103547991;
|
|
23
|
+
for (let i = 0; i < path.length; i++) {
|
|
24
|
+
const c = path.charCodeAt(i);
|
|
25
|
+
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
26
|
+
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
27
|
+
}
|
|
28
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
29
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
30
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/kernel/signals.ts
|
|
34
|
+
var SIG = {
|
|
35
|
+
HUP: 1,
|
|
36
|
+
INT: 2,
|
|
37
|
+
QUIT: 3,
|
|
38
|
+
ILL: 4,
|
|
39
|
+
TRAP: 5,
|
|
40
|
+
ABRT: 6,
|
|
41
|
+
BUS: 7,
|
|
42
|
+
FPE: 8,
|
|
43
|
+
KILL: 9,
|
|
44
|
+
USR1: 10,
|
|
45
|
+
SEGV: 11,
|
|
46
|
+
USR2: 12,
|
|
47
|
+
PIPE: 13,
|
|
48
|
+
ALRM: 14,
|
|
49
|
+
TERM: 15,
|
|
50
|
+
STKFLT: 16,
|
|
51
|
+
CHLD: 17,
|
|
52
|
+
CONT: 18,
|
|
53
|
+
STOP: 19,
|
|
54
|
+
TSTP: 20,
|
|
55
|
+
TTIN: 21,
|
|
56
|
+
TTOU: 22,
|
|
57
|
+
URG: 23,
|
|
58
|
+
XCPU: 24,
|
|
59
|
+
XFSZ: 25,
|
|
60
|
+
VTALRM: 26,
|
|
61
|
+
PROF: 27,
|
|
62
|
+
WINCH: 28,
|
|
63
|
+
POLL: 29,
|
|
64
|
+
PWR: 30,
|
|
65
|
+
SYS: 31
|
|
66
|
+
};
|
|
67
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
68
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
69
|
+
function defaultAction(sig) {
|
|
70
|
+
if (sig === SIG.CONT) return "continue";
|
|
71
|
+
if (STOPPING.has(sig)) return "stop";
|
|
72
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
73
|
+
}
|
|
74
|
+
function isSignal(sig) {
|
|
75
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
76
|
+
}
|
|
77
|
+
function sigbit(sig) {
|
|
78
|
+
return 1 << sig;
|
|
79
|
+
}
|
|
80
|
+
var SIGNAL_BY_NAME = {
|
|
81
|
+
SIGINT: SIG.INT,
|
|
82
|
+
SIGTERM: SIG.TERM,
|
|
83
|
+
SIGKILL: SIG.KILL,
|
|
84
|
+
SIGSTOP: SIG.STOP,
|
|
85
|
+
SIGCONT: SIG.CONT
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/body-idle.ts
|
|
89
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
90
|
+
function idleFailure(ms) {
|
|
91
|
+
return Object.assign(new Error(`the response body sent no data for ${ms} ms`), {
|
|
92
|
+
code: "ETIMEDOUT"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function readWithin(reader, ms) {
|
|
96
|
+
if (!(ms > 0 && ms < Number.POSITIVE_INFINITY)) return reader.read();
|
|
97
|
+
let timer;
|
|
98
|
+
const idle = new Promise((_, reject) => {
|
|
99
|
+
timer = setTimeout(() => reject(idleFailure(ms)), ms);
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
return await Promise.race([reader.read(), idle]);
|
|
103
|
+
} finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/transport.ts
|
|
109
|
+
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
110
|
+
function joinHeaders(headers) {
|
|
111
|
+
const out = new Headers();
|
|
112
|
+
for (const [name2, value] of headers) {
|
|
113
|
+
try {
|
|
114
|
+
out.append(name2, value);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
async function* chunks(body, idleMs) {
|
|
121
|
+
if (!body) return;
|
|
122
|
+
const reader = body.getReader();
|
|
123
|
+
try {
|
|
124
|
+
for (let next = await readWithin(reader, idleMs); !next.done; next = await readWithin(reader, idleMs)) {
|
|
125
|
+
if (next.value.byteLength > 0) yield next.value;
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
await reader.cancel().catch(() => void 0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function fetchTransport(options = {}) {
|
|
132
|
+
const send = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
133
|
+
return {
|
|
134
|
+
traits: {
|
|
135
|
+
manualRedirects: false,
|
|
136
|
+
encodedBodies: false,
|
|
137
|
+
maxRequestBody: options.maxRequestBody ?? MAX_REQUEST_BODY,
|
|
138
|
+
crossOrigin: "cors"
|
|
139
|
+
},
|
|
140
|
+
async fetch(request) {
|
|
141
|
+
let response;
|
|
142
|
+
try {
|
|
143
|
+
response = await send(request.url, {
|
|
144
|
+
method: request.method,
|
|
145
|
+
headers: joinHeaders(request.headers),
|
|
146
|
+
...request.body ? { body: request.body } : {},
|
|
147
|
+
signal: request.signal,
|
|
148
|
+
redirect: "follow",
|
|
149
|
+
credentials: "omit",
|
|
150
|
+
mode: "cors"
|
|
151
|
+
});
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (request.signal.aborted) throw e;
|
|
154
|
+
throw Object.assign(
|
|
155
|
+
new Error(
|
|
156
|
+
`fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
|
|
157
|
+
),
|
|
158
|
+
{ status: 502 }
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const headers = [];
|
|
162
|
+
response.headers.forEach((value, name2) => {
|
|
163
|
+
headers.push([name2, value]);
|
|
164
|
+
});
|
|
165
|
+
const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
|
|
166
|
+
return {
|
|
167
|
+
status: response.status,
|
|
168
|
+
statusText: response.statusText,
|
|
169
|
+
headers,
|
|
170
|
+
body,
|
|
171
|
+
cancel: async () => {
|
|
172
|
+
await body.return(void 0);
|
|
173
|
+
await response.body?.cancel().catch(() => void 0);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function serveTransport(peer, transport) {
|
|
180
|
+
const open = /* @__PURE__ */ new Map();
|
|
181
|
+
const fail2 = (nid, e) => {
|
|
182
|
+
open.delete(nid);
|
|
183
|
+
const { status, code } = e;
|
|
184
|
+
peer.postMessage({
|
|
185
|
+
net: "error",
|
|
186
|
+
nid,
|
|
187
|
+
message: e instanceof Error ? e.message : String(e),
|
|
188
|
+
...typeof status === "number" ? { status } : {},
|
|
189
|
+
...typeof code === "string" ? { code } : {}
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const start = async (call) => {
|
|
193
|
+
const abort = new AbortController();
|
|
194
|
+
const entry = { abort };
|
|
195
|
+
open.set(call.nid, entry);
|
|
196
|
+
try {
|
|
197
|
+
const response = await transport.fetch({
|
|
198
|
+
url: call.url,
|
|
199
|
+
method: call.method,
|
|
200
|
+
headers: call.headers,
|
|
201
|
+
...call.body ? { body: call.body } : {},
|
|
202
|
+
signal: abort.signal
|
|
203
|
+
});
|
|
204
|
+
if (!open.has(call.nid)) {
|
|
205
|
+
await response.cancel();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
209
|
+
peer.postMessage({
|
|
210
|
+
net: "head",
|
|
211
|
+
nid: call.nid,
|
|
212
|
+
status: response.status,
|
|
213
|
+
statusText: response.statusText,
|
|
214
|
+
headers: response.headers
|
|
215
|
+
});
|
|
216
|
+
} catch (e) {
|
|
217
|
+
fail2(call.nid, e);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const read = async (nid) => {
|
|
221
|
+
const body = open.get(nid)?.body;
|
|
222
|
+
if (!body) return;
|
|
223
|
+
try {
|
|
224
|
+
const next = await body.next();
|
|
225
|
+
if (next.done) {
|
|
226
|
+
open.delete(nid);
|
|
227
|
+
peer.postMessage({ net: "end", nid });
|
|
228
|
+
} else {
|
|
229
|
+
const bytes = next.value.slice();
|
|
230
|
+
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
fail2(nid, e);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const drop = (nid) => {
|
|
237
|
+
const entry = open.get(nid);
|
|
238
|
+
open.delete(nid);
|
|
239
|
+
entry?.abort.abort();
|
|
240
|
+
void entry?.response?.cancel().catch(() => void 0);
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
answer(call) {
|
|
244
|
+
if (call.net === "fetch") void start(call);
|
|
245
|
+
else if (call.net === "read") void read(call.nid);
|
|
246
|
+
else drop(call.nid);
|
|
247
|
+
},
|
|
248
|
+
close() {
|
|
249
|
+
for (const nid of [...open.keys()]) drop(nid);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/client/protocol.ts
|
|
255
|
+
var PROTOCOL = [1, 0];
|
|
256
|
+
var FS_METHODS = [
|
|
257
|
+
"readFile",
|
|
258
|
+
"writeFile",
|
|
259
|
+
"stat",
|
|
260
|
+
"lstat",
|
|
261
|
+
"readdir",
|
|
262
|
+
"mkdir",
|
|
263
|
+
"rm",
|
|
264
|
+
"rename",
|
|
265
|
+
"realpath",
|
|
266
|
+
"symlink",
|
|
267
|
+
"readlink",
|
|
268
|
+
"exists"
|
|
269
|
+
];
|
|
270
|
+
function locksOf(scope = globalThis) {
|
|
271
|
+
const locks = scope.navigator?.locks;
|
|
272
|
+
return typeof locks?.request === "function" ? locks : void 0;
|
|
273
|
+
}
|
|
274
|
+
function versionError(theirs) {
|
|
275
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
276
|
+
if (major === PROTOCOL[0]) return void 0;
|
|
277
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
278
|
+
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/client/serve-client.ts
|
|
282
|
+
var MAX_LINKS = 40;
|
|
283
|
+
function act(session, req, signal) {
|
|
284
|
+
if (req.action === "write") session.write(req.bytes);
|
|
285
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
286
|
+
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
287
|
+
else session.close();
|
|
288
|
+
}
|
|
289
|
+
async function realpath(fs, path) {
|
|
290
|
+
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
291
|
+
let resolved = "";
|
|
292
|
+
for (let hops = 0; parts.length > 0; ) {
|
|
293
|
+
const [name2, ...rest] = parts;
|
|
294
|
+
const next = `${resolved}/${name2}`;
|
|
295
|
+
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
296
|
+
resolved = next;
|
|
297
|
+
parts = rest;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
|
|
301
|
+
const target = await fs.readlink(next);
|
|
302
|
+
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
303
|
+
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
304
|
+
resolved = "";
|
|
305
|
+
}
|
|
306
|
+
return resolved || "/";
|
|
307
|
+
}
|
|
308
|
+
async function fsCall(fs, method, args) {
|
|
309
|
+
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
310
|
+
switch (method) {
|
|
311
|
+
case "readFile": {
|
|
312
|
+
const bytes = (await fs.readFileBuffer(path(0))).slice();
|
|
313
|
+
return { result: bytes, transfer: [bytes.buffer] };
|
|
314
|
+
}
|
|
315
|
+
case "writeFile":
|
|
316
|
+
await fs.writeFile(path(0), args[1]);
|
|
317
|
+
return { result: true };
|
|
318
|
+
case "stat":
|
|
319
|
+
return { result: await fs.stat(path(0)) };
|
|
320
|
+
case "lstat":
|
|
321
|
+
return { result: await fs.lstat(path(0)) };
|
|
322
|
+
case "readdir":
|
|
323
|
+
return { result: await fs.readdir(path(0)) };
|
|
324
|
+
case "mkdir":
|
|
325
|
+
await fs.mkdir(path(0), { recursive: true });
|
|
326
|
+
return { result: true };
|
|
327
|
+
case "rm":
|
|
328
|
+
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
329
|
+
return { result: true };
|
|
330
|
+
case "rename":
|
|
331
|
+
await fs.rename(path(0), path(1));
|
|
332
|
+
return { result: true };
|
|
333
|
+
case "realpath":
|
|
334
|
+
return { result: await realpath(fs, String(args[0])) };
|
|
335
|
+
case "symlink":
|
|
336
|
+
await fs.symlink(String(args[0]), path(1));
|
|
337
|
+
return { result: true };
|
|
338
|
+
case "readlink":
|
|
339
|
+
return { result: await fs.readlink(path(0)) };
|
|
340
|
+
default:
|
|
341
|
+
return { result: await fs.exists(path(0)) };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function serveClient(port, host) {
|
|
345
|
+
let state = "new";
|
|
346
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
347
|
+
const groups = /* @__PURE__ */ new Set();
|
|
348
|
+
let net;
|
|
349
|
+
const ended = Promise.withResolvers();
|
|
350
|
+
const send = (message2, transfer = []) => {
|
|
351
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
352
|
+
};
|
|
353
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
354
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
355
|
+
if (state === "closed") return;
|
|
356
|
+
send({ bye });
|
|
357
|
+
state = "closed";
|
|
358
|
+
net?.close();
|
|
359
|
+
for (const session of terminals.values()) session.close();
|
|
360
|
+
terminals.clear();
|
|
361
|
+
const pids = [...groups];
|
|
362
|
+
groups.clear();
|
|
363
|
+
if (kill && pids.length > 0) {
|
|
364
|
+
void host.launcher().then((launcher) => {
|
|
365
|
+
for (const pid of pids) launcher.kill(-pid, SIG.KILL);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
port.close?.();
|
|
369
|
+
ended.resolve();
|
|
370
|
+
}
|
|
371
|
+
async function hello(message2) {
|
|
372
|
+
const refused = versionError(message2.protocol);
|
|
373
|
+
if (refused) {
|
|
374
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
375
|
+
detach();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const launcher = await host.launcher();
|
|
379
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
380
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
381
|
+
state = "open";
|
|
382
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
383
|
+
send({
|
|
384
|
+
hello: {
|
|
385
|
+
protocol: PROTOCOL,
|
|
386
|
+
traits: transport.traits,
|
|
387
|
+
...host.lock ? { lock: host.lock } : {}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
async function terminal(req, l) {
|
|
392
|
+
const session = await l.openTerminal(req.argv, {
|
|
393
|
+
...req.options,
|
|
394
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
395
|
+
});
|
|
396
|
+
if (state === "closed") {
|
|
397
|
+
session.close();
|
|
398
|
+
} else {
|
|
399
|
+
terminals.set(req.id, session);
|
|
400
|
+
reply(req.id, { started: session.pid });
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
return { result: await session.exited };
|
|
404
|
+
} finally {
|
|
405
|
+
terminals.delete(req.id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function spawn(req, l) {
|
|
409
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
410
|
+
let leader;
|
|
411
|
+
const result = await l.run(req.argv, {
|
|
412
|
+
...req.options,
|
|
413
|
+
collect: false,
|
|
414
|
+
onStdout: out(1),
|
|
415
|
+
onStderr: out(2),
|
|
416
|
+
onStarted: (pid) => {
|
|
417
|
+
leader = pid;
|
|
418
|
+
if (state === "closed") return;
|
|
419
|
+
groups.add(pid);
|
|
420
|
+
reply(req.id, { started: pid });
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
424
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
425
|
+
return { result: result.status };
|
|
426
|
+
}
|
|
427
|
+
async function handle(req) {
|
|
428
|
+
const l = await host.launcher();
|
|
429
|
+
switch (req.op) {
|
|
430
|
+
case "spawn":
|
|
431
|
+
return spawn(req, l);
|
|
432
|
+
case "open-terminal":
|
|
433
|
+
return terminal(req, l);
|
|
434
|
+
case "terminal": {
|
|
435
|
+
const session = terminals.get(req.terminal);
|
|
436
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
437
|
+
act(session, req, host.signal);
|
|
438
|
+
return { result: true };
|
|
439
|
+
}
|
|
440
|
+
case "kill":
|
|
441
|
+
if (!l.kill(req.pid, host.signal(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
442
|
+
return { result: true };
|
|
443
|
+
case "ps":
|
|
444
|
+
return {
|
|
445
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
446
|
+
};
|
|
447
|
+
case "fs":
|
|
448
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
449
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
450
|
+
}
|
|
451
|
+
return fsCall(l.fs, req.method, req.args);
|
|
452
|
+
case "detach":
|
|
453
|
+
return { result: true };
|
|
454
|
+
default:
|
|
455
|
+
throw new Error(`unknown op ${req.op}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
port.addEventListener("message", (event) => {
|
|
459
|
+
const data = event.data;
|
|
460
|
+
if (state === "closed") return;
|
|
461
|
+
if (data.hello) {
|
|
462
|
+
if (state === "new") {
|
|
463
|
+
hello(data.hello).catch((err) => {
|
|
464
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
465
|
+
detach();
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (state !== "open") return;
|
|
471
|
+
if (data.net !== void 0) {
|
|
472
|
+
net?.answer(data);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const req = data;
|
|
476
|
+
handle(req).then(
|
|
477
|
+
({ result, transfer }) => {
|
|
478
|
+
reply(req.id, { result }, transfer);
|
|
479
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
480
|
+
},
|
|
481
|
+
(err) => {
|
|
482
|
+
const code = err?.code;
|
|
483
|
+
reply(req.id, {
|
|
484
|
+
error: err instanceof Error ? err.message : String(err),
|
|
485
|
+
...typeof code === "string" ? { code } : {}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
});
|
|
490
|
+
port.addEventListener("close", () => detach());
|
|
491
|
+
port.start?.();
|
|
492
|
+
return { detach, closed: ended.promise };
|
|
493
|
+
}
|
|
3
494
|
|
|
4
495
|
// src/fs/meta.ts
|
|
5
496
|
function parentOf(path) {
|
|
@@ -35,48 +526,19 @@ var MemoryMeta = class {
|
|
|
35
526
|
}
|
|
36
527
|
async remove(paths) {
|
|
37
528
|
for (const key of [...this.entries.keys()]) {
|
|
38
|
-
if (paths.some((path) => under(key, path))) this.entries.delete(key);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async links(dir) {
|
|
42
|
-
return [...this.entries.values()].filter((entry) => entry.dir === dir);
|
|
43
|
-
}
|
|
44
|
-
async under(path) {
|
|
45
|
-
return [...this.entries.values()].filter((entry) => under(entry.path, path));
|
|
46
|
-
}
|
|
47
|
-
async all() {
|
|
48
|
-
return [...this.entries.values()];
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// src/fs/types.ts
|
|
53
|
-
function fsError(code, path) {
|
|
54
|
-
return Object.assign(new Error(`${code}: ${path}`), { code });
|
|
55
|
-
}
|
|
56
|
-
function normalizePath(path) {
|
|
57
|
-
const parts = [];
|
|
58
|
-
for (const part of path.split("/")) {
|
|
59
|
-
if (part === "" || part === ".") continue;
|
|
60
|
-
if (part === "..") parts.pop();
|
|
61
|
-
else parts.push(part);
|
|
529
|
+
if (paths.some((path) => under(key, path))) this.entries.delete(key);
|
|
530
|
+
}
|
|
62
531
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
function resolvePath(base, path) {
|
|
66
|
-
return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
|
|
67
|
-
}
|
|
68
|
-
function inodeOf(path) {
|
|
69
|
-
let h1 = 3735928559;
|
|
70
|
-
let h2 = 1103547991;
|
|
71
|
-
for (let i = 0; i < path.length; i++) {
|
|
72
|
-
const c = path.charCodeAt(i);
|
|
73
|
-
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
74
|
-
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
532
|
+
async links(dir) {
|
|
533
|
+
return [...this.entries.values()].filter((entry) => entry.dir === dir);
|
|
75
534
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
535
|
+
async under(path) {
|
|
536
|
+
return [...this.entries.values()].filter((entry) => under(entry.path, path));
|
|
537
|
+
}
|
|
538
|
+
async all() {
|
|
539
|
+
return [...this.entries.values()];
|
|
540
|
+
}
|
|
541
|
+
};
|
|
80
542
|
|
|
81
543
|
// src/fs/opfs.ts
|
|
82
544
|
var S_IFDIR = 16384;
|
|
@@ -84,7 +546,7 @@ var S_IFREG = 32768;
|
|
|
84
546
|
var S_IFLNK = 40960;
|
|
85
547
|
var PERM_MASK = 4095;
|
|
86
548
|
var READ_ATTEMPTS = 3;
|
|
87
|
-
var
|
|
549
|
+
var MAX_LINKS2 = 40;
|
|
88
550
|
var CHECK_MS = 1e3;
|
|
89
551
|
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
90
552
|
var ERRNO_BY_DOM_ERROR = {
|
|
@@ -235,7 +697,7 @@ var OpfsFs = class {
|
|
|
235
697
|
}
|
|
236
698
|
async locate(path, follow) {
|
|
237
699
|
let current = path;
|
|
238
|
-
for (let hops = 0; hops <=
|
|
700
|
+
for (let hops = 0; hops <= MAX_LINKS2; hops++) {
|
|
239
701
|
try {
|
|
240
702
|
return { path: current, handle: await this.handle(current) };
|
|
241
703
|
} catch (err) {
|
|
@@ -699,7 +1161,7 @@ async function scanCommands(fs, modules) {
|
|
|
699
1161
|
var S_IFDIR2 = 16384;
|
|
700
1162
|
var S_IFREG2 = 32768;
|
|
701
1163
|
var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
|
|
702
|
-
var
|
|
1164
|
+
var MAX_LINKS3 = 40;
|
|
703
1165
|
function missing2(err) {
|
|
704
1166
|
return err?.code === "ENOENT";
|
|
705
1167
|
}
|
|
@@ -719,7 +1181,7 @@ function synthetic(directory, path) {
|
|
|
719
1181
|
}
|
|
720
1182
|
async function followLinks(fs, path) {
|
|
721
1183
|
let current = path;
|
|
722
|
-
for (let hops = 0; hops <
|
|
1184
|
+
for (let hops = 0; hops < MAX_LINKS3; hops++) {
|
|
723
1185
|
const st = await fs.lstat(current).catch(() => void 0);
|
|
724
1186
|
if (!st?.isSymbolicLink) return current;
|
|
725
1187
|
const parent = current.slice(0, current.lastIndexOf("/")) || "/";
|
|
@@ -1379,6 +1841,11 @@ function statJson(s) {
|
|
|
1379
1841
|
async function readdirStat(fs, dir) {
|
|
1380
1842
|
return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
|
|
1381
1843
|
}
|
|
1844
|
+
async function storageUsage() {
|
|
1845
|
+
const estimate = await globalThis.navigator?.storage?.estimate?.();
|
|
1846
|
+
if (typeof estimate?.quota !== "number") return null;
|
|
1847
|
+
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
1848
|
+
}
|
|
1382
1849
|
var done = { ok: true, kind: "void" };
|
|
1383
1850
|
function json(value) {
|
|
1384
1851
|
return { ok: true, kind: "json", json: value };
|
|
@@ -1429,6 +1896,8 @@ async function run(fs, path, req, cwd) {
|
|
|
1429
1896
|
case "utimes":
|
|
1430
1897
|
await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
|
|
1431
1898
|
return done;
|
|
1899
|
+
case "statfs":
|
|
1900
|
+
return json(await storageUsage());
|
|
1432
1901
|
default:
|
|
1433
1902
|
return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
|
|
1434
1903
|
}
|
|
@@ -3152,61 +3621,6 @@ var HttpHandles = class {
|
|
|
3152
3621
|
}
|
|
3153
3622
|
};
|
|
3154
3623
|
|
|
3155
|
-
// src/kernel/signals.ts
|
|
3156
|
-
var SIG = {
|
|
3157
|
-
HUP: 1,
|
|
3158
|
-
INT: 2,
|
|
3159
|
-
QUIT: 3,
|
|
3160
|
-
ILL: 4,
|
|
3161
|
-
TRAP: 5,
|
|
3162
|
-
ABRT: 6,
|
|
3163
|
-
BUS: 7,
|
|
3164
|
-
FPE: 8,
|
|
3165
|
-
KILL: 9,
|
|
3166
|
-
USR1: 10,
|
|
3167
|
-
SEGV: 11,
|
|
3168
|
-
USR2: 12,
|
|
3169
|
-
PIPE: 13,
|
|
3170
|
-
ALRM: 14,
|
|
3171
|
-
TERM: 15,
|
|
3172
|
-
STKFLT: 16,
|
|
3173
|
-
CHLD: 17,
|
|
3174
|
-
CONT: 18,
|
|
3175
|
-
STOP: 19,
|
|
3176
|
-
TSTP: 20,
|
|
3177
|
-
TTIN: 21,
|
|
3178
|
-
TTOU: 22,
|
|
3179
|
-
URG: 23,
|
|
3180
|
-
XCPU: 24,
|
|
3181
|
-
XFSZ: 25,
|
|
3182
|
-
VTALRM: 26,
|
|
3183
|
-
PROF: 27,
|
|
3184
|
-
WINCH: 28,
|
|
3185
|
-
POLL: 29,
|
|
3186
|
-
PWR: 30,
|
|
3187
|
-
SYS: 31
|
|
3188
|
-
};
|
|
3189
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
3190
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
3191
|
-
function defaultAction(sig) {
|
|
3192
|
-
if (sig === SIG.CONT) return "continue";
|
|
3193
|
-
if (STOPPING.has(sig)) return "stop";
|
|
3194
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
3195
|
-
}
|
|
3196
|
-
function isSignal(sig) {
|
|
3197
|
-
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
3198
|
-
}
|
|
3199
|
-
function sigbit(sig) {
|
|
3200
|
-
return 1 << sig;
|
|
3201
|
-
}
|
|
3202
|
-
var SIGNAL_BY_NAME = {
|
|
3203
|
-
SIGINT: SIG.INT,
|
|
3204
|
-
SIGTERM: SIG.TERM,
|
|
3205
|
-
SIGKILL: SIG.KILL,
|
|
3206
|
-
SIGSTOP: SIG.STOP,
|
|
3207
|
-
SIGCONT: SIG.CONT
|
|
3208
|
-
};
|
|
3209
|
-
|
|
3210
3624
|
// src/kernel/tty.ts
|
|
3211
3625
|
var ICRNL = 256;
|
|
3212
3626
|
var IUTF8 = 16384;
|
|
@@ -4409,6 +4823,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
4409
4823
|
"proc-captured",
|
|
4410
4824
|
"proc-fork",
|
|
4411
4825
|
"proc-kill",
|
|
4826
|
+
"proc-list",
|
|
4412
4827
|
"proc-exec",
|
|
4413
4828
|
"proc-setpgid",
|
|
4414
4829
|
"proc-getpgid",
|
|
@@ -4857,6 +5272,12 @@ var WasmProcess = class {
|
|
|
4857
5272
|
throw new KernelError("ESRCH");
|
|
4858
5273
|
}
|
|
4859
5274
|
return { ok: true, kind: "void" };
|
|
5275
|
+
case "proc-list":
|
|
5276
|
+
return {
|
|
5277
|
+
ok: true,
|
|
5278
|
+
kind: "json",
|
|
5279
|
+
json: this.options.processes?.() ?? { boot: 0, processes: [] }
|
|
5280
|
+
};
|
|
4860
5281
|
case "proc-alarm":
|
|
4861
5282
|
this.setAlarm(req.sig, req.firstMs ?? req.ms, req.repeat ? req.ms : 0, req.timer);
|
|
4862
5283
|
return { ok: true, kind: "void" };
|
|
@@ -4960,6 +5381,7 @@ function spawnWasmProcess(opts) {
|
|
|
4960
5381
|
forker: opts.forker,
|
|
4961
5382
|
fs: opts.fs,
|
|
4962
5383
|
kill: opts.kill,
|
|
5384
|
+
processes: opts.processes,
|
|
4963
5385
|
jobs: opts.jobs,
|
|
4964
5386
|
ptys: opts.ptys,
|
|
4965
5387
|
net: opts.net,
|
|
@@ -5104,6 +5526,9 @@ var JobTable = class {
|
|
|
5104
5526
|
this.members.set(pid, member);
|
|
5105
5527
|
return member;
|
|
5106
5528
|
}
|
|
5529
|
+
list() {
|
|
5530
|
+
return [...this.members.values()];
|
|
5531
|
+
}
|
|
5107
5532
|
remove(pid) {
|
|
5108
5533
|
this.members.delete(pid);
|
|
5109
5534
|
}
|
|
@@ -5434,6 +5859,7 @@ var Launcher = class {
|
|
|
5434
5859
|
processes = /* @__PURE__ */ new Map();
|
|
5435
5860
|
zombies = /* @__PURE__ */ new Set();
|
|
5436
5861
|
orphans = /* @__PURE__ */ new Set();
|
|
5862
|
+
described = /* @__PURE__ */ new Map();
|
|
5437
5863
|
jobs = new JobTable();
|
|
5438
5864
|
locks = new LockTable();
|
|
5439
5865
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
@@ -5441,6 +5867,7 @@ var Launcher = class {
|
|
|
5441
5867
|
ca;
|
|
5442
5868
|
transport;
|
|
5443
5869
|
nextPid = 1e3;
|
|
5870
|
+
boot = Date.now();
|
|
5444
5871
|
terminals = 0;
|
|
5445
5872
|
constructor(options) {
|
|
5446
5873
|
this.base = options.fs;
|
|
@@ -5612,6 +6039,7 @@ var Launcher = class {
|
|
|
5612
6039
|
spawner: this.spawner(pid, req.report),
|
|
5613
6040
|
forker: this.forker(pid, req),
|
|
5614
6041
|
kill: (target, sig) => this.kill(target, sig),
|
|
6042
|
+
processes: () => ({ boot: this.boot, processes: this.list() }),
|
|
5615
6043
|
jobs: this.jobs,
|
|
5616
6044
|
ptys: this.ptys,
|
|
5617
6045
|
net: this.net,
|
|
@@ -5623,17 +6051,57 @@ var Launcher = class {
|
|
|
5623
6051
|
});
|
|
5624
6052
|
this.processes.set(pid, handle);
|
|
5625
6053
|
this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
|
|
6054
|
+
this.described.set(pid, {
|
|
6055
|
+
argv: [req.argv0, ...req.args],
|
|
6056
|
+
tty: terminal?.name ?? null,
|
|
6057
|
+
started: Date.now()
|
|
6058
|
+
});
|
|
5626
6059
|
void handle.exited.then(() => {
|
|
5627
6060
|
this.processes.delete(pid);
|
|
5628
|
-
if (req.ppid === void 0 || this.orphans.delete(pid)) this.
|
|
6061
|
+
if (req.ppid === void 0 || this.orphans.delete(pid)) this.forget(pid);
|
|
5629
6062
|
else this.zombies.add(pid);
|
|
5630
6063
|
});
|
|
5631
6064
|
return handle;
|
|
5632
6065
|
}
|
|
6066
|
+
forget(pid) {
|
|
6067
|
+
this.jobs.remove(pid);
|
|
6068
|
+
this.described.delete(pid);
|
|
6069
|
+
}
|
|
5633
6070
|
reaped(pid) {
|
|
5634
|
-
if (this.zombies.delete(pid)) this.
|
|
6071
|
+
if (this.zombies.delete(pid)) this.forget(pid);
|
|
5635
6072
|
else this.orphans.add(pid);
|
|
5636
6073
|
}
|
|
6074
|
+
list() {
|
|
6075
|
+
const members = new Map(this.jobs.list().map((member) => [member.pid, member]));
|
|
6076
|
+
const rootOf = (member) => {
|
|
6077
|
+
let root = member;
|
|
6078
|
+
for (let up = members.get(root.execParent ?? -1); up; up = members.get(up.execParent ?? -1)) {
|
|
6079
|
+
root = up;
|
|
6080
|
+
}
|
|
6081
|
+
return root;
|
|
6082
|
+
};
|
|
6083
|
+
const shown = (pid) => {
|
|
6084
|
+
const member = pid === void 0 ? void 0 : members.get(pid);
|
|
6085
|
+
return member ? rootOf(member).pid : pid ?? 0;
|
|
6086
|
+
};
|
|
6087
|
+
const listed = [];
|
|
6088
|
+
for (const member of members.values()) {
|
|
6089
|
+
const described = this.described.get(member.pid);
|
|
6090
|
+
if (member.execed || !described) continue;
|
|
6091
|
+
const root = rootOf(member);
|
|
6092
|
+
listed.push({
|
|
6093
|
+
pid: root.pid,
|
|
6094
|
+
tid: member.pid,
|
|
6095
|
+
ppid: shown(root.ppid),
|
|
6096
|
+
pgid: member.pgid,
|
|
6097
|
+
sid: member.sid,
|
|
6098
|
+
...described,
|
|
6099
|
+
started: (this.described.get(root.pid) ?? described).started,
|
|
6100
|
+
state: this.processes.has(member.pid) ? "S" : "Z"
|
|
6101
|
+
});
|
|
6102
|
+
}
|
|
6103
|
+
return listed;
|
|
6104
|
+
}
|
|
5637
6105
|
spawner(ppid, report) {
|
|
5638
6106
|
return async (req, fds) => {
|
|
5639
6107
|
const planned = await this.plan(req.file, req.argv, req.cwd);
|
|
@@ -5711,8 +6179,9 @@ var Launcher = class {
|
|
|
5711
6179
|
const env = this.environment(cwd, options.env);
|
|
5712
6180
|
const out = [];
|
|
5713
6181
|
const err = [];
|
|
6182
|
+
const keep = options.collect !== false;
|
|
5714
6183
|
const collect = (chunks2, tee) => (bytes) => {
|
|
5715
|
-
chunks2.push(bytes);
|
|
6184
|
+
if (keep) chunks2.push(bytes);
|
|
5716
6185
|
tee?.(bytes);
|
|
5717
6186
|
};
|
|
5718
6187
|
const stdout = collect(out, options.onStdout);
|
|
@@ -5728,6 +6197,7 @@ var Launcher = class {
|
|
|
5728
6197
|
fds.installAt(1, sinkFile(stdout));
|
|
5729
6198
|
fds.installAt(2, sinkFile(stderr));
|
|
5730
6199
|
const handle = await this.launch(planned, { env, cwd, fds, report });
|
|
6200
|
+
options.onStarted?.(handle.pid);
|
|
5731
6201
|
const status = await handle.exited;
|
|
5732
6202
|
return { status, stdout: concat4(out), stderr: concat4(err) };
|
|
5733
6203
|
}
|
|
@@ -5869,6 +6339,118 @@ function memoryRoot() {
|
|
|
5869
6339
|
return new MemoryDirectory("", null);
|
|
5870
6340
|
}
|
|
5871
6341
|
|
|
6342
|
+
// src/kernel/net/remote-transport.ts
|
|
6343
|
+
var TransportError = class extends Error {
|
|
6344
|
+
status;
|
|
6345
|
+
code;
|
|
6346
|
+
constructor(message2, status, code) {
|
|
6347
|
+
super(message2);
|
|
6348
|
+
this.status = status;
|
|
6349
|
+
this.code = code;
|
|
6350
|
+
}
|
|
6351
|
+
};
|
|
6352
|
+
var RemoteTransport = class {
|
|
6353
|
+
traits;
|
|
6354
|
+
port;
|
|
6355
|
+
waiting = /* @__PURE__ */ new Map();
|
|
6356
|
+
nextId = 0;
|
|
6357
|
+
failure;
|
|
6358
|
+
constructor(port, traits) {
|
|
6359
|
+
this.port = port;
|
|
6360
|
+
this.traits = traits;
|
|
6361
|
+
}
|
|
6362
|
+
receive(reply) {
|
|
6363
|
+
const waiter = this.waiting.get(reply.nid);
|
|
6364
|
+
if (!waiter) return;
|
|
6365
|
+
this.waiting.delete(reply.nid);
|
|
6366
|
+
if (reply.net === "error") {
|
|
6367
|
+
waiter.reject(new TransportError(reply.message, reply.status, reply.code));
|
|
6368
|
+
} else waiter.resolve(reply);
|
|
6369
|
+
}
|
|
6370
|
+
fail(error) {
|
|
6371
|
+
this.failure = error;
|
|
6372
|
+
for (const waiter of this.waiting.values()) waiter.reject(error);
|
|
6373
|
+
this.waiting.clear();
|
|
6374
|
+
}
|
|
6375
|
+
ask(call) {
|
|
6376
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
6377
|
+
return new Promise((resolve, reject) => {
|
|
6378
|
+
this.waiting.set(call.nid, { resolve, reject });
|
|
6379
|
+
this.port.postMessage(call);
|
|
6380
|
+
});
|
|
6381
|
+
}
|
|
6382
|
+
cancel(nid) {
|
|
6383
|
+
const waiter = this.waiting.get(nid);
|
|
6384
|
+
this.waiting.delete(nid);
|
|
6385
|
+
waiter?.reject(new TransportError("the request was cancelled"));
|
|
6386
|
+
this.port.postMessage({ net: "cancel", nid });
|
|
6387
|
+
}
|
|
6388
|
+
async fetch(request) {
|
|
6389
|
+
const nid = ++this.nextId;
|
|
6390
|
+
const { signal } = request;
|
|
6391
|
+
signal.throwIfAborted();
|
|
6392
|
+
let finished = false;
|
|
6393
|
+
const abort = () => {
|
|
6394
|
+
finished = true;
|
|
6395
|
+
this.cancel(nid);
|
|
6396
|
+
};
|
|
6397
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
6398
|
+
const finish = () => {
|
|
6399
|
+
if (finished) return;
|
|
6400
|
+
finished = true;
|
|
6401
|
+
signal.removeEventListener("abort", abort);
|
|
6402
|
+
};
|
|
6403
|
+
let head;
|
|
6404
|
+
try {
|
|
6405
|
+
head = await this.ask({
|
|
6406
|
+
net: "fetch",
|
|
6407
|
+
nid,
|
|
6408
|
+
url: request.url,
|
|
6409
|
+
method: request.method,
|
|
6410
|
+
headers: request.headers,
|
|
6411
|
+
...request.body ? { body: request.body } : {}
|
|
6412
|
+
});
|
|
6413
|
+
} catch (e) {
|
|
6414
|
+
finish();
|
|
6415
|
+
throw e;
|
|
6416
|
+
}
|
|
6417
|
+
const { status, statusText, headers } = head;
|
|
6418
|
+
const read = async () => {
|
|
6419
|
+
if (finished) return void 0;
|
|
6420
|
+
const reply = await this.ask({ net: "read", nid }).catch((e) => {
|
|
6421
|
+
finish();
|
|
6422
|
+
throw e;
|
|
6423
|
+
});
|
|
6424
|
+
if (reply.net === "chunk") return reply.bytes;
|
|
6425
|
+
finish();
|
|
6426
|
+
return void 0;
|
|
6427
|
+
};
|
|
6428
|
+
const cancel = async () => {
|
|
6429
|
+
if (finished) return;
|
|
6430
|
+
finish();
|
|
6431
|
+
this.cancel(nid);
|
|
6432
|
+
};
|
|
6433
|
+
return {
|
|
6434
|
+
status,
|
|
6435
|
+
statusText,
|
|
6436
|
+
headers,
|
|
6437
|
+
cancel,
|
|
6438
|
+
body: {
|
|
6439
|
+
[Symbol.asyncIterator]: () => ({
|
|
6440
|
+
next: async () => {
|
|
6441
|
+
const bytes = await read();
|
|
6442
|
+
return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
|
|
6443
|
+
},
|
|
6444
|
+
return: async () => {
|
|
6445
|
+
await cancel();
|
|
6446
|
+
return { value: void 0, done: true };
|
|
6447
|
+
}
|
|
6448
|
+
})
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
}
|
|
6452
|
+
};
|
|
6453
|
+
|
|
5872
6454
|
// src/serve.ts
|
|
5873
6455
|
function signalNumber(name2) {
|
|
5874
6456
|
const key = name2.slice(3);
|
|
@@ -5877,94 +6459,262 @@ function signalNumber(name2) {
|
|
|
5877
6459
|
return SIG[key];
|
|
5878
6460
|
}
|
|
5879
6461
|
|
|
5880
|
-
// src/
|
|
5881
|
-
var
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
6462
|
+
// src/client/attach.ts
|
|
6463
|
+
var KernelGoneError = class extends Error {
|
|
6464
|
+
constructor(message2 = "the slicc-kernel this client was attached to is gone") {
|
|
6465
|
+
super(message2);
|
|
6466
|
+
this.name = "KernelGoneError";
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6469
|
+
var KernelCallError = class extends Error {
|
|
6470
|
+
code;
|
|
6471
|
+
constructor(message2, code) {
|
|
6472
|
+
super(message2);
|
|
6473
|
+
this.name = "KernelCallError";
|
|
6474
|
+
this.code = code;
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
var encoder4 = new TextEncoder();
|
|
6478
|
+
var bytesOf2 = (data) => typeof data === "string" ? encoder4.encode(data) : data;
|
|
6479
|
+
var UNAVAILABLE = {
|
|
6480
|
+
manualRedirects: false,
|
|
6481
|
+
encodedBodies: false,
|
|
6482
|
+
maxRequestBody: 0,
|
|
6483
|
+
unavailable: true
|
|
6484
|
+
};
|
|
6485
|
+
async function holdLock(locks) {
|
|
6486
|
+
if (!locks) return { release() {
|
|
6487
|
+
} };
|
|
6488
|
+
const name2 = `slicc-kernel-client:${crypto.randomUUID()}`;
|
|
6489
|
+
const granted = Promise.withResolvers();
|
|
6490
|
+
const held = Promise.withResolvers();
|
|
6491
|
+
void locks.request(name2, () => {
|
|
6492
|
+
granted.resolve();
|
|
6493
|
+
return held.promise;
|
|
5885
6494
|
});
|
|
6495
|
+
await granted.promise;
|
|
6496
|
+
return { name: name2, release: () => held.resolve() };
|
|
5886
6497
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
let
|
|
5890
|
-
const
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
try {
|
|
5894
|
-
return await Promise.race([reader.read(), idle]);
|
|
5895
|
-
} finally {
|
|
5896
|
-
clearTimeout(timer);
|
|
6498
|
+
function concat5(chunks2) {
|
|
6499
|
+
const out = new Uint8Array(chunks2.reduce((n, c) => n + c.length, 0));
|
|
6500
|
+
let at = 0;
|
|
6501
|
+
for (const chunk2 of chunks2) {
|
|
6502
|
+
out.set(chunk2, at);
|
|
6503
|
+
at += chunk2.length;
|
|
5897
6504
|
}
|
|
6505
|
+
return out;
|
|
5898
6506
|
}
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
6507
|
+
var Channel = class {
|
|
6508
|
+
greeted = Promise.withResolvers();
|
|
6509
|
+
ended = Promise.withResolvers();
|
|
6510
|
+
remote;
|
|
6511
|
+
failure;
|
|
6512
|
+
pending = /* @__PURE__ */ new Map();
|
|
6513
|
+
nextId = 0;
|
|
6514
|
+
port;
|
|
6515
|
+
release;
|
|
6516
|
+
constructor(port, release) {
|
|
6517
|
+
this.port = port;
|
|
6518
|
+
this.release = release;
|
|
6519
|
+
this.greeted.promise.catch(() => void 0);
|
|
6520
|
+
port.addEventListener("message", (event) => this.receive(event.data));
|
|
6521
|
+
port.addEventListener("close", () => this.end(new KernelGoneError()));
|
|
6522
|
+
port.start?.();
|
|
6523
|
+
}
|
|
6524
|
+
post(message2) {
|
|
6525
|
+
if (!this.failure) this.port.postMessage(message2);
|
|
6526
|
+
}
|
|
6527
|
+
end(error) {
|
|
6528
|
+
if (this.failure) return;
|
|
6529
|
+
this.failure = error;
|
|
6530
|
+
this.greeted.reject(error);
|
|
6531
|
+
this.remote?.fail(error);
|
|
6532
|
+
for (const call of this.pending.values()) call.reject(error);
|
|
6533
|
+
this.pending.clear();
|
|
6534
|
+
this.release();
|
|
6535
|
+
this.port.close?.();
|
|
6536
|
+
this.ended.resolve(error);
|
|
6537
|
+
}
|
|
6538
|
+
request(call, handlers = {}) {
|
|
6539
|
+
const id = ++this.nextId;
|
|
6540
|
+
const { failure } = this;
|
|
6541
|
+
if (failure) return { id, done: Promise.reject(failure) };
|
|
6542
|
+
const done4 = new Promise((resolve, reject) => {
|
|
6543
|
+
this.pending.set(id, { resolve, reject, ...handlers });
|
|
6544
|
+
this.port.postMessage({ ...call, id });
|
|
6545
|
+
});
|
|
6546
|
+
return { id, done: done4 };
|
|
6547
|
+
}
|
|
6548
|
+
call(call) {
|
|
6549
|
+
return this.request(call).done;
|
|
6550
|
+
}
|
|
6551
|
+
receive(data) {
|
|
6552
|
+
if (data.bye !== void 0) {
|
|
6553
|
+
this.end(new KernelGoneError(data.bye));
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6556
|
+
if (data.hello) {
|
|
6557
|
+
this.greeted.resolve(data.hello);
|
|
6558
|
+
return;
|
|
5908
6559
|
}
|
|
6560
|
+
if (data.net !== void 0) {
|
|
6561
|
+
this.remote?.receive(data);
|
|
6562
|
+
return;
|
|
6563
|
+
}
|
|
6564
|
+
const reply = data;
|
|
6565
|
+
const call = this.pending.get(reply.id);
|
|
6566
|
+
if (!call) return;
|
|
6567
|
+
if (reply.fd !== void 0) {
|
|
6568
|
+
call.output?.(reply.fd, reply.bytes);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6571
|
+
if (reply.started !== void 0) {
|
|
6572
|
+
call.started?.(reply.started);
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
this.pending.delete(reply.id);
|
|
6576
|
+
if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
|
|
6577
|
+
else call.resolve(reply.result);
|
|
5909
6578
|
}
|
|
5910
|
-
|
|
6579
|
+
};
|
|
6580
|
+
async function spawnOn(channel, argv, opts) {
|
|
6581
|
+
const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
|
|
6582
|
+
const started = Promise.withResolvers();
|
|
6583
|
+
const options = { ...rest, ...stdin !== void 0 ? { stdin: bytesOf2(stdin) } : {} };
|
|
6584
|
+
const { done: done4 } = channel.request(
|
|
6585
|
+
{ op: "spawn", argv, options },
|
|
6586
|
+
{
|
|
6587
|
+
output: (fd, bytes) => (fd === 1 ? onStdout : onStderr)?.(bytes),
|
|
6588
|
+
started: started.resolve
|
|
6589
|
+
}
|
|
6590
|
+
);
|
|
6591
|
+
done4.catch(started.reject);
|
|
6592
|
+
const pid = await started.promise;
|
|
6593
|
+
return {
|
|
6594
|
+
pid,
|
|
6595
|
+
pgid: pid,
|
|
6596
|
+
exited: done4,
|
|
6597
|
+
signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pid, signal: name2 })
|
|
6598
|
+
};
|
|
5911
6599
|
}
|
|
5912
|
-
async function
|
|
5913
|
-
|
|
5914
|
-
const
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
6600
|
+
async function runOn(channel, argv, opts) {
|
|
6601
|
+
const { onStdout, onStderr, ...rest } = opts;
|
|
6602
|
+
const chunks2 = { 1: [], 2: [] };
|
|
6603
|
+
const live = { 1: new TextDecoder(), 2: new TextDecoder() };
|
|
6604
|
+
const tee = (fd, on) => (bytes) => {
|
|
6605
|
+
chunks2[fd].push(bytes);
|
|
6606
|
+
on?.(live[fd].decode(bytes, { stream: true }));
|
|
6607
|
+
};
|
|
6608
|
+
const child = await spawnOn(channel, argv, {
|
|
6609
|
+
...rest,
|
|
6610
|
+
onStdout: tee(1, onStdout),
|
|
6611
|
+
onStderr: tee(2, onStderr)
|
|
6612
|
+
});
|
|
6613
|
+
const status = await child.exited;
|
|
6614
|
+
const text = (fd) => new TextDecoder().decode(concat5(chunks2[fd]));
|
|
6615
|
+
return { pid: child.pid, status, stdout: text(1), stderr: text(2) };
|
|
6616
|
+
}
|
|
6617
|
+
async function terminalOn(channel, argv, opts) {
|
|
6618
|
+
const { onData, ...options } = opts;
|
|
6619
|
+
let listener = onData ?? null;
|
|
6620
|
+
const backlog = [];
|
|
6621
|
+
const started = Promise.withResolvers();
|
|
6622
|
+
const { id, done: done4 } = channel.request(
|
|
6623
|
+
{ op: "open-terminal", argv, options },
|
|
6624
|
+
{
|
|
6625
|
+
output: (_fd, bytes) => listener ? listener(bytes) : backlog.push(bytes),
|
|
6626
|
+
started: started.resolve
|
|
5918
6627
|
}
|
|
6628
|
+
);
|
|
6629
|
+
done4.catch(started.reject);
|
|
6630
|
+
const pid = await started.promise;
|
|
6631
|
+
const send = (action) => void channel.call({ op: "terminal", terminal: id, ...action }).catch(() => void 0);
|
|
6632
|
+
return {
|
|
6633
|
+
pid,
|
|
6634
|
+
exited: done4,
|
|
6635
|
+
get onData() {
|
|
6636
|
+
return listener;
|
|
6637
|
+
},
|
|
6638
|
+
set onData(next) {
|
|
6639
|
+
listener = next;
|
|
6640
|
+
if (next) for (const bytes of backlog.splice(0)) next(bytes);
|
|
6641
|
+
},
|
|
6642
|
+
write: (data) => send({ action: "write", bytes: bytesOf2(data) }),
|
|
6643
|
+
resize: (cols, rows) => send({ action: "resize", cols, rows }),
|
|
6644
|
+
signal: (name2) => send({ action: "signal", signal: name2 }),
|
|
6645
|
+
close: () => send({ action: "close" })
|
|
6646
|
+
};
|
|
6647
|
+
}
|
|
6648
|
+
function fsOn(channel) {
|
|
6649
|
+
const fs = (method, ...args) => channel.call({ op: "fs", method, args });
|
|
6650
|
+
const done4 = async (method, ...args) => void await fs(method, ...args);
|
|
6651
|
+
return {
|
|
6652
|
+
readFile: (path) => fs("readFile", path),
|
|
6653
|
+
readText: async (path) => new TextDecoder().decode(await fs("readFile", path)),
|
|
6654
|
+
writeFile: (path, data) => done4("writeFile", path, data),
|
|
6655
|
+
stat: (path) => fs("stat", path),
|
|
6656
|
+
lstat: (path) => fs("lstat", path),
|
|
6657
|
+
readdir: (path) => fs("readdir", path),
|
|
6658
|
+
mkdir: (path) => done4("mkdir", path),
|
|
6659
|
+
rm: (path, o = {}) => done4("rm", path, o.force === true),
|
|
6660
|
+
rename: (from, to) => done4("rename", from, to),
|
|
6661
|
+
realpath: (path) => fs("realpath", path),
|
|
6662
|
+
symlink: (target, path) => done4("symlink", target, path),
|
|
6663
|
+
readlink: (path) => fs("readlink", path),
|
|
6664
|
+
exists: (path) => fs("exists", path)
|
|
6665
|
+
};
|
|
6666
|
+
}
|
|
6667
|
+
async function greet(channel, lock, timeoutMs) {
|
|
6668
|
+
channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
|
|
6669
|
+
const timer = setTimeout(
|
|
6670
|
+
() => channel.end(
|
|
6671
|
+
new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
|
|
6672
|
+
),
|
|
6673
|
+
timeoutMs
|
|
6674
|
+
);
|
|
6675
|
+
let hello;
|
|
6676
|
+
try {
|
|
6677
|
+
hello = await channel.greeted.promise;
|
|
5919
6678
|
} finally {
|
|
5920
|
-
|
|
6679
|
+
clearTimeout(timer);
|
|
5921
6680
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6681
|
+
const refused = hello.error ?? versionError(hello.protocol);
|
|
6682
|
+
if (refused) {
|
|
6683
|
+
const error = new Error(refused);
|
|
6684
|
+
channel.end(error);
|
|
6685
|
+
throw error;
|
|
6686
|
+
}
|
|
6687
|
+
return hello;
|
|
6688
|
+
}
|
|
6689
|
+
async function attachKernel(port, options = {}) {
|
|
6690
|
+
const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
|
|
6691
|
+
const own = await holdLock(locks);
|
|
6692
|
+
const channel = new Channel(port, own.release);
|
|
6693
|
+
const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
|
|
6694
|
+
if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
|
|
6695
|
+
const transport = new RemoteTransport(
|
|
6696
|
+
{ postMessage: (call) => channel.post(call) },
|
|
6697
|
+
hello.traits ?? UNAVAILABLE
|
|
6698
|
+
);
|
|
6699
|
+
channel.remote = transport;
|
|
5925
6700
|
return {
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
6701
|
+
protocol: hello.protocol,
|
|
6702
|
+
transport,
|
|
6703
|
+
closed: channel.ended.promise,
|
|
6704
|
+
spawn: (argv, opts = {}) => spawnOn(channel, argv, opts),
|
|
6705
|
+
run: (argv, opts = {}) => runOn(channel, argv, opts),
|
|
6706
|
+
openTerminal: (argv, opts = {}) => terminalOn(channel, argv, opts),
|
|
6707
|
+
ps: () => channel.call({ op: "ps" }),
|
|
6708
|
+
kill: async (pid, signal = "SIGTERM") => void await channel.call({ op: "kill", pid, signal }),
|
|
6709
|
+
fetch: (req) => {
|
|
6710
|
+
const { signal, ...rest } = req;
|
|
6711
|
+
return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
|
|
5931
6712
|
},
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
headers: joinHeaders(request.headers),
|
|
5938
|
-
...request.body ? { body: request.body } : {},
|
|
5939
|
-
signal: request.signal,
|
|
5940
|
-
redirect: "follow",
|
|
5941
|
-
credentials: "omit",
|
|
5942
|
-
mode: "cors"
|
|
5943
|
-
});
|
|
5944
|
-
} catch (e) {
|
|
5945
|
-
if (request.signal.aborted) throw e;
|
|
5946
|
-
throw Object.assign(
|
|
5947
|
-
new Error(
|
|
5948
|
-
`fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
|
|
5949
|
-
),
|
|
5950
|
-
{ status: 502 }
|
|
5951
|
-
);
|
|
5952
|
-
}
|
|
5953
|
-
const headers = [];
|
|
5954
|
-
response.headers.forEach((value, name2) => {
|
|
5955
|
-
headers.push([name2, value]);
|
|
5956
|
-
});
|
|
5957
|
-
const body = chunks(response.body, options.bodyIdleMs ?? BODY_IDLE_MS);
|
|
5958
|
-
return {
|
|
5959
|
-
status: response.status,
|
|
5960
|
-
statusText: response.statusText,
|
|
5961
|
-
headers,
|
|
5962
|
-
body,
|
|
5963
|
-
cancel: async () => {
|
|
5964
|
-
await body.return(void 0);
|
|
5965
|
-
await response.body?.cancel().catch(() => void 0);
|
|
5966
|
-
}
|
|
5967
|
-
};
|
|
6713
|
+
fs: fsOn(channel),
|
|
6714
|
+
async close(o = {}) {
|
|
6715
|
+
if (channel.failure) return;
|
|
6716
|
+
await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
|
|
6717
|
+
channel.end(new KernelGoneError("this client is closed"));
|
|
5968
6718
|
}
|
|
5969
6719
|
};
|
|
5970
6720
|
}
|
|
@@ -6026,6 +6776,7 @@ async function createNodeKernel(options = {}) {
|
|
|
6026
6776
|
await fs.reconcile();
|
|
6027
6777
|
const live = /* @__PURE__ */ new Set();
|
|
6028
6778
|
const pending = /* @__PURE__ */ new Set();
|
|
6779
|
+
const clients = /* @__PURE__ */ new Set();
|
|
6029
6780
|
let terminated = false;
|
|
6030
6781
|
const file = options.worker ?? new URL("./node-process-worker.js", import.meta.url);
|
|
6031
6782
|
const transport = options.network?.transport;
|
|
@@ -6095,14 +6846,29 @@ async function createNodeKernel(options = {}) {
|
|
|
6095
6846
|
await fs.writeFile(path, data);
|
|
6096
6847
|
},
|
|
6097
6848
|
readFile: async (path) => fs.readFileBuffer(path),
|
|
6849
|
+
async connect() {
|
|
6850
|
+
if (terminated) throw new Error(TERMINATED);
|
|
6851
|
+
const { port1, port2 } = new MessageChannel2();
|
|
6852
|
+
const served = serveClient(port1, {
|
|
6853
|
+
launcher: async () => launcher,
|
|
6854
|
+
signal: signalNumber
|
|
6855
|
+
});
|
|
6856
|
+
clients.add(served);
|
|
6857
|
+
void served.closed.then(() => clients.delete(served));
|
|
6858
|
+
return port2;
|
|
6859
|
+
},
|
|
6098
6860
|
terminate() {
|
|
6099
6861
|
terminated = true;
|
|
6862
|
+
for (const client of [...clients]) client.detach();
|
|
6100
6863
|
for (const reject of [...pending]) reject(new Error(TERMINATED));
|
|
6101
6864
|
for (const kill of [...live]) kill();
|
|
6102
6865
|
}
|
|
6103
6866
|
};
|
|
6104
6867
|
}
|
|
6105
6868
|
export {
|
|
6869
|
+
KernelCallError,
|
|
6870
|
+
KernelGoneError,
|
|
6871
|
+
attachKernel,
|
|
6106
6872
|
createNodeKernel,
|
|
6107
6873
|
fetchTransport,
|
|
6108
6874
|
memoryRoot,
|