@ai-ecoverse/slicc-kernel 1.152.2 → 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.2" : "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
  };
@@ -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) {
@@ -12963,18 +13570,18 @@ var TlsEngine = class {
12963
13570
  chainFlags(store, chain2, hostname) {
12964
13571
  const verify = this.m._tls_chain_verify;
12965
13572
  if (!verify || chain2.length === 0) return void 0;
12966
- 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));
12967
13574
  let at = 0;
12968
13575
  for (const der2 of chain2) {
12969
- joined4.set(der2, at);
13576
+ joined5.set(der2, at);
12970
13577
  at += der2.length;
12971
13578
  }
12972
13579
  const name2 = this.put(new TextEncoder().encode(hostname));
12973
13580
  try {
12974
- return this.withBytes(joined4, (p) => {
13581
+ return this.withBytes(joined5, (p) => {
12975
13582
  const out = this.scratch;
12976
13583
  this.m.HEAPU8.fill(255, out, out + 4);
12977
- const ret = verify(store.ptr, p, joined4.length, name2, out);
13584
+ const ret = verify(store.ptr, p, joined5.length, name2, out);
12978
13585
  const flags2 = new DataView(this.m.HEAPU8.buffer, out, 4).getUint32(0, true);
12979
13586
  if (ret === 0) return flags2 === 0 ? 0 : void 0;
12980
13587
  return ret === 1 && flags2 !== 0 ? flags2 : void 0;
@@ -15708,6 +16315,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
15708
16315
  ...file.tty ? { tty: file.tty } : {},
15709
16316
  identity: file.identity ?? file,
15710
16317
  ...file.clipboard ? { clipboard: file.clipboard } : {},
16318
+ ...file.embedder ? { embedder: file.embedder } : {},
15711
16319
  ...file.write ? {
15712
16320
  write: async (bytes, signal) => {
15713
16321
  await turn(signal);
@@ -15764,7 +16372,8 @@ function scrubStdio(fds, scrubber) {
15764
16372
  const stdio = stdioFiles(fds);
15765
16373
  for (const fd of fds.numbers()) {
15766
16374
  const original = fds.get(fd);
15767
- 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;
15768
16377
  const known = wrapped.get(original);
15769
16378
  const flags2 = fds.statusFlags(fd);
15770
16379
  const wrapper = known ? known.retain() : scrubbed(original.retain(), scrubber);
@@ -15777,9 +16386,9 @@ function scrubStdio(fds, scrubber) {
15777
16386
  // src/kernel/net/sealed-scrubs.ts
15778
16387
  function addTo(tree2, host, secrets) {
15779
16388
  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];
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];
15783
16392
  entry.version += 1;
15784
16393
  entry.scrubber = void 0;
15785
16394
  tree2.set(host, entry);
@@ -15993,13 +16602,13 @@ function serialized(reply) {
15993
16602
  const text = JSON.stringify(reply);
15994
16603
  plain3 = text === void 0 ? void 0 : JSON.parse(text);
15995
16604
  } catch (err) {
15996
- return JSON.stringify(errorReply("EIO", lineOf2(err)));
16605
+ return JSON.stringify(errorReply("EIO", lineOf3(err)));
15997
16606
  }
15998
16607
  if (!isRecord(plain3)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
15999
16608
  const { v: _, ...rest2 } = plain3;
16000
16609
  return JSON.stringify({ v: 1, ...rest2 });
16001
16610
  }
16002
- function lineOf2(err) {
16611
+ function lineOf3(err) {
16003
16612
  const text = err instanceof Error ? err.message : String(err);
16004
16613
  return (text.split("\n")[0] ?? "").slice(0, 200);
16005
16614
  }
@@ -16050,7 +16659,7 @@ var SealedChannel = class {
16050
16659
  if (signal.aborted) return void 0;
16051
16660
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
16052
16661
  (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
16053
- (err) => errorReply("EIO", lineOf2(err))
16662
+ (err) => errorReply("EIO", lineOf3(err))
16054
16663
  );
16055
16664
  const reply = await Promise.race([asked, stopped]);
16056
16665
  if (!signal.aborted) return reply;
@@ -17167,13 +17776,13 @@ var PgServer = class {
17167
17776
  at += whole;
17168
17777
  }
17169
17778
  this.buffer = data.slice(at);
17170
- 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));
17171
17780
  let to = 0;
17172
17781
  for (const b of out) {
17173
- joined4.set(b, to);
17782
+ joined5.set(b, to);
17174
17783
  to += b.length;
17175
17784
  }
17176
- return joined4;
17785
+ return joined5;
17177
17786
  }
17178
17787
  };
17179
17788
  function frame3(type, body) {
@@ -17396,7 +18005,7 @@ function latin12(bytes) {
17396
18005
  for (const b of bytes) out += String.fromCharCode(b);
17397
18006
  return out;
17398
18007
  }
17399
- function bytesOf(text) {
18008
+ function bytesOf2(text) {
17400
18009
  const out = new Uint8Array(text.length);
17401
18010
  for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i);
17402
18011
  return out;
@@ -17515,12 +18124,12 @@ var SessionWriter = class {
17515
18124
  if (this.failed !== void 0) throw this.failed;
17516
18125
  if (this.mode === "pass") return this.conn.write(bytes);
17517
18126
  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);
18127
+ const joined5 = new Uint8Array(this.buffer.length + bytes.length);
18128
+ joined5.set(this.buffer);
18129
+ joined5.set(bytes, this.buffer.length);
17521
18130
  this.buffer.fill(0);
17522
- this.buffer = joined4;
17523
- 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";
17524
18133
  if (this.mode === "pg" && !this.sawPg) {
17525
18134
  this.sawPg = true;
17526
18135
  if (!this.s.verified) void this.proof("pg");
@@ -17548,7 +18157,7 @@ var SessionWriter = class {
17548
18157
  const out = await this.smtp.line(latin12(all.subarray(0, at))).catch(() => ({ line: latin12(all.subarray(0, at)) }));
17549
18158
  all.fill(0);
17550
18159
  if ("values" in out && out.values) this.remember(out.values);
17551
- const line = bytesOf(`${out.line}\r
18160
+ const line = bytesOf2(`${out.line}\r
17552
18161
  `);
17553
18162
  try {
17554
18163
  await writeAll3(this.conn, line);
@@ -17605,7 +18214,7 @@ var SessionWriter = class {
17605
18214
  latin12(all.subarray(0, end)),
17606
18215
  (position, placeholders, head2) => this.ask("http1", position, { placeholders, head: head2 })
17607
18216
  ).catch(() => void 0);
17608
- const head = done4 ? bytesOf(done4.head) : all.subarray(0, end);
18217
+ const head = done4 ? bytesOf2(done4.head) : all.subarray(0, end);
17609
18218
  if (done4) this.remember(done4.values);
17610
18219
  try {
17611
18220
  await writeAll3(this.conn, head);
@@ -17668,7 +18277,7 @@ var ScrubReader = class {
17668
18277
  const before = this.current();
17669
18278
  const out = this.through(got);
17670
18279
  if (before.length === 0 && out !== void 0) return out;
17671
- if (before.length > 0) return joined2(before, out);
18280
+ if (before.length > 0) return joined3(before, out);
17672
18281
  }
17673
18282
  }
17674
18283
  through(got) {
@@ -17684,7 +18293,7 @@ var ScrubReader = class {
17684
18293
  return out.length > 0 ? out : void 0;
17685
18294
  }
17686
18295
  };
17687
- function joined2(a, b) {
18296
+ function joined3(a, b) {
17688
18297
  if (!b) return a;
17689
18298
  const out = new Uint8Array(a.length + b.length);
17690
18299
  out.set(a);
@@ -18592,7 +19201,7 @@ async function connect(req, proc) {
18592
19201
  dialing?.catch(() => void 0);
18593
19202
  throw new KernelError("EINPROGRESS");
18594
19203
  }
18595
- if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
19204
+ if (dialing !== void 0) await interruptible3(dialing, proc.blocking());
18596
19205
  return ok3();
18597
19206
  }
18598
19207
  async function bindUnixDgram(proc, socket, addr) {
@@ -18657,7 +19266,7 @@ async function unixDatagram(req, dgram, proc) {
18657
19266
  function domainOf(socket) {
18658
19267
  return socket instanceof UnixDgram ? "unix" : "inet";
18659
19268
  }
18660
- function interruptible2(dialing, signal) {
19269
+ function interruptible3(dialing, signal) {
18661
19270
  return new Promise((resolve2, reject) => {
18662
19271
  const abort = () => reject(new KernelError("EINTR"));
18663
19272
  signal.addEventListener("abort", abort, { once: true });
@@ -19661,7 +20270,7 @@ var ProcessWatches = class {
19661
20270
  };
19662
20271
 
19663
20272
  // src/kernel/process.ts
19664
- function aborted(signal) {
20273
+ function aborted2(signal) {
19665
20274
  return new Promise((_, reject) => {
19666
20275
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
19667
20276
  });
@@ -19751,6 +20360,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
19751
20360
  "fd-open-sealed",
19752
20361
  "fd-open-clipboard",
19753
20362
  "fd-clipboard-abort",
20363
+ "fd-open-embedder",
19754
20364
  "tty-get",
19755
20365
  "tty-set",
19756
20366
  "tty-winsz",
@@ -20304,6 +20914,7 @@ var WasmProcess = class {
20304
20914
  if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
20305
20915
  if (file.tty) this.checkForeground(file.tty);
20306
20916
  if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
20917
+ if (file.embedder) return this.readEmbedder(file, req, cancelled);
20307
20918
  if (file instanceof EventFd && req.max < EventFd.SIZE) throw new KernelError("EINVAL");
20308
20919
  if (req.max <= 0) return new Uint8Array(0);
20309
20920
  if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
@@ -20324,6 +20935,81 @@ var WasmProcess = class {
20324
20935
  throw err;
20325
20936
  }
20326
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
+ }
20327
21013
  async complain(line) {
20328
21014
  const stderr = this.fds.has(2) ? this.fds.get(2).file : void 0;
20329
21015
  if (!stderr?.write || stderr.sealed || stderr.clipboard) return false;
@@ -20354,6 +21040,7 @@ var WasmProcess = class {
20354
21040
  const file = this.fds.get(fd).file;
20355
21041
  if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
20356
21042
  if (!file.write) throw new KernelError("EBADF");
21043
+ if (file.embedder) return this.writeEmbedder(file, body, nonblock, cancelled);
20357
21044
  if (!pollFile(file).writable) {
20358
21045
  if (nonblock) throw new KernelError("EAGAIN");
20359
21046
  return file.write(body, this.blockingSignal(cancelled));
@@ -20401,11 +21088,11 @@ var WasmProcess = class {
20401
21088
  } else if (req.create) {
20402
21089
  await nodes.onEntry(path, async () => {
20403
21090
  const marks = this.options.owners || plain3 !== void 0;
20404
- const fresh2 = marks && !await present3(fs, path, true);
21091
+ const fresh3 = marks && !await present3(fs, path, true);
20405
21092
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
20406
21093
  await this.fds.get(fd).file.stat?.();
20407
- if (fresh2 && plain3 !== void 0) await fs.chmod?.(path, plain3);
20408
- else if (fresh2) {
21094
+ if (fresh3 && plain3 !== void 0) await fs.chmod?.(path, plain3);
21095
+ else if (fresh3) {
20409
21096
  await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
20410
21097
  }
20411
21098
  });
@@ -20623,17 +21310,9 @@ var WasmProcess = class {
20623
21310
  kind: "json",
20624
21311
  json: await this.write(req.fd, req.body, req.nonblock, cancelled)
20625
21312
  };
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
- });
21313
+ case "fd-close":
21314
+ await this.closeFd(req.fd);
20635
21315
  return { ok: true, kind: "void" };
20636
- }
20637
21316
  case "fd-pipe": {
20638
21317
  const pipe = openPipe();
20639
21318
  const read = this.fds.install(pipe.read, 3);
@@ -20693,6 +21372,8 @@ var WasmProcess = class {
20693
21372
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
20694
21373
  case "fd-open-clipboard":
20695
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) };
20696
21377
  case "fd-clipboard-abort": {
20697
21378
  const file = this.fds.get(req.fd);
20698
21379
  file.file.clipboard?.abort();
@@ -21187,7 +21868,7 @@ var WasmProcess = class {
21187
21868
  else {
21188
21869
  const { source, target, type, flags: flags2, data } = req;
21189
21870
  const signal = this.blockingSignal(cancelled);
21190
- 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)]);
21191
21872
  }
21192
21873
  } catch (err) {
21193
21874
  const code = err?.code;
@@ -21197,7 +21878,7 @@ var WasmProcess = class {
21197
21878
  return { ok: true, kind: "void" };
21198
21879
  }
21199
21880
  pause(cancelled) {
21200
- return aborted(this.blockingSignal(cancelled));
21881
+ return aborted2(this.blockingSignal(cancelled));
21201
21882
  }
21202
21883
  alarm;
21203
21884
  dlLog = [];
@@ -21747,6 +22428,15 @@ function linksOf(opts) {
21747
22428
  function clipboardName(opts) {
21748
22429
  return opts.program.abi === "js" ? {} : { clipboardName: opts.argv0 };
21749
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
+ }
21750
22440
  function processOptions(opts, header5, hooks, created4) {
21751
22441
  return {
21752
22442
  ...inherited(opts),
@@ -21754,6 +22444,7 @@ function processOptions(opts, header5, hooks, created4) {
21754
22444
  ...opts.sealed ? { sealed: opts.sealed } : {},
21755
22445
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
21756
22446
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
22447
+ ...embedderOptions(opts),
21757
22448
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
21758
22449
  ...opts.source ? { source: opts.source } : {},
21759
22450
  noLinks: opts.noLinks,
@@ -24502,9 +25193,9 @@ var OpfsFs = class {
24502
25193
  this.lookups++;
24503
25194
  if (split2(path).length === 0) return this.root;
24504
25195
  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) {
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) {
24508
25199
  if (this.glanced(cached)) return this.remember(path, cached);
24509
25200
  try {
24510
25201
  await this.glance(cached);
@@ -24722,8 +25413,8 @@ var OpfsFs = class {
24722
25413
  glanced(handle) {
24723
25414
  if (this.exact > 0) return void 0;
24724
25415
  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;
25416
+ const fresh3 = seen && seen.changes === this.changes && this.recent(seen, GLANCE_LOOKUPS);
25417
+ return fresh3 ? seen.file : void 0;
24727
25418
  }
24728
25419
  async glance(handle) {
24729
25420
  const at = performance.now();
@@ -24913,13 +25604,13 @@ var OpfsFs = class {
24913
25604
  async keptFor(path) {
24914
25605
  const at = normalizePath(path);
24915
25606
  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);
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);
24918
25609
  if (!existed) return [void 0, target, false];
24919
25610
  this.refuse(target);
24920
25611
  const [entry] = await this.meta.get([target]);
24921
25612
  if (entry?.fifo || entry?.socket) throw fsError("ENXIO", path);
24922
- const kept = fresh2 ? cached : this.files.get(target);
25613
+ const kept = fresh3 ? cached : this.files.get(target);
24923
25614
  const usable2 = kept && performance.now() - (this.found.get(kept) ?? -CHECK_MS) < CHECK_MS;
24924
25615
  return [usable2 ? kept : void 0, target, true];
24925
25616
  }
@@ -26634,14 +27325,14 @@ var RESTORE_AGAIN_MS = 3e4;
26634
27325
  function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26635
27326
  const handles = /* @__PURE__ */ new Map();
26636
27327
  let nextFh = 0;
26637
- const fresh2 = (remote) => {
27328
+ const fresh3 = (remote) => {
26638
27329
  if (remote.client.token === remote.token && !remote.client.gone) return remote;
26639
27330
  throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
26640
27331
  };
26641
27332
  const live = (fh) => {
26642
27333
  const remote = handles.get(fh);
26643
27334
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26644
- return fresh2(remote);
27335
+ return fresh3(remote);
26645
27336
  };
26646
27337
  const call = (op, body = {}) => current().call(op, body);
26647
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);
@@ -26723,7 +27414,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
26723
27414
  if (!remote) throw fsError2("EBADF", `handle ${fh}`);
26724
27415
  handles.delete(fh);
26725
27416
  try {
26726
- await fresh2(remote).client.call("release", { fh: remote.fh }, true);
27417
+ await fresh3(remote).client.call("release", { fh: remote.fh }, true);
26727
27418
  } finally {
26728
27419
  if (remote.restore) await settle(current(), remote.restore);
26729
27420
  }
@@ -27087,7 +27778,7 @@ var PortTransport = class {
27087
27778
  }).finally(() => this.fail("the folder's watch ended"));
27088
27779
  }
27089
27780
  };
27090
- var errorOf = (err) => ({
27781
+ var errorOf2 = (err) => ({
27091
27782
  code: typeof codeOf7(err) === "string" ? codeOf7(err) : "EIO",
27092
27783
  message: err?.message ?? String(err)
27093
27784
  });
@@ -27137,7 +27828,7 @@ function serveHostfsPort(port, transport) {
27137
27828
  );
27138
27829
  send2({ id: id2, end: true });
27139
27830
  } catch (err) {
27140
- send2({ id: id2, error: errorOf(err) });
27831
+ send2({ id: id2, error: errorOf2(err) });
27141
27832
  } finally {
27142
27833
  watches.delete(id2);
27143
27834
  }
@@ -27165,7 +27856,7 @@ function serveHostfsPort(port, transport) {
27165
27856
  if (op !== "call") throw fsError2("EINVAL", `unknown hostfs port op ${op}`);
27166
27857
  send2({ id: id2, result: await answer2(data.name, data.body) });
27167
27858
  } catch (err) {
27168
- send2({ id: id2, error: errorOf(err) });
27859
+ send2({ id: id2, error: errorOf2(err) });
27169
27860
  }
27170
27861
  };
27171
27862
  const stop = () => {
@@ -27798,7 +28489,7 @@ function homeModeOf(mode) {
27798
28489
  }
27799
28490
  return mode;
27800
28491
  }
27801
- function joined3(name2, rows) {
28492
+ function joined4(name2, rows) {
27802
28493
  let changed = false;
27803
28494
  for (const g of rows) {
27804
28495
  if (g.members.includes(name2)) continue;
@@ -27985,7 +28676,7 @@ var UserDb = class {
27985
28676
  await this.provision(found, this.umaskFor(options), shared);
27986
28677
  await this.stampHome(found, shared, homeMode);
27987
28678
  }
27988
- if (joined3(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
28679
+ if (joined4(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
27989
28680
  remember();
27990
28681
  return this.account(found, groups);
27991
28682
  }
@@ -27994,10 +28685,10 @@ var UserDb = class {
27994
28685
  if (groups.some((r) => r.name === name2)) throw fsError("EEXIST", `group ${name2} exists`);
27995
28686
  const uid = freeId(users, groups);
27996
28687
  const row = { name: name2, uid, gid: uid, gecos: name2, home, shell: "/bin/bash" };
27997
- joined3(name2, extra);
28688
+ joined4(name2, extra);
27998
28689
  group.push({ name: name2, gid: uid, members: [] });
27999
28690
  passwd.push(row);
28000
- const fresh2 = !await this.fs.exists(home);
28691
+ const fresh3 = !await this.fs.exists(home);
28001
28692
  await this.fs.mkdir(home, { recursive: true });
28002
28693
  try {
28003
28694
  await this.provision(row, this.umaskFor(options), shared);
@@ -28005,7 +28696,7 @@ var UserDb = class {
28005
28696
  await this.fs.mkdir("/etc", { recursive: true });
28006
28697
  await this.commit(formatPasswd(passwd), formatGroup(group));
28007
28698
  } catch (err) {
28008
- if (fresh2) await this.fs.rm(home, { recursive: true, force: true });
28699
+ if (fresh3) await this.fs.rm(home, { recursive: true, force: true });
28009
28700
  throw err;
28010
28701
  }
28011
28702
  remember();
@@ -28383,12 +29074,12 @@ ${lines2}
28383
29074
  const timer = setTimeout(() => control.abort("expired"), timeout);
28384
29075
  const withdraw = () => control.abort("withdrawn");
28385
29076
  requester.addEventListener("abort", withdraw, { once: true });
28386
- const aborted2 = new Promise(
29077
+ const aborted3 = new Promise(
28387
29078
  (resolve2) => control.signal.addEventListener("abort", () => resolve2("aborted"), { once: true })
28388
29079
  );
28389
29080
  try {
28390
29081
  const answered = Promise.resolve().then(() => approve(req, { signal: control.signal })).catch(() => "deny");
28391
- const answer2 = await Promise.race([answered, aborted2]);
29082
+ const answer2 = await Promise.race([answered, aborted3]);
28392
29083
  if (answer2 === "aborted" || control.signal.aborted) {
28393
29084
  return control.signal.reason === "expired" ? failed2("ETIMEDOUT", "sudo: the request expired") : failed2("EINTR", "sudo: the request was withdrawn");
28394
29085
  }
@@ -28514,6 +29205,7 @@ var SUDO_SOURCE = [
28514
29205
  ].join("\n");
28515
29206
 
28516
29207
  // src/launcher.ts
29208
+ var NO_SESSION = { client: -1, uid: -1 };
28517
29209
  var MODULE_URLS = 16;
28518
29210
  function dataModuleUrl(source) {
28519
29211
  const bytes = new TextEncoder().encode(source);
@@ -28772,6 +29464,7 @@ var Launcher = class {
28772
29464
  described = /* @__PURE__ */ new Map();
28773
29465
  creds = /* @__PURE__ */ new Map();
28774
29466
  tags = /* @__PURE__ */ new Map();
29467
+ sessions = /* @__PURE__ */ new Map();
28775
29468
  tagBases = /* @__PURE__ */ new Map();
28776
29469
  links = /* @__PURE__ */ new Map();
28777
29470
  freshModules = new FreshModules();
@@ -28832,6 +29525,7 @@ var Launcher = class {
28832
29525
  sealed;
28833
29526
  scrubs = /* @__PURE__ */ new Map();
28834
29527
  clipboard;
29528
+ embedder;
28835
29529
  rootFs;
28836
29530
  fileWatches;
28837
29531
  nextPid = 1e3;
@@ -28853,14 +29547,14 @@ var Launcher = class {
28853
29547
  void 0,
28854
29548
  (pid) => this.tagsOf(pid)
28855
29549
  );
29550
+ this.embedder = this.embedderBroker(options.embedder);
28856
29551
  this.sealed = new SealedBroker(
28857
29552
  options.sealed,
28858
29553
  this.usersMode,
28859
29554
  void 0,
28860
29555
  (pid) => this.tagsOf(pid)
28861
29556
  );
28862
- this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
28863
- this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
29557
+ this.wireSealed();
28864
29558
  if (this.owners) options.fs.trackOwners?.();
28865
29559
  this.watchers.tap((paths) => {
28866
29560
  this.changes.publish(...paths);
@@ -29921,6 +30615,8 @@ var Launcher = class {
29921
30615
  }
29922
30616
  marks(pid, req) {
29923
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);
29924
30620
  if (req.tags?.length) this.tagBases.set(pid, req.tagBase ?? req.tags.length);
29925
30621
  if (req.links !== void 0) this.links.set(pid, req.links);
29926
30622
  }
@@ -30000,6 +30696,7 @@ var Launcher = class {
30000
30696
  sealed: this.sealed,
30001
30697
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
30002
30698
  clipboard: this.clipboard,
30699
+ embedder: { broker: this.embedder, session: () => this.sessionOf(pid) },
30003
30700
  ...this.dac ? { dac: this.dac } : {},
30004
30701
  program: req.program,
30005
30702
  changes: this.changes.sab,
@@ -30128,6 +30825,7 @@ var Launcher = class {
30128
30825
  this.childTimes.delete(pid);
30129
30826
  this.creds.delete(pid);
30130
30827
  this.tags.delete(pid);
30828
+ this.sessions.delete(pid);
30131
30829
  this.tagBases.delete(pid);
30132
30830
  this.links.delete(pid);
30133
30831
  this.granted.delete(pid);
@@ -30173,8 +30871,8 @@ var Launcher = class {
30173
30871
  this.sampledAt = Math.max(this.sampledAt, now);
30174
30872
  this.load = this.load.map((avg, i) => {
30175
30873
  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;
30874
+ const fresh3 = 1 - Math.exp(-LOAD_SAMPLE_MS / window);
30875
+ return avg * Math.exp(-elapsed / window) + running * fresh3;
30178
30876
  });
30179
30877
  }
30180
30878
  idleSince;
@@ -30404,7 +31102,7 @@ var Launcher = class {
30404
31102
  forker(ppid, parent) {
30405
31103
  const { exec: _exec, pgid: _pgid, group: _group, pid: _pid, scrub: _scrub, ...image } = parent;
30406
31104
  return async (state, given) => {
30407
- const fresh2 = (fds, quietOom) => this.start({
31105
+ const fresh3 = (fds, quietOom) => this.start({
30408
31106
  ...image,
30409
31107
  quietOom,
30410
31108
  cwd: state.cwd ?? parent.cwd,
@@ -30420,7 +31118,7 @@ var Launcher = class {
30420
31118
  tagBase: this.tagBases.get(ppid),
30421
31119
  links: this.links.get(ppid)
30422
31120
  });
30423
- return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh2));
31121
+ return childHandle(await this.admitted(parent.program.abi !== "js", given, fresh3));
30424
31122
  };
30425
31123
  }
30426
31124
  keySignal(tty, fallback, sig, send2) {
@@ -30456,6 +31154,25 @@ var Launcher = class {
30456
31154
  credOf(pid) {
30457
31155
  return this.creds.get(pid)?.() ?? copyCred(ROOT);
30458
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
+ }
30459
31176
  tagOf(pid) {
30460
31177
  return this.tags.get(pid)?.[0];
30461
31178
  }
@@ -30727,8 +31444,8 @@ var Launcher = class {
30727
31444
  for (const [path, mode] of layout) {
30728
31445
  if (isProtected(this.prefixes, normalizePath(path))) continue;
30729
31446
  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 });
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 });
30732
31449
  }
30733
31450
  }
30734
31451
  async realLayoutPath(given) {
@@ -30901,9 +31618,10 @@ var Launcher = class {
30901
31618
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
30902
31619
  return found;
30903
31620
  }
30904
- identity(account, given) {
31621
+ identity(account, given, session = 0) {
30905
31622
  return {
30906
31623
  ...given,
31624
+ session: { client: session, uid: account.uid },
30907
31625
  cred: credOf(account),
30908
31626
  ...account.umask !== void 0 ? { umask: account.umask } : {}
30909
31627
  };
@@ -31090,7 +31808,7 @@ var Launcher = class {
31090
31808
  cwd,
31091
31809
  fds,
31092
31810
  report,
31093
- ...this.identity(account, tagged)
31811
+ ...this.identity(account, tagged, options.session)
31094
31812
  });
31095
31813
  leader = handle.pid;
31096
31814
  void handle.exited.then(() => tty.hangup());
@@ -31147,7 +31865,7 @@ var Launcher = class {
31147
31865
  fds,
31148
31866
  report,
31149
31867
  ...group,
31150
- ...this.identity(account, tagged)
31868
+ ...this.identity(account, tagged, options.session)
31151
31869
  });
31152
31870
  options.onStarted?.(handle.pid);
31153
31871
  const status = await handle.exited;
@@ -31590,7 +32308,7 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS3 } = {})
31590
32308
  }
31591
32309
 
31592
32310
  // src/client/protocol.ts
31593
- var PROTOCOL = [1, 12];
32311
+ var PROTOCOL = [1, 13];
31594
32312
  var FS_METHODS = [
31595
32313
  "readFile",
31596
32314
  "writeFile",
@@ -31757,14 +32475,15 @@ async function runAs(l, self, requested) {
31757
32475
  function clientCred(self) {
31758
32476
  return self ? credOf(self) : copyCred(ROOT);
31759
32477
  }
31760
- async function runTagged(l, self, options, tag) {
32478
+ async function runTagged(l, self, options, tag, session = -1) {
31761
32479
  tagOption(options.tag);
31762
32480
  const tags = withTag(withTag(void 0, tag), options.tag);
31763
32481
  return {
31764
32482
  user: await runAs(l, self, options.user),
31765
32483
  tag,
31766
32484
  tags,
31767
- tagBase: tag === void 0 ? 0 : 1
32485
+ tagBase: tag === void 0 ? 0 : 1,
32486
+ session
31768
32487
  };
31769
32488
  }
31770
32489
  function processes(l, self) {
@@ -31845,10 +32564,12 @@ function facadeRegistry(send2, gone) {
31845
32564
  }
31846
32565
  };
31847
32566
  }
31848
- function hostRegistries(send2, gone) {
32567
+ function hostRegistries(send2, gone, port) {
31849
32568
  const cdps = cdpRegistry(send2, gone);
31850
32569
  const facades = facadeRegistry(send2, gone);
32570
+ const requests = requestBinding(send2, port);
31851
32571
  return {
32572
+ requests,
31852
32573
  answer(l, req) {
31853
32574
  if (req.op === "serve-cdp") return cdps.serve(l, req);
31854
32575
  if (req.op === "unserve-cdp") return cdps.drop(req.registration);
@@ -31856,6 +32577,7 @@ function hostRegistries(send2, gone) {
31856
32577
  return facades.drop(req.registration);
31857
32578
  },
31858
32579
  close() {
32580
+ requests.close();
31859
32581
  cdps.close();
31860
32582
  facades.close();
31861
32583
  }
@@ -31890,6 +32612,28 @@ var TRAITS_WATCH = -1;
31890
32612
  function traitsWatch(launcher, transport, send2) {
31891
32613
  return launcher.onTraits(() => send2({ traits: transport.traits }));
31892
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
+ }
31893
32637
  function serveClient(port, host) {
31894
32638
  let state = "new";
31895
32639
  let self;
@@ -31904,7 +32648,7 @@ function serveClient(port, host) {
31904
32648
  if (state !== "closed") port.postMessage(message3, transfer);
31905
32649
  };
31906
32650
  const reply = (id2, body, transfer) => send2({ id: id2, ...body }, transfer);
31907
- const hosts = hostRegistries(send2, ended.promise);
32651
+ const hosts = hostRegistries(send2, ended.promise, port);
31908
32652
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
31909
32653
  if (state === "closed") return;
31910
32654
  send2({ bye });
@@ -31921,7 +32665,7 @@ function serveClient(port, host) {
31921
32665
  ended.resolve();
31922
32666
  }
31923
32667
  async function hello(message3) {
31924
- const refused3 = versionError(message3.protocol);
32668
+ const refused3 = versionError(message3.protocol) ?? await hosts.requests.accept(host, message3);
31925
32669
  if (refused3) {
31926
32670
  send2({ hello: { protocol: PROTOCOL, error: refused3 } });
31927
32671
  detach();
@@ -31946,7 +32690,7 @@ function serveClient(port, host) {
31946
32690
  async function terminal(req, l) {
31947
32691
  const session = await l.openTerminal(req.argv, {
31948
32692
  ...req.options,
31949
- ...await runTagged(l, self, req.options, host.tag),
32693
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31950
32694
  onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
31951
32695
  });
31952
32696
  if (state === "closed") {
@@ -31969,7 +32713,7 @@ function serveClient(port, host) {
31969
32713
  let leader;
31970
32714
  const result = await l.run(req.argv, {
31971
32715
  ...req.options,
31972
- ...await runTagged(l, self, req.options, host.tag),
32716
+ ...await runTagged(l, self, req.options, host.tag, host.session),
31973
32717
  collect: false,
31974
32718
  onStdout: out(1),
31975
32719
  onStderr: out(2),
@@ -32149,11 +32893,11 @@ function guarded2(check, signal) {
32149
32893
  signal.throwIfAborted();
32150
32894
  let stop = () => {
32151
32895
  };
32152
- const aborted2 = new Promise((_, reject) => {
32896
+ const aborted3 = new Promise((_, reject) => {
32153
32897
  stop = () => reject(signal.reason);
32154
32898
  signal.addEventListener("abort", stop, { once: true });
32155
32899
  });
32156
- return Promise.race([check, aborted2]).finally(() => signal.removeEventListener("abort", stop));
32900
+ return Promise.race([check, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32157
32901
  }
32158
32902
  var loopbackStore = new AsyncLocalStorage();
32159
32903
  var subscribed = false;
@@ -32268,7 +33012,7 @@ function valid(name2) {
32268
33012
  }
32269
33013
  }
32270
33014
  var CHUNK4 = 1024 * 1024;
32271
- function bytesOf2(data) {
33015
+ function bytesOf3(data) {
32272
33016
  if (typeof data === "string") return new TextEncoder().encode(data);
32273
33017
  if (data instanceof Blob) return data.arrayBuffer().then((buffer) => new Uint8Array(buffer));
32274
33018
  if (ArrayBuffer.isView(data)) {
@@ -32348,7 +33092,7 @@ var MemoryFile = class {
32348
33092
  const next3 = keepExistingData ? new Chunks([...this.data.blobs], this.data.size) : new Chunks();
32349
33093
  let position = 0;
32350
33094
  const put = async (data) => {
32351
- const bytes = await bytesOf2(data);
33095
+ const bytes = await bytesOf3(data);
32352
33096
  await next3.write(position, bytes);
32353
33097
  position += bytes.length;
32354
33098
  };
@@ -32480,11 +33224,11 @@ function abortable(work, signal) {
32480
33224
  if (signal.aborted) return Promise.reject(signal.reason);
32481
33225
  let stop = () => {
32482
33226
  };
32483
- const aborted2 = new Promise((_, reject) => {
33227
+ const aborted3 = new Promise((_, reject) => {
32484
33228
  stop = () => reject(signal.reason);
32485
33229
  signal.addEventListener("abort", stop, { once: true });
32486
33230
  });
32487
- return Promise.race([work, aborted2]).finally(() => signal.removeEventListener("abort", stop));
33231
+ return Promise.race([work, aborted3]).finally(() => signal.removeEventListener("abort", stop));
32488
33232
  }
32489
33233
  function probe(secure2, host, port, ms, signal) {
32490
33234
  return new Promise((resolve2) => {
@@ -32843,10 +33587,10 @@ var ByteSource = class {
32843
33587
  async more() {
32844
33588
  const { done: done4, value: value2 } = await this.reader.read();
32845
33589
  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;
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;
32850
33594
  return true;
32851
33595
  }
32852
33596
  async until(marker) {
@@ -33013,7 +33757,7 @@ var KernelCallError = class extends Error {
33013
33757
  }
33014
33758
  };
33015
33759
  var encoder12 = new TextEncoder();
33016
- var bytesOf3 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
33760
+ var bytesOf4 = (data) => typeof data === "string" ? encoder12.encode(data) : data;
33017
33761
  var UNAVAILABLE = {
33018
33762
  manualRedirects: false,
33019
33763
  encodedBodies: false,
@@ -33097,7 +33841,9 @@ var Channel = class {
33097
33841
  call(call) {
33098
33842
  return this.request(call).done;
33099
33843
  }
33844
+ requests;
33100
33845
  receive(data) {
33846
+ if (this.requests?.handle(data)) return;
33101
33847
  if (data.traits && data.id === void 0) {
33102
33848
  if (this.remote) this.remote.traits = data.traits;
33103
33849
  for (const listener of [...this.traitsListeners]) listener(data.traits);
@@ -33220,7 +33966,7 @@ async function spawnOn(channel, argv, opts) {
33220
33966
  });
33221
33967
  const started = Promise.withResolvers();
33222
33968
  let endedBy;
33223
- const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf3(stdin) } : {} };
33969
+ const options = { ...rest2, ...stdin !== void 0 ? { stdin: bytesOf4(stdin) } : {} };
33224
33970
  const { done: done4 } = channel.request(
33225
33971
  { op: "spawn", argv, options },
33226
33972
  {
@@ -33303,7 +34049,7 @@ async function terminalOn(channel, argv, opts) {
33303
34049
  listener = next3;
33304
34050
  if (next3) for (const bytes of backlog.splice(0)) next3(bytes);
33305
34051
  },
33306
- write: (data) => send2({ action: "write", bytes: bytesOf3(data) }),
34052
+ write: (data) => send2({ action: "write", bytes: bytesOf4(data) }),
33307
34053
  resize: (cols, rows) => send2({ action: "resize", cols, rows }),
33308
34054
  signal: (name2) => send2({ action: "signal", signal: name2 }),
33309
34055
  close: () => send2({ action: "close" })
@@ -33365,8 +34111,10 @@ function since(channel, minor, what, run2) {
33365
34111
  }
33366
34112
  return run2();
33367
34113
  }
33368
- async function greet(channel, lock, timeoutMs) {
33369
- 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
+ });
33370
34118
  const timer = setTimeout(
33371
34119
  () => channel.end(
33372
34120
  new KernelGoneError(`no slicc-kernel answered on this port within ${timeoutMs} ms`)
@@ -33391,7 +34139,14 @@ async function attachKernel(port, options = {}) {
33391
34139
  const locks = options.locks === null ? void 0 : options.locks ?? locksOf();
33392
34140
  const own = await holdLock(locks);
33393
34141
  const channel = new Channel(port, own.release);
33394
- 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
+ );
33395
34150
  channel.protocol = hello.protocol;
33396
34151
  if (hello.lock && locks) void locks.request(hello.lock, () => channel.end(new KernelGoneError()));
33397
34152
  const transport = new RemoteTransport(
@@ -33605,6 +34360,31 @@ function cacheDirOf(dir) {
33605
34360
  }
33606
34361
  return dir;
33607
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
+ }
33608
34388
  async function createNodeKernel(options = {}) {
33609
34389
  const root = options.root ?? memoryRoot();
33610
34390
  const fs = new OpfsFs(root, void 0, void 0, { ranged: isMemoryRoot(root) });
@@ -33645,6 +34425,7 @@ async function createNodeKernel(options = {}) {
33645
34425
  ...options.protect ? { protect: options.protect } : {},
33646
34426
  ...options.sealed ? { sealed: options.sealed } : {},
33647
34427
  ...options.clipboard ? { clipboard: options.clipboard } : {},
34428
+ ...options.requests ? { embedder: checkedRequests(options.requests) } : {},
33648
34429
  ...options.approve ? { approve: options.approve } : {},
33649
34430
  ...options.wasmMemories ? { wasmMemories: options.wasmMemories } : {},
33650
34431
  cron: { lock: cronLock(root), ...options.cron?.clock ? { clock: options.cron.clock } : {} }
@@ -33735,22 +34516,13 @@ async function createNodeKernel(options = {}) {
33735
34516
  addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
33736
34517
  removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
33737
34518
  },
33738
- async connect({ user, tag } = {}) {
34519
+ async connect(connectOptions = {}) {
33739
34520
  if (terminated) throw new Error(TERMINATED);
33740
- tagOption(tag);
34521
+ const { user } = connectOptions;
33741
34522
  if (user !== void 0 && !await guard2(() => launcher.users.lookup(user))) {
33742
34523
  throw fsError("ENOENT", `no user ${String(user)}`);
33743
34524
  }
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;
34525
+ return nodeClient(launcher, clients, connectOptions);
33754
34526
  },
33755
34527
  terminate() {
33756
34528
  terminated = true;