@ai-ecoverse/slicc-kernel 1.152.2 → 1.154.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.
Files changed (36) hide show
  1. package/README.md +28 -3
  2. package/dist/driver-worker.js +147 -10
  3. package/dist/driver-worker.js.map +4 -4
  4. package/dist/index.js +168 -14
  5. package/dist/index.js.map +4 -4
  6. package/dist/kernel-worker.js +844 -167
  7. package/dist/kernel-worker.js.map +4 -4
  8. package/dist/node-driver-worker.js +147 -10
  9. package/dist/node-driver-worker.js.map +4 -4
  10. package/dist/node-process-worker.js +81 -24
  11. package/dist/node-process-worker.js.map +3 -3
  12. package/dist/node.js +982 -182
  13. package/dist/node.js.map +4 -4
  14. package/dist/process-worker.js +81 -24
  15. package/dist/process-worker.js.map +3 -3
  16. package/dist/types/client/attach.d.ts +2 -0
  17. package/dist/types/client/protocol.d.ts +4 -0
  18. package/dist/types/client/serve-client.d.ts +2 -0
  19. package/dist/types/embedder-relay.d.ts +58 -0
  20. package/dist/types/index.d.ts +4 -0
  21. package/dist/types/kernel/embedder.d.ts +100 -0
  22. package/dist/types/kernel/fd-table.d.ts +3 -1
  23. package/dist/types/kernel/host.d.ts +8 -0
  24. package/dist/types/kernel/net/tls-client.d.ts +4 -1
  25. package/dist/types/kernel/net/tls-engine.d.ts +5 -2
  26. package/dist/types/kernel/net/tls-server.d.ts +2 -0
  27. package/dist/types/kernel/net/tls-uplink.d.ts +2 -0
  28. package/dist/types/kernel/process.d.ts +17 -0
  29. package/dist/types/kernel/socket-syscalls.d.ts +3 -0
  30. package/dist/types/launcher.d.ts +13 -0
  31. package/dist/types/node.d.ts +3 -0
  32. package/dist/types/process/js/js-net.d.ts +3 -0
  33. package/dist/types/process/kernel-streams.d.ts +2 -1
  34. package/dist/types/process/wasi/wasi-fds.d.ts +1 -0
  35. package/dist/types/serve.d.ts +3 -0
  36. package/package.json +4 -4
package/dist/node.js CHANGED
@@ -93,6 +93,202 @@ function portCdpHook(ask, gone) {
93
93
  });
94
94
  }
95
95
 
96
+ // src/embedder-relay.ts
97
+ function fresh2(bytes) {
98
+ return bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice();
99
+ }
100
+ function errorOf(err) {
101
+ const name2 = err?.name;
102
+ const message3 = err instanceof Error ? err.message : String(err);
103
+ return { name: typeof name2 === "string" ? name2 : "Error", message: message3 };
104
+ }
105
+ function embedderAsker(port) {
106
+ const asks = /* @__PURE__ */ new Map();
107
+ let next3 = 0;
108
+ const post = (message3, transfer = []) => port.postMessage({ embedder: message3 }, transfer);
109
+ const push = (asked, item) => {
110
+ asked.chunks.push(item);
111
+ asked.wake?.();
112
+ };
113
+ const reply = (id2, asked) => new ReadableStream(
114
+ {
115
+ pull: async (controller) => {
116
+ if (asked.chunks.length === 0) {
117
+ post({ id: id2, pull: true });
118
+ await new Promise((resolve2) => {
119
+ asked.wake = resolve2;
120
+ });
121
+ }
122
+ const item = asked.chunks.shift();
123
+ if (item instanceof Uint8Array) controller.enqueue(item);
124
+ else if (item === null || item === void 0) {
125
+ asks.delete(id2);
126
+ controller.close();
127
+ } else {
128
+ asks.delete(id2);
129
+ controller.error(Object.assign(new Error(item.message), { name: item.name }));
130
+ }
131
+ },
132
+ cancel: (reason) => {
133
+ asks.delete(id2);
134
+ post({ id: id2, cancel: String(reason ?? "closed") });
135
+ }
136
+ },
137
+ { highWaterMark: 0 }
138
+ );
139
+ const sendBody = async (id2, asked) => {
140
+ const got = await asked.body.read().catch(() => ({ done: true, value: void 0 }));
141
+ if (got.done) post({ id: id2, bodyEnd: true });
142
+ else {
143
+ const bytes = fresh2(got.value);
144
+ post({ id: id2, body: bytes }, [bytes.buffer]);
145
+ }
146
+ };
147
+ const hookFor = (client, kinds) => ({
148
+ kinds,
149
+ handle: async (request) => {
150
+ const id2 = ++next3;
151
+ const asked = {
152
+ ready: Promise.withResolvers(),
153
+ chunks: [],
154
+ wake: void 0,
155
+ body: request.body.getReader(),
156
+ progress: request.progress
157
+ };
158
+ asks.set(id2, asked);
159
+ const cancel = () => {
160
+ asks.delete(id2);
161
+ post({
162
+ id: id2,
163
+ cancel: String(request.signal.reason?.message ?? "closed")
164
+ });
165
+ asked.ready.reject(request.signal.reason);
166
+ push(asked, { name: "AbortError", message: "cancelled" });
167
+ };
168
+ request.signal.addEventListener("abort", cancel, { once: true });
169
+ const { kind, options, stamp } = request;
170
+ post({ ask: { id: id2, client, kind, options, stamp } });
171
+ await asked.ready.promise;
172
+ return reply(id2, asked);
173
+ }
174
+ });
175
+ const answer2 = (message3) => {
176
+ const asked = asks.get(message3.id);
177
+ if (!asked) return;
178
+ if ("ready" in message3) asked.ready.resolve();
179
+ else if ("chunk" in message3) push(asked, message3.chunk);
180
+ else if ("done" in message3) push(asked, null);
181
+ else if ("error" in message3) {
182
+ asked.ready.reject(
183
+ Object.assign(new Error(message3.error.message), { name: message3.error.name })
184
+ );
185
+ asks.delete(message3.id);
186
+ void asked.body.cancel(message3.error.message).catch(() => void 0);
187
+ push(asked, message3.error);
188
+ } else if ("progress" in message3) asked.progress(message3.progress);
189
+ else void sendBody(message3.id, asked);
190
+ };
191
+ return { hookFor, answer: answer2 };
192
+ }
193
+ function bytesOf(reply) {
194
+ if (reply instanceof ReadableStream) return reply;
195
+ const bytes = typeof reply === "string" ? new TextEncoder().encode(reply) : reply instanceof Uint8Array ? reply : new Uint8Array(0);
196
+ return new ReadableStream({
197
+ start(controller) {
198
+ if (bytes.length > 0) controller.enqueue(bytes);
199
+ controller.close();
200
+ }
201
+ });
202
+ }
203
+ function bodyStream(id2, state, post) {
204
+ return new ReadableStream(
205
+ {
206
+ pull: async (controller) => {
207
+ if (state.body.length === 0) post({ id: id2, bodyPull: true });
208
+ while (state.body.length === 0 && !state.abort.signal.aborted) {
209
+ await new Promise((resolve2) => {
210
+ state.wakeBody = resolve2;
211
+ });
212
+ }
213
+ const next3 = state.body.shift();
214
+ if (next3) controller.enqueue(next3);
215
+ else controller.close();
216
+ }
217
+ },
218
+ { highWaterMark: 0 }
219
+ );
220
+ }
221
+ function embedderAnswerer(port, hookFor) {
222
+ const running = /* @__PURE__ */ new Map();
223
+ const post = (message3, transfer = []) => port.postMessage({ embedderReply: message3 }, transfer);
224
+ const fail3 = (id2, err) => {
225
+ running.delete(id2);
226
+ post({ id: id2, error: errorOf(err) });
227
+ };
228
+ const start2 = async (ask) => {
229
+ const state = { abort: new AbortController(), body: [], wakeBody: void 0 };
230
+ running.set(ask.id, state);
231
+ try {
232
+ const hook = hookFor(ask.client);
233
+ if (!hook || !Object.hasOwn(hook.kinds, ask.kind))
234
+ throw Object.assign(new Error(`this page does not answer ${ask.kind}`), {
235
+ name: "NotSupportedError"
236
+ });
237
+ const reply = await hook.handle({
238
+ kind: ask.kind,
239
+ options: ask.options,
240
+ body: bodyStream(ask.id, state, post),
241
+ stamp: ask.stamp,
242
+ signal: state.abort.signal,
243
+ progress: (line) => post({ id: ask.id, progress: String(line) })
244
+ });
245
+ state.reader = bytesOf(reply).getReader();
246
+ post({ id: ask.id, ready: true });
247
+ } catch (err) {
248
+ fail3(ask.id, err);
249
+ }
250
+ };
251
+ const pull = async (id2, state) => {
252
+ try {
253
+ const got = await state.reader.read();
254
+ if (got.done) {
255
+ running.delete(id2);
256
+ post({ id: id2, done: true });
257
+ return;
258
+ }
259
+ const bytes = fresh2(got.value);
260
+ post({ id: id2, chunk: bytes }, [bytes.buffer]);
261
+ } catch (err) {
262
+ fail3(id2, err);
263
+ }
264
+ };
265
+ const step2 = (message3) => {
266
+ const state = running.get(message3.id);
267
+ if (!state) return;
268
+ if ("body" in message3) state.body.push(message3.body);
269
+ else if ("bodyEnd" in message3) state.body.push(null);
270
+ else if ("pull" in message3) void pull(message3.id, state);
271
+ else {
272
+ running.delete(message3.id);
273
+ state.abort.abort(new DOMException(message3.cancel, "AbortError"));
274
+ void state.reader?.cancel(message3.cancel).catch(() => void 0);
275
+ }
276
+ state.wakeBody?.();
277
+ };
278
+ return {
279
+ handle(data) {
280
+ const message3 = data?.embedder;
281
+ if (!message3) return false;
282
+ if ("ask" in message3) void start2(message3.ask);
283
+ else step2(message3);
284
+ return true;
285
+ }
286
+ };
287
+ }
288
+ function embedderReplyOf(data) {
289
+ return data?.embedderReply;
290
+ }
291
+
96
292
  // src/facade/port.ts
97
293
  var FACADE_GONE = "the facade host is gone";
98
294
  function serveFacadePort(port, hook, { name: name2, stamp }, onDone) {
@@ -1082,6 +1278,7 @@ function kernelFdKind(file) {
1082
1278
  if (file.tty) return "tty";
1083
1279
  if (file.sealed) return "sealed";
1084
1280
  if (file.clipboard) return "clipboard";
1281
+ if (file.embedder) return "embedder";
1085
1282
  return file.seek ? "file" : "stream";
1086
1283
  }
1087
1284
  var FdTable = class _FdTable {
@@ -3365,7 +3562,7 @@ function satisfies(version, range2) {
3365
3562
  }
3366
3563
 
3367
3564
  // src/version.ts
3368
- var KERNEL_VERSION = true ? "1.152.2" : "0.0.0-dev";
3565
+ var KERNEL_VERSION = true ? "1.154.0" : "0.0.0-dev";
3369
3566
  var OS_RELEASE = "6.0.0-slicc";
3370
3567
  function procVersion() {
3371
3568
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -4210,9 +4407,9 @@ var Crontabs = class {
4210
4407
  const json5 = JSON.stringify({ v: TAGS_VERSION, ...kept });
4211
4408
  if (json5 === await fs.readFile(TAGS_PATH).catch(() => "")) return;
4212
4409
  const owner = owners ? { uid: 0, gid: 0, mode: 384 } : void 0;
4213
- const fresh2 = `${TAGS_PATH}.${crypto.randomUUID()}.new`;
4214
- await fs.writeFile(fresh2, json5, owner);
4215
- await fs.rename(fresh2, TAGS_PATH);
4410
+ const fresh3 = `${TAGS_PATH}.${crypto.randomUUID()}.new`;
4411
+ await fs.writeFile(fresh3, json5, owner);
4412
+ await fs.rename(fresh3, TAGS_PATH);
4216
4413
  }
4217
4414
  async install(path, target, text, caller) {
4218
4415
  if (text.length > CRONTAB_MAX) return { error: "crontab: the table is too long" };
@@ -4226,11 +4423,11 @@ var Crontabs = class {
4226
4423
  await fs.mkdir(SPOOL_DIR, { recursive: true });
4227
4424
  if (owners) await fs.chown?.(SPOOL_DIR, { uid: 0, gid: 0, mode: 448, follow: false });
4228
4425
  }
4229
- const fresh2 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
4426
+ const fresh3 = `${SPOOL_DIR}/.${target.name}.${crypto.randomUUID()}.new`;
4230
4427
  const owner = owners ? { uid: target.uid, gid: target.gid, mode: 384 } : void 0;
4231
4428
  await this.retag(path, text, caller);
4232
- await fs.writeFile(fresh2, text, owner);
4233
- await fs.rename(fresh2, path);
4429
+ await fs.writeFile(fresh3, text, owner);
4430
+ await fs.rename(fresh3, path);
4234
4431
  return { text: "" };
4235
4432
  }
4236
4433
  };
@@ -4519,15 +4716,15 @@ var UnlinkedKeeper = class {
4519
4716
  const releases = [];
4520
4717
  const take = async () => {
4521
4718
  for (; ; ) {
4522
- const fresh2 = [...this.heldWithin(target).keys()].filter((path) => !paths.includes(path));
4523
- if (fresh2.length === 0) return;
4524
- fresh2.sort();
4525
- if (paths.some((path) => path > fresh2[0])) {
4719
+ const fresh3 = [...this.heldWithin(target).keys()].filter((path) => !paths.includes(path));
4720
+ if (fresh3.length === 0) return;
4721
+ fresh3.sort();
4722
+ if (paths.some((path) => path > fresh3[0])) {
4526
4723
  for (const release of releases.splice(0)) release();
4527
- fresh2.push(...paths.splice(0));
4528
- fresh2.sort();
4724
+ fresh3.push(...paths.splice(0));
4725
+ fresh3.sort();
4529
4726
  }
4530
- for (const path of fresh2) {
4727
+ for (const path of fresh3) {
4531
4728
  paths.push(path);
4532
4729
  releases.push(await this.gate.acquire(path));
4533
4730
  }
@@ -4862,10 +5059,10 @@ var CronService = class {
4862
5059
  const text = JSON.stringify(stamp);
4863
5060
  const old = await fs.readFile(STAMP_PATH).catch(() => void 0);
4864
5061
  if (old === text || old === void 0 && text === EMPTY_STAMP) return;
4865
- const fresh2 = `${STAMP_PATH}.new`;
5062
+ const fresh3 = `${STAMP_PATH}.new`;
4866
5063
  const owner = owners ? { uid: 0, gid: 0, mode: 384 } : void 0;
4867
- await fs.writeFile(fresh2, text, owner);
4868
- await fs.rename(fresh2, STAMP_PATH);
5064
+ await fs.writeFile(fresh3, text, owner);
5065
+ await fs.rename(fresh3, STAMP_PATH);
4869
5066
  }
4870
5067
  async load() {
4871
5068
  const { fs } = this.host;
@@ -6301,13 +6498,13 @@ function materializer(fs, listedAbsent) {
6301
6498
  };
6302
6499
  }
6303
6500
  function withEtcFiles(fs, files) {
6304
- const bytesOf4 = (path) => {
6501
+ const bytesOf5 = (path) => {
6305
6502
  const text = files.get(path)?.();
6306
6503
  return text === void 0 ? void 0 : new TextEncoder().encode(text);
6307
6504
  };
6308
6505
  const absent2 = (path) => fs.lstat(path).then(() => false, missing2);
6309
6506
  const follow2 = linkFollower(fs, files);
6310
- const listedAbsent = async (path) => files.has(path) && await absent2(path) ? bytesOf4(path) : void 0;
6507
+ const listedAbsent = async (path) => files.has(path) && await absent2(path) ? bytesOf5(path) : void 0;
6311
6508
  const fallback = (path, make) => async (err) => {
6312
6509
  const at = missing2(err) ? await follow2(path) : void 0;
6313
6510
  const bytes = at === void 0 ? void 0 : await listedAbsent(at);
@@ -6380,10 +6577,10 @@ var FreshModules = class {
6380
6577
  this.fresh.set(key, { umask: mask, pid, added: 0, setup: mask !== DEFAULT_UMASK });
6381
6578
  }
6382
6579
  forget(pid) {
6383
- for (const [path, fresh2] of this.fresh) if (fresh2.pid === pid) this.fresh.delete(path);
6580
+ for (const [path, fresh3] of this.fresh) if (fresh3.pid === pid) this.fresh.delete(path);
6384
6581
  }
6385
- drop(fresh2) {
6386
- for (const [key, note] of [...this.fresh]) if (note === fresh2) this.fresh.delete(key);
6582
+ drop(fresh3) {
6583
+ for (const [key, note] of [...this.fresh]) if (note === fresh3) this.fresh.delete(key);
6387
6584
  }
6388
6585
  moved(from, to) {
6389
6586
  const source = normalizePath(from);
@@ -6392,10 +6589,10 @@ var FreshModules = class {
6392
6589
  for (const key of [...this.fresh.keys()]) {
6393
6590
  if (key === target || within3(key, target)) this.fresh.delete(key);
6394
6591
  }
6395
- for (const [key, fresh2] of [...this.fresh]) {
6592
+ for (const [key, fresh3] of [...this.fresh]) {
6396
6593
  if (key !== source && !within3(key, source)) continue;
6397
6594
  this.fresh.delete(key);
6398
- this.fresh.set(target + key.slice(source.length), fresh2);
6595
+ this.fresh.set(target + key.slice(source.length), fresh3);
6399
6596
  }
6400
6597
  }
6401
6598
  wrap(fs) {
@@ -6405,15 +6602,15 @@ var FreshModules = class {
6405
6602
  };
6406
6603
  const decide = async (given, known) => {
6407
6604
  const path = normalizePath(given);
6408
- const fresh2 = this.fresh.get(path);
6409
- if (!fresh2) return;
6605
+ const fresh3 = this.fresh.get(path);
6606
+ if (!fresh3) return;
6410
6607
  const bytes = known && known.length >= MAGIC.length ? known : await head(path);
6411
- const want = isModule(bytes) ? 73 & ~fresh2.umask : 0;
6412
- fresh2.setup = false;
6413
- if (want === fresh2.added) return;
6608
+ const want = isModule(bytes) ? 73 & ~fresh3.umask : 0;
6609
+ fresh3.setup = false;
6610
+ if (want === fresh3.added) return;
6414
6611
  const mode = (await fs.stat(path)).mode & 4095;
6415
- await fs.chmod(path, mode & ~fresh2.added | want);
6416
- fresh2.added = want;
6612
+ await fs.chmod(path, mode & ~fresh3.added | want);
6613
+ fresh3.added = want;
6417
6614
  };
6418
6615
  const overrides = {
6419
6616
  writeFile: async (path, content, owner) => {
@@ -6426,9 +6623,9 @@ var FreshModules = class {
6426
6623
  }
6427
6624
  },
6428
6625
  chmod: async (path, mode) => {
6429
- const fresh2 = this.fresh.get(normalizePath(path));
6430
- if (fresh2?.setup && (mode & 4095) === (438 & ~fresh2.umask)) fresh2.setup = false;
6431
- else if (fresh2) this.drop(fresh2);
6626
+ const fresh3 = this.fresh.get(normalizePath(path));
6627
+ if (fresh3?.setup && (mode & 4095) === (438 & ~fresh3.umask)) fresh3.setup = false;
6628
+ else if (fresh3) this.drop(fresh3);
6432
6629
  await fs.chmod(path, mode);
6433
6630
  },
6434
6631
  rm: async (path, options) => {
@@ -6445,8 +6642,8 @@ var FreshModules = class {
6445
6642
  link: async (from, to, follow2) => {
6446
6643
  await fs.link(from, to, follow2);
6447
6644
  const source = follow2 && fs.realpath ? await fs.realpath(from, true).catch(() => from) : from;
6448
- const fresh2 = this.fresh.get(normalizePath(source));
6449
- if (fresh2) this.fresh.set(normalizePath(to), fresh2);
6645
+ const fresh3 = this.fresh.get(normalizePath(source));
6646
+ if (fresh3) this.fresh.set(normalizePath(to), fresh3);
6450
6647
  }
6451
6648
  };
6452
6649
  const ranged2 = rangedOps(fs);
@@ -7150,7 +7347,7 @@ var FsWatchers = class {
7150
7347
  };
7151
7348
  const content = (run2, types) => async (...args) => {
7152
7349
  const at = normalizePath(args[0]);
7153
- const fresh2 = this.covers(at) && !await fs.exists(at).catch(() => true);
7350
+ const fresh3 = this.covers(at) && !await fs.exists(at).catch(() => true);
7154
7351
  let done4 = false;
7155
7352
  try {
7156
7353
  await run2(...args);
@@ -7159,11 +7356,11 @@ var FsWatchers = class {
7159
7356
  const real = await realOf(at);
7160
7357
  const paths = real === at ? [at] : [at, real];
7161
7358
  this.report(paths);
7162
- if (done4) for (const type of types(fresh2, args)) this.emit(type, paths);
7359
+ if (done4) for (const type of types(fresh3, args)) this.emit(type, paths);
7163
7360
  }
7164
7361
  };
7165
- const written2 = (fresh2) => fresh2 ? ["rename", "change"] : ["change"];
7166
- const overwritten = (fresh2, args) => fresh2 || args[1].length === 0 ? written2(fresh2) : ["change", "change"];
7362
+ const written2 = (fresh3) => fresh3 ? ["rename", "change"] : ["change"];
7363
+ const overwritten = (fresh3, args) => fresh3 || args[1].length === 0 ? written2(fresh3) : ["change", "change"];
7167
7364
  const reshaping = (run2) => async (...args) => {
7168
7365
  this.reshaped();
7169
7366
  try {
@@ -7190,14 +7387,14 @@ var FsWatchers = class {
7190
7387
  if (created4.length > 0 || !await fs.exists(ancestor)) created4.push(ancestor);
7191
7388
  }
7192
7389
  }
7193
- const fresh2 = !options?.recursive || this.typed.size > 0 && !await fs.exists(path);
7390
+ const fresh3 = !options?.recursive || this.typed.size > 0 && !await fs.exists(path);
7194
7391
  let done4 = false;
7195
7392
  try {
7196
7393
  await fs.mkdir(path, options);
7197
7394
  done4 = true;
7198
7395
  } finally {
7199
7396
  this.report([...created4, path]);
7200
- if (done4) this.emit("rename", fresh2 ? [...created4, path] : created4);
7397
+ if (done4) this.emit("rename", fresh3 ? [...created4, path] : created4);
7201
7398
  }
7202
7399
  },
7203
7400
  rm: reshaping(after(fs.rm.bind(fs), (path) => [path], "rename")),
@@ -7653,6 +7850,416 @@ var ClipboardBroker = class {
7653
7850
  }
7654
7851
  };
7655
7852
 
7853
+ // src/kernel/embedder.ts
7854
+ var EMBEDDER_PATH = "/dev/embedder";
7855
+ var EMBEDDER_HEADER_MAX = 64 * 1024;
7856
+ var EMBEDDER_WINDOW = 1024 * 1024;
7857
+ var EMBEDDER_BUDGET = 256 * 1024 * 1024;
7858
+ var EMBEDDER_PER_PROCESS = 16;
7859
+ var EMBEDDER_PROGRESS_PER_SECOND = 4;
7860
+ var KIND = /^[a-z][a-z0-9-]*(\.[a-z0-9-]+)*$/;
7861
+ var NL = 10;
7862
+ var READY2 = { readable: true, writable: true, hangup: false };
7863
+ function lineOf2(err) {
7864
+ const text = err instanceof Error ? err.message : String(err);
7865
+ return (text.split("\n")[0] ?? "").slice(0, 200);
7866
+ }
7867
+ var ERRNOS = {
7868
+ NotAllowedError: "EACCES",
7869
+ AbortError: "EACCES",
7870
+ NotSupportedError: "ENOTSUP",
7871
+ NotFoundError: "ENOENT"
7872
+ };
7873
+ function embedderFailure(err) {
7874
+ if (err instanceof KernelError) return err;
7875
+ const name2 = err?.name;
7876
+ return new KernelError(ERRNOS[String(name2)] ?? "EIO", lineOf2(err));
7877
+ }
7878
+ function headerOf(bytes) {
7879
+ let parsed2;
7880
+ try {
7881
+ parsed2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
7882
+ } catch {
7883
+ throw new KernelError("EINVAL", "the request header is not JSON");
7884
+ }
7885
+ if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2))
7886
+ throw new KernelError("EINVAL", "the request header is not an object");
7887
+ const { kind, ...options } = parsed2;
7888
+ if (typeof kind !== "string" || kind.length > 64 || !KIND.test(kind))
7889
+ throw new KernelError("EINVAL", "the request has no valid kind");
7890
+ return { kind, options };
7891
+ }
7892
+ function joined2(a, b) {
7893
+ const out = new Uint8Array(a.length + b.length);
7894
+ out.set(a);
7895
+ out.set(b, a.length);
7896
+ return out;
7897
+ }
7898
+ function aborted(signal) {
7899
+ if (!signal) return void 0;
7900
+ return new Promise((_, reject) => {
7901
+ const stop = () => reject(new KernelError("EINTR"));
7902
+ if (signal.aborted) stop();
7903
+ else signal.addEventListener("abort", stop, { once: true });
7904
+ });
7905
+ }
7906
+ async function interruptible2(work, signal) {
7907
+ const stop = aborted(signal);
7908
+ if (!stop) return work;
7909
+ stop.catch(() => void 0);
7910
+ return Promise.race([work, stop]);
7911
+ }
7912
+ var Budget2 = class {
7913
+ used = 0;
7914
+ limit;
7915
+ constructor(limit) {
7916
+ this.limit = limit;
7917
+ }
7918
+ take(n) {
7919
+ if (this.used + n > this.limit) return false;
7920
+ this.used += n;
7921
+ return true;
7922
+ }
7923
+ give(n) {
7924
+ this.used = Math.max(0, this.used - n);
7925
+ }
7926
+ };
7927
+ var BodyPipe = class {
7928
+ queue = [];
7929
+ size = 0;
7930
+ ended = false;
7931
+ wake;
7932
+ room;
7933
+ stream;
7934
+ constructor() {
7935
+ this.stream = new ReadableStream(
7936
+ {
7937
+ pull: async (controller) => {
7938
+ while (this.queue.length === 0 && !this.ended) {
7939
+ await new Promise((resolve2) => {
7940
+ this.wake = resolve2;
7941
+ });
7942
+ }
7943
+ const next3 = this.queue.shift();
7944
+ if (next3) {
7945
+ this.size -= next3.length;
7946
+ this.room?.();
7947
+ controller.enqueue(next3);
7948
+ } else controller.close();
7949
+ }
7950
+ },
7951
+ { highWaterMark: 0 }
7952
+ );
7953
+ }
7954
+ get full() {
7955
+ return this.size >= EMBEDDER_WINDOW;
7956
+ }
7957
+ push(bytes) {
7958
+ if (bytes.length === 0) return;
7959
+ this.queue.push(bytes.slice());
7960
+ this.size += bytes.length;
7961
+ this.wake?.();
7962
+ }
7963
+ waitRoom() {
7964
+ return new Promise((resolve2) => {
7965
+ this.room = resolve2;
7966
+ });
7967
+ }
7968
+ end() {
7969
+ this.ended = true;
7970
+ this.wake?.();
7971
+ this.room?.();
7972
+ }
7973
+ };
7974
+ function replyStream(reply) {
7975
+ if (reply instanceof ReadableStream) return reply;
7976
+ const bytes = typeof reply === "string" ? new TextEncoder().encode(reply) : reply instanceof Uint8Array ? reply : new Uint8Array(0);
7977
+ return new ReadableStream({
7978
+ start(controller) {
7979
+ if (bytes.length > 0) controller.enqueue(bytes);
7980
+ controller.close();
7981
+ }
7982
+ });
7983
+ }
7984
+ var Throttle = class {
7985
+ window = 0;
7986
+ count = 0;
7987
+ allow(now) {
7988
+ if (now - this.window >= 1e3) {
7989
+ this.window = now;
7990
+ this.count = 0;
7991
+ }
7992
+ this.count += 1;
7993
+ return this.count <= EMBEDDER_PROGRESS_PER_SECOND;
7994
+ }
7995
+ };
7996
+ var EmbedderChannel = class {
7997
+ access;
7998
+ owner;
7999
+ caller;
8000
+ broker;
8001
+ header = new Uint8Array(0);
8002
+ body;
8003
+ reply;
8004
+ replied;
8005
+ rest;
8006
+ done = false;
8007
+ failure;
8008
+ reported = false;
8009
+ abort = new AbortController();
8010
+ closed = false;
8011
+ throttle = new Throttle();
8012
+ constructor(broker, access2, caller) {
8013
+ this.broker = broker;
8014
+ this.access = access2;
8015
+ this.caller = caller;
8016
+ this.owner = caller.pid;
8017
+ }
8018
+ get dispatched() {
8019
+ return this.body !== void 0;
8020
+ }
8021
+ fail(err) {
8022
+ this.failure ??= embedderFailure(err);
8023
+ return this.failure;
8024
+ }
8025
+ report(err) {
8026
+ this.reported = true;
8027
+ return this.fail(err);
8028
+ }
8029
+ async dispatch(bytes) {
8030
+ const { kind, options } = headerOf(bytes);
8031
+ const stamp = await this.broker.admit(kind, this.caller);
8032
+ const hook = this.broker.hookFor(this.caller.client);
8033
+ const body = new BodyPipe();
8034
+ this.body = body;
8035
+ const caller = this.caller;
8036
+ const progress = (line) => {
8037
+ if (this.throttle.allow(Date.now())) caller.progress(lineOf2(line));
8038
+ };
8039
+ const asked = Promise.resolve().then(
8040
+ () => hook.handle({
8041
+ kind,
8042
+ options,
8043
+ body: body.stream,
8044
+ stamp,
8045
+ signal: this.abort.signal,
8046
+ progress
8047
+ })
8048
+ ).then((reply) => replyStream(reply).getReader());
8049
+ this.replied = asked.catch((err) => {
8050
+ body.end();
8051
+ throw this.fail(err);
8052
+ });
8053
+ this.replied.catch(() => void 0);
8054
+ }
8055
+ async write(bytes, signal) {
8056
+ if (this.access === "read") throw new KernelError("EBADF");
8057
+ if (this.failure) throw this.report(this.failure);
8058
+ if (this.reply || this.done) throw new KernelError("EBADF");
8059
+ if (this.body) return this.writeBody(bytes, signal);
8060
+ const at = bytes.indexOf(NL);
8061
+ const head = at < 0 ? bytes : bytes.subarray(0, at);
8062
+ if (this.header.length + head.length > EMBEDDER_HEADER_MAX)
8063
+ throw this.report(new KernelError("EINVAL", "the request header is too long"));
8064
+ this.header = joined2(this.header, head);
8065
+ if (at < 0) return bytes.length;
8066
+ try {
8067
+ await this.dispatch(this.header);
8068
+ } catch (err) {
8069
+ throw this.report(err);
8070
+ }
8071
+ this.body?.push(bytes.subarray(at + 1));
8072
+ return bytes.length;
8073
+ }
8074
+ async writeBody(bytes, signal) {
8075
+ const body = this.body;
8076
+ while (body.full && !this.failure) {
8077
+ await interruptible2(body.waitRoom(), signal).catch((err) => {
8078
+ this.cancel("interrupted");
8079
+ throw err;
8080
+ });
8081
+ }
8082
+ if (this.failure) throw this.report(this.failure);
8083
+ body.push(bytes);
8084
+ return bytes.length;
8085
+ }
8086
+ async readerOf(signal) {
8087
+ if (this.reply) return this.reply;
8088
+ if (!this.body) {
8089
+ try {
8090
+ await this.dispatch(this.header);
8091
+ } catch (err) {
8092
+ throw this.fail(err);
8093
+ }
8094
+ }
8095
+ this.body?.end();
8096
+ this.reply = await interruptible2(
8097
+ this.replied,
8098
+ signal
8099
+ );
8100
+ return this.reply;
8101
+ }
8102
+ async read(max, signal) {
8103
+ if (this.access === "write") throw new KernelError("EBADF");
8104
+ if (this.failure) throw this.report(this.failure);
8105
+ if (this.done || max <= 0) return new Uint8Array(0);
8106
+ try {
8107
+ return await this.next(max, signal);
8108
+ } catch (err) {
8109
+ if (err instanceof KernelError && err.code === "EINTR") {
8110
+ this.cancel("interrupted");
8111
+ throw err;
8112
+ }
8113
+ throw this.report(err);
8114
+ }
8115
+ }
8116
+ async next(max, signal) {
8117
+ const reader = await this.readerOf(signal);
8118
+ let chunk2 = this.rest;
8119
+ this.rest = void 0;
8120
+ if (!chunk2) {
8121
+ const got = await interruptible2(reader.read(), signal);
8122
+ if (got.done) {
8123
+ this.done = true;
8124
+ return new Uint8Array(0);
8125
+ }
8126
+ chunk2 = got.value;
8127
+ }
8128
+ if (chunk2.length > max) this.rest = chunk2.subarray(max);
8129
+ return chunk2.subarray(0, max);
8130
+ }
8131
+ poll() {
8132
+ return this.body?.full ? { ...READY2, writable: false } : READY2;
8133
+ }
8134
+ cancel(reason) {
8135
+ if (this.abort.signal.aborted) return;
8136
+ this.abort.abort(new DOMException(reason, "AbortError"));
8137
+ this.body?.end();
8138
+ void this.reply?.cancel(reason).catch(() => void 0);
8139
+ }
8140
+ async close() {
8141
+ if (this.closed) return;
8142
+ const signal = this.caller.closing();
8143
+ this.closed = true;
8144
+ try {
8145
+ if (this.access === "read") return;
8146
+ if (this.failure) {
8147
+ if (this.reported) return;
8148
+ throw this.report(this.failure);
8149
+ }
8150
+ if (this.access === "both" && this.dispatched) return;
8151
+ if (!this.dispatched && this.header.length === 0) return;
8152
+ await this.drain(signal);
8153
+ } finally {
8154
+ if (!this.done) this.cancel("closed");
8155
+ this.broker.release(this);
8156
+ }
8157
+ }
8158
+ async drain(signal) {
8159
+ try {
8160
+ const reader = await this.readerOf(signal);
8161
+ for (; ; ) {
8162
+ const got = await interruptible2(reader.read(), signal);
8163
+ if (got.done) break;
8164
+ }
8165
+ this.done = true;
8166
+ } catch (err) {
8167
+ if (err instanceof KernelError && err.code === "EINTR") this.cancel("interrupted");
8168
+ throw this.fail(err);
8169
+ }
8170
+ }
8171
+ };
8172
+ var EmbedderBroker = class {
8173
+ users;
8174
+ lookup;
8175
+ hooks = /* @__PURE__ */ new Map();
8176
+ budget;
8177
+ open = /* @__PURE__ */ new Map();
8178
+ tagsOf;
8179
+ constructor(users, lookup4, tagsOf = () => void 0, budget = EMBEDDER_BUDGET) {
8180
+ this.users = users;
8181
+ this.tagsOf = tagsOf;
8182
+ this.lookup = lookup4;
8183
+ this.budget = new Budget2(budget);
8184
+ }
8185
+ register(client, hook) {
8186
+ if (!hook) return () => void 0;
8187
+ this.hooks.set(client, hook);
8188
+ return () => {
8189
+ if (this.hooks.get(client) === hook) this.hooks.delete(client);
8190
+ };
8191
+ }
8192
+ hookFor(client) {
8193
+ return this.hooks.get(client);
8194
+ }
8195
+ async allowed(policy, caller) {
8196
+ if (caller.euid === 0 || caller.euid === caller.sessionUid) return true;
8197
+ if (policy.group !== void 0) {
8198
+ const gid = await this.lookup.groupId(policy.group);
8199
+ if (gid !== void 0 && (caller.egid === gid || caller.groups.includes(gid))) return true;
8200
+ }
8201
+ for (const user of policy.users ?? []) {
8202
+ const uid = typeof user === "number" ? user : await this.lookup.userId(user);
8203
+ if (uid === caller.euid) return true;
8204
+ }
8205
+ return false;
8206
+ }
8207
+ async admit(kind, caller) {
8208
+ const hook = this.hooks.get(caller.client);
8209
+ if (!hook) throw new KernelError("ENOTSUP", "no page answers requests for this session");
8210
+ const policy = Object.hasOwn(hook.kinds, kind) ? hook.kinds[kind] : void 0;
8211
+ if (!policy) throw new KernelError("ENOTSUP", `this embedder does not answer ${kind}`);
8212
+ if (this.users !== "off" && !await this.allowed(policy, caller))
8213
+ throw new KernelError("EACCES", `${kind} is not allowed for this user`);
8214
+ const tags = caller.tags;
8215
+ return {
8216
+ pid: caller.shownPid,
8217
+ uid: caller.euid,
8218
+ gid: caller.egid,
8219
+ groups: [...caller.groups],
8220
+ users: this.users,
8221
+ kind,
8222
+ ...tags?.length ? { tag: tags[0], tags: [...tags] } : {}
8223
+ };
8224
+ }
8225
+ channel(access2, caller) {
8226
+ const mine = this.open.get(caller.pid) ?? /* @__PURE__ */ new Set();
8227
+ if (mine.size >= EMBEDDER_PER_PROCESS)
8228
+ throw new KernelError("EMFILE", `at most ${EMBEDDER_PER_PROCESS} requests per process`);
8229
+ if (!this.budget.take(2 * EMBEDDER_WINDOW))
8230
+ throw new KernelError("EAGAIN", "the embedder request budget is spent");
8231
+ const channel = new EmbedderChannel(this, access2, caller);
8232
+ mine.add(channel);
8233
+ this.open.set(caller.pid, mine);
8234
+ return new OpenFile({
8235
+ read: channel.read.bind(channel),
8236
+ write: (bytes, signal) => channel.write(bytes, signal),
8237
+ poll: () => channel.poll(),
8238
+ close: () => channel.close(),
8239
+ embedder: channel
8240
+ });
8241
+ }
8242
+ release(channel) {
8243
+ const mine = this.open.get(channel.owner);
8244
+ if (!mine?.delete(channel)) return;
8245
+ if (mine.size === 0) this.open.delete(channel.owner);
8246
+ this.budget.give(2 * EMBEDDER_WINDOW);
8247
+ }
8248
+ };
8249
+ function kindsOf(hook) {
8250
+ if (typeof hook?.handle !== "function" || typeof hook.kinds !== "object" || !hook.kinds)
8251
+ throw new TypeError("requests needs kinds and handle");
8252
+ return Object.fromEntries(
8253
+ Object.entries(hook.kinds).map(([kind, policy]) => [
8254
+ kind,
8255
+ {
8256
+ ...policy?.group !== void 0 ? { group: String(policy.group) } : {},
8257
+ ...policy?.users ? { users: [...policy.users] } : {}
8258
+ }
8259
+ ])
8260
+ );
8261
+ }
8262
+
7656
8263
  // src/kernel/exec-ticket.ts
7657
8264
  var TICKET_ENV = "SLICC_SEALED_TICKET";
7658
8265
  var TICKET_TTL_MS = 3e4;
@@ -8221,9 +8828,9 @@ var HeldPaths = class extends Map {
8221
8828
  return false;
8222
8829
  }
8223
8830
  keep(path, file) {
8224
- const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
8225
- for (const id2 of fresh2) this.keptHandles.set(id2, file);
8226
- this.uses.set(file, Math.max(fresh2.length, 1));
8831
+ const fresh3 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
8832
+ for (const id2 of fresh3) this.keptHandles.set(id2, file);
8833
+ this.uses.set(file, Math.max(fresh3.length, 1));
8227
8834
  this.unlinked.set(path, file);
8228
8835
  }
8229
8836
  unkeep(path, file) {
@@ -9419,8 +10026,8 @@ var MountFs = class _MountFs {
9419
10026
  }
9420
10027
  this.writable(mount, real);
9421
10028
  const { uid, gid, mode } = change;
9422
- const fresh2 = change.kind === "new" && { uid, gid, mode };
9423
- const set3 = fresh2 ? await this.driverOwner(found, real, fresh2, false) : change;
10029
+ const fresh3 = change.kind === "new" && { uid, gid, mode };
10030
+ const set3 = fresh3 ? await this.driverOwner(found, real, fresh3, false) : change;
9424
10031
  this.table.forget(mount, real);
9425
10032
  const attrs = Object.entries({ uid: set3.uid, gid: set3.gid, mode: set3.mode });
9426
10033
  await mount.conn.call({
@@ -9692,10 +10299,10 @@ async function runMkdir(entry, path, req) {
9692
10299
  const plain3 = plainDirMode(entry, req.mode);
9693
10300
  const parentsMode = !req.exclusive ? plainParentsMode(entry) : void 0;
9694
10301
  const fixParents = parentsMode !== void 0 ? await missingParents(fs, path, parentsMode) : void 0;
9695
- const fresh2 = plain3 !== void 0 && !await fs.exists(path);
10302
+ const fresh3 = plain3 !== void 0 && !await fs.exists(path);
9696
10303
  await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
9697
10304
  await fixParents?.();
9698
- if (fresh2) await fs.chmod(path, plain3);
10305
+ if (fresh3) await fs.chmod(path, plain3);
9699
10306
  return done;
9700
10307
  }
9701
10308
  async function shownAs(entry, path, st) {
@@ -10699,9 +11306,9 @@ var Pages = class {
10699
11306
  const page = (start2 + at) / PAGE;
10700
11307
  if (this.pages.has(page)) continue;
10701
11308
  const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
10702
- const fresh2 = this.take(part.length);
10703
- fresh2.set(part);
10704
- this.pages.set(page, fresh2);
11309
+ const fresh3 = this.take(part.length);
11310
+ fresh3.set(part);
11311
+ this.pages.set(page, fresh3);
10705
11312
  }
10706
11313
  }
10707
11314
  read(at, length2) {
@@ -12820,6 +13427,7 @@ var TLS_SERVER_REQUEST_CERT = 1;
12820
13427
  var TLS_SERVER_REQUIRE_CERT = 2;
12821
13428
  var TLS_SERVER_SELECT = 4;
12822
13429
  var TLS_HANDSHAKE_SELECT = 2;
13430
+ var TLS_KEYLOG = 8;
12823
13431
  var SCRATCH = 64 * 1024;
12824
13432
  var EOF = -1;
12825
13433
  var TlsError = class extends Error {
@@ -12887,6 +13495,9 @@ var TlsEngine = class {
12887
13495
  if (!ptr) throw this.error(this.m._tls_last_error(), "server certificate");
12888
13496
  return new TlsLeaf(this, this.m, ptr);
12889
13497
  }
13498
+ get keylogs() {
13499
+ return typeof this.m._tls_keylog === "function";
13500
+ }
12890
13501
  get serverSelect() {
12891
13502
  return typeof this.m._tls_server_select === "function";
12892
13503
  }
@@ -12917,9 +13528,11 @@ var TlsEngine = class {
12917
13528
  this.m._free(ptr);
12918
13529
  }
12919
13530
  }
12920
- intoScratch(fn) {
13531
+ intoScratch(fn, secret = false) {
12921
13532
  const n = fn(this.scratch, SCRATCH);
12922
- return n > 0 ? this.taken(n) : n;
13533
+ const out = n > 0 ? this.taken(n) : n;
13534
+ if (secret && n > 0) this.m.HEAPU8.fill(0, this.scratch, this.scratch + n);
13535
+ return out;
12923
13536
  }
12924
13537
  get sessionControls() {
12925
13538
  return typeof this.m._tls_client_cert === "function";
@@ -12963,18 +13576,18 @@ var TlsEngine = class {
12963
13576
  chainFlags(store, chain2, hostname) {
12964
13577
  const verify = this.m._tls_chain_verify;
12965
13578
  if (!verify || chain2.length === 0) return void 0;
12966
- const joined4 = new Uint8Array(chain2.reduce((n, c) => n + c.length, 0));
13579
+ const joined5 = new Uint8Array(chain2.reduce((n, c) => n + c.length, 0));
12967
13580
  let at = 0;
12968
13581
  for (const der2 of chain2) {
12969
- joined4.set(der2, at);
13582
+ joined5.set(der2, at);
12970
13583
  at += der2.length;
12971
13584
  }
12972
13585
  const name2 = this.put(new TextEncoder().encode(hostname));
12973
13586
  try {
12974
- return this.withBytes(joined4, (p) => {
13587
+ return this.withBytes(joined5, (p) => {
12975
13588
  const out = this.scratch;
12976
13589
  this.m.HEAPU8.fill(255, out, out + 4);
12977
- const ret = verify(store.ptr, p, joined4.length, name2, out);
13590
+ const ret = verify(store.ptr, p, joined5.length, name2, out);
12978
13591
  const flags2 = new DataView(this.m.HEAPU8.buffer, out, 4).getUint32(0, true);
12979
13592
  if (ret === 0) return flags2 === 0 ? 0 : void 0;
12980
13593
  return ret === 1 && flags2 !== 0 ? flags2 : void 0;
@@ -13130,6 +13743,14 @@ var TlsSession = class {
13130
13743
  get version() {
13131
13744
  return this.c._tls_version(this.ptr);
13132
13745
  }
13746
+ keylog() {
13747
+ const drain2 = this.m._tls_keylog;
13748
+ const out = this.engine.intoScratch((p, max) => drain2(this.ptr, p, max), true);
13749
+ if (!(out instanceof Uint8Array)) return [];
13750
+ const text = new TextDecoder().decode(out);
13751
+ out.fill(0);
13752
+ return text.split("\n").filter((line) => line.length > 0);
13753
+ }
13133
13754
  get sni() {
13134
13755
  const out = this.engine.intoScratch(
13135
13756
  (p, max) => this.m._tls_sni(this.ptr, p, max)
@@ -14650,7 +15271,7 @@ function isIP(name2) {
14650
15271
  return ipv4Bytes(name2) !== void 0 || name2.includes(":") && ipv6Groups(name2) !== void 0;
14651
15272
  }
14652
15273
  function clientFlags(o) {
14653
- return (o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0) | (o.sni === false ? TLS_CLIENT_NO_SNI : 0) | (o.checkHostname === false ? TLS_CLIENT_VERIFY_CHAIN_ONLY : 0);
15274
+ return (o.insecure ? TLS_CLIENT_INSECURE_SKIP_VERIFY : 0) | (o.sni === false ? TLS_CLIENT_NO_SNI : 0) | (o.checkHostname === false ? TLS_CLIENT_VERIFY_CHAIN_ONLY : 0) | (o.keylog ? TLS_KEYLOG : 0);
14654
15275
  }
14655
15276
  function openSession(o) {
14656
15277
  return o.engine.clientSession(o.store, o.servername, o.alpn, clientFlags(o));
@@ -14765,7 +15386,8 @@ async function infoOf(o, session) {
14765
15386
  peerCertificates: chain2,
14766
15387
  ...controlsOf(o, session, protocol, chain2, shape),
14767
15388
  verified: verdictOf(o, session),
14768
- handshakeAt: Date.now()
15389
+ handshakeAt: Date.now(),
15390
+ ...o.keylog ? { keylog: session.keylog() } : {}
14769
15391
  };
14770
15392
  }
14771
15393
  var FATAL_ALERT = -30592;
@@ -14885,7 +15507,7 @@ async function serverHandshake(conn, session, inbox, hooks) {
14885
15507
  }
14886
15508
  }
14887
15509
  }
14888
- async function tlsServer(conn, session, clientCerts, select) {
15510
+ async function tlsServer(conn, session, clientCerts, select, keylog = false) {
14889
15511
  let first;
14890
15512
  const watched = {
14891
15513
  ...conn,
@@ -14908,7 +15530,8 @@ async function tlsServer(conn, session, clientCerts, select) {
14908
15530
  servername: session.sni,
14909
15531
  authorized: clientCerts && chain2.length > 0 && code === void 0,
14910
15532
  ...clientCerts && chain2.length > 0 && code ? { authorizationError: code } : {},
14911
- peerCertificates: chain2
15533
+ peerCertificates: chain2,
15534
+ ...keylog ? { keylog: session.keylog() } : {}
14912
15535
  };
14913
15536
  return { conn: plaintext(conn, session, inbox), info };
14914
15537
  } catch (err) {
@@ -15708,6 +16331,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15708
16331
  ...file.tty ? { tty: file.tty } : {},
15709
16332
  identity: file.identity ?? file,
15710
16333
  ...file.clipboard ? { clipboard: file.clipboard } : {},
16334
+ ...file.embedder ? { embedder: file.embedder } : {},
15711
16335
  ...file.write ? {
15712
16336
  write: async (bytes, signal) => {
15713
16337
  await turn(signal);
@@ -15764,7 +16388,8 @@ function scrubStdio(fds, scrubber) {
15764
16388
  const stdio = stdioFiles(fds);
15765
16389
  for (const fd of fds.numbers()) {
15766
16390
  const original = fds.get(fd);
15767
- if (!stdio.has(original) && !original.file.tty && !original.file.clipboard) continue;
16391
+ const named = original.file.tty || original.file.clipboard || original.file.embedder;
16392
+ if (!stdio.has(original) && !named) continue;
15768
16393
  const known = wrapped.get(original);
15769
16394
  const flags2 = fds.statusFlags(fd);
15770
16395
  const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
@@ -15777,9 +16402,9 @@ function scrubStdio(fds, scrubber) {
15777
16402
  // src/kernel/net/sealed-scrubs.ts
15778
16403
  function addTo(tree2, host, secrets) {
15779
16404
  const entry = tree2.get(host) ?? { secrets: [], version: 0, scrubber: void 0 };
15780
- const fresh2 = secrets.filter((s) => !entry.secrets.some((e) => e.value === s.value));
15781
- if (fresh2.length === 0) return;
15782
- entry.secrets = [...entry.secrets, ...fresh2];
16405
+ const fresh3 = secrets.filter((s) => !entry.secrets.some((e) => e.value === s.value));
16406
+ if (fresh3.length === 0) return;
16407
+ entry.secrets = [...entry.secrets, ...fresh3];
15783
16408
  entry.version += 1;
15784
16409
  entry.scrubber = void 0;
15785
16410
  tree2.set(host, entry);
@@ -15993,13 +16618,13 @@ function serialized(reply) {
15993
16618
  const text = JSON.stringify(reply);
15994
16619
  plain3 = text === void 0 ? void 0 : JSON.parse(text);
15995
16620
  } catch (err) {
15996
- return JSON.stringify(errorReply("EIO", lineOf2(err)));
16621
+ return JSON.stringify(errorReply("EIO", lineOf3(err)));
15997
16622
  }
15998
16623
  if (!isRecord(plain3)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
15999
16624
  const { v: _, ...rest2 } = plain3;
16000
16625
  return JSON.stringify({ v: 1, ...rest2 });
16001
16626
  }
16002
- function lineOf2(err) {
16627
+ function lineOf3(err) {
16003
16628
  const text = err instanceof Error ? err.message : String(err);
16004
16629
  return (text.split("\n")[0] ?? "").slice(0, 200);
16005
16630
  }
@@ -16050,7 +16675,7 @@ var SealedChannel = class {
16050
16675
  if (signal.aborted) return void 0;
16051
16676
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
16052
16677
  (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
16053
- (err) => errorReply("EIO", lineOf2(err))
16678
+ (err) => errorReply("EIO", lineOf3(err))
16054
16679
  );
16055
16680
  const reply = await Promise.race([asked, stopped]);
16056
16681
  if (!signal.aborted) return reply;
@@ -17167,13 +17792,13 @@ var PgServer = class {
17167
17792
  at += whole;
17168
17793
  }
17169
17794
  this.buffer = data.slice(at);
17170
- const joined4 = new Uint8Array(out.reduce((n, b) => n + b.length, 0));
17795
+ const joined5 = new Uint8Array(out.reduce((n, b) => n + b.length, 0));
17171
17796
  let to = 0;
17172
17797
  for (const b of out) {
17173
- joined4.set(b, to);
17798
+ joined5.set(b, to);
17174
17799
  to += b.length;
17175
17800
  }
17176
- return joined4;
17801
+ return joined5;
17177
17802
  }
17178
17803
  };
17179
17804
  function frame3(type, body) {
@@ -17396,7 +18021,7 @@ function latin12(bytes) {
17396
18021
  for (const b of bytes) out += String.fromCharCode(b);
17397
18022
  return out;
17398
18023
  }
17399
- function bytesOf(text) {
18024
+ function bytesOf2(text) {
17400
18025
  const out = new Uint8Array(text.length);
17401
18026
  for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i);
17402
18027
  return out;
@@ -17515,12 +18140,12 @@ var SessionWriter = class {
17515
18140
  if (this.failed !== void 0) throw this.failed;
17516
18141
  if (this.mode === "pass") return this.conn.write(bytes);
17517
18142
  clearTimeout(this.timer);
17518
- const joined4 = new Uint8Array(this.buffer.length + bytes.length);
17519
- joined4.set(this.buffer);
17520
- joined4.set(bytes, this.buffer.length);
18143
+ const joined5 = new Uint8Array(this.buffer.length + bytes.length);
18144
+ joined5.set(this.buffer);
18145
+ joined5.set(bytes, this.buffer.length);
17521
18146
  this.buffer.fill(0);
17522
- this.buffer = joined4;
17523
- if (this.mode === "sniff") this.mode = modeOf(latin12(joined4.subarray(0, 8))) ?? "sniff";
18147
+ this.buffer = joined5;
18148
+ if (this.mode === "sniff") this.mode = modeOf(latin12(joined5.subarray(0, 8))) ?? "sniff";
17524
18149
  if (this.mode === "pg" && !this.sawPg) {
17525
18150
  this.sawPg = true;
17526
18151
  if (!this.s.verified) void this.proof("pg");
@@ -17548,7 +18173,7 @@ var SessionWriter = class {
17548
18173
  const out = await this.smtp.line(latin12(all.subarray(0, at))).catch(() => ({ line: latin12(all.subarray(0, at)) }));
17549
18174
  all.fill(0);
17550
18175
  if ("values" in out && out.values) this.remember(out.values);
17551
- const line = bytesOf(`${out.line}\r
18176
+ const line = bytesOf2(`${out.line}\r
17552
18177
  `);
17553
18178
  try {
17554
18179
  await writeAll3(this.conn, line);
@@ -17605,7 +18230,7 @@ var SessionWriter = class {
17605
18230
  latin12(all.subarray(0, end)),
17606
18231
  (position, placeholders, head2) => this.ask("http1", position, { placeholders, head: head2 })
17607
18232
  ).catch(() => void 0);
17608
- const head = done4 ? bytesOf(done4.head) : all.subarray(0, end);
18233
+ const head = done4 ? bytesOf2(done4.head) : all.subarray(0, end);
17609
18234
  if (done4) this.remember(done4.values);
17610
18235
  try {
17611
18236
  await writeAll3(this.conn, head);
@@ -17668,7 +18293,7 @@ var ScrubReader = class {
17668
18293
  const before = this.current();
17669
18294
  const out = this.through(got);
17670
18295
  if (before.length === 0 && out !== void 0) return out;
17671
- if (before.length > 0) return joined2(before, out);
18296
+ if (before.length > 0) return joined3(before, out);
17672
18297
  }
17673
18298
  }
17674
18299
  through(got) {
@@ -17684,7 +18309,7 @@ var ScrubReader = class {
17684
18309
  return out.length > 0 ? out : void 0;
17685
18310
  }
17686
18311
  };
17687
- function joined2(a, b) {
18312
+ function joined3(a, b) {
17688
18313
  if (!b) return a;
17689
18314
  const out = new Uint8Array(a.length + b.length);
17690
18315
  out.set(a);
@@ -17799,7 +18424,8 @@ function sessionOf(info, servername) {
17799
18424
  } : {},
17800
18425
  servername,
17801
18426
  peerCertificates: info.peerCertificates.map(encode),
17802
- ...controls(info)
18427
+ ...controls(info),
18428
+ ...info.keylog ? { keylog: info.keylog } : {}
17803
18429
  };
17804
18430
  }
17805
18431
  function controls(info) {
@@ -17941,11 +18567,14 @@ async function prepareWith(support, req, servername, cert) {
17941
18567
  insecure: req.insecure === true,
17942
18568
  ...req.sni === false ? { sni: false } : {},
17943
18569
  ...req.checkHostname === false ? { checkHostname: false } : {},
18570
+ ...req.keylog === true ? { keylog: true } : {},
17944
18571
  clientCertificate: cert !== void 0,
17945
18572
  bundled: own === void 0
17946
18573
  };
17947
18574
  let session;
17948
18575
  try {
18576
+ if (req.keylog === true && !engine.keylogs)
18577
+ throw new KernelError("ENOTSUP", "the installed tls engine has no key log");
17949
18578
  const controls2 = req.sni === false || req.checkHostname === false || cert !== void 0;
17950
18579
  if (controls2 && !engine.sessionControls) {
17951
18580
  throw new KernelError("ENOTSUP", "the installed tls engine has no client certificates");
@@ -17993,7 +18622,7 @@ function secure(prepared, seal) {
17993
18622
  try {
17994
18623
  const made2 = await tlsClient(raw2, prepared.options);
17995
18624
  const engine = prepared.options.engine;
17996
- const session = sealing({ ...seal, engine }, raw2, made2.info, prepared.options.servername);
18625
+ const session = prepared.options.keylog ? void 0 : sealing({ ...seal, engine }, raw2, made2.info, prepared.options.servername);
17997
18626
  if (!session) return made2;
17998
18627
  made2.info.substitutes = substitutes(session);
17999
18628
  if (made2.info.substitutes && session.verified)
@@ -18106,25 +18735,31 @@ function certFlags(req) {
18106
18735
  if (!req.requestCert) return 0;
18107
18736
  return req.rejectUnauthorized === false ? TLS_SERVER_REQUEST_CERT : TLS_SERVER_REQUIRE_CERT;
18108
18737
  }
18109
- function flagsOf(req) {
18110
- if (!req.select) return certFlags(req);
18738
+ var CERT_FLAGS = TLS_SERVER_REQUEST_CERT | TLS_SERVER_REQUIRE_CERT;
18739
+ function selectFlag(req) {
18740
+ if (!req.select) return 0;
18111
18741
  if (req.alpn && req.alpn.length > 0)
18112
18742
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
18113
- return certFlags(req) | TLS_SERVER_SELECT;
18743
+ return TLS_SERVER_SELECT;
18744
+ }
18745
+ function flagsOf(req) {
18746
+ return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
18114
18747
  }
18115
- async function engineOf(proc, select) {
18748
+ async function engineOf(proc, { select, keylog }) {
18116
18749
  const engine = await supportOf(proc.net).engine();
18117
18750
  if (!engine.serverSessions)
18118
18751
  throw new KernelError("ENOTSUP", "the installed tls engine has no server sessions");
18752
+ if (keylog && !engine.keylogs)
18753
+ throw new KernelError("ENOTSUP", "the installed tls engine has no key log");
18119
18754
  if (select && !engine.serverSelect)
18120
18755
  throw new KernelError("ENOTSUP", "the installed tls engine has no server selection");
18121
18756
  return engine;
18122
18757
  }
18123
- async function importedLeaf(proc, own, select) {
18758
+ async function importedLeaf(proc, own, needs = {}) {
18124
18759
  const cert = certBytes(own);
18125
18760
  let engine;
18126
18761
  try {
18127
- engine = await engineOf(proc, select);
18762
+ engine = await engineOf(proc, needs);
18128
18763
  } catch (err) {
18129
18764
  wipe(cert);
18130
18765
  throw err;
@@ -18154,12 +18789,12 @@ async function serverOf(proc, req) {
18154
18789
  delete req.cert;
18155
18790
  if (!own && !req.select)
18156
18791
  throw new KernelError("EINVAL", "ERR_INVALID_ARG_VALUE: a server needs cert and key");
18157
- const { engine, leaf } = own ? await importedLeaf(proc, own, req.select) : { engine: await engineOf(proc, true), leaf: void 0 };
18792
+ const { engine, leaf } = own ? await importedLeaf(proc, own, req) : { engine: await engineOf(proc, { ...req, select: true }), leaf: void 0 };
18158
18793
  let mine;
18159
18794
  try {
18160
18795
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
18161
18796
  const flags2 = flagsOf(req);
18162
- const store = mine ?? (flags2 & ~TLS_SERVER_SELECT ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
18797
+ const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
18163
18798
  return {
18164
18799
  engine,
18165
18800
  leaf,
@@ -18193,7 +18828,8 @@ function servedOf(info) {
18193
18828
  authorized: info.authorized,
18194
18829
  ...info.authorizationError ? { authorizationError: info.authorizationError } : {},
18195
18830
  peerCertificates: info.peerCertificates.map(encode),
18196
- cipher: info.cipher
18831
+ cipher: info.cipher,
18832
+ ...info.keylog ? { keylog: info.keylog } : {}
18197
18833
  };
18198
18834
  }
18199
18835
  var selections = /* @__PURE__ */ new WeakMap();
@@ -18216,13 +18852,14 @@ async function serveSecure(proc, socket, req) {
18216
18852
  const { engine, leaf, alpn, store, flags: flags2 } = server;
18217
18853
  const limit = handshakeSignal(proc, server.timeoutMs);
18218
18854
  const { pausedAt, select } = pausing(limit.signal);
18219
- const certs = (flags2 & ~TLS_SERVER_SELECT) !== 0;
18855
+ const certs = (flags2 & CERT_FLAGS) !== 0;
18220
18856
  const run2 = socket.secureLink(
18221
18857
  (raw2) => tlsServer(
18222
18858
  raw2,
18223
18859
  engine.serverSession(leaf, alpn, store, flags2),
18224
18860
  certs,
18225
- flags2 & TLS_SERVER_SELECT ? select : void 0
18861
+ flags2 & TLS_SERVER_SELECT ? select : void 0,
18862
+ (flags2 & TLS_KEYLOG) !== 0
18226
18863
  ),
18227
18864
  limit.signal,
18228
18865
  (reason) => !shared && reason === timedOut
@@ -18592,7 +19229,7 @@ async function connect(req, proc) {
18592
19229
  dialing?.catch(() => void 0);
18593
19230
  throw new KernelError("EINPROGRESS");
18594
19231
  }
18595
- if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
19232
+ if (dialing !== void 0) await interruptible3(dialing, proc.blocking());
18596
19233
  return ok3();
18597
19234
  }
18598
19235
  async function bindUnixDgram(proc, socket, addr) {
@@ -18657,7 +19294,7 @@ async function unixDatagram(req, dgram, proc) {
18657
19294
  function domainOf(socket) {
18658
19295
  return socket instanceof UnixDgram ? "unix" : "inet";
18659
19296
  }
18660
- function interruptible2(dialing, signal) {
19297
+ function interruptible3(dialing, signal) {
18661
19298
  return new Promise((resolve2, reject) => {
18662
19299
  const abort = () => reject(new KernelError("EINTR"));
18663
19300
  signal.addEventListener("abort", abort, { once: true });
@@ -18692,7 +19329,7 @@ var NOT_HTTP_PORTS = /* @__PURE__ */ new Set([
18692
19329
  ]);
18693
19330
  var HOST_NAME = /^[A-Za-z0-9.-]+$|^\[?[0-9A-Fa-f:.]+\]?$/;
18694
19331
  function sessionControls(req) {
18695
- return req.sni === false || req.checkHostname === false || req.clientCert !== void 0;
19332
+ return req.sni === false || req.checkHostname === false || req.clientCert !== void 0 || req.keylog === true;
18696
19333
  }
18697
19334
  function wantsKernel(req) {
18698
19335
  if (req.path === "kernel") return true;
@@ -19661,7 +20298,7 @@ var ProcessWatches = class {
19661
20298
  };
19662
20299
 
19663
20300
  // src/kernel/process.ts
19664
- function aborted(signal) {
20301
+ function aborted2(signal) {
19665
20302
  return new Promise((_, reject) => {
19666
20303
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
19667
20304
  });
@@ -19751,6 +20388,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
19751
20388
  "fd-open-sealed",
19752
20389
  "fd-open-clipboard",
19753
20390
  "fd-clipboard-abort",
20391
+ "fd-open-embedder",
19754
20392
  "tty-get",
19755
20393
  "tty-set",
19756
20394
  "tty-winsz",
@@ -20304,6 +20942,7 @@ var WasmProcess = class {
20304
20942
  if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
20305
20943
  if (file.tty) this.checkForeground(file.tty);
20306
20944
  if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
20945
+ if (file.embedder) return this.readEmbedder(file, req, cancelled);
20307
20946
  if (file instanceof EventFd && req.max < EventFd.SIZE) throw new KernelError("EINVAL");
20308
20947
  if (req.max <= 0) return new Uint8Array(0);
20309
20948
  if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
@@ -20324,6 +20963,81 @@ var WasmProcess = class {
20324
20963
  throw err;
20325
20964
  }
20326
20965
  }
20966
+ async readEmbedder(file, req, cancelled) {
20967
+ const channel = file.embedder;
20968
+ if (req.max <= 0) return new Uint8Array(0);
20969
+ channel.caller = await this.embedderCaller();
20970
+ try {
20971
+ return await channel.read(Math.min(req.max, MAX_READ), this.blockingSignal(cancelled));
20972
+ } catch (err) {
20973
+ await this.deviceComplaint(EMBEDDER_PATH, err);
20974
+ throw err;
20975
+ }
20976
+ }
20977
+ async writeEmbedder(file, body, nonblock, cancelled) {
20978
+ const channel = file.embedder;
20979
+ channel.caller = await this.embedderCaller();
20980
+ const write = file.write;
20981
+ if (!pollFile(file).writable && nonblock) throw new KernelError("EAGAIN");
20982
+ try {
20983
+ return await write.call(file, body, this.blockingSignal(cancelled));
20984
+ } catch (err) {
20985
+ await this.deviceComplaint(EMBEDDER_PATH, err);
20986
+ throw err;
20987
+ }
20988
+ }
20989
+ async deviceComplaint(path, err) {
20990
+ const name2 = this.options.clipboardName;
20991
+ if (name2 === void 0 || !(err instanceof KernelError) || err.detail === void 0) return;
20992
+ if (err.code === "EINTR") return;
20993
+ await this.complainLine(`${name2}: ${path}: ${clipboardLine(err.detail)}`);
20994
+ }
20995
+ async complainLine(line) {
20996
+ const stderr = this.fds.has(2) ? this.fds.get(2).file : void 0;
20997
+ if (!stderr?.write || stderr.clipboard || stderr.embedder || stderr.sealed) return;
20998
+ const timeout = new Promise((r) => setTimeout(() => r(0), 1e3));
20999
+ await Promise.race([
21000
+ stderr.write(new TextEncoder().encode(`${line}
21001
+ `), AbortSignal.timeout(1e3)).catch(() => 0),
21002
+ timeout
21003
+ ]);
21004
+ }
21005
+ async embedderCaller() {
21006
+ const shown = await this.options.identity?.();
21007
+ const session = this.options.embedderSession?.() ?? { client: 0, uid: 0 };
21008
+ const name2 = this.options.clipboardName ?? this.options.embedderName ?? "program";
21009
+ return {
21010
+ pid: this.pid,
21011
+ shownPid: shown?.pid ?? this.pid,
21012
+ euid: this.cred.euid,
21013
+ egid: this.cred.egid,
21014
+ groups: [...this.cred.groups],
21015
+ sessionUid: session.uid,
21016
+ client: session.client,
21017
+ tags: this.options.embedder?.tagsOf(this.pid),
21018
+ progress: (line) => void this.complainLine(`${name2}: ${line}`),
21019
+ closing: () => AbortSignal.any([this.interrupt.signal, this.gone.signal])
21020
+ };
21021
+ }
21022
+ async closeFd(fd) {
21023
+ this.options.locks?.closed(this.pid, fd);
21024
+ await this.dropRecordLocks(fd);
21025
+ const closing2 = this.fds.get(fd).file;
21026
+ const channel = closing2.clipboard;
21027
+ if (channel) channel.stamp = await this.clipboardStamp();
21028
+ if (closing2.embedder) closing2.embedder.caller = await this.embedderCaller();
21029
+ await Promise.resolve(this.fds.close(fd)).catch(async (err) => {
21030
+ if (channel) await this.clipboardComplaint(err);
21031
+ if (closing2.embedder) await this.deviceComplaint(EMBEDDER_PATH, err);
21032
+ throw err;
21033
+ });
21034
+ }
21035
+ async openEmbedder(flags2) {
21036
+ const broker = this.options.embedder;
21037
+ if (!broker) throw new KernelError("ENOENT");
21038
+ const file = broker.channel(openAccess(flags2), await this.embedderCaller());
21039
+ return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
21040
+ }
20327
21041
  async complain(line) {
20328
21042
  const stderr = this.fds.has(2) ? this.fds.get(2).file : void 0;
20329
21043
  if (!stderr?.write || stderr.sealed || stderr.clipboard) return false;
@@ -20354,6 +21068,7 @@ var WasmProcess = class {
20354
21068
  const file = this.fds.get(fd).file;
20355
21069
  if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
20356
21070
  if (!file.write) throw new KernelError("EBADF");
21071
+ if (file.embedder) return this.writeEmbedder(file, body, nonblock, cancelled);
20357
21072
  if (!pollFile(file).writable) {
20358
21073
  if (nonblock) throw new KernelError("EAGAIN");
20359
21074
  return file.write(body, this.blockingSignal(cancelled));
@@ -20401,11 +21116,11 @@ var WasmProcess = class {
20401
21116
  } else if (req.create) {
20402
21117
  await nodes.onEntry(path, async () => {
20403
21118
  const marks = this.options.owners || plain3 !== void 0;
20404
- const fresh2 = marks && !await present3(fs, path, true);
21119
+ const fresh3 = marks && !await present3(fs, path, true);
20405
21120
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
20406
21121
  await this.fds.get(fd).file.stat?.();
20407
- if (fresh2 && plain3 !== void 0) await fs.chmod?.(path, plain3);
20408
- else if (fresh2) {
21122
+ if (fresh3 && plain3 !== void 0) await fs.chmod?.(path, plain3);
21123
+ else if (fresh3) {
20409
21124
  await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
20410
21125
  }
20411
21126
  });
@@ -20623,17 +21338,9 @@ var WasmProcess = class {
20623
21338
  kind: "json",
20624
21339
  json: await this.write(req.fd, req.body, req.nonblock, cancelled)
20625
21340
  };
20626
- case "fd-close": {
20627
- this.options.locks?.closed(this.pid, req.fd);
20628
- await this.dropRecordLocks(req.fd);
20629
- const channel = this.fds.get(req.fd).file.clipboard;
20630
- if (channel) channel.stamp = await this.clipboardStamp();
20631
- await Promise.resolve(this.fds.close(req.fd)).catch(async (err) => {
20632
- if (channel) await this.clipboardComplaint(err);
20633
- throw err;
20634
- });
21341
+ case "fd-close":
21342
+ await this.closeFd(req.fd);
20635
21343
  return { ok: true, kind: "void" };
20636
- }
20637
21344
  case "fd-pipe": {
20638
21345
  const pipe = openPipe();
20639
21346
  const read = this.fds.install(pipe.read, 3);
@@ -20693,6 +21400,8 @@ var WasmProcess = class {
20693
21400
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
20694
21401
  case "fd-open-clipboard":
20695
21402
  return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
21403
+ case "fd-open-embedder":
21404
+ return { ok: true, kind: "json", json: await this.openEmbedder(req.flags) };
20696
21405
  case "fd-clipboard-abort": {
20697
21406
  const file = this.fds.get(req.fd);
20698
21407
  file.file.clipboard?.abort();
@@ -21187,7 +21896,7 @@ var WasmProcess = class {
21187
21896
  else {
21188
21897
  const { source, target, type, flags: flags2, data } = req;
21189
21898
  const signal = this.blockingSignal(cancelled);
21190
- await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
21899
+ await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted2(signal)]);
21191
21900
  }
21192
21901
  } catch (err) {
21193
21902
  const code = err?.code;
@@ -21197,7 +21906,7 @@ var WasmProcess = class {
21197
21906
  return { ok: true, kind: "void" };
21198
21907
  }
21199
21908
  pause(cancelled) {
21200
- return aborted(this.blockingSignal(cancelled));
21909
+ return aborted2(this.blockingSignal(cancelled));
21201
21910
  }
21202
21911
  alarm;
21203
21912
  dlLog = [];
@@ -21747,6 +22456,15 @@ function linksOf(opts) {
21747
22456
  function clipboardName(opts) {
21748
22457
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
21749
22458
  }
22459
+ function embedderOptions(opts) {
22460
+ if (!opts.embedder) return {};
22461
+ return {
22462
+ embedder: opts.embedder.broker,
22463
+ embedderSession: opts.embedder.session,
22464
+ embedderName: opts.argv0,
22465
+ ...clipboardName(opts)
22466
+ };
22467
+ }
21750
22468
  function processOptions(opts, header5, hooks, created4) {
21751
22469
  return {
21752
22470
  ...inherited(opts),
@@ -21754,6 +22472,7 @@ function processOptions(opts, header5, hooks, created4) {
21754
22472
  ...opts.sealed ? { sealed: opts.sealed } : {},
21755
22473
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
21756
22474
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
22475
+ ...embedderOptions(opts),
21757
22476
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
21758
22477
  ...opts.source ? { source: opts.source } : {},
21759
22478
  noLinks: opts.noLinks,
@@ -24502,9 +25221,9 @@ var OpfsFs = class {
24502
25221
  this.lookups++;
24503
25222
  if (split2(path).length === 0) return this.root;
24504
25223
  const cached = this.files.get(path);
24505
- const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
24506
- if (cached && !fresh2) this.files.delete(path);
24507
- if (cached && fresh2) {
25224
+ const fresh3 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
25225
+ if (cached && !fresh3) this.files.delete(path);
25226
+ if (cached && fresh3) {
24508
25227
  if (this.glanced(cached)) return this.remember(path, cached);
24509
25228
  try {
24510
25229
  await this.glance(cached);
@@ -24722,8 +25441,8 @@ var OpfsFs = class {
24722
25441
  glanced(handle) {
24723
25442
  if (this.exact > 0) return void 0;
24724
25443
  const seen = this.glances.get(handle);
24725
- const fresh2 = seen && seen.changes === this.changes && this.recent(seen, GLANCE_LOOKUPS);
24726
- return fresh2 ? seen.file : void 0;
25444
+ const fresh3 = seen && seen.changes === this.changes && this.recent(seen, GLANCE_LOOKUPS);
25445
+ return fresh3 ? seen.file : void 0;
24727
25446
  }
24728
25447
  async glance(handle) {
24729
25448
  const at = performance.now();
@@ -24913,13 +25632,13 @@ var OpfsFs = class {
24913
25632
  async keptFor(path) {
24914
25633
  const at = normalizePath(path);
24915
25634
  const cached = this.files.get(at);
24916
- const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
24917
- const [target, existed] = fresh2 ? [at, true] : await this.writeTarget(path);
25635
+ const fresh3 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
25636
+ const [target, existed] = fresh3 ? [at, true] : await this.writeTarget(path);
24918
25637
  if (!existed) return [void 0, target, false];
24919
25638
  this.refuse(target);
24920
25639
  const [entry] = await this.meta.get([target]);
24921
25640
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
24922
- const kept = fresh2 ? cached : this.files.get(target);
25641
+ const kept = fresh3 ? cached : this.files.get(target);
24923
25642
  const usable2 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
24924
25643
  return [usable2 ? kept : void 0, target, true];
24925
25644
  }
@@ -26634,14 +27353,14 @@ var RESTORE_AGAIN_MS = 3e4;
26634
27353
  function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26635
27354
  const handles = /* @__PURE__ */ new Map();
26636
27355
  let nextFh = 0;
26637
- const fresh2 = (remote) => {
27356
+ const fresh3 = (remote) => {
26638
27357
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
26639
27358
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
26640
27359
  };
26641
27360
  const live = (fh) => {
26642
27361
  const remote = handles.get(fh);
26643
27362
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26644
- return fresh2(remote);
27363
+ return fresh3(remote);
26645
27364
  };
26646
27365
  const call = (op, body = {}) => current().call(op, body);
26647
27366
  const same2 = (was, mode, seen) => seen?.mode === (mode | 128) && (was.ino !== void 0 && seen.ino !== void 0 ? seen.ino === was.ino : seen.size === was.size && seen.mtime === was.mtime);
@@ -26723,7 +27442,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26723
27442
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26724
27443
  handles.delete(fh);
26725
27444
  try {
26726
- await fresh2(remote).client.call("release", { fh: remote.fh }, true);
27445
+ await fresh3(remote).client.call("release", { fh: remote.fh }, true);
26727
27446
  } finally {
26728
27447
  if (remote.restore) await settle(current(), remote.restore);
26729
27448
  }
@@ -27087,7 +27806,7 @@ var PortTransport = class {
27087
27806
  }).finally(() => this.fail("the folder's watch ended"));
27088
27807
  }
27089
27808
  };
27090
- var errorOf = (err) => ({
27809
+ var errorOf2 = (err) => ({
27091
27810
  code: typeof codeOf7(err) === "string" ? codeOf7(err) : "EIO",
27092
27811
  message: err?.message ?? String(err)
27093
27812
  });
@@ -27137,7 +27856,7 @@ function serveHostfsPort(port, transport) {
27137
27856
  );
27138
27857
  send2({ id: id2, end: true });
27139
27858
  } catch (err) {
27140
- send2({ id: id2, error: errorOf(err) });
27859
+ send2({ id: id2, error: errorOf2(err) });
27141
27860
  } finally {
27142
27861
  watches.delete(id2);
27143
27862
  }
@@ -27165,7 +27884,7 @@ function serveHostfsPort(port, transport) {
27165
27884
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
27166
27885
  send2({ id: id2, result: await answer2(data.name, data.body) });
27167
27886
  } catch (err) {
27168
- send2({ id: id2, error: errorOf(err) });
27887
+ send2({ id: id2, error: errorOf2(err) });
27169
27888
  }
27170
27889
  };
27171
27890
  const stop = () => {
@@ -27798,7 +28517,7 @@ function homeModeOf(mode) {
27798
28517
  }
27799
28518
  return mode;
27800
28519
  }
27801
- function joined3(name2, rows) {
28520
+ function joined4(name2, rows) {
27802
28521
  let changed = false;
27803
28522
  for (const g of rows) {
27804
28523
  if (g.members.includes(name2)) continue;
@@ -27985,7 +28704,7 @@ var UserDb = class {
27985
28704
  await this.provision(found, this.umaskFor(options), shared);
27986
28705
  await this.stampHome(found, shared, homeMode);
27987
28706
  }
27988
- if (joined3(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
28707
+ if (joined4(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
27989
28708
  remember();
27990
28709
  return this.account(found, groups);
27991
28710
  }
@@ -27994,10 +28713,10 @@ var UserDb = class {
27994
28713
  if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
27995
28714
  const uid = freeId(users, groups);
27996
28715
  const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
27997
- joined3(name2, extra);
28716
+ joined4(name2, extra);
27998
28717
  group.push({ name: name2, gid: uid, members: [] });
27999
28718
  passwd.push(row);
28000
- const fresh2 = !await this.fs.exists(home);
28719
+ const fresh3 = !await this.fs.exists(home);
28001
28720
  await this.fs.mkdir(home, { recursive: true });
28002
28721
  try {
28003
28722
  await this.provision(row, this.umaskFor(options), shared);
@@ -28005,7 +28724,7 @@ var UserDb = class {
28005
28724
  await this.fs.mkdir("/etc", { recursive: true });
28006
28725
  await this.commit(formatPasswd(passwd), formatGroup(group));
28007
28726
  } catch (err) {
28008
- if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
28727
+ if (fresh3) await this.fs.rm(home, { recursive: true, force: true });
28009
28728
  throw err;
28010
28729
  }
28011
28730
  remember();
@@ -28383,12 +29102,12 @@ ${lines2}
28383
29102
  const timer = setTimeout(() => control.abort("expired"), timeout);
28384
29103
  const withdraw = () => control.abort("withdrawn");
28385
29104
  requester.addEventListener("abort", withdraw, { once: true });
28386
- const aborted2 = new Promise(
29105
+ const aborted3 = new Promise(
28387
29106
  (resolve2) => control.signal.addEventListener("abort", () => resolve2("aborted"), { once: true })
28388
29107
  );
28389
29108
  try {
28390
29109
  const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
28391
- const answer2 = await Promise.race([answered, aborted2]);
29110
+ const answer2 = await Promise.race([answered, aborted3]);
28392
29111
  if (answer2 === "aborted" || control.signal.aborted) {
28393
29112
  return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
28394
29113
  }
@@ -28514,6 +29233,7 @@ var SUDO_SOURCE = [
28514
29233
  ].join("\n");
28515
29234
 
28516
29235
  // src/launcher.ts
29236
+ var NO_SESSION = { client: -1, uid: -1 };
28517
29237
  var MODULE_URLS = 16;
28518
29238
  function dataModuleUrl(source) {
28519
29239
  const bytes = new TextEncoder().encode(source);
@@ -28772,6 +29492,7 @@ var Launcher = class {
28772
29492
  described = /* @__PURE__ */ new Map();
28773
29493
  creds = /* @__PURE__ */ new Map();
28774
29494
  tags = /* @__PURE__ */ new Map();
29495
+ sessions = /* @__PURE__ */ new Map();
28775
29496
  tagBases = /* @__PURE__ */ new Map();
28776
29497
  links = /* @__PURE__ */ new Map();
28777
29498
  freshModules = new FreshModules();
@@ -28832,6 +29553,7 @@ var Launcher = class {
28832
29553
  sealed;
28833
29554
  scrubs = /* @__PURE__ */ new Map();
28834
29555
  clipboard;
29556
+ embedder;
28835
29557
  rootFs;
28836
29558
  fileWatches;
28837
29559
  nextPid = 1e3;
@@ -28853,14 +29575,14 @@ var Launcher = class {
28853
29575
  void 0,
28854
29576
  (pid) => this.tagsOf(pid)
28855
29577
  );
29578
+ this.embedder = this.embedderBroker(options.embedder);
28856
29579
  this.sealed = new SealedBroker(
28857
29580
  options.sealed,
28858
29581
  this.usersMode,
28859
29582
  void 0,
28860
29583
  (pid) => this.tagsOf(pid)
28861
29584
  );
28862
- this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
28863
- this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
29585
+ this.wireSealed();
28864
29586
  if (this.owners) options.fs.trackOwners?.();
28865
29587
  this.watchers.tap((paths) => {
28866
29588
  this.changes.publish(...paths);
@@ -29921,6 +30643,8 @@ var Launcher = class {
29921
30643
  }
29922
30644
  marks(pid, req) {
29923
30645
  if (req.tags?.length) this.tags.set(pid, req.tags);
30646
+ const session = req.session ?? this.sessions.get(req.ppid ?? -1);
30647
+ if (session) this.sessions.set(pid, session);
29924
30648
  if (req.tags?.length) this.tagBases.set(pid, req.tagBase ?? req.tags.length);
29925
30649
  if (req.links !== void 0) this.links.set(pid, req.links);
29926
30650
  }
@@ -30000,6 +30724,7 @@ var Launcher = class {
30000
30724
  sealed: this.sealed,
30001
30725
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
30002
30726
  clipboard: this.clipboard,
30727
+ embedder: { broker: this.embedder, session: () => this.sessionOf(pid) },
30003
30728
  ...this.dac ? { dac: this.dac } : {},
30004
30729
  program: req.program,
30005
30730
  changes: this.changes.sab,
@@ -30128,6 +30853,7 @@ var Launcher = class {
30128
30853
  this.childTimes.delete(pid);
30129
30854
  this.creds.delete(pid);
30130
30855
  this.tags.delete(pid);
30856
+ this.sessions.delete(pid);
30131
30857
  this.tagBases.delete(pid);
30132
30858
  this.links.delete(pid);
30133
30859
  this.granted.delete(pid);
@@ -30173,8 +30899,8 @@ var Launcher = class {
30173
30899
  this.sampledAt = Math.max(this.sampledAt, now);
30174
30900
  this.load = this.load.map((avg, i) => {
30175
30901
  const window = LOAD_WINDOWS_MS[i];
30176
- const fresh2 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
30177
- return avg * Math.exp(-elapsed / window) + running * fresh2;
30902
+ const fresh3 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
30903
+ return avg * Math.exp(-elapsed / window) + running * fresh3;
30178
30904
  });
30179
30905
  }
30180
30906
  idleSince;
@@ -30404,7 +31130,7 @@ var Launcher = class {
30404
31130
  forker(ppid, parent) {
30405
31131
  const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
30406
31132
  return async (state, given) => {
30407
- const fresh2 = (fds, quietOom) => this.start({
31133
+ const fresh3 = (fds, quietOom) => this.start({
30408
31134
  ...image,
30409
31135
  quietOom,
30410
31136
  cwd: state.cwd ?? parent.cwd,
@@ -30420,7 +31146,7 @@ var Launcher = class {
30420
31146
  tagBase: this.tagBases.get(ppid),
30421
31147
  links: this.links.get(ppid)
30422
31148
  });
30423
- return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh2));
31149
+ return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh3));
30424
31150
  };
30425
31151
  }
30426
31152
  keySignal(tty, fallback, sig, send2) {
@@ -30456,6 +31182,25 @@ var Launcher = class {
30456
31182
  credOf(pid) {
30457
31183
  return this.creds.get(pid)?.() ?? copyCred(ROOT);
30458
31184
  }
31185
+ wireSealed() {
31186
+ this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
31187
+ this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
31188
+ }
31189
+ embedderBroker(hook) {
31190
+ const broker = new EmbedderBroker(
31191
+ this.usersMode,
31192
+ {
31193
+ groupId: async (name2) => (await this.users.groups()).find((g) => g.name === name2)?.gid,
31194
+ userId: async (name2) => (await this.users.lookup(name2))?.uid
31195
+ },
31196
+ (pid) => this.tagsOf(pid)
31197
+ );
31198
+ broker.register(0, hook);
31199
+ return broker;
31200
+ }
31201
+ sessionOf(pid) {
31202
+ return this.sessions.get(pid) ?? NO_SESSION;
31203
+ }
30459
31204
  tagOf(pid) {
30460
31205
  return this.tags.get(pid)?.[0];
30461
31206
  }
@@ -30727,8 +31472,8 @@ var Launcher = class {
30727
31472
  for (const [path, mode] of layout) {
30728
31473
  if (isProtected(this.prefixes, normalizePath(path))) continue;
30729
31474
  const real = await this.realLayoutPath(path);
30730
- const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
30731
- if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
31475
+ const fresh3 = real !== void 0 && !await this.base.hasOwner?.(real);
31476
+ if (fresh3) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
30732
31477
  }
30733
31478
  }
30734
31479
  async realLayoutPath(given) {
@@ -30901,9 +31646,10 @@ var Launcher = class {
30901
31646
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
30902
31647
  return found;
30903
31648
  }
30904
- identity(account, given) {
31649
+ identity(account, given, session = 0) {
30905
31650
  return {
30906
31651
  ...given,
31652
+ session: { client: session, uid: account.uid },
30907
31653
  cred: credOf(account),
30908
31654
  ...account.umask !== void 0 ? { umask: account.umask } : {}
30909
31655
  };
@@ -31090,7 +31836,7 @@ var Launcher = class {
31090
31836
  cwd,
31091
31837
  fds,
31092
31838
  report,
31093
- ...this.identity(account, tagged)
31839
+ ...this.identity(account, tagged, options.session)
31094
31840
  });
31095
31841
  leader = handle.pid;
31096
31842
  void handle.exited.then(() => tty.hangup());
@@ -31147,7 +31893,7 @@ var Launcher = class {
31147
31893
  fds,
31148
31894
  report,
31149
31895
  ...group,
31150
- ...this.identity(account, tagged)
31896
+ ...this.identity(account, tagged, options.session)
31151
31897
  });
31152
31898
  options.onStarted?.(handle.pid);
31153
31899
  const status = await handle.exited;
@@ -31590,7 +32336,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS3 } = {})
31590
32336
  }
31591
32337
 
31592
32338
  // src/client/protocol.ts
31593
- var PROTOCOL = [1, 12];
32339
+ var PROTOCOL = [1, 13];
31594
32340
  var FS_METHODS = [
31595
32341
  "readFile",
31596
32342
  "writeFile",
@@ -31757,14 +32503,15 @@ async function runAs(l, self, requested) {
31757
32503
  function clientCred(self) {
31758
32504
  return self ? credOf(self) : copyCred(ROOT);
31759
32505
  }
31760
- async function runTagged(l, self, options, tag) {
32506
+ async function runTagged(l, self, options, tag, session = -1) {
31761
32507
  tagOption(options.tag);
31762
32508
  const tags = withTag(withTag(void 0, tag), options.tag);
31763
32509
  return {
31764
32510
  user: await runAs(l, self, options.user),
31765
32511
  tag,
31766
32512
  tags,
31767
- tagBase: tag === void 0 ? 0 : 1
32513
+ tagBase: tag === void 0 ? 0 : 1,
32514
+ session
31768
32515
  };
31769
32516
  }
31770
32517
  function processes(l, self) {
@@ -31845,10 +32592,12 @@ function facadeRegistry(send2, gone) {
31845
32592
  }
31846
32593
  };
31847
32594
  }
31848
- function hostRegistries(send2, gone) {
32595
+ function hostRegistries(send2, gone, port) {
31849
32596
  const cdps = cdpRegistry(send2, gone);
31850
32597
  const facades = facadeRegistry(send2, gone);
32598
+ const requests = requestBinding(send2, port);
31851
32599
  return {
32600
+ requests,
31852
32601
  answer(l, req) {
31853
32602
  if (req.op === "serve-cdp") return cdps.serve(l, req);
31854
32603
  if (req.op === "unserve-cdp") return cdps.drop(req.registration);
@@ -31856,6 +32605,7 @@ function hostRegistries(send2, gone) {
31856
32605
  return facades.drop(req.registration);
31857
32606
  },
31858
32607
  close() {
32608
+ requests.close();
31859
32609
  cdps.close();
31860
32610
  facades.close();
31861
32611
  }
@@ -31890,6 +32640,28 @@ var TRAITS_WATCH = -1;
31890
32640
  function traitsWatch(launcher, transport, send2) {
31891
32641
  return launcher.onTraits(() => send2({ traits: transport.traits }));
31892
32642
  }
32643
+ function requestBinding(send2, port) {
32644
+ const asker = embedderAsker({ postMessage: (m, t) => send2(m, t) });
32645
+ let unbind;
32646
+ port.addEventListener("message", (event) => {
32647
+ const reply = embedderReplyOf(event.data);
32648
+ if (reply) asker.answer(reply);
32649
+ });
32650
+ return {
32651
+ async accept(host, hello) {
32652
+ if (!hello.requests) return void 0;
32653
+ if (!host.attachRequests || host.session === void 0)
32654
+ return "EPERM: this port may not answer requests";
32655
+ const launcher = await host.launcher();
32656
+ unbind = launcher.embedder.register(
32657
+ host.session,
32658
+ asker.hookFor(host.session, hello.requests)
32659
+ );
32660
+ return void 0;
32661
+ },
32662
+ close: () => unbind?.()
32663
+ };
32664
+ }
31893
32665
  function serveClient(port, host) {
31894
32666
  let state = "new";
31895
32667
  let self;
@@ -31904,7 +32676,7 @@ function serveClient(port, host) {
31904
32676
  if (state !== "closed") port.postMessage(message3, transfer);
31905
32677
  };
31906
32678
  const reply = (id2, body, transfer) => send2({ id: id2, ...body }, transfer);
31907
- const hosts = hostRegistries(send2, ended.promise);
32679
+ const hosts = hostRegistries(send2, ended.promise, port);
31908
32680
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
31909
32681
  if (state === "closed") return;
31910
32682
  send2({ bye });
@@ -31921,7 +32693,7 @@ function serveClient(port, host) {
31921
32693
  ended.resolve();
31922
32694
  }
31923
32695
  async function hello(message3) {
31924
- const refused3 = versionError(message3.protocol);
32696
+ const refused3 = versionError(message3.protocol) ?? await hosts.requests.accept(host, message3);
31925
32697
  if (refused3) {
31926
32698
  send2({ hello: { protocol: PROTOCOL, error: refused3 } });
31927
32699
  detach();
@@ -31946,7 +32718,7 @@ function serveClient(port, host) {
31946
32718
  async function terminal(req, l) {
31947
32719
  const session = await l.openTerminal(req.argv, {
31948
32720
  ...req.options,
31949
- ...await runTagged(l, self, req.options, host.tag),
32721
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31950
32722
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
31951
32723
  });
31952
32724
  if (state === "closed") {
@@ -31969,7 +32741,7 @@ function serveClient(port, host) {
31969
32741
  let leader;
31970
32742
  const result = await l.run(req.argv, {
31971
32743
  ...req.options,
31972
- ...await runTagged(l, self, req.options, host.tag),
32744
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31973
32745
  collect: false,
31974
32746
  onStdout: out(1),
31975
32747
  onStderr: out(2),
@@ -32149,11 +32921,11 @@ function guarded2(check, signal) {
32149
32921
  signal.throwIfAborted();
32150
32922
  let stop = () => {
32151
32923
  };
32152
- const aborted2 = new Promise((_, reject) => {
32924
+ const aborted3 = new Promise((_, reject) => {
32153
32925
  stop = () => reject(signal.reason);
32154
32926
  signal.addEventListener("abort", stop, { once: true });
32155
32927
  });
32156
- return Promise.race([check, aborted2]).finally(() => signal.removeEventListener("abort", stop));
32928
+ return Promise.race([check, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32157
32929
  }
32158
32930
  var loopbackStore = new AsyncLocalStorage();
32159
32931
  var subscribed = false;
@@ -32268,7 +33040,7 @@ function valid(name2) {
32268
33040
  }
32269
33041
  }
32270
33042
  var CHUNK4 = 1024 * 1024;
32271
- function bytesOf2(data) {
33043
+ function bytesOf3(data) {
32272
33044
  if (typeof data === "string") return new TextEncoder().encode(data);
32273
33045
  if (data instanceof Blob) return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
32274
33046
  if (ArrayBuffer.isView(data)) {
@@ -32348,7 +33120,7 @@ var MemoryFile = class {
32348
33120
  const next3 = keepExistingData ? new Chunks([...this.data.blobs], this.data.size) : new Chunks();
32349
33121
  let position = 0;
32350
33122
  const put = async (data) => {
32351
- const bytes = await bytesOf2(data);
33123
+ const bytes = await bytesOf3(data);
32352
33124
  await next3.write(position, bytes);
32353
33125
  position += bytes.length;
32354
33126
  };
@@ -32480,11 +33252,11 @@ function abortable(work, signal) {
32480
33252
  if (signal.aborted) return Promise.reject(signal.reason);
32481
33253
  let stop = () => {
32482
33254
  };
32483
- const aborted2 = new Promise((_, reject) => {
33255
+ const aborted3 = new Promise((_, reject) => {
32484
33256
  stop = () => reject(signal.reason);
32485
33257
  signal.addEventListener("abort", stop, { once: true });
32486
33258
  });
32487
- return Promise.race([work, aborted2]).finally(() => signal.removeEventListener("abort", stop));
33259
+ return Promise.race([work, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32488
33260
  }
32489
33261
  function probe(secure2, host, port, ms, signal) {
32490
33262
  return new Promise((resolve2) => {
@@ -32843,10 +33615,10 @@ var ByteSource = class {
32843
33615
  async more() {
32844
33616
  const { done: done4, value: value2 } = await this.reader.read();
32845
33617
  if (done4) return false;
32846
- const joined4 = new Uint8Array(this.buffered.length + value2.length);
32847
- joined4.set(this.buffered);
32848
- joined4.set(value2, this.buffered.length);
32849
- this.buffered = joined4;
33618
+ const joined5 = new Uint8Array(this.buffered.length + value2.length);
33619
+ joined5.set(this.buffered);
33620
+ joined5.set(value2, this.buffered.length);
33621
+ this.buffered = joined5;
32850
33622
  return true;
32851
33623
  }
32852
33624
  async until(marker) {
@@ -33013,7 +33785,7 @@ var KernelCallError = class extends Error {
33013
33785
  }
33014
33786
  };
33015
33787
  var encoder12 = new TextEncoder();
33016
- var bytesOf3 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
33788
+ var bytesOf4 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
33017
33789
  var UNAVAILABLE = {
33018
33790
  manualRedirects: false,
33019
33791
  encodedBodies: false,
@@ -33097,7 +33869,9 @@ var Channel = class {
33097
33869
  call(call) {
33098
33870
  return this.request(call).done;
33099
33871
  }
33872
+ requests;
33100
33873
  receive(data) {
33874
+ if (this.requests?.handle(data)) return;
33101
33875
  if (data.traits && data.id === void 0) {
33102
33876
  if (this.remote) this.remote.traits = data.traits;
33103
33877
  for (const listener of [...this.traitsListeners]) listener(data.traits);
@@ -33220,7 +33994,7 @@ async function spawnOn(channel, argv, opts) {
33220
33994
  });
33221
33995
  const started = Promise.withResolvers();
33222
33996
  let endedBy;
33223
- const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf3(stdin) } : {} };
33997
+ const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf4(stdin) } : {} };
33224
33998
  const { done: done4 } = channel.request(
33225
33999
  { op: "spawn", argv, options },
33226
34000
  {
@@ -33303,7 +34077,7 @@ async function terminalOn(channel, argv, opts) {
33303
34077
  listener = next3;
33304
34078
  if (next3) for (const bytes of backlog.splice(0)) next3(bytes);
33305
34079
  },
33306
- write: (data) => send2({ action: "write", bytes: bytesOf3(data) }),
34080
+ write: (data) => send2({ action: "write", bytes: bytesOf4(data) }),
33307
34081
  resize: (cols, rows) => send2({ action: "resize", cols, rows }),
33308
34082
  signal: (name2) => send2({ action: "signal", signal: name2 }),
33309
34083
  close: () => send2({ action: "close" })
@@ -33365,8 +34139,10 @@ function since(channel, minor, what, run2) {
33365
34139
  }
33366
34140
  return run2();
33367
34141
  }
33368
- async function greet(channel, lock, timeoutMs) {
33369
- channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
34142
+ async function greet(channel, lock, timeoutMs, requests) {
34143
+ channel.post({
34144
+ hello: { protocol: PROTOCOL, ...lock ? { lock } : {}, ...requests ? { requests } : {} }
34145
+ });
33370
34146
  const timer = setTimeout(
33371
34147
  () => channel.end(
33372
34148
  new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
@@ -33391,7 +34167,14 @@ async function attachKernel(port, options = {}) {
33391
34167
  const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
33392
34168
  const own = await holdLock(locks);
33393
34169
  const channel = new Channel(port, own.release);
33394
- const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
34170
+ const requests = options.requests;
34171
+ if (requests) channel.requests = embedderAnswerer(port, () => requests);
34172
+ const hello = await greet(
34173
+ channel,
34174
+ own.name,
34175
+ options.timeoutMs ?? 1e4,
34176
+ requests ? kindsOf(requests) : void 0
34177
+ );
33395
34178
  channel.protocol = hello.protocol;
33396
34179
  if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
33397
34180
  const transport = new RemoteTransport(
@@ -33605,6 +34388,31 @@ function cacheDirOf(dir) {
33605
34388
  }
33606
34389
  return dir;
33607
34390
  }
34391
+ var nodeSessions = 0;
34392
+ function nodeClient(launcher, clients, { user, tag, requests }) {
34393
+ tagOption(tag);
34394
+ const { port1, port2 } = new MessageChannel2();
34395
+ const session = ++nodeSessions;
34396
+ const served2 = serveClient(port1, {
34397
+ launcher: async () => launcher,
34398
+ signal: signalNumber,
34399
+ session,
34400
+ ...requests === "attach" ? { attachRequests: true } : {},
34401
+ ...user !== void 0 ? { user } : {},
34402
+ ...tag !== void 0 ? { tag } : {}
34403
+ });
34404
+ clients.add(served2);
34405
+ const unbind = requests && requests !== "attach" ? launcher.embedder.register(session, checkedRequests(requests)) : void 0;
34406
+ void served2.closed.then(() => {
34407
+ clients.delete(served2);
34408
+ unbind?.();
34409
+ });
34410
+ return port2;
34411
+ }
34412
+ function checkedRequests(hook) {
34413
+ const kinds = kindsOf(hook);
34414
+ return { kinds, handle: (request) => hook.handle(request) };
34415
+ }
33608
34416
  async function createNodeKernel(options = {}) {
33609
34417
  const root = options.root ?? memoryRoot();
33610
34418
  const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
@@ -33645,6 +34453,7 @@ async function createNodeKernel(options = {}) {
33645
34453
  ...options.protect ? { protect: options.protect } : {},
33646
34454
  ...options.sealed ? { sealed: options.sealed } : {},
33647
34455
  ...options.clipboard ? { clipboard: options.clipboard } : {},
34456
+ ...options.requests ? { embedder: checkedRequests(options.requests) } : {},
33648
34457
  ...options.approve ? { approve: options.approve } : {},
33649
34458
  ...options.wasmMemories ? { wasmMemories: options.wasmMemories } : {},
33650
34459
  cron: { lock: cronLock(root), ...options.cron?.clock ? { clock: options.cron.clock } : {} }
@@ -33735,22 +34544,13 @@ async function createNodeKernel(options = {}) {
33735
34544
  addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
33736
34545
  removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
33737
34546
  },
33738
- async connect({ user, tag } = {}) {
34547
+ async connect(connectOptions = {}) {
33739
34548
  if (terminated) throw new Error(TERMINATED);
33740
- tagOption(tag);
34549
+ const { user } = connectOptions;
33741
34550
  if (user !== void 0 && !await guard2(() => launcher.users.lookup(user))) {
33742
34551
  throw fsError("ENOENT", `no user ${String(user)}`);
33743
34552
  }
33744
- const { port1, port2 } = new MessageChannel2();
33745
- const served2 = serveClient(port1, {
33746
- launcher: async () => launcher,
33747
- signal: signalNumber,
33748
- ...user !== void 0 ? { user } : {},
33749
- ...tag !== void 0 ? { tag } : {}
33750
- });
33751
- clients.add(served2);
33752
- void served2.closed.then(() => clients.delete(served2));
33753
- return port2;
34553
+ return nodeClient(launcher, clients, connectOptions);
33754
34554
  },
33755
34555
  terminate() {
33756
34556
  terminated = true;