@ai-ecoverse/slicc-kernel 1.73.3 → 1.75.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.
@@ -2255,9 +2255,11 @@ var KernelPipe = class {
2255
2255
  // src/kernel/fd-table.ts
2256
2256
  var KernelError = class extends Error {
2257
2257
  code;
2258
- constructor(code) {
2258
+ detail;
2259
+ constructor(code, detail) {
2259
2260
  super(code);
2260
2261
  this.code = code;
2262
+ if (detail !== void 0) this.detail = detail;
2261
2263
  }
2262
2264
  };
2263
2265
  function pollFile(file) {
@@ -2400,6 +2402,7 @@ function kernelFdKind(file) {
2400
2402
  if (file.heldMeta && "device" in file.heldMeta) return "device";
2401
2403
  if (file.tty) return "tty";
2402
2404
  if (file.sealed) return "sealed";
2405
+ if (file.clipboard) return "clipboard";
2403
2406
  return file.seek ? "file" : "stream";
2404
2407
  }
2405
2408
  var FdTable = class _FdTable {
@@ -4190,6 +4193,203 @@ function serveClient(port, host) {
4190
4193
  return { detach, closed: ended.promise };
4191
4194
  }
4192
4195
 
4196
+ // src/kernel/clipboard.ts
4197
+ var CLIPBOARD_MAX_BYTES = 1024 * 1024;
4198
+ var CLIPBOARD_TIMEOUT_MS = 1e4;
4199
+ var encoder = new TextEncoder();
4200
+ var decoder = new TextDecoder("utf-8", { fatal: true });
4201
+ var READY = { readable: true, writable: true, hangup: false };
4202
+ function lineOf(err) {
4203
+ const text = err instanceof Error ? err.message : String(err);
4204
+ return (text.split("\n")[0] ?? "").slice(0, 200);
4205
+ }
4206
+ function clipboardFailure(err) {
4207
+ if (err instanceof KernelError) return err;
4208
+ const name2 = err?.name;
4209
+ return new KernelError(name2 === "NotAllowedError" ? "EACCES" : "EIO", lineOf(err));
4210
+ }
4211
+ function joined2(chunks, size) {
4212
+ if (chunks.length === 1) return chunks[0];
4213
+ const out = new Uint8Array(size);
4214
+ let at = 0;
4215
+ for (const chunk2 of chunks) {
4216
+ out.set(chunk2, at);
4217
+ at += chunk2.length;
4218
+ }
4219
+ return out;
4220
+ }
4221
+ async function interruptible(work, signal) {
4222
+ if (!signal) return work;
4223
+ if (signal.aborted) throw new KernelError("EINTR");
4224
+ let stop = () => {
4225
+ };
4226
+ const interrupted2 = new Promise((_, reject) => {
4227
+ stop = () => reject(new KernelError("EINTR"));
4228
+ signal.addEventListener("abort", stop, { once: true });
4229
+ });
4230
+ try {
4231
+ return await Promise.race([work, interrupted2]);
4232
+ } finally {
4233
+ signal.removeEventListener("abort", stop);
4234
+ }
4235
+ }
4236
+ var ClipboardChannel = class {
4237
+ stamp;
4238
+ access;
4239
+ broker;
4240
+ snapshot;
4241
+ taken;
4242
+ offset = 0;
4243
+ chunks = [];
4244
+ size = 0;
4245
+ wrote = false;
4246
+ failed = false;
4247
+ constructor(broker, access2, stamp) {
4248
+ this.broker = broker;
4249
+ this.access = access2;
4250
+ this.stamp = stamp;
4251
+ }
4252
+ get unread() {
4253
+ return this.taken === void 0 && this.snapshot === void 0;
4254
+ }
4255
+ get settled() {
4256
+ return this.taken !== void 0;
4257
+ }
4258
+ async read(max, signal) {
4259
+ if (this.access === "write") throw new KernelError("EBADF");
4260
+ if (!this.taken) {
4261
+ if (!this.snapshot) {
4262
+ const stamp = this.stamp;
4263
+ const asked = this.broker.ask(async (hook) => {
4264
+ const text = await hook.read(stamp);
4265
+ if (typeof text !== "string") throw new KernelError("EIO", "the clipboard gave no text");
4266
+ return encoder.encode(text);
4267
+ });
4268
+ this.snapshot = asked;
4269
+ asked.then(
4270
+ (bytes) => {
4271
+ if (this.snapshot === asked) this.taken = bytes;
4272
+ },
4273
+ () => {
4274
+ if (this.snapshot === asked) this.snapshot = void 0;
4275
+ }
4276
+ );
4277
+ }
4278
+ this.taken = await interruptible(this.snapshot, signal);
4279
+ }
4280
+ const out = this.taken.subarray(this.offset, this.offset + max);
4281
+ this.offset += out.length;
4282
+ return out;
4283
+ }
4284
+ write(bytes) {
4285
+ if (this.access === "read") throw new KernelError("EBADF");
4286
+ this.wrote = true;
4287
+ if (this.failed) throw new KernelError("EFBIG");
4288
+ if (this.size + bytes.length > CLIPBOARD_MAX_BYTES) {
4289
+ this.failed = true;
4290
+ this.chunks = [];
4291
+ this.size = 0;
4292
+ throw new KernelError("EFBIG");
4293
+ }
4294
+ if (bytes.length > 0) this.chunks.push(bytes.slice());
4295
+ this.size += bytes.length;
4296
+ return bytes.length;
4297
+ }
4298
+ poll() {
4299
+ return READY;
4300
+ }
4301
+ async close() {
4302
+ this.snapshot = void 0;
4303
+ this.taken = void 0;
4304
+ const commits = this.access === "write" || this.access === "both" && this.wrote;
4305
+ if (!commits || this.failed) return;
4306
+ const bytes = joined2(this.chunks, this.size);
4307
+ this.chunks = [];
4308
+ this.size = 0;
4309
+ let text;
4310
+ try {
4311
+ text = decoder.decode(bytes);
4312
+ } catch {
4313
+ throw new KernelError("EILSEQ", "the text is not valid UTF-8");
4314
+ }
4315
+ const stamp = this.stamp;
4316
+ await this.broker.ask(async (hook) => {
4317
+ await hook.write(text, stamp);
4318
+ });
4319
+ }
4320
+ };
4321
+ var ClipboardBroker = class {
4322
+ hook;
4323
+ users;
4324
+ timeoutMs;
4325
+ tagOf;
4326
+ constructor(hook, users, timeoutMs = CLIPBOARD_TIMEOUT_MS, tagOf = () => void 0) {
4327
+ this.hook = hook;
4328
+ this.users = users;
4329
+ this.timeoutMs = timeoutMs;
4330
+ this.tagOf = tagOf;
4331
+ }
4332
+ async ask(work) {
4333
+ const hook = this.hook;
4334
+ if (!hook) throw new KernelError("ENOTSUP", "no clipboard in this embedder");
4335
+ let timer;
4336
+ const late = new Promise((_, reject) => {
4337
+ timer = setTimeout(
4338
+ () => reject(new KernelError("EIO", "clipboard timed out")),
4339
+ this.timeoutMs
4340
+ );
4341
+ });
4342
+ const asked = Promise.resolve().then(() => work(hook)).catch((err) => {
4343
+ throw clipboardFailure(err);
4344
+ });
4345
+ try {
4346
+ return await Promise.race([asked, late]);
4347
+ } finally {
4348
+ clearTimeout(timer);
4349
+ }
4350
+ }
4351
+ open(access2, stamp) {
4352
+ const channel = new ClipboardChannel(this, access2, stamp);
4353
+ return new OpenFile({
4354
+ read: (max, signal) => channel.read(max, signal),
4355
+ write: async (bytes) => channel.write(bytes),
4356
+ poll: () => channel.poll(),
4357
+ close: () => channel.close(),
4358
+ clipboard: channel
4359
+ });
4360
+ }
4361
+ };
4362
+
4363
+ // src/clipboard-relay.ts
4364
+ function clipboardAsker(port) {
4365
+ const asks = /* @__PURE__ */ new Map();
4366
+ let next2 = 0;
4367
+ const ask = (op, stamp, text) => {
4368
+ const id2 = ++next2;
4369
+ const waiting = Promise.withResolvers();
4370
+ asks.set(id2, waiting);
4371
+ const timer = setTimeout(() => {
4372
+ if (asks.delete(id2)) waiting.reject(new Error("clipboard timed out"));
4373
+ }, CLIPBOARD_TIMEOUT_MS + 1e3);
4374
+ port.postMessage({ clipboardAsk: { id: id2, op, stamp, ...text !== void 0 ? { text } : {} } });
4375
+ return waiting.promise.finally(() => clearTimeout(timer));
4376
+ };
4377
+ const hook = {
4378
+ read: async (stamp) => await ask("read", stamp) ?? "",
4379
+ write: async (text, stamp) => {
4380
+ await ask("write", stamp, text);
4381
+ }
4382
+ };
4383
+ const answer = ({ id: id2, text, error }) => {
4384
+ const waiting = asks.get(id2);
4385
+ if (!waiting) return;
4386
+ asks.delete(id2);
4387
+ if (error) waiting.reject(Object.assign(new Error(error.message), { name: error.name }));
4388
+ else waiting.resolve(text);
4389
+ };
4390
+ return { hook, answer };
4391
+ }
4392
+
4193
4393
  // src/kernel/net/loopback-names.ts
4194
4394
  var HOST_LOOPBACK = "host.slicc.internal";
4195
4395
  var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
@@ -9191,20 +9391,20 @@ var SAB_DEFAULT_WINDOW_BYTES = 1024 * 1024;
9191
9391
  var SAB_MIN_BYTES = SAB_HEADER_BYTES + 4096;
9192
9392
  var SYNC_SAB_REQ_MSG = "sync-sab-req";
9193
9393
  var SYNC_SAB_NEXT_MSG = "sync-sab-next";
9194
- var encoder = new TextEncoder();
9195
- var decoder = new TextDecoder();
9394
+ var encoder2 = new TextEncoder();
9395
+ var decoder2 = new TextDecoder();
9196
9396
  function encodeSabResult(result) {
9197
9397
  if (!result.ok) {
9198
9398
  return {
9199
9399
  status: SAB_STATUS_ERRNO,
9200
- payload: encoder.encode(JSON.stringify({ errno: result.errno, message: result.message }))
9400
+ payload: encoder2.encode(JSON.stringify({ errno: result.errno, message: result.message }))
9201
9401
  };
9202
9402
  }
9203
9403
  switch (result.kind) {
9204
9404
  case "bytes":
9205
9405
  return { status: SAB_STATUS_BYTES, payload: result.bytes };
9206
9406
  case "json":
9207
- return { status: SAB_STATUS_JSON, payload: encoder.encode(JSON.stringify(result.json)) };
9407
+ return { status: SAB_STATUS_JSON, payload: encoder2.encode(JSON.stringify(result.json)) };
9208
9408
  default:
9209
9409
  return { status: SAB_STATUS_VOID, payload: new Uint8Array(0) };
9210
9410
  }
@@ -10678,7 +10878,7 @@ function defaultTermios() {
10678
10878
  c_cc: cCc
10679
10879
  };
10680
10880
  }
10681
- var encoder2 = new TextEncoder();
10881
+ var encoder3 = new TextEncoder();
10682
10882
  function isContinuation(byte) {
10683
10883
  return (byte & 192) === 128;
10684
10884
  }
@@ -10800,7 +11000,7 @@ var KernelTty = class {
10800
11000
  this.line.push(byte);
10801
11001
  this.readable.push(Uint8Array.from(this.line));
10802
11002
  this.line = [];
10803
- if (echo) this.output(encoder2.encode("\n"));
11003
+ if (echo) this.output(encoder3.encode("\n"));
10804
11004
  } else {
10805
11005
  this.line.push(byte);
10806
11006
  if (echo) this.echo(byte);
@@ -10825,7 +11025,7 @@ var KernelTty = class {
10825
11025
  const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
10826
11026
  const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
10827
11027
  if (cells > 0 && this.termios.c_lflag & ECHO) {
10828
- this.output(encoder2.encode("\b \b".repeat(cells)), false);
11028
+ this.output(encoder3.encode("\b \b".repeat(cells)), false);
10829
11029
  }
10830
11030
  }
10831
11031
  echo(byte) {
@@ -11175,7 +11375,14 @@ async function runChild(req, deps) {
11175
11375
  ];
11176
11376
  try {
11177
11377
  pid = await deps.children.spawn(
11178
- { file: req.file, argv: req.argv, env: req.env, cwd: req.cwd },
11378
+ {
11379
+ file: req.file,
11380
+ argv: req.argv,
11381
+ env: req.env,
11382
+ cwd: req.cwd,
11383
+ ...req.pgid !== void 0 ? { pgid: req.pgid } : {},
11384
+ ...req.session ? { session: req.session } : {}
11385
+ },
11179
11386
  stdio
11180
11387
  );
11181
11388
  } catch (e) {
@@ -11307,7 +11514,7 @@ async function socketSyscall(req, proc) {
11307
11514
  dialing?.catch(() => void 0);
11308
11515
  throw new KernelError("EINPROGRESS");
11309
11516
  }
11310
- if (dialing !== void 0) await interruptible(dialing, proc.blocking());
11517
+ if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
11311
11518
  return ok();
11312
11519
  }
11313
11520
  case "sock-shutdown":
@@ -11331,7 +11538,7 @@ async function socketSyscall(req, proc) {
11331
11538
  }
11332
11539
  }
11333
11540
  }
11334
- function interruptible(dialing, signal) {
11541
+ function interruptible2(dialing, signal) {
11335
11542
  return new Promise((resolve, reject) => {
11336
11543
  const abort = () => reject(new KernelError("EINTR"));
11337
11544
  signal.addEventListener("abort", abort, { once: true });
@@ -11998,6 +12205,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11998
12205
  "fd-tty-names",
11999
12206
  "fd-tty-owner",
12000
12207
  "fd-open-sealed",
12208
+ "fd-open-clipboard",
12001
12209
  "tty-get",
12002
12210
  "tty-set",
12003
12211
  "tty-winsz",
@@ -12128,8 +12336,9 @@ var WasmProcess = class {
12128
12336
  return "deliver";
12129
12337
  }
12130
12338
  async spawn(req) {
12131
- const { file, argv, env, cwd, stdio, inherit: inherit2, exec } = req;
12132
- const spawned = { file, argv, env, cwd, ...exec ? { exec } : {} };
12339
+ const { file, argv, env, cwd, stdio, inherit: inherit2, exec, pgid, session } = req;
12340
+ const group = { ...pgid !== void 0 ? { pgid } : {}, ...session ? { session } : {} };
12341
+ const spawned = { file, argv, env, cwd, ...group, ...exec ? { exec } : {} };
12133
12342
  const pid = await this.children.spawn(spawned, stdio, inherit2);
12134
12343
  if (!exec) return pid;
12135
12344
  if (this.killed) void Promise.allSettled([this.options.kill?.(pid, SIG.KILL)]);
@@ -12239,7 +12448,7 @@ var WasmProcess = class {
12239
12448
  return await this.procSyscall(req, cancelled);
12240
12449
  } catch (e) {
12241
12450
  if (e instanceof KernelError || e instanceof SpawnError) {
12242
- return { ok: false, errno: e.code, message: e.code };
12451
+ return { ok: false, errno: e.code, message: e.detail ?? e.code };
12243
12452
  }
12244
12453
  const code = e?.code;
12245
12454
  if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
@@ -12282,6 +12491,7 @@ var WasmProcess = class {
12282
12491
  const read = req.peek ? file.peek : file.read;
12283
12492
  if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
12284
12493
  if (file.tty) this.checkForeground(file.tty);
12494
+ if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
12285
12495
  if (req.max <= 0) return new Uint8Array(0);
12286
12496
  if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
12287
12497
  const ready2 = pollFile(file).readable;
@@ -12289,6 +12499,13 @@ var WasmProcess = class {
12289
12499
  const signal = ready2 ? this.interrupt.signal : this.blockingSignal(cancelled);
12290
12500
  return read.call(file, Math.max(0, Math.min(req.max, MAX_READ)), signal);
12291
12501
  }
12502
+ async readClipboard(channel, req, cancelled) {
12503
+ if (channel.access === "write") throw new KernelError("EBADF");
12504
+ if (req.max <= 0) return new Uint8Array(0);
12505
+ if (channel.unread) channel.stamp = await this.clipboardStamp();
12506
+ const signal = channel.settled ? this.interrupt.signal : this.blockingSignal(cancelled);
12507
+ return channel.read(Math.min(req.max, MAX_READ), signal);
12508
+ }
12292
12509
  async write(fd, body, nonblock = false, cancelled) {
12293
12510
  const file = this.fds.get(fd).file;
12294
12511
  if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
@@ -12392,6 +12609,36 @@ var WasmProcess = class {
12392
12609
  if (!broker) throw new KernelError("ENOENT");
12393
12610
  return this.fds.install(broker.open(), 3);
12394
12611
  }
12612
+ async openClipboard(flags2) {
12613
+ const broker = this.options.clipboard;
12614
+ if (!broker) throw new KernelError("ENOENT");
12615
+ const mode = flags2 & WRITE_ACCESS;
12616
+ const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
12617
+ return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
12618
+ }
12619
+ async clipboardStamp() {
12620
+ const shown = await this.options.identity?.();
12621
+ const broker = this.options.clipboard;
12622
+ const tag = broker.tagOf(this.pid);
12623
+ return {
12624
+ pid: shown?.pid ?? this.pid,
12625
+ uid: this.cred.euid,
12626
+ gid: this.cred.egid,
12627
+ users: broker.users,
12628
+ ...tag !== void 0 ? { tag } : {}
12629
+ };
12630
+ }
12631
+ async stampClipboards() {
12632
+ if (!this.options.clipboard) return;
12633
+ const open = this.fds.numbers().filter((fd) => this.fds.get(fd).file.clipboard);
12634
+ if (open.length === 0) return;
12635
+ const stamp = await this.clipboardStamp().catch(() => void 0);
12636
+ if (!stamp) return;
12637
+ for (const fd of open) {
12638
+ const channel = this.fds.get(fd).file.clipboard;
12639
+ if (channel) channel.stamp = stamp;
12640
+ }
12641
+ }
12395
12642
  async sealedCaller() {
12396
12643
  const shown = await this.options.identity?.();
12397
12644
  const broker = this.options.sealed;
@@ -12466,10 +12713,13 @@ var WasmProcess = class {
12466
12713
  kind: "json",
12467
12714
  json: await this.write(req.fd, req.body, req.nonblock, cancelled)
12468
12715
  };
12469
- case "fd-close":
12716
+ case "fd-close": {
12470
12717
  this.options.locks?.closed(this.pid, req.fd);
12718
+ const channel = this.fds.get(req.fd).file.clipboard;
12719
+ if (channel) channel.stamp = await this.clipboardStamp();
12471
12720
  await Promise.resolve(this.fds.close(req.fd));
12472
12721
  return { ok: true, kind: "void" };
12722
+ }
12473
12723
  case "fd-pipe": {
12474
12724
  const pipe = openPipe();
12475
12725
  const read = this.fds.install(pipe.read, 3);
@@ -12527,6 +12777,8 @@ var WasmProcess = class {
12527
12777
  return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
12528
12778
  case "fd-open-sealed":
12529
12779
  return { ok: true, kind: "json", json: this.openSealed() };
12780
+ case "fd-open-clipboard":
12781
+ return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
12530
12782
  case "fd-tty-owner":
12531
12783
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
12532
12784
  case "fd-select": {
@@ -12978,6 +13230,7 @@ var WasmProcess = class {
12978
13230
  this.options.locks?.release(this.pid);
12979
13231
  this.asyncOps.close();
12980
13232
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
13233
+ await this.stampClipboards();
12981
13234
  await this.fds.closeAll();
12982
13235
  if (!this.options.nodes) this.options.openFiles?.delete(this.nodes);
12983
13236
  await this.options.http?.closeAll();
@@ -13160,6 +13413,7 @@ function processOptions(opts, header3, hooks, created3) {
13160
13413
  ...inherited(opts),
13161
13414
  ...opts.owners ? { owners: true } : {},
13162
13415
  ...opts.sealed ? { sealed: opts.sealed } : {},
13416
+ ...opts.clipboard ? { clipboard: opts.clipboard } : {},
13163
13417
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
13164
13418
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
13165
13419
  ...opts.identity ? { identity: opts.identity } : {},
@@ -13423,6 +13677,9 @@ var JobTable = class {
13423
13677
  member.pgid = pgid;
13424
13678
  member.sid = sid;
13425
13679
  }
13680
+ lead(pid) {
13681
+ this.member(pid).pgid = this.idOf(pid);
13682
+ }
13426
13683
  pgidOf(pid) {
13427
13684
  return this.members.get(pid)?.pgid;
13428
13685
  }
@@ -13661,14 +13918,14 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
13661
13918
  "cancel",
13662
13919
  "materialize"
13663
13920
  ]);
13664
- var encoder3 = new TextEncoder();
13665
- var decoder2 = new TextDecoder("utf-8", { fatal: true });
13921
+ var encoder4 = new TextEncoder();
13922
+ var decoder3 = new TextDecoder("utf-8", { fatal: true });
13666
13923
  function parseSealed(bytes) {
13667
13924
  try {
13668
13925
  if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
13669
13926
  let parsed2;
13670
13927
  try {
13671
- parsed2 = JSON.parse(decoder2.decode(bytes));
13928
+ parsed2 = JSON.parse(decoder3.decode(bytes));
13672
13929
  } catch {
13673
13930
  throw new KernelError("EINVAL");
13674
13931
  }
@@ -13694,13 +13951,13 @@ function serialized(reply) {
13694
13951
  const text = JSON.stringify(reply);
13695
13952
  plain = text === void 0 ? void 0 : JSON.parse(text);
13696
13953
  } catch (err) {
13697
- return JSON.stringify(errorReply("EIO", lineOf(err)));
13954
+ return JSON.stringify(errorReply("EIO", lineOf2(err)));
13698
13955
  }
13699
13956
  if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
13700
13957
  const { v: _, ...rest2 } = plain;
13701
13958
  return JSON.stringify({ v: 1, ...rest2 });
13702
13959
  }
13703
- function lineOf(err) {
13960
+ function lineOf2(err) {
13704
13961
  const text = err instanceof Error ? err.message : String(err);
13705
13962
  return (text.split("\n")[0] ?? "").slice(0, 200);
13706
13963
  }
@@ -13730,7 +13987,7 @@ var SealedChannel = class {
13730
13987
  gone.removeEventListener("abort", withdraw);
13731
13988
  if (this.inflight !== flight) return;
13732
13989
  this.inflight = void 0;
13733
- if (reply) this.reply = encoder3.encode(serialized(reply));
13990
+ if (reply) this.reply = encoder4.encode(serialized(reply));
13734
13991
  });
13735
13992
  this.inflight = flight;
13736
13993
  return length2;
@@ -13744,7 +14001,7 @@ var SealedChannel = class {
13744
14001
  if (signal.aborted) return void 0;
13745
14002
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
13746
14003
  (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
13747
- (err) => errorReply("EIO", lineOf(err))
14004
+ (err) => errorReply("EIO", lineOf2(err))
13748
14005
  );
13749
14006
  const reply = await Promise.race([asked, stopped]);
13750
14007
  if (!signal.aborted) return reply;
@@ -14467,13 +14724,13 @@ var HostfsClient = class {
14467
14724
  const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
14468
14725
  onOpen();
14469
14726
  const reader = response.body.getReader();
14470
- const decoder3 = new TextDecoder();
14727
+ const decoder4 = new TextDecoder();
14471
14728
  let buffered = "";
14472
14729
  try {
14473
14730
  for (; ; ) {
14474
14731
  const { done: done4, value } = await reader.read();
14475
14732
  if (done4) return;
14476
- buffered += decoder3.decode(value, { stream: true });
14733
+ buffered += decoder4.decode(value, { stream: true });
14477
14734
  let end = buffered.indexOf("\n");
14478
14735
  while (end >= 0) {
14479
14736
  const line = buffered.slice(0, end).trim();
@@ -15852,7 +16109,7 @@ var LAYOUT = [
15852
16109
  ];
15853
16110
  var LOAD_SAMPLE_MS = 5e3;
15854
16111
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
15855
- var encoder4 = new TextEncoder();
16112
+ var encoder5 = new TextEncoder();
15856
16113
  var USERS_MODES = ["off", "report", "enforce"];
15857
16114
  function usersMode(mode = "off") {
15858
16115
  if (USERS_MODES.includes(mode)) return mode;
@@ -16009,6 +16266,7 @@ var Launcher = class {
16009
16266
  usersMode;
16010
16267
  dac;
16011
16268
  sealed;
16269
+ clipboard;
16012
16270
  rootFs;
16013
16271
  fileWatches;
16014
16272
  nextPid = 1e3;
@@ -16019,6 +16277,12 @@ var Launcher = class {
16019
16277
  this.rootFs = options.fs;
16020
16278
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
16021
16279
  if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
16280
+ this.clipboard = new ClipboardBroker(
16281
+ options.clipboard,
16282
+ this.usersMode,
16283
+ void 0,
16284
+ (pid) => this.tagOf(pid)
16285
+ );
16022
16286
  this.sealed = new SealedBroker(
16023
16287
  options.sealed,
16024
16288
  this.usersMode,
@@ -16408,7 +16672,7 @@ var Launcher = class {
16408
16672
  start(req) {
16409
16673
  this.sampling();
16410
16674
  if (req.pgid !== void 0 && this.jobs.sessionOf(req.pgid) === void 0) {
16411
- throw fsError("ESRCH", `no process group ${req.pgid}`);
16675
+ throw new SpawnError("ESRCH");
16412
16676
  }
16413
16677
  const pid = this.nextPid++;
16414
16678
  const stack = this.stackFor(req.tag);
@@ -16435,6 +16699,7 @@ var Launcher = class {
16435
16699
  cred: req.cred,
16436
16700
  ...this.owners ? { owners: true } : {},
16437
16701
  sealed: this.sealed,
16702
+ clipboard: this.clipboard,
16438
16703
  ...this.dac ? { dac: this.dac } : {},
16439
16704
  program: req.program,
16440
16705
  argv0: req.argv0,
@@ -16483,6 +16748,8 @@ var Launcher = class {
16483
16748
  this.jobs.add(pid, req.ppid, (sig) => handle.signal(sig), terminal);
16484
16749
  if (req.exec && req.ppid !== void 0) this.jobs.exec(req.ppid, pid, true);
16485
16750
  if (req.pgid !== void 0) this.jobs.join(pid, req.pgid);
16751
+ if (req.group === "lead") this.jobs.lead(pid);
16752
+ if (req.group === "session") this.jobs.setsid(pid);
16486
16753
  this.described.set(pid, {
16487
16754
  argv: [req.argv0, ...req.args],
16488
16755
  tty: terminal?.name ?? null,
@@ -16644,6 +16911,11 @@ var Launcher = class {
16644
16911
  await fds.closeAll();
16645
16912
  throw new SpawnError("EACCES");
16646
16913
  }
16914
+ const group = this.childGroup(ppid, req);
16915
+ if (typeof group === "string") {
16916
+ await fds.closeAll();
16917
+ throw new SpawnError(group);
16918
+ }
16647
16919
  const cred = grant ? grant.cred : this.credOf(ppid);
16648
16920
  const scope = grant ? "" : await this.scopeOf(cred.euid);
16649
16921
  const planned = await this.plan(req.file, req.argv, req.cwd, scope);
@@ -16670,6 +16942,7 @@ var Launcher = class {
16670
16942
  umask: this.umaskOf(ppid),
16671
16943
  cred,
16672
16944
  tag: this.tagOf(ppid),
16945
+ ...group,
16673
16946
  ...exec
16674
16947
  });
16675
16948
  deciding?.attach();
@@ -16677,6 +16950,17 @@ var Launcher = class {
16677
16950
  return childHandle(handle);
16678
16951
  };
16679
16952
  }
16953
+ childGroup(ppid, req) {
16954
+ const { pgid, session } = req;
16955
+ if (pgid === void 0 && session === void 0) return {};
16956
+ const valid = pgid === void 0 || Number.isInteger(pgid) && pgid >= 0;
16957
+ if (req.exec || !valid || session !== void 0 && session !== true) return "EINVAL";
16958
+ if (session) return pgid === void 0 ? { group: "session" } : "EINVAL";
16959
+ if (pgid === 0) return { group: "lead" };
16960
+ const sid = this.jobs.sessionOf(pgid);
16961
+ if (sid === void 0) return "ESRCH";
16962
+ return sid === this.jobs.sidOf(ppid) ? { pgid } : "EPERM";
16963
+ }
16680
16964
  undecided(ppid) {
16681
16965
  const decided = Promise.withResolvers();
16682
16966
  return {
@@ -16707,7 +16991,7 @@ var Launcher = class {
16707
16991
  }, 0);
16708
16992
  }
16709
16993
  forker(ppid, parent) {
16710
- const { exec: _exec, ...image } = parent;
16994
+ const { exec: _exec, pgid: _pgid, group: _group, ...image } = parent;
16711
16995
  return async (state, fds) => childHandle(
16712
16996
  this.start({
16713
16997
  ...image,
@@ -17176,7 +17460,7 @@ var Launcher = class {
17176
17460
  },
17177
17461
  account
17178
17462
  );
17179
- const report = (message3) => options.onData(encoder4.encode(`${message3}\r
17463
+ const report = (message3) => options.onData(encoder5.encode(`${message3}\r
17180
17464
  `));
17181
17465
  const handle = await this.launch(planned, {
17182
17466
  env,
@@ -17213,7 +17497,7 @@ var Launcher = class {
17213
17497
  };
17214
17498
  const stdout = collect(out, options.onStdout);
17215
17499
  const stderr = collect(err, options.onStderr);
17216
- const report = (message3) => stderr(encoder4.encode(`${message3}
17500
+ const report = (message3) => stderr(encoder5.encode(`${message3}
17217
17501
  `));
17218
17502
  if (!planned) {
17219
17503
  report(`${file}: command not found`);
@@ -17401,13 +17685,14 @@ function usersOp(l, req) {
17401
17685
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
17402
17686
  return l.users.list();
17403
17687
  }
17404
- function hostOptions(req, port, sealed) {
17688
+ function hostOptions(req, port, sealed, clipboard) {
17405
17689
  return {
17406
17690
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
17407
17691
  ...req.users !== void 0 ? { users: req.users } : {},
17408
17692
  ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
17409
17693
  ...req.protect ? { protect: protectInit(req.protect) } : {},
17410
- ...req.sealed ? { sealed } : {}
17694
+ ...req.sealed ? { sealed } : {},
17695
+ ...req.clipboard ? { clipboard } : {}
17411
17696
  };
17412
17697
  }
17413
17698
  function policyAsker(port) {
@@ -17437,6 +17722,7 @@ function serveKernel(port, deps) {
17437
17722
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
17438
17723
  const clients = /* @__PURE__ */ new Set();
17439
17724
  const sealed = sealedAsker(port);
17725
+ const clipboard = clipboardAsker(port);
17440
17726
  const approvals = sudoApprovals(port);
17441
17727
  const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
17442
17728
  let holding;
@@ -17501,7 +17787,7 @@ function serveKernel(port, deps) {
17501
17787
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
17502
17788
  ...req.modules ? { modules: req.modules } : {},
17503
17789
  ...req.env ? { env: req.env } : {},
17504
- ...hostOptions(req, port, sealed.hook),
17790
+ ...hostOptions(req, port, sealed.hook, clipboard.hook),
17505
17791
  ...req.approve ? { approve: approvals.ask } : {},
17506
17792
  ...net,
17507
17793
  ...media ? { media } : {},
@@ -17570,6 +17856,8 @@ function serveKernel(port, deps) {
17570
17856
  port.addEventListener("message", (event) => {
17571
17857
  const sealedReply = event.data.sealedReply;
17572
17858
  if (sealedReply) return sealed.answer(sealedReply);
17859
+ const clipboardReply = event.data.clipboardReply;
17860
+ if (clipboardReply) return clipboard.answer(clipboardReply);
17573
17861
  const grant = event.data.hostfsGrant;
17574
17862
  if (grant) return granted2(grant);
17575
17863
  if (allowed(event.data)) return;