@ai-ecoverse/slicc-kernel 1.179.0 → 1.181.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.
@@ -652,8 +652,8 @@ function dirsChannel(metadata) {
652
652
  function noticeOf(data) {
653
653
  if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === "string"), trees: [] };
654
654
  const notice = data ?? {};
655
- const strings = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
656
- return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };
655
+ const strings2 = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
656
+ return { paths: strings2(notice.paths), trees: strings2(notice.trees), outside: true };
657
657
  }
658
658
 
659
659
  // src/fs/types.ts
@@ -7451,6 +7451,82 @@ var KERNEL_SIGINFO = Object.freeze({
7451
7451
  status: 0
7452
7452
  });
7453
7453
 
7454
+ // src/kernel/trace.ts
7455
+ var TRACE_LIMIT = 4096;
7456
+ var TRACE_LIMIT_MAX = 65536;
7457
+ function traceLimit(value2) {
7458
+ if (value2 === void 0) return TRACE_LIMIT;
7459
+ if (!Number.isInteger(value2) || value2 < 1 || value2 > TRACE_LIMIT_MAX) {
7460
+ throw Object.assign(new Error(`a trace limit is an integer from 1 to ${TRACE_LIMIT_MAX}`), {
7461
+ code: "EINVAL"
7462
+ });
7463
+ }
7464
+ return value2;
7465
+ }
7466
+ var TraceHub = class {
7467
+ subs = /* @__PURE__ */ new Set();
7468
+ seq = 0;
7469
+ get idle() {
7470
+ return this.subs.size === 0;
7471
+ }
7472
+ subscribe(tag, viewer, deliver, limit = TRACE_LIMIT) {
7473
+ const sub = {
7474
+ tag,
7475
+ viewer: copyCred(viewer),
7476
+ limit,
7477
+ queue: [],
7478
+ dropped: 0,
7479
+ waiting: false,
7480
+ scheduled: false,
7481
+ deliver
7482
+ };
7483
+ this.subs.add(sub);
7484
+ return {
7485
+ ack: () => {
7486
+ sub.waiting = false;
7487
+ this.schedule(sub);
7488
+ },
7489
+ close: () => {
7490
+ this.subs.delete(sub);
7491
+ sub.queue = [];
7492
+ }
7493
+ };
7494
+ }
7495
+ wants(tags, cred) {
7496
+ if (!tags?.length || this.subs.size === 0) return false;
7497
+ for (const sub of this.subs) if (this.matches(sub, tags, cred)) return true;
7498
+ return false;
7499
+ }
7500
+ publish(tags, cred, draft) {
7501
+ if (!tags?.length) return;
7502
+ let event;
7503
+ for (const sub of this.subs) {
7504
+ if (!this.matches(sub, tags, cred)) continue;
7505
+ event ??= { ...draft, seq: ++this.seq };
7506
+ if (sub.queue.length >= sub.limit) sub.dropped++;
7507
+ else sub.queue.push(event);
7508
+ this.schedule(sub);
7509
+ }
7510
+ }
7511
+ matches(sub, tags, cred) {
7512
+ return tags.includes(sub.tag) && maySee(sub.viewer, cred);
7513
+ }
7514
+ schedule(sub) {
7515
+ if (sub.scheduled || sub.waiting) return;
7516
+ if (sub.queue.length === 0 && sub.dropped === 0) return;
7517
+ sub.scheduled = true;
7518
+ queueMicrotask(() => {
7519
+ sub.scheduled = false;
7520
+ if (!this.subs.has(sub)) return;
7521
+ const batch = { events: sub.queue, dropped: sub.dropped };
7522
+ sub.queue = [];
7523
+ sub.dropped = 0;
7524
+ sub.waiting = true;
7525
+ sub.deliver(batch);
7526
+ });
7527
+ }
7528
+ };
7529
+
7454
7530
  // src/body-idle.ts
7455
7531
  var BODY_IDLE_MS = 5 * 60 * 1e3;
7456
7532
 
@@ -8130,7 +8206,7 @@ var UserDb = class {
8130
8206
  };
8131
8207
 
8132
8208
  // src/client/protocol.ts
8133
- var PROTOCOL = [1, 14];
8209
+ var PROTOCOL = [1, 16];
8134
8210
  var FS_METHODS = [
8135
8211
  "readFile",
8136
8212
  "writeFile",
@@ -8286,9 +8362,9 @@ async function accountOf(l, user) {
8286
8362
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
8287
8363
  return found;
8288
8364
  }
8289
- async function runAs(l, self, requested) {
8290
- if (requested === void 0) return self;
8291
- const target = await accountOf(l, requested);
8365
+ async function runAs(l, self, requested2) {
8366
+ if (requested2 === void 0) return self;
8367
+ const target = await accountOf(l, requested2);
8292
8368
  if (self.uid !== 0 && target.uid !== self.uid) {
8293
8369
  throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
8294
8370
  }
@@ -8298,8 +8374,8 @@ function clientCred(self) {
8298
8374
  return self ? credOf(self) : copyCred(ROOT);
8299
8375
  }
8300
8376
  async function runTagged(l, self, options, tag, session = -1) {
8301
- tagOption(options.tag);
8302
- const tags = withTag(withTag(void 0, tag), options.tag);
8377
+ let tags = withTag(void 0, tag);
8378
+ for (const added of tagList(options.tag)) tags = withTag(tags, added);
8303
8379
  return {
8304
8380
  user: await runAs(l, self, options.user),
8305
8381
  tag,
@@ -8430,6 +8506,34 @@ async function watchOp(l, req, watches, send2, g) {
8430
8506
  watches.set(req.id, unwatch);
8431
8507
  return { result: req.id };
8432
8508
  }
8509
+ function traceRegistry(send2) {
8510
+ const traces = /* @__PURE__ */ new Map();
8511
+ return {
8512
+ answer(l, self, req) {
8513
+ if (req.op === "untrace") {
8514
+ traces.get(req.trace)?.close();
8515
+ return { result: traces.delete(req.trace) };
8516
+ }
8517
+ if (typeof req.tag !== "string" || req.tag === "") {
8518
+ throw fsError("EINVAL", "trace: a tag is a non-empty string");
8519
+ }
8520
+ const limit = traceLimit(req.limit);
8521
+ const id2 = req.id;
8522
+ const deliver = ({ events, dropped: dropped2 }) => send2({ trace: id2, events, ...dropped2 > 0 ? { dropped: dropped2 } : {} });
8523
+ traces.set(id2, l.traces.subscribe(req.tag, credOf(self), deliver, limit));
8524
+ return { result: id2 };
8525
+ },
8526
+ ack(data) {
8527
+ if (typeof data.traceAck !== "number") return false;
8528
+ traces.get(data.traceAck)?.ack();
8529
+ return true;
8530
+ },
8531
+ close() {
8532
+ for (const trace of traces.values()) trace.close();
8533
+ traces.clear();
8534
+ }
8535
+ };
8536
+ }
8433
8537
  var TRAITS_WATCH = -1;
8434
8538
  function traitsWatch(launcher, transport, send2) {
8435
8539
  return launcher.onTraits(() => send2({ traits: transport.traits }));
@@ -8456,6 +8560,35 @@ function requestBinding(send2, port) {
8456
8560
  close: () => unbind?.()
8457
8561
  };
8458
8562
  }
8563
+ async function sudoRunAs(l, self, host, req, reply) {
8564
+ if (self.uid !== 0) throw sudoFailure("EPERM", "sudo: only a root client may run a sudo request");
8565
+ const { stdout, stderr, tag } = req.options;
8566
+ const running = await l.sudoRun(req.replay, {
8567
+ stdout,
8568
+ stderr,
8569
+ tag,
8570
+ base: host.tag,
8571
+ session: host.session
8572
+ });
8573
+ reply(req.id, { started: running.pid });
8574
+ const { status, signal } = await running.ended;
8575
+ return { result: status, ...signalField(signal) };
8576
+ }
8577
+ function answered(pending, req, reply, detach) {
8578
+ pending.then(
8579
+ ({ result, signal, transfer }) => {
8580
+ reply(req.id, { result, ...signal ? { signal } : {} }, transfer);
8581
+ if (req.op === "detach") detach(req.kill === true);
8582
+ },
8583
+ (err) => {
8584
+ const code = err?.code;
8585
+ reply(req.id, {
8586
+ error: err instanceof Error ? err.message : String(err),
8587
+ ...typeof code === "string" ? { code } : {}
8588
+ });
8589
+ }
8590
+ );
8591
+ }
8459
8592
  function serveClient(port, host) {
8460
8593
  let state = "new";
8461
8594
  let self;
@@ -8471,6 +8604,7 @@ function serveClient(port, host) {
8471
8604
  };
8472
8605
  const reply = (id2, body, transfer) => send2({ id: id2, ...body }, transfer);
8473
8606
  const hosts = hostRegistries(send2, ended.promise, port);
8607
+ const traces = traceRegistry(send2);
8474
8608
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
8475
8609
  if (state === "closed") return;
8476
8610
  send2({ bye });
@@ -8478,6 +8612,7 @@ function serveClient(port, host) {
8478
8612
  net?.close();
8479
8613
  for (const unwatch of watches.values()) unwatch();
8480
8614
  watches.clear();
8615
+ traces.close();
8481
8616
  hosts.close();
8482
8617
  for (const session of terminals.values()) session.close();
8483
8618
  terminals.clear();
@@ -8585,6 +8720,11 @@ function serveClient(port, host) {
8585
8720
  case "serve-facade":
8586
8721
  case "unserve-facade":
8587
8722
  return { result: hosts.answer(l, req) };
8723
+ case "trace":
8724
+ case "untrace":
8725
+ return traces.answer(l, self, req);
8726
+ case "sudo-run":
8727
+ return sudoRunAs(l, self, host, req, reply);
8588
8728
  case "detach":
8589
8729
  return { result: true };
8590
8730
  default:
@@ -8608,20 +8748,9 @@ function serveClient(port, host) {
8608
8748
  net?.answer(data);
8609
8749
  return;
8610
8750
  }
8751
+ if (traces.ack(data)) return;
8611
8752
  const req = data;
8612
- handle(req).then(
8613
- ({ result, signal, transfer }) => {
8614
- reply(req.id, { result, ...signal ? { signal } : {} }, transfer);
8615
- if (req.op === "detach") detach(req.kill === true);
8616
- },
8617
- (err) => {
8618
- const code = err?.code;
8619
- reply(req.id, {
8620
- error: err instanceof Error ? err.message : String(err),
8621
- ...typeof code === "string" ? { code } : {}
8622
- });
8623
- }
8624
- );
8753
+ answered(handle(req), req, reply, detach);
8625
8754
  });
8626
8755
  port.addEventListener("close", () => detach());
8627
8756
  port.start?.();
@@ -8779,7 +8908,7 @@ function satisfies(version, range2) {
8779
8908
  }
8780
8909
 
8781
8910
  // src/version.ts
8782
- var KERNEL_VERSION = true ? "1.179.0" : "0.0.0-dev";
8911
+ var KERNEL_VERSION = true ? "1.181.0" : "0.0.0-dev";
8783
8912
  var OS_RELEASE = "6.0.0-slicc";
8784
8913
  function procVersion() {
8785
8914
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9644,27 +9773,27 @@ function hashId(text) {
9644
9773
  return hex4(a) + hex4(b);
9645
9774
  }
9646
9775
  function split2(text, count2) {
9647
- const words2 = [];
9776
+ const words3 = [];
9648
9777
  let rest2 = text;
9649
9778
  for (let i = 0; i < count2; i++) {
9650
9779
  const match = /^(\S+)\s+/.exec(rest2);
9651
9780
  if (!match) return void 0;
9652
- words2.push(match[1]);
9781
+ words3.push(match[1]);
9653
9782
  rest2 = rest2.slice(match[0].length);
9654
9783
  }
9655
- return [words2, rest2.trim()];
9784
+ return [words3, rest2.trim()];
9656
9785
  }
9657
9786
  function jobOf(content, pending, ctx, env) {
9658
9787
  const fields = content.startsWith("@") ? 1 : 5;
9659
9788
  const parts2 = split2(content, fields + (ctx.system ? 1 : 0));
9660
9789
  if (!parts2 || parts2[1] === "") throw new Error("no command");
9661
- const [words2, command] = parts2;
9662
- const schedule = words2.slice(0, fields).join(" ");
9790
+ const [words3, command] = parts2;
9791
+ const schedule = words3.slice(0, fields).join(" ");
9663
9792
  const when = parseWhen(schedule);
9664
9793
  const zone = env.CRON_TZ;
9665
9794
  if (zone && !when.reboot && canonicalZone(zone) === void 0)
9666
9795
  throw new Error(`CRON_TZ: unknown time zone "${zone}"`);
9667
- const user = ctx.system ? words2[fields] : void 0;
9796
+ const user = ctx.system ? words3[fields] : void 0;
9668
9797
  if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
9669
9798
  if (pending !== void 0 && !JOB_NAME.test(pending))
9670
9799
  throw new Error(`bad job name "${pending}"`);
@@ -12645,9 +12774,11 @@ var SpawnError = class extends Error {
12645
12774
  };
12646
12775
  var ChangedError = class extends SpawnError {
12647
12776
  notice;
12648
- constructor(notice) {
12777
+ file;
12778
+ constructor(notice, file) {
12649
12779
  super("EPERM");
12650
12780
  this.notice = notice;
12781
+ this.file = file;
12651
12782
  }
12652
12783
  };
12653
12784
  function interrupted(signal) {
@@ -19693,21 +19824,21 @@ function unescaped(double) {
19693
19824
  }
19694
19825
  function splitWords(text) {
19695
19826
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19696
- const words2 = [];
19827
+ const words3 = [];
19697
19828
  let word;
19698
19829
  while (piece.lastIndex < text.length) {
19699
19830
  const m = piece.exec(text);
19700
19831
  if (!m) return void 0;
19701
19832
  const [, single, double, escaped, plain3, space] = m;
19702
19833
  if (space !== void 0) {
19703
- if (word !== void 0) words2.push(word);
19834
+ if (word !== void 0) words3.push(word);
19704
19835
  word = void 0;
19705
19836
  continue;
19706
19837
  }
19707
19838
  word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
19708
19839
  }
19709
- if (word !== void 0) words2.push(word);
19710
- return words2;
19840
+ if (word !== void 0) words3.push(word);
19841
+ return words3;
19711
19842
  }
19712
19843
  function quoteWord(word) {
19713
19844
  const text = hasControl(word) ? withoutControl(word) : word;
@@ -19728,14 +19859,14 @@ function addPlain(word, plain3) {
19728
19859
  }
19729
19860
  function splitPatterns(text) {
19730
19861
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
19731
- const words2 = [];
19862
+ const words3 = [];
19732
19863
  let word;
19733
19864
  while (piece.lastIndex < text.length) {
19734
19865
  const m = piece.exec(text);
19735
19866
  if (!m) return void 0;
19736
19867
  const [, single, double, escaped, plain3, space] = m;
19737
19868
  if (space !== void 0) {
19738
- if (word !== void 0) words2.push(word);
19869
+ if (word !== void 0) words3.push(word);
19739
19870
  word = void 0;
19740
19871
  continue;
19741
19872
  }
@@ -19743,8 +19874,8 @@ function splitPatterns(text) {
19743
19874
  if (plain3 !== void 0) addPlain(word, plain3);
19744
19875
  else addPart(word, single ?? (double !== void 0 ? unescaped(double) : escaped));
19745
19876
  }
19746
- if (word !== void 0) words2.push(word);
19747
- return words2;
19877
+ if (word !== void 0) words3.push(word);
19878
+ return words3;
19748
19879
  }
19749
19880
  var literal = (pattern) => pattern.map((p) => p ?? "*").join("");
19750
19881
  var isAny = (pattern) => pattern?.length === 1 && pattern[0] === null;
@@ -19817,14 +19948,14 @@ function whoError(who) {
19817
19948
  return `expected a user name, %group or ALL first, not ${shown(who)}`;
19818
19949
  }
19819
19950
  function cwdOf(word) {
19820
- const words2 = splitWords(word);
19821
- const path = words2?.length === 1 ? words2[0] : "";
19951
+ const words3 = splitWords(word);
19952
+ const path = words3?.length === 1 ? words3[0] : "";
19822
19953
  return path.startsWith("/") ? normalizePath(path) : "";
19823
19954
  }
19824
19955
  function commandOf2(text, after) {
19825
- const words2 = splitPatterns(text);
19826
- if (words2 === void 0) return "a quote in the command is not closed, or it ends in a \\";
19827
- const [first, ...args] = words2;
19956
+ const words3 = splitPatterns(text);
19957
+ if (words3 === void 0) return "a quote in the command is not closed, or it ends in a \\";
19958
+ const [first, ...args] = words3;
19828
19959
  const path = first ? literal(first) : "";
19829
19960
  if (path === "") return `expected a command after ${after}`;
19830
19961
  if (!path.startsWith("/")) return `expected an absolute command path, not ${shown(path)}`;
@@ -20137,6 +20268,9 @@ function recordable(cwd, argv) {
20137
20268
  function changedText(file) {
20138
20269
  return `sudo: ${file} changed since it was approved`;
20139
20270
  }
20271
+ function requestedText(file) {
20272
+ return `sudo: ${file} changed since it was requested`;
20273
+ }
20140
20274
  function changedFile(approved, now) {
20141
20275
  const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
20142
20276
  return [...files].find((file) => approved[file] !== now[file]);
@@ -20144,6 +20278,67 @@ function changedFile(approved, now) {
20144
20278
  function pinned(rule, bound3) {
20145
20279
  return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
20146
20280
  }
20281
+ var REPLAY_ID = /^[!-~]{1,256}$/;
20282
+ var REPLAY_TAGS_MAX = 64;
20283
+ var REPLAY_ARGV_MAX = 4096;
20284
+ var REPLAY_BYTES_MAX = 1024 * 1024;
20285
+ var NOT_A_REQUEST = "sudo: that isn't a sudo request this kernel can run";
20286
+ function strings(value2, max) {
20287
+ return Array.isArray(value2) && value2.length <= max && value2.every((s) => typeof s === "string");
20288
+ }
20289
+ function words(value2) {
20290
+ if (!strings(value2, REPLAY_ARGV_MAX) || value2.length === 0) return false;
20291
+ const bytes = value2.reduce((n, s) => n + s.length + 1, 0);
20292
+ return bytes <= REPLAY_BYTES_MAX && !value2.some((s) => s.includes("\0"));
20293
+ }
20294
+ function filesOf(value2) {
20295
+ if (value2 === void 0) return {};
20296
+ if (!Array.isArray(value2) || value2.length > OPERANDS_MAX) return void 0;
20297
+ const files = {};
20298
+ for (const file of value2) {
20299
+ const { path, sha256: hex4 } = file ?? {};
20300
+ if (typeof path !== "string" || typeof hex4 !== "string") return void 0;
20301
+ files[path] = hex4;
20302
+ }
20303
+ return files;
20304
+ }
20305
+ function flagsOf(value2) {
20306
+ if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return void 0;
20307
+ const entries = Object.entries(value2);
20308
+ if (entries.length > ROOT_ONLY_MAX || entries.some(([, v]) => typeof v !== "boolean")) {
20309
+ return void 0;
20310
+ }
20311
+ return Object.fromEntries(entries);
20312
+ }
20313
+ function replayOf(value2) {
20314
+ if (typeof value2 !== "object" || value2 === null) return failed2("EINVAL", NOT_A_REQUEST);
20315
+ const r = value2;
20316
+ if (r.validate === true) return failed2("EINVAL", "sudo: a sudo -v request runs no command");
20317
+ if (r.stdin === "pipe" || r.stdin === "file") {
20318
+ return failed2("EINVAL", `sudo: the request read a ${r.stdin}, and that input is gone`);
20319
+ }
20320
+ const { id: id2, user, uid, target, argv, path, cwd, stdin, tag, tags } = r;
20321
+ const rootOnly2 = flagsOf(r.rootOnly);
20322
+ const bound3 = filesOf(r.userWritable);
20323
+ const named = (s) => typeof s === "string" && s !== "";
20324
+ const absolute2 = (s) => typeof s === "string" && s.startsWith("/") && s.length <= PATH_MAX && !s.includes("\0");
20325
+ const valid = typeof id2 === "string" && REPLAY_ID.test(id2) && named(user) && named(target) && Number.isInteger(uid) && uid >= 0 && words(argv) && absolute2(path) && absolute2(cwd) && (stdin === "tty" || stdin === "none") && (tag === void 0 || typeof tag === "string") && (tags === void 0 || strings(tags, REPLAY_TAGS_MAX)) && rootOnly2 !== void 0 && bound3 !== void 0;
20326
+ if (!valid) return failed2("EINVAL", NOT_A_REQUEST);
20327
+ const all = tags ?? (tag === void 0 ? [] : [tag]);
20328
+ return {
20329
+ id: id2,
20330
+ user,
20331
+ uid,
20332
+ target,
20333
+ argv,
20334
+ path,
20335
+ cwd,
20336
+ stdin,
20337
+ rootOnly: rootOnly2,
20338
+ bound: bound3,
20339
+ tags: [...new Set(all)]
20340
+ };
20341
+ }
20147
20342
  var VISUDO_FILES_MAX = 64;
20148
20343
  var VISUDO_TEXT_MAX = 1024 * 1024;
20149
20344
  function visudoCall(req) {
@@ -20545,6 +20740,61 @@ var Sudo = class {
20545
20740
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
20546
20741
  return this.command(session, root, mine, call);
20547
20742
  }
20743
+ async replay(value2) {
20744
+ const r = replayOf(value2);
20745
+ if ("error" in r) return r;
20746
+ if (this.o.mode !== "enforce") {
20747
+ return failed2("EINVAL", `sudo: users are '${this.o.mode}', so no request needed approval`);
20748
+ }
20749
+ const self = await this.o.users.lookup(r.user);
20750
+ if (!self) return failed2("ENOENT", `sudo: unknown user ${r.user}`);
20751
+ if (self.uid !== r.uid) return failed2("EPERM", `sudo: ${self.name} is no longer uid ${r.uid}`);
20752
+ const target = await this.lookup(r.target);
20753
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${r.target}`);
20754
+ const cred = credOf(self);
20755
+ const [word = "", ...args] = r.argv;
20756
+ const argv = [r.path, ...args];
20757
+ if (!await this.mayAsk(self, target, r, argv)) {
20758
+ const quoted = argv.map(quoteWord).join(" ");
20759
+ return failed2(
20760
+ "EACCES",
20761
+ `sudo: ${self.name} may no longer ask to run ${quoted} as ${target.name}`
20762
+ );
20763
+ }
20764
+ if (await this.canonical(r.cwd) !== r.cwd) {
20765
+ return failed2("EACCES", `sudo: ${r.cwd} changed since it was requested`);
20766
+ }
20767
+ if (!await this.reachable(cred, r.cwd)) {
20768
+ return failed2(
20769
+ "EACCES",
20770
+ `sudo: cannot run in ${r.cwd}: it isn't a directory ${self.name} can enter`
20771
+ );
20772
+ }
20773
+ const path = await this.locate(word, r.cwd);
20774
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
20775
+ if (path !== r.path) return failed2("EPERM", `sudo: ${word} now runs ${path}, not ${r.path}`);
20776
+ const bound3 = await this.bound(path, r.cwd);
20777
+ if (!bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
20778
+ const changed = changedFile(r.bound, bound3);
20779
+ if (changed !== void 0) return failed2("EPERM", requestedText(changed));
20780
+ const now = await this.rootOnly(r.cwd, args);
20781
+ const flipped = Object.keys(r.rootOnly).find((at) => r.rootOnly[at] && now[at] !== true);
20782
+ if (flipped !== void 0) return failed2("EPERM", `sudo: ${flipped} is no longer root-only`);
20783
+ const { grant } = this.grant(self, target, cred, r.argv, path, r.cwd, void 0, bound3);
20784
+ (this.o.warn ?? console.warn)(
20785
+ `sudo: ${self.name} : TTY=unknown ; PWD=${quoteWord(r.cwd)} ; USER=${target.name} ; REPLAY=${r.id} ; COMMAND=${argv.map(quoteWord).join(" ")}`
20786
+ );
20787
+ return { grant, caller: self, target, tags: r.tags };
20788
+ }
20789
+ async mayAsk(self, target, r, argv) {
20790
+ if (self.uid === 0) return true;
20791
+ const cred = credOf(self);
20792
+ const { rules } = await this.policyRules({ cred, pid: 0, stdin: r.stdin });
20793
+ const applies = await this.applies(self, cred);
20794
+ return rules.some(
20795
+ (rule) => applies(rule.who) && (rule.target === "ALL" || rule.target === target.name) && (rule.cwd === void 0 || rule.cwd === r.cwd) && stdinMatches(rule, r.stdin) && commandMatches(rule.cmnd, argv) && pinned(rule, r.bound)
20796
+ );
20797
+ }
20548
20798
  async command(s, root, mine, call) {
20549
20799
  const { caller, self, target, parsed: parsed2 } = s;
20550
20800
  const { cred } = caller;
@@ -20724,8 +20974,8 @@ ${lines2}
20724
20974
  const st = await fs.lstat(path).catch(() => void 0);
20725
20975
  if (st && !await this.trusted(path)) return false;
20726
20976
  const old = st ? await fs.readFile(path) : "";
20727
- const words2 = recordedCommand(argv);
20728
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
20977
+ const words3 = recordedCommand(argv);
20978
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words3}
20729
20979
  `;
20730
20980
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
20731
20981
  ${line}`;
@@ -22030,8 +22280,8 @@ function ipv6(host) {
22030
22280
  const head = groups(halves[0]);
22031
22281
  const back = halves.length === 2 ? groups(halves[1]) : [];
22032
22282
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
22033
- const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
22034
- const missing6 = 8 - words2.length - tail2.length;
22283
+ const words3 = [...head, ...back].map((g) => Number.parseInt(g, 16));
22284
+ const missing6 = 8 - words3.length - tail2.length;
22035
22285
  if (halves.length === 2 ? missing6 < 1 : missing6 !== 0) return void 0;
22036
22286
  const all = [
22037
22287
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -25672,7 +25922,7 @@ function selectFlag(req) {
25672
25922
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
25673
25923
  return TLS_SERVER_SELECT;
25674
25924
  }
25675
- function flagsOf(req) {
25925
+ function flagsOf2(req) {
25676
25926
  return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
25677
25927
  }
25678
25928
  async function engineOf(proc, { select, keylog }) {
@@ -25723,7 +25973,7 @@ async function serverOf(proc, req) {
25723
25973
  let mine;
25724
25974
  try {
25725
25975
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
25726
- const flags2 = flagsOf(req);
25976
+ const flags2 = flagsOf2(req);
25727
25977
  const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
25728
25978
  return {
25729
25979
  engine,
@@ -27612,7 +27862,7 @@ var WRITE_ACCESS = 3;
27612
27862
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
27613
27863
  var STUCK_ESCAPES = /* @__PURE__ */ new Set([SIG.INT, SIG.TERM, SIG.QUIT, SIG.HUP]);
27614
27864
  var DEFAULT_UMASK2 = 18;
27615
- function words(value2) {
27865
+ function words2(value2) {
27616
27866
  if (!Array.isArray(value2) || !value2.every((w) => typeof w === "string")) {
27617
27867
  throw new KernelError("EINVAL");
27618
27868
  }
@@ -29065,7 +29315,7 @@ var WasmProcess = class {
29065
29315
  if (!sudo) throw new KernelError("ENOSYS");
29066
29316
  const call = {
29067
29317
  op: "proc-sudo",
29068
- args: words(req.args),
29318
+ args: words2(req.args),
29069
29319
  cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
29070
29320
  ...typeof req.term === "string" ? { term: req.term } : {}
29071
29321
  };
@@ -30069,6 +30319,9 @@ var JobTable = class {
30069
30319
  idOf(pid) {
30070
30320
  return this.members.get(pid)?.shownAs ?? pid;
30071
30321
  }
30322
+ execParentOf(pid) {
30323
+ return this.members.get(pid)?.execParent;
30324
+ }
30072
30325
  byId(id2) {
30073
30326
  for (const member of this.members.values()) if (member.shownAs === id2) return member;
30074
30327
  return this.member(id2);
@@ -33672,7 +33925,40 @@ function describe(req, tty) {
33672
33925
  }
33673
33926
  function refuseChanged(approved, now) {
33674
33927
  const changed = changedFile(approved, now);
33675
- if (changed !== void 0) throw new ChangedError(changedText(changed));
33928
+ if (changed !== void 0) throw new ChangedError(changedText(changed), changed);
33929
+ }
33930
+ var O_WRONLY2 = 1;
33931
+ var OUTPUT_MODE = 384;
33932
+ var ERRNO_TEXT = {
33933
+ EACCES: "Permission denied",
33934
+ EISDIR: "Is a directory",
33935
+ ENOENT: "No such file or directory",
33936
+ ENOTDIR: "Not a directory",
33937
+ EROFS: "Read-only file system"
33938
+ };
33939
+ function sudoFailure(errno3, message3) {
33940
+ return Object.assign(new Error(message3), { code: errno3 });
33941
+ }
33942
+ function sudoCause(err) {
33943
+ const code = codeOf8(err);
33944
+ if (typeof code !== "string") throw err;
33945
+ return code;
33946
+ }
33947
+ function requested(err) {
33948
+ if (err instanceof ChangedError) throw sudoFailure(err.code, requestedText(err.file));
33949
+ throw err;
33950
+ }
33951
+ var MAX_SPAWN_TAGS = 8;
33952
+ function tagList(value2) {
33953
+ if (!Array.isArray(value2)) {
33954
+ tagOption(value2);
33955
+ return value2 === void 0 ? [] : [value2];
33956
+ }
33957
+ if (value2.length > MAX_SPAWN_TAGS) {
33958
+ throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
33959
+ }
33960
+ for (const each of value2) tagOption(each ?? 0);
33961
+ return value2;
33676
33962
  }
33677
33963
  function linksOf2(req) {
33678
33964
  return req.links === void 0 ? {} : { links: req.links };
@@ -33798,27 +34084,27 @@ function appendedRoots(given) {
33798
34084
  }
33799
34085
  return [...roots];
33800
34086
  }
33801
- function plainEnv(words2) {
33802
- const next3 = words2[1];
33803
- if (baseName2(words2[0] ?? "") !== "env" || next3 === void 0) return false;
34087
+ function plainEnv(words3) {
34088
+ const next3 = words3[1];
34089
+ if (baseName2(words3[0] ?? "") !== "env" || next3 === void 0) return false;
33804
34090
  return next3 === "-S" || !next3.startsWith("-") && !next3.includes("=");
33805
34091
  }
33806
- function shebangCommand(words2) {
33807
- if (!plainEnv(words2)) {
33808
- const arg2 = words2.slice(1).join(" ");
33809
- return { interp: words2[0], args: arg2 ? [arg2] : [] };
34092
+ function shebangCommand(words3) {
34093
+ if (!plainEnv(words3)) {
34094
+ const arg2 = words3.slice(1).join(" ");
34095
+ return { interp: words3[0], args: arg2 ? [arg2] : [] };
33810
34096
  }
33811
- const split3 = words2[1] === "-S";
33812
- const [interp, ...rest2] = words2.slice(split3 ? 2 : 1);
34097
+ const split3 = words3[1] === "-S";
34098
+ const [interp, ...rest2] = words3.slice(split3 ? 2 : 1);
33813
34099
  const arg = rest2.join(" ");
33814
34100
  return { interp, args: split3 ? rest2 : arg ? [arg] : [] };
33815
34101
  }
33816
34102
  function interpreter(head) {
33817
- const words2 = shebang(head);
33818
- if (!words2) return "node";
33819
- if (baseName2(words2[0]) === "env") words2.shift();
33820
- if (words2[0] === "-S") words2.shift();
33821
- return baseName2(words2[0] ?? "");
34103
+ const words3 = shebang(head);
34104
+ if (!words3) return "node";
34105
+ if (baseName2(words3[0]) === "env") words3.shift();
34106
+ if (words3[0] === "-S") words3.shift();
34107
+ return baseName2(words3[0] ?? "");
33822
34108
  }
33823
34109
  function expandDefaults(defaults2, env) {
33824
34110
  const out = {};
@@ -34593,9 +34879,9 @@ var Launcher = class {
34593
34879
  const head = await this.head(script);
34594
34880
  if (!head) return void 0;
34595
34881
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
34596
- const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
34597
- if (!words2) return void 0;
34598
- const { interp, args } = shebangCommand(words2);
34882
+ const words3 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
34883
+ if (!words3) return void 0;
34884
+ const { interp, args } = shebangCommand(words3);
34599
34885
  const passed = [...args, command ? script : file, ...argv.slice(1)];
34600
34886
  if (!interp) return void 0;
34601
34887
  const found = await this.resolve(interp, interp, cwd, scope);
@@ -34826,7 +35112,7 @@ var Launcher = class {
34826
35112
  const hex4 = pinned2[real];
34827
35113
  if (hex4 !== void 0) return { [real]: hex4 };
34828
35114
  if (await this.sudoer.trusted(real)) return {};
34829
- throw new ChangedError(changedText(real));
35115
+ throw new ChangedError(changedText(real), real);
34830
35116
  }
34831
35117
  async pinnedBytes(pin, bytes) {
34832
35118
  const [real = ""] = Object.keys(pin);
@@ -35105,6 +35391,10 @@ var Launcher = class {
35105
35391
  }
35106
35392
  async launch(asked, req) {
35107
35393
  const cwd = await physicalPathAsync(this.base, req.cwd);
35394
+ if (req.exactCwd && cwd !== req.cwd) {
35395
+ await req.fds.closeAll();
35396
+ throw sudoFailure("EACCES", `sudo: ${req.cwd} changed since it was requested`);
35397
+ }
35108
35398
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
35109
35399
  await this.pnpmReady();
35110
35400
  let loaded;
@@ -35283,10 +35573,12 @@ var Launcher = class {
35283
35573
  });
35284
35574
  const fit = await (handle.ready ?? true);
35285
35575
  if (fit) {
35576
+ this.traceAdmitted(handle.pid, true);
35286
35577
  await fds.closeAll();
35287
35578
  return handle;
35288
35579
  }
35289
35580
  await handle.exited;
35581
+ this.traceAdmitted(handle.pid, false);
35290
35582
  await this.memories.refused().catch(async (err) => {
35291
35583
  await fds.closeAll();
35292
35584
  throw err;
@@ -35417,10 +35709,12 @@ var Launcher = class {
35417
35709
  if (req.group === "lead") this.jobs.lead(pid);
35418
35710
  if (req.group === "session") this.jobs.setsid(pid);
35419
35711
  this.described.set(pid, describe(req, terminal?.name ?? null));
35712
+ this.traceStarted(pid, req);
35420
35713
  handle.onState(
35421
35714
  (state) => state === "stopped" ? this.stopped.add(pid) : this.stopped.delete(pid)
35422
35715
  );
35423
- void handle.exited.then(() => {
35716
+ void handle.exited.then((status) => {
35717
+ this.traceEnded(pid, handle, status);
35424
35718
  this.cwds.release(this.cwdSlots.get(pid));
35425
35719
  this.cwdSlots.delete(pid);
35426
35720
  this.cwdPaths.delete(pid);
@@ -35535,6 +35829,71 @@ var Launcher = class {
35535
35829
  imageTimes = /* @__PURE__ */ new Map();
35536
35830
  childTimes = /* @__PURE__ */ new Map();
35537
35831
  stopped = /* @__PURE__ */ new Set();
35832
+ traces = new TraceHub();
35833
+ traceHeld = /* @__PURE__ */ new Map();
35834
+ replacedImages = /* @__PURE__ */ new Set();
35835
+ traceStarted(pid, req) {
35836
+ const replaces = req.exec ? req.ppid : void 0;
35837
+ if (req.quietOom) this.traceHeld.set(pid, { replaces, events: [] });
35838
+ else if (replaces !== void 0) this.replacedImages.add(replaces);
35839
+ this.traceEmit(pid, () => {
35840
+ const { pid: shown2, ppid } = this.identityOf(pid);
35841
+ const described2 = this.described.get(pid);
35842
+ const { argv, started, tty: _tty, exeId: _exeId, ...exe } = described2;
35843
+ return {
35844
+ type: req.fork ? "fork" : "exec",
35845
+ pid: shown2,
35846
+ ppid,
35847
+ pgid: this.jobs.pgidOf(pid),
35848
+ argv: [...argv],
35849
+ ...exe,
35850
+ uid: this.credOf(pid).euid,
35851
+ at: started,
35852
+ ...req.exec ? { replaced: true } : {}
35853
+ };
35854
+ });
35855
+ }
35856
+ traceEnded(pid, handle, status) {
35857
+ if (this.replacedImages.delete(pid)) return;
35858
+ this.traceEmit(pid, () => ({
35859
+ type: "exit",
35860
+ pid: this.jobs.idOf(pid),
35861
+ status,
35862
+ ...signalField(handle.termsig()),
35863
+ ...this.chainTimes(pid),
35864
+ at: Date.now()
35865
+ }));
35866
+ }
35867
+ traceEmit(pid, build) {
35868
+ if (this.traces.idle) return;
35869
+ const tags = this.tagsOf(pid);
35870
+ const cred = this.credOf(pid);
35871
+ if (!this.traces.wants(tags, cred)) return;
35872
+ const held = this.traceHeld.get(pid);
35873
+ if (held) held.events.push({ tags, cred: copyCred(cred), draft: build() });
35874
+ else this.traces.publish(tags, cred, build());
35875
+ }
35876
+ traceAdmitted(pid, fit) {
35877
+ const held = this.traceHeld.get(pid);
35878
+ if (!held) return;
35879
+ this.traceHeld.delete(pid);
35880
+ if (!fit) return;
35881
+ if (held.replaces !== void 0) this.replacedImages.add(held.replaces);
35882
+ for (const { tags, cred, draft } of held.events) this.traces.publish(tags, cred, draft);
35883
+ }
35884
+ chainTimes(pid) {
35885
+ let utime = 0;
35886
+ let stime = 0;
35887
+ let timed2 = false;
35888
+ for (let at = pid; at !== void 0; at = this.jobs.execParentOf(at)) {
35889
+ const t = this.timesAt(at);
35890
+ if (!t) continue;
35891
+ timed2 = true;
35892
+ utime += t.utime;
35893
+ stime += t.stime;
35894
+ }
35895
+ return timed2 ? { utime, stime } : {};
35896
+ }
35538
35897
  stateOf(pid) {
35539
35898
  if (!this.processes.has(pid)) return "Z";
35540
35899
  return this.stopped.has(pid) ? "T" : "S";
@@ -36731,6 +37090,96 @@ var Launcher = class {
36731
37090
  const signal = handle.termsig();
36732
37091
  return { status, ...signal ? { signal } : {}, stdout: concat7(out), stderr: concat7(err) };
36733
37092
  }
37093
+ async sudoRun(replay, options = {}) {
37094
+ const added = tagList(options.tag);
37095
+ const replayed = await this.sudoer.replay(replay);
37096
+ if ("error" in replayed) throw sudoFailure(replayed.errno, replayed.error);
37097
+ const { grant, caller, target } = replayed;
37098
+ let tags = withTag(void 0, options.base);
37099
+ for (const tag of [...replayed.tags, ...added]) tags = withTag(tags, tag);
37100
+ const tagged = { tags, tagBase: options.base === void 0 ? 0 : 1 };
37101
+ await this.swaps.settled();
37102
+ this.resetCatalogs();
37103
+ const req = { file: grant.file, argv: grant.argv, env: grant.env, cwd: grant.cwd };
37104
+ const ctx = { ppid: 0, cred: grant.cred, scope: "" };
37105
+ const planned = await this.planOne(req, ctx).catch((err2) => {
37106
+ const code = sudoCause(err2);
37107
+ const word = grant.argv[0] ?? grant.file;
37108
+ if (code === "ENOENT") throw sudoFailure(code, `sudo: ${word}: command not found`);
37109
+ throw sudoFailure(code, `sudo: ${grant.file}: ${ERRNO_TEXT[code] ?? code}`);
37110
+ });
37111
+ const pin = await this.unchanged(planned, grant.file, grant.cwd, grant).catch(requested);
37112
+ const fds = await this.sudoOutputs(options, caller, this.stackFor(tags, tagged.tagBase));
37113
+ const err = fds.get(2).file;
37114
+ const report = (message3) => void Promise.resolve(err.write?.(encoder10.encode(`${message3}
37115
+ `))).catch(() => void 0);
37116
+ const replan = () => this.planOne(req, ctx);
37117
+ if (!await this.grantedCwd(grant.caller, grant.cwd)) {
37118
+ await fds.closeAll();
37119
+ throw sudoFailure("EACCES", `sudo: ${grant.cwd} changed since it was requested`);
37120
+ }
37121
+ const handle = await this.launch(
37122
+ { ...planned, replan },
37123
+ {
37124
+ ...pin,
37125
+ env: grant.env,
37126
+ cwd: grant.cwd,
37127
+ fds,
37128
+ report,
37129
+ ...this.identity(target, tagged, options.session),
37130
+ ...caller.umask !== void 0 ? { umask: caller.umask } : {},
37131
+ links: grant.cred.euid,
37132
+ exactCwd: true
37133
+ }
37134
+ ).catch(requested);
37135
+ const ended = handle.exited.then((status) => {
37136
+ const signal = handle.termsig();
37137
+ return { status, ...signal ? { signal } : {} };
37138
+ });
37139
+ return { pid: handle.pid, ended };
37140
+ }
37141
+ async sudoOutputs(options, caller, stack) {
37142
+ const fds = new FdTable();
37143
+ const opened2 = /* @__PURE__ */ new Map();
37144
+ fds.installAt(0, deviceFile("null"));
37145
+ try {
37146
+ fds.installAt(1, await this.sudoOutput(options.stdout, caller, stack, opened2));
37147
+ fds.installAt(2, await this.sudoOutput(options.stderr, caller, stack, opened2));
37148
+ } catch (err) {
37149
+ await fds.closeAll();
37150
+ throw err;
37151
+ }
37152
+ return fds;
37153
+ }
37154
+ async sudoOutput(path, caller, stack, opened2) {
37155
+ if (path === void 0) return deviceFile("null");
37156
+ if (typeof path !== "string" || !path.startsWith("/")) {
37157
+ throw sudoFailure("EINVAL", `sudo: ${String(path)} is not an absolute path`);
37158
+ }
37159
+ const cred = credOf(caller);
37160
+ const walked = await walk(this.fs, cred, normalizePath(path), true, caller.uid);
37161
+ const real = walked.path;
37162
+ const same3 = opened2.get(real);
37163
+ if (same3) return same3.retain();
37164
+ try {
37165
+ if (walked.denied !== void 0) throw fsError("EACCES", path);
37166
+ await writable({ ...this.execGuard(cred, 0), links: caller.uid }, "open", real);
37167
+ const owner = { uid: caller.uid, gid: caller.gid, mode: OUTPUT_MODE };
37168
+ await stack.noLinks.writeFile(real, new Uint8Array(0), owner);
37169
+ } catch (err) {
37170
+ const code = sudoCause(err);
37171
+ throw sudoFailure(code, `sudo: ${path}: ${ERRNO_TEXT[code] ?? code}`);
37172
+ }
37173
+ const file = vfsFile(stack.fs, { path: real, flags: O_WRONLY2, position: 0 }, stack.nodes);
37174
+ const again = await walk(this.fs, cred, real, true, caller.uid);
37175
+ const st = await stack.noLinks.lstat(real).catch(() => void 0);
37176
+ if (again.path !== real || again.denied !== void 0 || !st?.isFile) {
37177
+ await file.release();
37178
+ throw sudoFailure("EACCES", `sudo: ${path} changed while it was opened`);
37179
+ }
37180
+ opened2.set(real, file);
37181
+ return file;
37182
+ }
36734
37183
  };
36735
37184
  function cronLocks() {
36736
37185
  return globalThis.navigator?.locks;
@@ -37061,7 +37510,7 @@ function sudoApprovals(port) {
37061
37510
  }
37062
37511
  };
37063
37512
  }
37064
- function answered(data, remote, uplink, approvals) {
37513
+ function answered2(data, remote, uplink, approvals) {
37065
37514
  if (data.sudoAnswer) approvals.answer(data.sudoAnswer);
37066
37515
  else if (data.net !== void 0) remote?.receive(data);
37067
37516
  else if (typeof data.uplink === "string") uplink?.receive(data);
@@ -37196,11 +37645,38 @@ function policyAsker(port) {
37196
37645
  }
37197
37646
  };
37198
37647
  }
37648
+ function processOps(ready2, reply) {
37649
+ const terminals = /* @__PURE__ */ new Map();
37650
+ async function terminal(req) {
37651
+ const onData = (bytes) => reply(req.id, { fd: 1, bytes });
37652
+ const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
37653
+ terminals.set(req.id, session);
37654
+ reply(req.id, { started: session.pid });
37655
+ try {
37656
+ const status = await session.exited;
37657
+ const signal = session.termsig();
37658
+ return signal ? { status, signal } : status;
37659
+ } finally {
37660
+ terminals.delete(req.id);
37661
+ }
37662
+ }
37663
+ async function sudoRun(req) {
37664
+ const running = await (await ready2()).sudoRun(req.replay, req.options);
37665
+ reply(req.id, { started: running.pid });
37666
+ return running.ended;
37667
+ }
37668
+ function onTerminal(req) {
37669
+ const session = terminals.get(req.terminal);
37670
+ if (!session) throw new Error(`no terminal ${req.terminal}`);
37671
+ act2(session, req);
37672
+ return true;
37673
+ }
37674
+ return { terminal, sudoRun, act: onTerminal };
37675
+ }
37199
37676
  function serveKernel(port, deps) {
37200
37677
  let launcher;
37201
37678
  let remote;
37202
37679
  let uplink;
37203
- const terminals = /* @__PURE__ */ new Map();
37204
37680
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
37205
37681
  const clients = /* @__PURE__ */ new Set();
37206
37682
  const sealed = sealedAsker(port);
@@ -37229,19 +37705,7 @@ function serveKernel(port, deps) {
37229
37705
  if (!launcher) throw new Error("the kernel is not initialized");
37230
37706
  return launcher;
37231
37707
  }
37232
- async function terminal(req) {
37233
- const onData = (bytes) => reply(req.id, { fd: 1, bytes });
37234
- const session = await (await ready2()).openTerminal(req.argv, { ...req.options, onData });
37235
- terminals.set(req.id, session);
37236
- reply(req.id, { started: session.pid });
37237
- try {
37238
- const status = await session.exited;
37239
- const signal = session.termsig();
37240
- return signal ? { status, signal } : status;
37241
- } finally {
37242
- terminals.delete(req.id);
37243
- }
37244
- }
37708
+ const processes2 = processOps(ready2, reply);
37245
37709
  async function init(req) {
37246
37710
  launcher = rootOf(deps, req).then(async (root) => {
37247
37711
  req = await rootNamed(deps, req, root);
@@ -37296,7 +37760,8 @@ function serveKernel(port, deps) {
37296
37760
  if (req.op === "init") return init(req);
37297
37761
  if (isAttestRequest(req)) return attestOp(await ready2(), req);
37298
37762
  if (req.op === "sealed-placeholders") return (await ready2()).setPlaceholders(req.list);
37299
- if (req.op === "open-terminal") return terminal(req);
37763
+ if (req.op === "open-terminal") return processes2.terminal(req);
37764
+ if (req.op === "sudo-run") return processes2.sudoRun(req);
37300
37765
  if (req.op === "connect") return connect2(req.user, req.tag, req.requests);
37301
37766
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
37302
37767
  if (req.op === "routes" || req.op === "transport-traits") {
@@ -37305,12 +37770,7 @@ function serveKernel(port, deps) {
37305
37770
  if (req.op === "dial") return dial(await ready2(), req);
37306
37771
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
37307
37772
  return mountOp(req);
37308
- if (req.op === "terminal") {
37309
- const session = terminals.get(req.terminal);
37310
- if (!session) throw new Error(`no terminal ${req.terminal}`);
37311
- act2(session, req);
37312
- return true;
37313
- }
37773
+ if (req.op === "terminal") return processes2.act(req);
37314
37774
  return (await ready2()).run(req.argv, {
37315
37775
  ...req.options,
37316
37776
  onStdout: (bytes) => reply(req.id, { fd: 1, bytes }),
@@ -37346,7 +37806,7 @@ function serveKernel(port, deps) {
37346
37806
  const grant = event.data.hostfsGrant;
37347
37807
  if (grant) return granted2(grant);
37348
37808
  if (allowed(event.data)) return;
37349
- if (answered(event.data, remote, uplink, approvals)) return;
37809
+ if (answered2(event.data, remote, uplink, approvals)) return;
37350
37810
  const req = event.data;
37351
37811
  handle(req).then(
37352
37812
  (result) => reply(req.id, { result }, transferOf2(req, result)),