@ai-ecoverse/slicc-kernel 1.152.1 → 1.153.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.1" : "0.0.0-dev";
3565
+ var KERNEL_VERSION = true ? "1.153.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
  };
@@ -4242,6 +4439,7 @@ var PathMarks = class {
4242
4439
  floor = 0;
4243
4440
  touched = /* @__PURE__ */ new Map();
4244
4441
  flying = /* @__PURE__ */ new Map();
4442
+ waiting = /* @__PURE__ */ new Set();
4245
4443
  generation() {
4246
4444
  return this.gen;
4247
4445
  }
@@ -4274,10 +4472,22 @@ var PathMarks = class {
4274
4472
  const n = (this.flying.get(p) ?? 1) - 1;
4275
4473
  if (n > 0) this.flying.set(p, n);
4276
4474
  else this.flying.delete(p);
4475
+ for (const wake of [...this.waiting]) wake();
4277
4476
  this.touch(p);
4278
4477
  }
4279
4478
  }
4280
4479
  }
4480
+ async landed(path) {
4481
+ if (!this.inFlight(path)) return;
4482
+ await new Promise((resolve2) => {
4483
+ const wake = () => {
4484
+ if (this.inFlight(path)) return;
4485
+ this.waiting.delete(wake);
4486
+ resolve2();
4487
+ };
4488
+ this.waiting.add(wake);
4489
+ });
4490
+ }
4281
4491
  inFlight(path) {
4282
4492
  for (let p = path; this.flying.size > 0; p = parentOf2(p)) {
4283
4493
  if (this.flying.has(p)) return true;
@@ -4468,6 +4678,9 @@ var UnlinkedKeeper = class {
4468
4678
  changedSince(path, gen) {
4469
4679
  return this.marks.changedSince(path, gen);
4470
4680
  }
4681
+ landed(path) {
4682
+ return this.marks.landed(path);
4683
+ }
4471
4684
  inFlight(path) {
4472
4685
  return this.marks.inFlight(path);
4473
4686
  }
@@ -4503,15 +4716,15 @@ var UnlinkedKeeper = class {
4503
4716
  const releases = [];
4504
4717
  const take = async () => {
4505
4718
  for (; ; ) {
4506
- const fresh2 = [...this.heldWithin(target).keys()].filter((path) => !paths.includes(path));
4507
- if (fresh2.length === 0) return;
4508
- fresh2.sort();
4509
- 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])) {
4510
4723
  for (const release of releases.splice(0)) release();
4511
- fresh2.push(...paths.splice(0));
4512
- fresh2.sort();
4724
+ fresh3.push(...paths.splice(0));
4725
+ fresh3.sort();
4513
4726
  }
4514
- for (const path of fresh2) {
4727
+ for (const path of fresh3) {
4515
4728
  paths.push(path);
4516
4729
  releases.push(await this.gate.acquire(path));
4517
4730
  }
@@ -4846,10 +5059,10 @@ var CronService = class {
4846
5059
  const text = JSON.stringify(stamp);
4847
5060
  const old = await fs.readFile(STAMP_PATH).catch(() => void 0);
4848
5061
  if (old === text || old === void 0 && text === EMPTY_STAMP) return;
4849
- const fresh2 = `${STAMP_PATH}.new`;
5062
+ const fresh3 = `${STAMP_PATH}.new`;
4850
5063
  const owner = owners ? { uid: 0, gid: 0, mode: 384 } : void 0;
4851
- await fs.writeFile(fresh2, text, owner);
4852
- await fs.rename(fresh2, STAMP_PATH);
5064
+ await fs.writeFile(fresh3, text, owner);
5065
+ await fs.rename(fresh3, STAMP_PATH);
4853
5066
  }
4854
5067
  async load() {
4855
5068
  const { fs } = this.host;
@@ -6285,13 +6498,13 @@ function materializer(fs, listedAbsent) {
6285
6498
  };
6286
6499
  }
6287
6500
  function withEtcFiles(fs, files) {
6288
- const bytesOf4 = (path) => {
6501
+ const bytesOf5 = (path) => {
6289
6502
  const text = files.get(path)?.();
6290
6503
  return text === void 0 ? void 0 : new TextEncoder().encode(text);
6291
6504
  };
6292
6505
  const absent2 = (path) => fs.lstat(path).then(() => false, missing2);
6293
6506
  const follow2 = linkFollower(fs, files);
6294
- 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;
6295
6508
  const fallback = (path, make) => async (err) => {
6296
6509
  const at = missing2(err) ? await follow2(path) : void 0;
6297
6510
  const bytes = at === void 0 ? void 0 : await listedAbsent(at);
@@ -6364,10 +6577,10 @@ var FreshModules = class {
6364
6577
  this.fresh.set(key, { umask: mask, pid, added: 0, setup: mask !== DEFAULT_UMASK });
6365
6578
  }
6366
6579
  forget(pid) {
6367
- 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);
6368
6581
  }
6369
- drop(fresh2) {
6370
- 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);
6371
6584
  }
6372
6585
  moved(from, to) {
6373
6586
  const source = normalizePath(from);
@@ -6376,10 +6589,10 @@ var FreshModules = class {
6376
6589
  for (const key of [...this.fresh.keys()]) {
6377
6590
  if (key === target || within3(key, target)) this.fresh.delete(key);
6378
6591
  }
6379
- for (const [key, fresh2] of [...this.fresh]) {
6592
+ for (const [key, fresh3] of [...this.fresh]) {
6380
6593
  if (key !== source && !within3(key, source)) continue;
6381
6594
  this.fresh.delete(key);
6382
- this.fresh.set(target + key.slice(source.length), fresh2);
6595
+ this.fresh.set(target + key.slice(source.length), fresh3);
6383
6596
  }
6384
6597
  }
6385
6598
  wrap(fs) {
@@ -6389,15 +6602,15 @@ var FreshModules = class {
6389
6602
  };
6390
6603
  const decide = async (given, known) => {
6391
6604
  const path = normalizePath(given);
6392
- const fresh2 = this.fresh.get(path);
6393
- if (!fresh2) return;
6605
+ const fresh3 = this.fresh.get(path);
6606
+ if (!fresh3) return;
6394
6607
  const bytes = known && known.length >= MAGIC.length ? known : await head(path);
6395
- const want = isModule(bytes) ? 73 & ~fresh2.umask : 0;
6396
- fresh2.setup = false;
6397
- if (want === fresh2.added) return;
6608
+ const want = isModule(bytes) ? 73 & ~fresh3.umask : 0;
6609
+ fresh3.setup = false;
6610
+ if (want === fresh3.added) return;
6398
6611
  const mode = (await fs.stat(path)).mode & 4095;
6399
- await fs.chmod(path, mode & ~fresh2.added | want);
6400
- fresh2.added = want;
6612
+ await fs.chmod(path, mode & ~fresh3.added | want);
6613
+ fresh3.added = want;
6401
6614
  };
6402
6615
  const overrides = {
6403
6616
  writeFile: async (path, content, owner) => {
@@ -6410,9 +6623,9 @@ var FreshModules = class {
6410
6623
  }
6411
6624
  },
6412
6625
  chmod: async (path, mode) => {
6413
- const fresh2 = this.fresh.get(normalizePath(path));
6414
- if (fresh2?.setup && (mode & 4095) === (438 & ~fresh2.umask)) fresh2.setup = false;
6415
- 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);
6416
6629
  await fs.chmod(path, mode);
6417
6630
  },
6418
6631
  rm: async (path, options) => {
@@ -6429,8 +6642,8 @@ var FreshModules = class {
6429
6642
  link: async (from, to, follow2) => {
6430
6643
  await fs.link(from, to, follow2);
6431
6644
  const source = follow2 && fs.realpath ? await fs.realpath(from, true).catch(() => from) : from;
6432
- const fresh2 = this.fresh.get(normalizePath(source));
6433
- 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);
6434
6647
  }
6435
6648
  };
6436
6649
  const ranged2 = rangedOps(fs);
@@ -7134,7 +7347,7 @@ var FsWatchers = class {
7134
7347
  };
7135
7348
  const content = (run2, types) => async (...args) => {
7136
7349
  const at = normalizePath(args[0]);
7137
- const fresh2 = this.covers(at) && !await fs.exists(at).catch(() => true);
7350
+ const fresh3 = this.covers(at) && !await fs.exists(at).catch(() => true);
7138
7351
  let done4 = false;
7139
7352
  try {
7140
7353
  await run2(...args);
@@ -7143,11 +7356,11 @@ var FsWatchers = class {
7143
7356
  const real = await realOf(at);
7144
7357
  const paths = real === at ? [at] : [at, real];
7145
7358
  this.report(paths);
7146
- 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);
7147
7360
  }
7148
7361
  };
7149
- const written2 = (fresh2) => fresh2 ? ["rename", "change"] : ["change"];
7150
- 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"];
7151
7364
  const reshaping = (run2) => async (...args) => {
7152
7365
  this.reshaped();
7153
7366
  try {
@@ -7174,14 +7387,14 @@ var FsWatchers = class {
7174
7387
  if (created4.length > 0 || !await fs.exists(ancestor)) created4.push(ancestor);
7175
7388
  }
7176
7389
  }
7177
- 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);
7178
7391
  let done4 = false;
7179
7392
  try {
7180
7393
  await fs.mkdir(path, options);
7181
7394
  done4 = true;
7182
7395
  } finally {
7183
7396
  this.report([...created4, path]);
7184
- if (done4) this.emit("rename", fresh2 ? [...created4, path] : created4);
7397
+ if (done4) this.emit("rename", fresh3 ? [...created4, path] : created4);
7185
7398
  }
7186
7399
  },
7187
7400
  rm: reshaping(after(fs.rm.bind(fs), (path) => [path], "rename")),
@@ -7637,6 +7850,416 @@ var ClipboardBroker = class {
7637
7850
  }
7638
7851
  };
7639
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
+
7640
8263
  // src/kernel/exec-ticket.ts
7641
8264
  var TICKET_ENV = "SLICC_SEALED_TICKET";
7642
8265
  var TICKET_TTL_MS = 3e4;
@@ -8190,6 +8813,9 @@ var HeldPaths = class extends Map {
8190
8813
  trust(handle, doubt) {
8191
8814
  if (this.doubted.get(handle) === doubt) this.doubted.delete(handle);
8192
8815
  }
8816
+ pathOf(handle) {
8817
+ return this.handles.get(handle);
8818
+ }
8193
8819
  keptFor(handle) {
8194
8820
  return this.keptHandles.get(handle);
8195
8821
  }
@@ -8202,9 +8828,9 @@ var HeldPaths = class extends Map {
8202
8828
  return false;
8203
8829
  }
8204
8830
  keep(path, file) {
8205
- const fresh2 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
8206
- for (const id2 of fresh2) this.keptHandles.set(id2, file);
8207
- 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));
8208
8834
  this.unlinked.set(path, file);
8209
8835
  }
8210
8836
  unkeep(path, file) {
@@ -9400,8 +10026,8 @@ var MountFs = class _MountFs {
9400
10026
  }
9401
10027
  this.writable(mount, real);
9402
10028
  const { uid, gid, mode } = change;
9403
- const fresh2 = change.kind === "new" && { uid, gid, mode };
9404
- 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;
9405
10031
  this.table.forget(mount, real);
9406
10032
  const attrs = Object.entries({ uid: set3.uid, gid: set3.gid, mode: set3.mode });
9407
10033
  await mount.conn.call({
@@ -9673,10 +10299,10 @@ async function runMkdir(entry, path, req) {
9673
10299
  const plain3 = plainDirMode(entry, req.mode);
9674
10300
  const parentsMode = !req.exclusive ? plainParentsMode(entry) : void 0;
9675
10301
  const fixParents = parentsMode !== void 0 ? await missingParents(fs, path, parentsMode) : void 0;
9676
- const fresh2 = plain3 !== void 0 && !await fs.exists(path);
10302
+ const fresh3 = plain3 !== void 0 && !await fs.exists(path);
9677
10303
  await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
9678
10304
  await fixParents?.();
9679
- if (fresh2) await fs.chmod(path, plain3);
10305
+ if (fresh3) await fs.chmod(path, plain3);
9680
10306
  return done;
9681
10307
  }
9682
10308
  async function shownAs(entry, path, st) {
@@ -10073,12 +10699,20 @@ async function linkedElsewhere(fs, path, from) {
10073
10699
  const source = from === void 0 ? void 0 : await fs.lstat(from).catch(() => void 0);
10074
10700
  return !(source && source.ino === st.ino && source.dev === st.dev);
10075
10701
  }
10702
+ async function gatedByHandle(entry, opEntry, path, req, opReq) {
10703
+ const heldAt2 = () => req.handle === void 0 ? void 0 : opEntry.heldAt?.(req.handle);
10704
+ if (req.handle !== void 0) await opEntry.replacements?.landed?.(heldAt2() ?? path);
10705
+ const at = heldAt2() ?? path;
10706
+ const serve = async () => run(opEntry, await keptOr(opEntry, at, req.handle), opReq);
10707
+ return entry.gate ? entry.gate(at, serve) : serve();
10708
+ }
10076
10709
  async function keptOr(entry, path, handle) {
10077
10710
  if (handle !== void 0) {
10078
10711
  const hidden = entry.keptFor?.(handle)?.hidden;
10079
10712
  if (hidden !== void 0) return hidden;
10080
- await bound(entry, path, handle);
10081
- return path;
10713
+ const at = entry.heldAt?.(handle) ?? path;
10714
+ await bound(entry, at, handle);
10715
+ return at;
10082
10716
  }
10083
10717
  const kept = entry.unlinked?.(path);
10084
10718
  return kept && !await entry.fs.exists(path) ? kept.hidden : path;
@@ -10271,8 +10905,7 @@ async function dispatchPath(entry, req) {
10271
10905
  if (entry.revoked?.(resolvedPath))
10272
10906
  return { ok: false, errno: "EIO", message: `${resolvedPath} was unmounted` };
10273
10907
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
10274
- const serve = async () => run(opEntry, await keptOr(opEntry, resolvedPath, req.handle), opReq);
10275
- return await (entry.gate ? entry.gate(resolvedPath, serve) : serve());
10908
+ return await gatedByHandle(entry, opEntry, resolvedPath, req, opReq);
10276
10909
  }
10277
10910
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(opEntry, resolvedPath, opReq);
10278
10911
  const target = resolvedArg2 ?? (TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : resolvedPath);
@@ -10673,9 +11306,9 @@ var Pages = class {
10673
11306
  const page = (start2 + at) / PAGE;
10674
11307
  if (this.pages.has(page)) continue;
10675
11308
  const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
10676
- const fresh2 = this.take(part.length);
10677
- fresh2.set(part);
10678
- this.pages.set(page, fresh2);
11309
+ const fresh3 = this.take(part.length);
11310
+ fresh3.set(part);
11311
+ this.pages.set(page, fresh3);
10679
11312
  }
10680
11313
  }
10681
11314
  read(at, length2) {
@@ -12937,18 +13570,18 @@ var TlsEngine = class {
12937
13570
  chainFlags(store, chain2, hostname) {
12938
13571
  const verify = this.m._tls_chain_verify;
12939
13572
  if (!verify || chain2.length === 0) return void 0;
12940
- const joined4 = new Uint8Array(chain2.reduce((n, c) => n + c.length, 0));
13573
+ const joined5 = new Uint8Array(chain2.reduce((n, c) => n + c.length, 0));
12941
13574
  let at = 0;
12942
13575
  for (const der2 of chain2) {
12943
- joined4.set(der2, at);
13576
+ joined5.set(der2, at);
12944
13577
  at += der2.length;
12945
13578
  }
12946
13579
  const name2 = this.put(new TextEncoder().encode(hostname));
12947
13580
  try {
12948
- return this.withBytes(joined4, (p) => {
13581
+ return this.withBytes(joined5, (p) => {
12949
13582
  const out = this.scratch;
12950
13583
  this.m.HEAPU8.fill(255, out, out + 4);
12951
- const ret = verify(store.ptr, p, joined4.length, name2, out);
13584
+ const ret = verify(store.ptr, p, joined5.length, name2, out);
12952
13585
  const flags2 = new DataView(this.m.HEAPU8.buffer, out, 4).getUint32(0, true);
12953
13586
  if (ret === 0) return flags2 === 0 ? 0 : void 0;
12954
13587
  return ret === 1 && flags2 !== 0 ? flags2 : void 0;
@@ -15682,6 +16315,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15682
16315
  ...file.tty ? { tty: file.tty } : {},
15683
16316
  identity: file.identity ?? file,
15684
16317
  ...file.clipboard ? { clipboard: file.clipboard } : {},
16318
+ ...file.embedder ? { embedder: file.embedder } : {},
15685
16319
  ...file.write ? {
15686
16320
  write: async (bytes, signal) => {
15687
16321
  await turn(signal);
@@ -15738,7 +16372,8 @@ function scrubStdio(fds, scrubber) {
15738
16372
  const stdio = stdioFiles(fds);
15739
16373
  for (const fd of fds.numbers()) {
15740
16374
  const original = fds.get(fd);
15741
- if (!stdio.has(original) && !original.file.tty && !original.file.clipboard) continue;
16375
+ const named = original.file.tty || original.file.clipboard || original.file.embedder;
16376
+ if (!stdio.has(original) && !named) continue;
15742
16377
  const known = wrapped.get(original);
15743
16378
  const flags2 = fds.statusFlags(fd);
15744
16379
  const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
@@ -15751,9 +16386,9 @@ function scrubStdio(fds, scrubber) {
15751
16386
  // src/kernel/net/sealed-scrubs.ts
15752
16387
  function addTo(tree2, host, secrets) {
15753
16388
  const entry = tree2.get(host) ?? { secrets: [], version: 0, scrubber: void 0 };
15754
- const fresh2 = secrets.filter((s) => !entry.secrets.some((e) => e.value === s.value));
15755
- if (fresh2.length === 0) return;
15756
- entry.secrets = [...entry.secrets, ...fresh2];
16389
+ const fresh3 = secrets.filter((s) => !entry.secrets.some((e) => e.value === s.value));
16390
+ if (fresh3.length === 0) return;
16391
+ entry.secrets = [...entry.secrets, ...fresh3];
15757
16392
  entry.version += 1;
15758
16393
  entry.scrubber = void 0;
15759
16394
  tree2.set(host, entry);
@@ -15967,13 +16602,13 @@ function serialized(reply) {
15967
16602
  const text = JSON.stringify(reply);
15968
16603
  plain3 = text === void 0 ? void 0 : JSON.parse(text);
15969
16604
  } catch (err) {
15970
- return JSON.stringify(errorReply("EIO", lineOf2(err)));
16605
+ return JSON.stringify(errorReply("EIO", lineOf3(err)));
15971
16606
  }
15972
16607
  if (!isRecord(plain3)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
15973
16608
  const { v: _, ...rest2 } = plain3;
15974
16609
  return JSON.stringify({ v: 1, ...rest2 });
15975
16610
  }
15976
- function lineOf2(err) {
16611
+ function lineOf3(err) {
15977
16612
  const text = err instanceof Error ? err.message : String(err);
15978
16613
  return (text.split("\n")[0] ?? "").slice(0, 200);
15979
16614
  }
@@ -16024,7 +16659,7 @@ var SealedChannel = class {
16024
16659
  if (signal.aborted) return void 0;
16025
16660
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
16026
16661
  (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
16027
- (err) => errorReply("EIO", lineOf2(err))
16662
+ (err) => errorReply("EIO", lineOf3(err))
16028
16663
  );
16029
16664
  const reply = await Promise.race([asked, stopped]);
16030
16665
  if (!signal.aborted) return reply;
@@ -17141,13 +17776,13 @@ var PgServer = class {
17141
17776
  at += whole;
17142
17777
  }
17143
17778
  this.buffer = data.slice(at);
17144
- const joined4 = new Uint8Array(out.reduce((n, b) => n + b.length, 0));
17779
+ const joined5 = new Uint8Array(out.reduce((n, b) => n + b.length, 0));
17145
17780
  let to = 0;
17146
17781
  for (const b of out) {
17147
- joined4.set(b, to);
17782
+ joined5.set(b, to);
17148
17783
  to += b.length;
17149
17784
  }
17150
- return joined4;
17785
+ return joined5;
17151
17786
  }
17152
17787
  };
17153
17788
  function frame3(type, body) {
@@ -17370,7 +18005,7 @@ function latin12(bytes) {
17370
18005
  for (const b of bytes) out += String.fromCharCode(b);
17371
18006
  return out;
17372
18007
  }
17373
- function bytesOf(text) {
18008
+ function bytesOf2(text) {
17374
18009
  const out = new Uint8Array(text.length);
17375
18010
  for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i);
17376
18011
  return out;
@@ -17489,12 +18124,12 @@ var SessionWriter = class {
17489
18124
  if (this.failed !== void 0) throw this.failed;
17490
18125
  if (this.mode === "pass") return this.conn.write(bytes);
17491
18126
  clearTimeout(this.timer);
17492
- const joined4 = new Uint8Array(this.buffer.length + bytes.length);
17493
- joined4.set(this.buffer);
17494
- joined4.set(bytes, this.buffer.length);
18127
+ const joined5 = new Uint8Array(this.buffer.length + bytes.length);
18128
+ joined5.set(this.buffer);
18129
+ joined5.set(bytes, this.buffer.length);
17495
18130
  this.buffer.fill(0);
17496
- this.buffer = joined4;
17497
- if (this.mode === "sniff") this.mode = modeOf(latin12(joined4.subarray(0, 8))) ?? "sniff";
18131
+ this.buffer = joined5;
18132
+ if (this.mode === "sniff") this.mode = modeOf(latin12(joined5.subarray(0, 8))) ?? "sniff";
17498
18133
  if (this.mode === "pg" && !this.sawPg) {
17499
18134
  this.sawPg = true;
17500
18135
  if (!this.s.verified) void this.proof("pg");
@@ -17522,7 +18157,7 @@ var SessionWriter = class {
17522
18157
  const out = await this.smtp.line(latin12(all.subarray(0, at))).catch(() => ({ line: latin12(all.subarray(0, at)) }));
17523
18158
  all.fill(0);
17524
18159
  if ("values" in out && out.values) this.remember(out.values);
17525
- const line = bytesOf(`${out.line}\r
18160
+ const line = bytesOf2(`${out.line}\r
17526
18161
  `);
17527
18162
  try {
17528
18163
  await writeAll3(this.conn, line);
@@ -17579,7 +18214,7 @@ var SessionWriter = class {
17579
18214
  latin12(all.subarray(0, end)),
17580
18215
  (position, placeholders, head2) => this.ask("http1", position, { placeholders, head: head2 })
17581
18216
  ).catch(() => void 0);
17582
- const head = done4 ? bytesOf(done4.head) : all.subarray(0, end);
18217
+ const head = done4 ? bytesOf2(done4.head) : all.subarray(0, end);
17583
18218
  if (done4) this.remember(done4.values);
17584
18219
  try {
17585
18220
  await writeAll3(this.conn, head);
@@ -17642,7 +18277,7 @@ var ScrubReader = class {
17642
18277
  const before = this.current();
17643
18278
  const out = this.through(got);
17644
18279
  if (before.length === 0 && out !== void 0) return out;
17645
- if (before.length > 0) return joined2(before, out);
18280
+ if (before.length > 0) return joined3(before, out);
17646
18281
  }
17647
18282
  }
17648
18283
  through(got) {
@@ -17658,7 +18293,7 @@ var ScrubReader = class {
17658
18293
  return out.length > 0 ? out : void 0;
17659
18294
  }
17660
18295
  };
17661
- function joined2(a, b) {
18296
+ function joined3(a, b) {
17662
18297
  if (!b) return a;
17663
18298
  const out = new Uint8Array(a.length + b.length);
17664
18299
  out.set(a);
@@ -18566,7 +19201,7 @@ async function connect(req, proc) {
18566
19201
  dialing?.catch(() => void 0);
18567
19202
  throw new KernelError("EINPROGRESS");
18568
19203
  }
18569
- if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
19204
+ if (dialing !== void 0) await interruptible3(dialing, proc.blocking());
18570
19205
  return ok3();
18571
19206
  }
18572
19207
  async function bindUnixDgram(proc, socket, addr) {
@@ -18631,7 +19266,7 @@ async function unixDatagram(req, dgram, proc) {
18631
19266
  function domainOf(socket) {
18632
19267
  return socket instanceof UnixDgram ? "unix" : "inet";
18633
19268
  }
18634
- function interruptible2(dialing, signal) {
19269
+ function interruptible3(dialing, signal) {
18635
19270
  return new Promise((resolve2, reject) => {
18636
19271
  const abort = () => reject(new KernelError("EINTR"));
18637
19272
  signal.addEventListener("abort", abort, { once: true });
@@ -19635,7 +20270,7 @@ var ProcessWatches = class {
19635
20270
  };
19636
20271
 
19637
20272
  // src/kernel/process.ts
19638
- function aborted(signal) {
20273
+ function aborted2(signal) {
19639
20274
  return new Promise((_, reject) => {
19640
20275
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
19641
20276
  });
@@ -19725,6 +20360,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
19725
20360
  "fd-open-sealed",
19726
20361
  "fd-open-clipboard",
19727
20362
  "fd-clipboard-abort",
20363
+ "fd-open-embedder",
19728
20364
  "tty-get",
19729
20365
  "tty-set",
19730
20366
  "tty-winsz",
@@ -20278,6 +20914,7 @@ var WasmProcess = class {
20278
20914
  if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
20279
20915
  if (file.tty) this.checkForeground(file.tty);
20280
20916
  if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
20917
+ if (file.embedder) return this.readEmbedder(file, req, cancelled);
20281
20918
  if (file instanceof EventFd && req.max < EventFd.SIZE) throw new KernelError("EINVAL");
20282
20919
  if (req.max <= 0) return new Uint8Array(0);
20283
20920
  if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
@@ -20298,6 +20935,81 @@ var WasmProcess = class {
20298
20935
  throw err;
20299
20936
  }
20300
20937
  }
20938
+ async readEmbedder(file, req, cancelled) {
20939
+ const channel = file.embedder;
20940
+ if (req.max <= 0) return new Uint8Array(0);
20941
+ channel.caller = await this.embedderCaller();
20942
+ try {
20943
+ return await channel.read(Math.min(req.max, MAX_READ), this.blockingSignal(cancelled));
20944
+ } catch (err) {
20945
+ await this.deviceComplaint(EMBEDDER_PATH, err);
20946
+ throw err;
20947
+ }
20948
+ }
20949
+ async writeEmbedder(file, body, nonblock, cancelled) {
20950
+ const channel = file.embedder;
20951
+ channel.caller = await this.embedderCaller();
20952
+ const write = file.write;
20953
+ if (!pollFile(file).writable && nonblock) throw new KernelError("EAGAIN");
20954
+ try {
20955
+ return await write.call(file, body, this.blockingSignal(cancelled));
20956
+ } catch (err) {
20957
+ await this.deviceComplaint(EMBEDDER_PATH, err);
20958
+ throw err;
20959
+ }
20960
+ }
20961
+ async deviceComplaint(path, err) {
20962
+ const name2 = this.options.clipboardName;
20963
+ if (name2 === void 0 || !(err instanceof KernelError) || err.detail === void 0) return;
20964
+ if (err.code === "EINTR") return;
20965
+ await this.complainLine(`${name2}: ${path}: ${clipboardLine(err.detail)}`);
20966
+ }
20967
+ async complainLine(line) {
20968
+ const stderr = this.fds.has(2) ? this.fds.get(2).file : void 0;
20969
+ if (!stderr?.write || stderr.clipboard || stderr.embedder || stderr.sealed) return;
20970
+ const timeout = new Promise((r) => setTimeout(() => r(0), 1e3));
20971
+ await Promise.race([
20972
+ stderr.write(new TextEncoder().encode(`${line}
20973
+ `), AbortSignal.timeout(1e3)).catch(() => 0),
20974
+ timeout
20975
+ ]);
20976
+ }
20977
+ async embedderCaller() {
20978
+ const shown = await this.options.identity?.();
20979
+ const session = this.options.embedderSession?.() ?? { client: 0, uid: 0 };
20980
+ const name2 = this.options.clipboardName ?? this.options.embedderName ?? "program";
20981
+ return {
20982
+ pid: this.pid,
20983
+ shownPid: shown?.pid ?? this.pid,
20984
+ euid: this.cred.euid,
20985
+ egid: this.cred.egid,
20986
+ groups: [...this.cred.groups],
20987
+ sessionUid: session.uid,
20988
+ client: session.client,
20989
+ tags: this.options.embedder?.tagsOf(this.pid),
20990
+ progress: (line) => void this.complainLine(`${name2}: ${line}`),
20991
+ closing: () => AbortSignal.any([this.interrupt.signal, this.gone.signal])
20992
+ };
20993
+ }
20994
+ async closeFd(fd) {
20995
+ this.options.locks?.closed(this.pid, fd);
20996
+ await this.dropRecordLocks(fd);
20997
+ const closing2 = this.fds.get(fd).file;
20998
+ const channel = closing2.clipboard;
20999
+ if (channel) channel.stamp = await this.clipboardStamp();
21000
+ if (closing2.embedder) closing2.embedder.caller = await this.embedderCaller();
21001
+ await Promise.resolve(this.fds.close(fd)).catch(async (err) => {
21002
+ if (channel) await this.clipboardComplaint(err);
21003
+ if (closing2.embedder) await this.deviceComplaint(EMBEDDER_PATH, err);
21004
+ throw err;
21005
+ });
21006
+ }
21007
+ async openEmbedder(flags2) {
21008
+ const broker = this.options.embedder;
21009
+ if (!broker) throw new KernelError("ENOENT");
21010
+ const file = broker.channel(openAccess(flags2), await this.embedderCaller());
21011
+ return this.fds.install(this.options.outputFilter?.(file) ?? file, 3);
21012
+ }
20301
21013
  async complain(line) {
20302
21014
  const stderr = this.fds.has(2) ? this.fds.get(2).file : void 0;
20303
21015
  if (!stderr?.write || stderr.sealed || stderr.clipboard) return false;
@@ -20328,6 +21040,7 @@ var WasmProcess = class {
20328
21040
  const file = this.fds.get(fd).file;
20329
21041
  if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
20330
21042
  if (!file.write) throw new KernelError("EBADF");
21043
+ if (file.embedder) return this.writeEmbedder(file, body, nonblock, cancelled);
20331
21044
  if (!pollFile(file).writable) {
20332
21045
  if (nonblock) throw new KernelError("EAGAIN");
20333
21046
  return file.write(body, this.blockingSignal(cancelled));
@@ -20375,11 +21088,11 @@ var WasmProcess = class {
20375
21088
  } else if (req.create) {
20376
21089
  await nodes.onEntry(path, async () => {
20377
21090
  const marks = this.options.owners || plain3 !== void 0;
20378
- const fresh2 = marks && !await present3(fs, path, true);
21091
+ const fresh3 = marks && !await present3(fs, path, true);
20379
21092
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
20380
21093
  await this.fds.get(fd).file.stat?.();
20381
- if (fresh2 && plain3 !== void 0) await fs.chmod?.(path, plain3);
20382
- else if (fresh2) {
21094
+ if (fresh3 && plain3 !== void 0) await fs.chmod?.(path, plain3);
21095
+ else if (fresh3) {
20383
21096
  await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
20384
21097
  }
20385
21098
  });
@@ -20597,17 +21310,9 @@ var WasmProcess = class {
20597
21310
  kind: "json",
20598
21311
  json: await this.write(req.fd, req.body, req.nonblock, cancelled)
20599
21312
  };
20600
- case "fd-close": {
20601
- this.options.locks?.closed(this.pid, req.fd);
20602
- await this.dropRecordLocks(req.fd);
20603
- const channel = this.fds.get(req.fd).file.clipboard;
20604
- if (channel) channel.stamp = await this.clipboardStamp();
20605
- await Promise.resolve(this.fds.close(req.fd)).catch(async (err) => {
20606
- if (channel) await this.clipboardComplaint(err);
20607
- throw err;
20608
- });
21313
+ case "fd-close":
21314
+ await this.closeFd(req.fd);
20609
21315
  return { ok: true, kind: "void" };
20610
- }
20611
21316
  case "fd-pipe": {
20612
21317
  const pipe = openPipe();
20613
21318
  const read = this.fds.install(pipe.read, 3);
@@ -20667,6 +21372,8 @@ var WasmProcess = class {
20667
21372
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
20668
21373
  case "fd-open-clipboard":
20669
21374
  return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
21375
+ case "fd-open-embedder":
21376
+ return { ok: true, kind: "json", json: await this.openEmbedder(req.flags) };
20670
21377
  case "fd-clipboard-abort": {
20671
21378
  const file = this.fds.get(req.fd);
20672
21379
  file.file.clipboard?.abort();
@@ -21161,7 +21868,7 @@ var WasmProcess = class {
21161
21868
  else {
21162
21869
  const { source, target, type, flags: flags2, data } = req;
21163
21870
  const signal = this.blockingSignal(cancelled);
21164
- await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted(signal)]);
21871
+ await Promise.race([mount({ source, target, type, flags: flags2, data }, signal), aborted2(signal)]);
21165
21872
  }
21166
21873
  } catch (err) {
21167
21874
  const code = err?.code;
@@ -21171,7 +21878,7 @@ var WasmProcess = class {
21171
21878
  return { ok: true, kind: "void" };
21172
21879
  }
21173
21880
  pause(cancelled) {
21174
- return aborted(this.blockingSignal(cancelled));
21881
+ return aborted2(this.blockingSignal(cancelled));
21175
21882
  }
21176
21883
  alarm;
21177
21884
  dlLog = [];
@@ -21707,6 +22414,7 @@ function holdingToken(opts, holds2, caller) {
21707
22414
  boundIno: holds2.boundIno.bind(holds2),
21708
22415
  trust: holds2.trust.bind(holds2),
21709
22416
  keptFor: (handle) => holds2.keptFor(handle),
22417
+ heldAt: (handle) => holds2.pathOf(handle),
21710
22418
  unlinked: (path) => holds2.keptAt(path),
21711
22419
  gate: (path, op) => opts.unlinkGate && holds2.holding(path) ? opts.unlinkGate.run(holds2.keyOf(path), op) : op(),
21712
22420
  own: (path, op) => holds2.own(path, op),
@@ -21720,6 +22428,15 @@ function linksOf(opts) {
21720
22428
  function clipboardName(opts) {
21721
22429
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
21722
22430
  }
22431
+ function embedderOptions(opts) {
22432
+ if (!opts.embedder) return {};
22433
+ return {
22434
+ embedder: opts.embedder.broker,
22435
+ embedderSession: opts.embedder.session,
22436
+ embedderName: opts.argv0,
22437
+ ...clipboardName(opts)
22438
+ };
22439
+ }
21723
22440
  function processOptions(opts, header5, hooks, created4) {
21724
22441
  return {
21725
22442
  ...inherited(opts),
@@ -21727,6 +22444,7 @@ function processOptions(opts, header5, hooks, created4) {
21727
22444
  ...opts.sealed ? { sealed: opts.sealed } : {},
21728
22445
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
21729
22446
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
22447
+ ...embedderOptions(opts),
21730
22448
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
21731
22449
  ...opts.source ? { source: opts.source } : {},
21732
22450
  noLinks: opts.noLinks,
@@ -24475,9 +25193,9 @@ var OpfsFs = class {
24475
25193
  this.lookups++;
24476
25194
  if (split2(path).length === 0) return this.root;
24477
25195
  const cached = this.files.get(path);
24478
- const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
24479
- if (cached && !fresh2) this.files.delete(path);
24480
- if (cached && fresh2) {
25196
+ const fresh3 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
25197
+ if (cached && !fresh3) this.files.delete(path);
25198
+ if (cached && fresh3) {
24481
25199
  if (this.glanced(cached)) return this.remember(path, cached);
24482
25200
  try {
24483
25201
  await this.glance(cached);
@@ -24695,8 +25413,8 @@ var OpfsFs = class {
24695
25413
  glanced(handle) {
24696
25414
  if (this.exact > 0) return void 0;
24697
25415
  const seen = this.glances.get(handle);
24698
- const fresh2 = seen && seen.changes === this.changes && this.recent(seen, GLANCE_LOOKUPS);
24699
- return fresh2 ? seen.file : void 0;
25416
+ const fresh3 = seen && seen.changes === this.changes && this.recent(seen, GLANCE_LOOKUPS);
25417
+ return fresh3 ? seen.file : void 0;
24700
25418
  }
24701
25419
  async glance(handle) {
24702
25420
  const at = performance.now();
@@ -24886,13 +25604,13 @@ var OpfsFs = class {
24886
25604
  async keptFor(path) {
24887
25605
  const at = normalizePath(path);
24888
25606
  const cached = this.files.get(at);
24889
- const fresh2 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
24890
- const [target, existed] = fresh2 ? [at, true] : await this.writeTarget(path);
25607
+ const fresh3 = cached && performance.now() - (this.found.get(cached) ?? -CHECK_MS) < CHECK_MS;
25608
+ const [target, existed] = fresh3 ? [at, true] : await this.writeTarget(path);
24891
25609
  if (!existed) return [void 0, target, false];
24892
25610
  this.refuse(target);
24893
25611
  const [entry] = await this.meta.get([target]);
24894
25612
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
24895
- const kept = fresh2 ? cached : this.files.get(target);
25613
+ const kept = fresh3 ? cached : this.files.get(target);
24896
25614
  const usable2 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
24897
25615
  return [usable2 ? kept : void 0, target, true];
24898
25616
  }
@@ -26607,14 +27325,14 @@ var RESTORE_AGAIN_MS = 3e4;
26607
27325
  function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26608
27326
  const handles = /* @__PURE__ */ new Map();
26609
27327
  let nextFh = 0;
26610
- const fresh2 = (remote) => {
27328
+ const fresh3 = (remote) => {
26611
27329
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
26612
27330
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
26613
27331
  };
26614
27332
  const live = (fh) => {
26615
27333
  const remote = handles.get(fh);
26616
27334
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26617
- return fresh2(remote);
27335
+ return fresh3(remote);
26618
27336
  };
26619
27337
  const call = (op, body = {}) => current().call(op, body);
26620
27338
  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);
@@ -26696,7 +27414,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26696
27414
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26697
27415
  handles.delete(fh);
26698
27416
  try {
26699
- await fresh2(remote).client.call("release", { fh: remote.fh }, true);
27417
+ await fresh3(remote).client.call("release", { fh: remote.fh }, true);
26700
27418
  } finally {
26701
27419
  if (remote.restore) await settle(current(), remote.restore);
26702
27420
  }
@@ -27060,7 +27778,7 @@ var PortTransport = class {
27060
27778
  }).finally(() => this.fail("the folder's watch ended"));
27061
27779
  }
27062
27780
  };
27063
- var errorOf = (err) => ({
27781
+ var errorOf2 = (err) => ({
27064
27782
  code: typeof codeOf7(err) === "string" ? codeOf7(err) : "EIO",
27065
27783
  message: err?.message ?? String(err)
27066
27784
  });
@@ -27110,7 +27828,7 @@ function serveHostfsPort(port, transport) {
27110
27828
  );
27111
27829
  send2({ id: id2, end: true });
27112
27830
  } catch (err) {
27113
- send2({ id: id2, error: errorOf(err) });
27831
+ send2({ id: id2, error: errorOf2(err) });
27114
27832
  } finally {
27115
27833
  watches.delete(id2);
27116
27834
  }
@@ -27138,7 +27856,7 @@ function serveHostfsPort(port, transport) {
27138
27856
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
27139
27857
  send2({ id: id2, result: await answer2(data.name, data.body) });
27140
27858
  } catch (err) {
27141
- send2({ id: id2, error: errorOf(err) });
27859
+ send2({ id: id2, error: errorOf2(err) });
27142
27860
  }
27143
27861
  };
27144
27862
  const stop = () => {
@@ -27771,7 +28489,7 @@ function homeModeOf(mode) {
27771
28489
  }
27772
28490
  return mode;
27773
28491
  }
27774
- function joined3(name2, rows) {
28492
+ function joined4(name2, rows) {
27775
28493
  let changed = false;
27776
28494
  for (const g of rows) {
27777
28495
  if (g.members.includes(name2)) continue;
@@ -27958,7 +28676,7 @@ var UserDb = class {
27958
28676
  await this.provision(found, this.umaskFor(options), shared);
27959
28677
  await this.stampHome(found, shared, homeMode);
27960
28678
  }
27961
- if (joined3(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
28679
+ if (joined4(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
27962
28680
  remember();
27963
28681
  return this.account(found, groups);
27964
28682
  }
@@ -27967,10 +28685,10 @@ var UserDb = class {
27967
28685
  if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
27968
28686
  const uid = freeId(users, groups);
27969
28687
  const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
27970
- joined3(name2, extra);
28688
+ joined4(name2, extra);
27971
28689
  group.push({ name: name2, gid: uid, members: [] });
27972
28690
  passwd.push(row);
27973
- const fresh2 = !await this.fs.exists(home);
28691
+ const fresh3 = !await this.fs.exists(home);
27974
28692
  await this.fs.mkdir(home, { recursive: true });
27975
28693
  try {
27976
28694
  await this.provision(row, this.umaskFor(options), shared);
@@ -27978,7 +28696,7 @@ var UserDb = class {
27978
28696
  await this.fs.mkdir("/etc", { recursive: true });
27979
28697
  await this.commit(formatPasswd(passwd), formatGroup(group));
27980
28698
  } catch (err) {
27981
- if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
28699
+ if (fresh3) await this.fs.rm(home, { recursive: true, force: true });
27982
28700
  throw err;
27983
28701
  }
27984
28702
  remember();
@@ -28356,12 +29074,12 @@ ${lines2}
28356
29074
  const timer = setTimeout(() => control.abort("expired"), timeout);
28357
29075
  const withdraw = () => control.abort("withdrawn");
28358
29076
  requester.addEventListener("abort", withdraw, { once: true });
28359
- const aborted2 = new Promise(
29077
+ const aborted3 = new Promise(
28360
29078
  (resolve2) => control.signal.addEventListener("abort", () => resolve2("aborted"), { once: true })
28361
29079
  );
28362
29080
  try {
28363
29081
  const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
28364
- const answer2 = await Promise.race([answered, aborted2]);
29082
+ const answer2 = await Promise.race([answered, aborted3]);
28365
29083
  if (answer2 === "aborted" || control.signal.aborted) {
28366
29084
  return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
28367
29085
  }
@@ -28487,6 +29205,7 @@ var SUDO_SOURCE = [
28487
29205
  ].join("\n");
28488
29206
 
28489
29207
  // src/launcher.ts
29208
+ var NO_SESSION = { client: -1, uid: -1 };
28490
29209
  var MODULE_URLS = 16;
28491
29210
  function dataModuleUrl(source) {
28492
29211
  const bytes = new TextEncoder().encode(source);
@@ -28745,6 +29464,7 @@ var Launcher = class {
28745
29464
  described = /* @__PURE__ */ new Map();
28746
29465
  creds = /* @__PURE__ */ new Map();
28747
29466
  tags = /* @__PURE__ */ new Map();
29467
+ sessions = /* @__PURE__ */ new Map();
28748
29468
  tagBases = /* @__PURE__ */ new Map();
28749
29469
  links = /* @__PURE__ */ new Map();
28750
29470
  freshModules = new FreshModules();
@@ -28805,6 +29525,7 @@ var Launcher = class {
28805
29525
  sealed;
28806
29526
  scrubs = /* @__PURE__ */ new Map();
28807
29527
  clipboard;
29528
+ embedder;
28808
29529
  rootFs;
28809
29530
  fileWatches;
28810
29531
  nextPid = 1e3;
@@ -28826,14 +29547,14 @@ var Launcher = class {
28826
29547
  void 0,
28827
29548
  (pid) => this.tagsOf(pid)
28828
29549
  );
29550
+ this.embedder = this.embedderBroker(options.embedder);
28829
29551
  this.sealed = new SealedBroker(
28830
29552
  options.sealed,
28831
29553
  this.usersMode,
28832
29554
  void 0,
28833
29555
  (pid) => this.tagsOf(pid)
28834
29556
  );
28835
- this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
28836
- this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
29557
+ this.wireSealed();
28837
29558
  if (this.owners) options.fs.trackOwners?.();
28838
29559
  this.watchers.tap((paths) => {
28839
29560
  this.changes.publish(...paths);
@@ -29894,6 +30615,8 @@ var Launcher = class {
29894
30615
  }
29895
30616
  marks(pid, req) {
29896
30617
  if (req.tags?.length) this.tags.set(pid, req.tags);
30618
+ const session = req.session ?? this.sessions.get(req.ppid ?? -1);
30619
+ if (session) this.sessions.set(pid, session);
29897
30620
  if (req.tags?.length) this.tagBases.set(pid, req.tagBase ?? req.tags.length);
29898
30621
  if (req.links !== void 0) this.links.set(pid, req.links);
29899
30622
  }
@@ -29973,6 +30696,7 @@ var Launcher = class {
29973
30696
  sealed: this.sealed,
29974
30697
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
29975
30698
  clipboard: this.clipboard,
30699
+ embedder: { broker: this.embedder, session: () => this.sessionOf(pid) },
29976
30700
  ...this.dac ? { dac: this.dac } : {},
29977
30701
  program: req.program,
29978
30702
  changes: this.changes.sab,
@@ -30101,6 +30825,7 @@ var Launcher = class {
30101
30825
  this.childTimes.delete(pid);
30102
30826
  this.creds.delete(pid);
30103
30827
  this.tags.delete(pid);
30828
+ this.sessions.delete(pid);
30104
30829
  this.tagBases.delete(pid);
30105
30830
  this.links.delete(pid);
30106
30831
  this.granted.delete(pid);
@@ -30146,8 +30871,8 @@ var Launcher = class {
30146
30871
  this.sampledAt = Math.max(this.sampledAt, now);
30147
30872
  this.load = this.load.map((avg, i) => {
30148
30873
  const window = LOAD_WINDOWS_MS[i];
30149
- const fresh2 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
30150
- return avg * Math.exp(-elapsed / window) + running * fresh2;
30874
+ const fresh3 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
30875
+ return avg * Math.exp(-elapsed / window) + running * fresh3;
30151
30876
  });
30152
30877
  }
30153
30878
  idleSince;
@@ -30377,7 +31102,7 @@ var Launcher = class {
30377
31102
  forker(ppid, parent) {
30378
31103
  const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
30379
31104
  return async (state, given) => {
30380
- const fresh2 = (fds, quietOom) => this.start({
31105
+ const fresh3 = (fds, quietOom) => this.start({
30381
31106
  ...image,
30382
31107
  quietOom,
30383
31108
  cwd: state.cwd ?? parent.cwd,
@@ -30393,7 +31118,7 @@ var Launcher = class {
30393
31118
  tagBase: this.tagBases.get(ppid),
30394
31119
  links: this.links.get(ppid)
30395
31120
  });
30396
- return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh2));
31121
+ return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh3));
30397
31122
  };
30398
31123
  }
30399
31124
  keySignal(tty, fallback, sig, send2) {
@@ -30429,6 +31154,25 @@ var Launcher = class {
30429
31154
  credOf(pid) {
30430
31155
  return this.creds.get(pid)?.() ?? copyCred(ROOT);
30431
31156
  }
31157
+ wireSealed() {
31158
+ this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
31159
+ this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
31160
+ }
31161
+ embedderBroker(hook) {
31162
+ const broker = new EmbedderBroker(
31163
+ this.usersMode,
31164
+ {
31165
+ groupId: async (name2) => (await this.users.groups()).find((g) => g.name === name2)?.gid,
31166
+ userId: async (name2) => (await this.users.lookup(name2))?.uid
31167
+ },
31168
+ (pid) => this.tagsOf(pid)
31169
+ );
31170
+ broker.register(0, hook);
31171
+ return broker;
31172
+ }
31173
+ sessionOf(pid) {
31174
+ return this.sessions.get(pid) ?? NO_SESSION;
31175
+ }
30432
31176
  tagOf(pid) {
30433
31177
  return this.tags.get(pid)?.[0];
30434
31178
  }
@@ -30700,8 +31444,8 @@ var Launcher = class {
30700
31444
  for (const [path, mode] of layout) {
30701
31445
  if (isProtected(this.prefixes, normalizePath(path))) continue;
30702
31446
  const real = await this.realLayoutPath(path);
30703
- const fresh2 = real !== void 0 && !await this.base.hasOwner?.(real);
30704
- if (fresh2) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
31447
+ const fresh3 = real !== void 0 && !await this.base.hasOwner?.(real);
31448
+ if (fresh3) await this.base.chown?.(real, { uid: 0, gid: 0, mode, follow: false });
30705
31449
  }
30706
31450
  }
30707
31451
  async realLayoutPath(given) {
@@ -30874,9 +31618,10 @@ var Launcher = class {
30874
31618
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
30875
31619
  return found;
30876
31620
  }
30877
- identity(account, given) {
31621
+ identity(account, given, session = 0) {
30878
31622
  return {
30879
31623
  ...given,
31624
+ session: { client: session, uid: account.uid },
30880
31625
  cred: credOf(account),
30881
31626
  ...account.umask !== void 0 ? { umask: account.umask } : {}
30882
31627
  };
@@ -30996,9 +31741,9 @@ var Launcher = class {
30996
31741
  }
30997
31742
  moved(from, to, source) {
30998
31743
  this.renameLog.add(from, to);
31744
+ for (const holds2 of this.held) holds2.renamed(from, to);
30999
31745
  if (source) return;
31000
31746
  for (const nodes of this.openFiles) nodes.renamed(from, to);
31001
- for (const holds2 of this.held) holds2.renamed(from, to);
31002
31747
  this.watchers.moved(from, to);
31003
31748
  }
31004
31749
  processChain(fs, nodes, unlinked) {
@@ -31063,7 +31808,7 @@ var Launcher = class {
31063
31808
  cwd,
31064
31809
  fds,
31065
31810
  report,
31066
- ...this.identity(account, tagged)
31811
+ ...this.identity(account, tagged, options.session)
31067
31812
  });
31068
31813
  leader = handle.pid;
31069
31814
  void handle.exited.then(() => tty.hangup());
@@ -31120,7 +31865,7 @@ var Launcher = class {
31120
31865
  fds,
31121
31866
  report,
31122
31867
  ...group,
31123
- ...this.identity(account, tagged)
31868
+ ...this.identity(account, tagged, options.session)
31124
31869
  });
31125
31870
  options.onStarted?.(handle.pid);
31126
31871
  const status = await handle.exited;
@@ -31563,7 +32308,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS3 } = {})
31563
32308
  }
31564
32309
 
31565
32310
  // src/client/protocol.ts
31566
- var PROTOCOL = [1, 12];
32311
+ var PROTOCOL = [1, 13];
31567
32312
  var FS_METHODS = [
31568
32313
  "readFile",
31569
32314
  "writeFile",
@@ -31730,14 +32475,15 @@ async function runAs(l, self, requested) {
31730
32475
  function clientCred(self) {
31731
32476
  return self ? credOf(self) : copyCred(ROOT);
31732
32477
  }
31733
- async function runTagged(l, self, options, tag) {
32478
+ async function runTagged(l, self, options, tag, session = -1) {
31734
32479
  tagOption(options.tag);
31735
32480
  const tags = withTag(withTag(void 0, tag), options.tag);
31736
32481
  return {
31737
32482
  user: await runAs(l, self, options.user),
31738
32483
  tag,
31739
32484
  tags,
31740
- tagBase: tag === void 0 ? 0 : 1
32485
+ tagBase: tag === void 0 ? 0 : 1,
32486
+ session
31741
32487
  };
31742
32488
  }
31743
32489
  function processes(l, self) {
@@ -31818,10 +32564,12 @@ function facadeRegistry(send2, gone) {
31818
32564
  }
31819
32565
  };
31820
32566
  }
31821
- function hostRegistries(send2, gone) {
32567
+ function hostRegistries(send2, gone, port) {
31822
32568
  const cdps = cdpRegistry(send2, gone);
31823
32569
  const facades = facadeRegistry(send2, gone);
32570
+ const requests = requestBinding(send2, port);
31824
32571
  return {
32572
+ requests,
31825
32573
  answer(l, req) {
31826
32574
  if (req.op === "serve-cdp") return cdps.serve(l, req);
31827
32575
  if (req.op === "unserve-cdp") return cdps.drop(req.registration);
@@ -31829,6 +32577,7 @@ function hostRegistries(send2, gone) {
31829
32577
  return facades.drop(req.registration);
31830
32578
  },
31831
32579
  close() {
32580
+ requests.close();
31832
32581
  cdps.close();
31833
32582
  facades.close();
31834
32583
  }
@@ -31863,6 +32612,28 @@ var TRAITS_WATCH = -1;
31863
32612
  function traitsWatch(launcher, transport, send2) {
31864
32613
  return launcher.onTraits(() => send2({ traits: transport.traits }));
31865
32614
  }
32615
+ function requestBinding(send2, port) {
32616
+ const asker = embedderAsker({ postMessage: (m, t) => send2(m, t) });
32617
+ let unbind;
32618
+ port.addEventListener("message", (event) => {
32619
+ const reply = embedderReplyOf(event.data);
32620
+ if (reply) asker.answer(reply);
32621
+ });
32622
+ return {
32623
+ async accept(host, hello) {
32624
+ if (!hello.requests) return void 0;
32625
+ if (!host.attachRequests || host.session === void 0)
32626
+ return "EPERM: this port may not answer requests";
32627
+ const launcher = await host.launcher();
32628
+ unbind = launcher.embedder.register(
32629
+ host.session,
32630
+ asker.hookFor(host.session, hello.requests)
32631
+ );
32632
+ return void 0;
32633
+ },
32634
+ close: () => unbind?.()
32635
+ };
32636
+ }
31866
32637
  function serveClient(port, host) {
31867
32638
  let state = "new";
31868
32639
  let self;
@@ -31877,7 +32648,7 @@ function serveClient(port, host) {
31877
32648
  if (state !== "closed") port.postMessage(message3, transfer);
31878
32649
  };
31879
32650
  const reply = (id2, body, transfer) => send2({ id: id2, ...body }, transfer);
31880
- const hosts = hostRegistries(send2, ended.promise);
32651
+ const hosts = hostRegistries(send2, ended.promise, port);
31881
32652
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
31882
32653
  if (state === "closed") return;
31883
32654
  send2({ bye });
@@ -31894,7 +32665,7 @@ function serveClient(port, host) {
31894
32665
  ended.resolve();
31895
32666
  }
31896
32667
  async function hello(message3) {
31897
- const refused3 = versionError(message3.protocol);
32668
+ const refused3 = versionError(message3.protocol) ?? await hosts.requests.accept(host, message3);
31898
32669
  if (refused3) {
31899
32670
  send2({ hello: { protocol: PROTOCOL, error: refused3 } });
31900
32671
  detach();
@@ -31919,7 +32690,7 @@ function serveClient(port, host) {
31919
32690
  async function terminal(req, l) {
31920
32691
  const session = await l.openTerminal(req.argv, {
31921
32692
  ...req.options,
31922
- ...await runTagged(l, self, req.options, host.tag),
32693
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31923
32694
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
31924
32695
  });
31925
32696
  if (state === "closed") {
@@ -31942,7 +32713,7 @@ function serveClient(port, host) {
31942
32713
  let leader;
31943
32714
  const result = await l.run(req.argv, {
31944
32715
  ...req.options,
31945
- ...await runTagged(l, self, req.options, host.tag),
32716
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31946
32717
  collect: false,
31947
32718
  onStdout: out(1),
31948
32719
  onStderr: out(2),
@@ -32122,11 +32893,11 @@ function guarded2(check, signal) {
32122
32893
  signal.throwIfAborted();
32123
32894
  let stop = () => {
32124
32895
  };
32125
- const aborted2 = new Promise((_, reject) => {
32896
+ const aborted3 = new Promise((_, reject) => {
32126
32897
  stop = () => reject(signal.reason);
32127
32898
  signal.addEventListener("abort", stop, { once: true });
32128
32899
  });
32129
- return Promise.race([check, aborted2]).finally(() => signal.removeEventListener("abort", stop));
32900
+ return Promise.race([check, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32130
32901
  }
32131
32902
  var loopbackStore = new AsyncLocalStorage();
32132
32903
  var subscribed = false;
@@ -32241,7 +33012,7 @@ function valid(name2) {
32241
33012
  }
32242
33013
  }
32243
33014
  var CHUNK4 = 1024 * 1024;
32244
- function bytesOf2(data) {
33015
+ function bytesOf3(data) {
32245
33016
  if (typeof data === "string") return new TextEncoder().encode(data);
32246
33017
  if (data instanceof Blob) return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
32247
33018
  if (ArrayBuffer.isView(data)) {
@@ -32321,7 +33092,7 @@ var MemoryFile = class {
32321
33092
  const next3 = keepExistingData ? new Chunks([...this.data.blobs], this.data.size) : new Chunks();
32322
33093
  let position = 0;
32323
33094
  const put = async (data) => {
32324
- const bytes = await bytesOf2(data);
33095
+ const bytes = await bytesOf3(data);
32325
33096
  await next3.write(position, bytes);
32326
33097
  position += bytes.length;
32327
33098
  };
@@ -32453,11 +33224,11 @@ function abortable(work, signal) {
32453
33224
  if (signal.aborted) return Promise.reject(signal.reason);
32454
33225
  let stop = () => {
32455
33226
  };
32456
- const aborted2 = new Promise((_, reject) => {
33227
+ const aborted3 = new Promise((_, reject) => {
32457
33228
  stop = () => reject(signal.reason);
32458
33229
  signal.addEventListener("abort", stop, { once: true });
32459
33230
  });
32460
- return Promise.race([work, aborted2]).finally(() => signal.removeEventListener("abort", stop));
33231
+ return Promise.race([work, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32461
33232
  }
32462
33233
  function probe(secure2, host, port, ms, signal) {
32463
33234
  return new Promise((resolve2) => {
@@ -32816,10 +33587,10 @@ var ByteSource = class {
32816
33587
  async more() {
32817
33588
  const { done: done4, value: value2 } = await this.reader.read();
32818
33589
  if (done4) return false;
32819
- const joined4 = new Uint8Array(this.buffered.length + value2.length);
32820
- joined4.set(this.buffered);
32821
- joined4.set(value2, this.buffered.length);
32822
- this.buffered = joined4;
33590
+ const joined5 = new Uint8Array(this.buffered.length + value2.length);
33591
+ joined5.set(this.buffered);
33592
+ joined5.set(value2, this.buffered.length);
33593
+ this.buffered = joined5;
32823
33594
  return true;
32824
33595
  }
32825
33596
  async until(marker) {
@@ -32986,7 +33757,7 @@ var KernelCallError = class extends Error {
32986
33757
  }
32987
33758
  };
32988
33759
  var encoder12 = new TextEncoder();
32989
- var bytesOf3 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
33760
+ var bytesOf4 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
32990
33761
  var UNAVAILABLE = {
32991
33762
  manualRedirects: false,
32992
33763
  encodedBodies: false,
@@ -33070,7 +33841,9 @@ var Channel = class {
33070
33841
  call(call) {
33071
33842
  return this.request(call).done;
33072
33843
  }
33844
+ requests;
33073
33845
  receive(data) {
33846
+ if (this.requests?.handle(data)) return;
33074
33847
  if (data.traits && data.id === void 0) {
33075
33848
  if (this.remote) this.remote.traits = data.traits;
33076
33849
  for (const listener of [...this.traitsListeners]) listener(data.traits);
@@ -33193,7 +33966,7 @@ async function spawnOn(channel, argv, opts) {
33193
33966
  });
33194
33967
  const started = Promise.withResolvers();
33195
33968
  let endedBy;
33196
- const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf3(stdin) } : {} };
33969
+ const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf4(stdin) } : {} };
33197
33970
  const { done: done4 } = channel.request(
33198
33971
  { op: "spawn", argv, options },
33199
33972
  {
@@ -33276,7 +34049,7 @@ async function terminalOn(channel, argv, opts) {
33276
34049
  listener = next3;
33277
34050
  if (next3) for (const bytes of backlog.splice(0)) next3(bytes);
33278
34051
  },
33279
- write: (data) => send2({ action: "write", bytes: bytesOf3(data) }),
34052
+ write: (data) => send2({ action: "write", bytes: bytesOf4(data) }),
33280
34053
  resize: (cols, rows) => send2({ action: "resize", cols, rows }),
33281
34054
  signal: (name2) => send2({ action: "signal", signal: name2 }),
33282
34055
  close: () => send2({ action: "close" })
@@ -33338,8 +34111,10 @@ function since(channel, minor, what, run2) {
33338
34111
  }
33339
34112
  return run2();
33340
34113
  }
33341
- async function greet(channel, lock, timeoutMs) {
33342
- channel.post({ hello: { protocol: PROTOCOL, ...lock ? { lock } : {} } });
34114
+ async function greet(channel, lock, timeoutMs, requests) {
34115
+ channel.post({
34116
+ hello: { protocol: PROTOCOL, ...lock ? { lock } : {}, ...requests ? { requests } : {} }
34117
+ });
33343
34118
  const timer = setTimeout(
33344
34119
  () => channel.end(
33345
34120
  new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
@@ -33364,7 +34139,14 @@ async function attachKernel(port, options = {}) {
33364
34139
  const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
33365
34140
  const own = await holdLock(locks);
33366
34141
  const channel = new Channel(port, own.release);
33367
- const hello = await greet(channel, own.name, options.timeoutMs ?? 1e4);
34142
+ const requests = options.requests;
34143
+ if (requests) channel.requests = embedderAnswerer(port, () => requests);
34144
+ const hello = await greet(
34145
+ channel,
34146
+ own.name,
34147
+ options.timeoutMs ?? 1e4,
34148
+ requests ? kindsOf(requests) : void 0
34149
+ );
33368
34150
  channel.protocol = hello.protocol;
33369
34151
  if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
33370
34152
  const transport = new RemoteTransport(
@@ -33578,6 +34360,31 @@ function cacheDirOf(dir) {
33578
34360
  }
33579
34361
  return dir;
33580
34362
  }
34363
+ var nodeSessions = 0;
34364
+ function nodeClient(launcher, clients, { user, tag, requests }) {
34365
+ tagOption(tag);
34366
+ const { port1, port2 } = new MessageChannel2();
34367
+ const session = ++nodeSessions;
34368
+ const served2 = serveClient(port1, {
34369
+ launcher: async () => launcher,
34370
+ signal: signalNumber,
34371
+ session,
34372
+ ...requests === "attach" ? { attachRequests: true } : {},
34373
+ ...user !== void 0 ? { user } : {},
34374
+ ...tag !== void 0 ? { tag } : {}
34375
+ });
34376
+ clients.add(served2);
34377
+ const unbind = requests && requests !== "attach" ? launcher.embedder.register(session, checkedRequests(requests)) : void 0;
34378
+ void served2.closed.then(() => {
34379
+ clients.delete(served2);
34380
+ unbind?.();
34381
+ });
34382
+ return port2;
34383
+ }
34384
+ function checkedRequests(hook) {
34385
+ const kinds = kindsOf(hook);
34386
+ return { kinds, handle: (request) => hook.handle(request) };
34387
+ }
33581
34388
  async function createNodeKernel(options = {}) {
33582
34389
  const root = options.root ?? memoryRoot();
33583
34390
  const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
@@ -33618,6 +34425,7 @@ async function createNodeKernel(options = {}) {
33618
34425
  ...options.protect ? { protect: options.protect } : {},
33619
34426
  ...options.sealed ? { sealed: options.sealed } : {},
33620
34427
  ...options.clipboard ? { clipboard: options.clipboard } : {},
34428
+ ...options.requests ? { embedder: checkedRequests(options.requests) } : {},
33621
34429
  ...options.approve ? { approve: options.approve } : {},
33622
34430
  ...options.wasmMemories ? { wasmMemories: options.wasmMemories } : {},
33623
34431
  cron: { lock: cronLock(root), ...options.cron?.clock ? { clock: options.cron.clock } : {} }
@@ -33708,22 +34516,13 @@ async function createNodeKernel(options = {}) {
33708
34516
  addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
33709
34517
  removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
33710
34518
  },
33711
- async connect({ user, tag } = {}) {
34519
+ async connect(connectOptions = {}) {
33712
34520
  if (terminated) throw new Error(TERMINATED);
33713
- tagOption(tag);
34521
+ const { user } = connectOptions;
33714
34522
  if (user !== void 0 && !await guard2(() => launcher.users.lookup(user))) {
33715
34523
  throw fsError("ENOENT", `no user ${String(user)}`);
33716
34524
  }
33717
- const { port1, port2 } = new MessageChannel2();
33718
- const served2 = serveClient(port1, {
33719
- launcher: async () => launcher,
33720
- signal: signalNumber,
33721
- ...user !== void 0 ? { user } : {},
33722
- ...tag !== void 0 ? { tag } : {}
33723
- });
33724
- clients.add(served2);
33725
- void served2.closed.then(() => clients.delete(served2));
33726
- return port2;
34525
+ return nodeClient(launcher, clients, connectOptions);
33727
34526
  },
33728
34527
  terminate() {
33729
34528
  terminated = true;