@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.
package/dist/node.js CHANGED
@@ -2751,6 +2751,82 @@ var KERNEL_SIGINFO = Object.freeze({
2751
2751
  status: 0
2752
2752
  });
2753
2753
 
2754
+ // src/kernel/trace.ts
2755
+ var TRACE_LIMIT = 4096;
2756
+ var TRACE_LIMIT_MAX = 65536;
2757
+ function traceLimit(value2) {
2758
+ if (value2 === void 0) return TRACE_LIMIT;
2759
+ if (!Number.isInteger(value2) || value2 < 1 || value2 > TRACE_LIMIT_MAX) {
2760
+ throw Object.assign(new Error(`a trace limit is an integer from 1 to ${TRACE_LIMIT_MAX}`), {
2761
+ code: "EINVAL"
2762
+ });
2763
+ }
2764
+ return value2;
2765
+ }
2766
+ var TraceHub = class {
2767
+ subs = /* @__PURE__ */ new Set();
2768
+ seq = 0;
2769
+ get idle() {
2770
+ return this.subs.size === 0;
2771
+ }
2772
+ subscribe(tag, viewer, deliver, limit = TRACE_LIMIT) {
2773
+ const sub = {
2774
+ tag,
2775
+ viewer: copyCred(viewer),
2776
+ limit,
2777
+ queue: [],
2778
+ dropped: 0,
2779
+ waiting: false,
2780
+ scheduled: false,
2781
+ deliver
2782
+ };
2783
+ this.subs.add(sub);
2784
+ return {
2785
+ ack: () => {
2786
+ sub.waiting = false;
2787
+ this.schedule(sub);
2788
+ },
2789
+ close: () => {
2790
+ this.subs.delete(sub);
2791
+ sub.queue = [];
2792
+ }
2793
+ };
2794
+ }
2795
+ wants(tags, cred) {
2796
+ if (!tags?.length || this.subs.size === 0) return false;
2797
+ for (const sub of this.subs) if (this.matches(sub, tags, cred)) return true;
2798
+ return false;
2799
+ }
2800
+ publish(tags, cred, draft) {
2801
+ if (!tags?.length) return;
2802
+ let event;
2803
+ for (const sub of this.subs) {
2804
+ if (!this.matches(sub, tags, cred)) continue;
2805
+ event ??= { ...draft, seq: ++this.seq };
2806
+ if (sub.queue.length >= sub.limit) sub.dropped++;
2807
+ else sub.queue.push(event);
2808
+ this.schedule(sub);
2809
+ }
2810
+ }
2811
+ matches(sub, tags, cred) {
2812
+ return tags.includes(sub.tag) && maySee(sub.viewer, cred);
2813
+ }
2814
+ schedule(sub) {
2815
+ if (sub.scheduled || sub.waiting) return;
2816
+ if (sub.queue.length === 0 && sub.dropped === 0) return;
2817
+ sub.scheduled = true;
2818
+ queueMicrotask(() => {
2819
+ sub.scheduled = false;
2820
+ if (!this.subs.has(sub)) return;
2821
+ const batch = { events: sub.queue, dropped: sub.dropped };
2822
+ sub.queue = [];
2823
+ sub.dropped = 0;
2824
+ sub.waiting = true;
2825
+ sub.deliver(batch);
2826
+ });
2827
+ }
2828
+ };
2829
+
2754
2830
  // src/kernel/net/http1.ts
2755
2831
  var HttpError = class extends Error {
2756
2832
  status;
@@ -3711,7 +3787,7 @@ function satisfies(version, range2) {
3711
3787
  }
3712
3788
 
3713
3789
  // src/version.ts
3714
- var KERNEL_VERSION = true ? "1.179.0" : "0.0.0-dev";
3790
+ var KERNEL_VERSION = true ? "1.181.0" : "0.0.0-dev";
3715
3791
  var OS_RELEASE = "6.0.0-slicc";
3716
3792
  function procVersion() {
3717
3793
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -4576,27 +4652,27 @@ function hashId(text) {
4576
4652
  return hex4(a) + hex4(b);
4577
4653
  }
4578
4654
  function split(text, count2) {
4579
- const words2 = [];
4655
+ const words3 = [];
4580
4656
  let rest2 = text;
4581
4657
  for (let i = 0; i < count2; i++) {
4582
4658
  const match = /^(\S+)\s+/.exec(rest2);
4583
4659
  if (!match) return void 0;
4584
- words2.push(match[1]);
4660
+ words3.push(match[1]);
4585
4661
  rest2 = rest2.slice(match[0].length);
4586
4662
  }
4587
- return [words2, rest2.trim()];
4663
+ return [words3, rest2.trim()];
4588
4664
  }
4589
4665
  function jobOf(content, pending, ctx, env) {
4590
4666
  const fields = content.startsWith("@") ? 1 : 5;
4591
4667
  const parts2 = split(content, fields + (ctx.system ? 1 : 0));
4592
4668
  if (!parts2 || parts2[1] === "") throw new Error("no command");
4593
- const [words2, command] = parts2;
4594
- const schedule = words2.slice(0, fields).join(" ");
4669
+ const [words3, command] = parts2;
4670
+ const schedule = words3.slice(0, fields).join(" ");
4595
4671
  const when = parseWhen(schedule);
4596
4672
  const zone = env.CRON_TZ;
4597
4673
  if (zone && !when.reboot && canonicalZone(zone) === void 0)
4598
4674
  throw new Error(`CRON_TZ: unknown time zone "${zone}"`);
4599
- const user = ctx.system ? words2[fields] : void 0;
4675
+ const user = ctx.system ? words3[fields] : void 0;
4600
4676
  if (user !== void 0 && !USER_NAME.test(user)) throw new Error(`bad user name ${user}`);
4601
4677
  if (pending !== void 0 && !JOB_NAME.test(pending))
4602
4678
  throw new Error(`bad job name "${pending}"`);
@@ -8298,9 +8374,11 @@ var SpawnError = class extends Error {
8298
8374
  };
8299
8375
  var ChangedError = class extends SpawnError {
8300
8376
  notice;
8301
- constructor(notice) {
8377
+ file;
8378
+ constructor(notice, file) {
8302
8379
  super("EPERM");
8303
8380
  this.notice = notice;
8381
+ this.file = file;
8304
8382
  }
8305
8383
  };
8306
8384
  function interrupted(signal) {
@@ -15377,21 +15455,21 @@ function unescaped(double) {
15377
15455
  }
15378
15456
  function splitWords(text) {
15379
15457
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
15380
- const words2 = [];
15458
+ const words3 = [];
15381
15459
  let word;
15382
15460
  while (piece.lastIndex < text.length) {
15383
15461
  const m = piece.exec(text);
15384
15462
  if (!m) return void 0;
15385
15463
  const [, single, double, escaped, plain3, space] = m;
15386
15464
  if (space !== void 0) {
15387
- if (word !== void 0) words2.push(word);
15465
+ if (word !== void 0) words3.push(word);
15388
15466
  word = void 0;
15389
15467
  continue;
15390
15468
  }
15391
15469
  word = (word ?? "") + (single ?? (double !== void 0 ? unescaped(double) : escaped ?? plain3));
15392
15470
  }
15393
- if (word !== void 0) words2.push(word);
15394
- return words2;
15471
+ if (word !== void 0) words3.push(word);
15472
+ return words3;
15395
15473
  }
15396
15474
  function quoteWord(word) {
15397
15475
  const text = hasControl(word) ? withoutControl(word) : word;
@@ -15412,14 +15490,14 @@ function addPlain(word, plain3) {
15412
15490
  }
15413
15491
  function splitPatterns(text) {
15414
15492
  const piece = /'([^']*)'|"((?:[^"\\]|\\[\s\S])*)"|\\([\s\S])|([^\s'"\\]+)|(\s+)/y;
15415
- const words2 = [];
15493
+ const words3 = [];
15416
15494
  let word;
15417
15495
  while (piece.lastIndex < text.length) {
15418
15496
  const m = piece.exec(text);
15419
15497
  if (!m) return void 0;
15420
15498
  const [, single, double, escaped, plain3, space] = m;
15421
15499
  if (space !== void 0) {
15422
- if (word !== void 0) words2.push(word);
15500
+ if (word !== void 0) words3.push(word);
15423
15501
  word = void 0;
15424
15502
  continue;
15425
15503
  }
@@ -15427,8 +15505,8 @@ function splitPatterns(text) {
15427
15505
  if (plain3 !== void 0) addPlain(word, plain3);
15428
15506
  else addPart(word, single ?? (double !== void 0 ? unescaped(double) : escaped));
15429
15507
  }
15430
- if (word !== void 0) words2.push(word);
15431
- return words2;
15508
+ if (word !== void 0) words3.push(word);
15509
+ return words3;
15432
15510
  }
15433
15511
  var literal = (pattern) => pattern.map((p) => p ?? "*").join("");
15434
15512
  var isAny = (pattern) => pattern?.length === 1 && pattern[0] === null;
@@ -15501,14 +15579,14 @@ function whoError(who) {
15501
15579
  return `expected a user name, %group or ALL first, not ${shown(who)}`;
15502
15580
  }
15503
15581
  function cwdOf(word) {
15504
- const words2 = splitWords(word);
15505
- const path = words2?.length === 1 ? words2[0] : "";
15582
+ const words3 = splitWords(word);
15583
+ const path = words3?.length === 1 ? words3[0] : "";
15506
15584
  return path.startsWith("/") ? normalizePath(path) : "";
15507
15585
  }
15508
15586
  function commandOf2(text, after) {
15509
- const words2 = splitPatterns(text);
15510
- if (words2 === void 0) return "a quote in the command is not closed, or it ends in a \\";
15511
- const [first, ...args] = words2;
15587
+ const words3 = splitPatterns(text);
15588
+ if (words3 === void 0) return "a quote in the command is not closed, or it ends in a \\";
15589
+ const [first, ...args] = words3;
15512
15590
  const path = first ? literal(first) : "";
15513
15591
  if (path === "") return `expected a command after ${after}`;
15514
15592
  if (!path.startsWith("/")) return `expected an absolute command path, not ${shown(path)}`;
@@ -16198,6 +16276,9 @@ function recordable(cwd, argv) {
16198
16276
  function changedText(file) {
16199
16277
  return `sudo: ${file} changed since it was approved`;
16200
16278
  }
16279
+ function requestedText(file) {
16280
+ return `sudo: ${file} changed since it was requested`;
16281
+ }
16201
16282
  function changedFile(approved, now) {
16202
16283
  const files = /* @__PURE__ */ new Set([...Object.keys(approved), ...Object.keys(now)]);
16203
16284
  return [...files].find((file) => approved[file] !== now[file]);
@@ -16205,6 +16286,67 @@ function changedFile(approved, now) {
16205
16286
  function pinned(rule, bound3) {
16206
16287
  return rule.sha256 === void 0 || changedFile(rule.sha256, bound3) === void 0;
16207
16288
  }
16289
+ var REPLAY_ID = /^[!-~]{1,256}$/;
16290
+ var REPLAY_TAGS_MAX = 64;
16291
+ var REPLAY_ARGV_MAX = 4096;
16292
+ var REPLAY_BYTES_MAX = 1024 * 1024;
16293
+ var NOT_A_REQUEST = "sudo: that isn't a sudo request this kernel can run";
16294
+ function strings(value2, max) {
16295
+ return Array.isArray(value2) && value2.length <= max && value2.every((s) => typeof s === "string");
16296
+ }
16297
+ function words(value2) {
16298
+ if (!strings(value2, REPLAY_ARGV_MAX) || value2.length === 0) return false;
16299
+ const bytes = value2.reduce((n, s) => n + s.length + 1, 0);
16300
+ return bytes <= REPLAY_BYTES_MAX && !value2.some((s) => s.includes("\0"));
16301
+ }
16302
+ function filesOf(value2) {
16303
+ if (value2 === void 0) return {};
16304
+ if (!Array.isArray(value2) || value2.length > OPERANDS_MAX) return void 0;
16305
+ const files = {};
16306
+ for (const file of value2) {
16307
+ const { path, sha256: hex4 } = file ?? {};
16308
+ if (typeof path !== "string" || typeof hex4 !== "string") return void 0;
16309
+ files[path] = hex4;
16310
+ }
16311
+ return files;
16312
+ }
16313
+ function flagsOf(value2) {
16314
+ if (typeof value2 !== "object" || value2 === null || Array.isArray(value2)) return void 0;
16315
+ const entries = Object.entries(value2);
16316
+ if (entries.length > ROOT_ONLY_MAX || entries.some(([, v]) => typeof v !== "boolean")) {
16317
+ return void 0;
16318
+ }
16319
+ return Object.fromEntries(entries);
16320
+ }
16321
+ function replayOf(value2) {
16322
+ if (typeof value2 !== "object" || value2 === null) return failed2("EINVAL", NOT_A_REQUEST);
16323
+ const r = value2;
16324
+ if (r.validate === true) return failed2("EINVAL", "sudo: a sudo -v request runs no command");
16325
+ if (r.stdin === "pipe" || r.stdin === "file") {
16326
+ return failed2("EINVAL", `sudo: the request read a ${r.stdin}, and that input is gone`);
16327
+ }
16328
+ const { id: id2, user, uid, target, argv, path, cwd, stdin, tag, tags } = r;
16329
+ const rootOnly2 = flagsOf(r.rootOnly);
16330
+ const bound3 = filesOf(r.userWritable);
16331
+ const named = (s) => typeof s === "string" && s !== "";
16332
+ const absolute2 = (s) => typeof s === "string" && s.startsWith("/") && s.length <= PATH_MAX && !s.includes("\0");
16333
+ const valid2 = 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;
16334
+ if (!valid2) return failed2("EINVAL", NOT_A_REQUEST);
16335
+ const all = tags ?? (tag === void 0 ? [] : [tag]);
16336
+ return {
16337
+ id: id2,
16338
+ user,
16339
+ uid,
16340
+ target,
16341
+ argv,
16342
+ path,
16343
+ cwd,
16344
+ stdin,
16345
+ rootOnly: rootOnly2,
16346
+ bound: bound3,
16347
+ tags: [...new Set(all)]
16348
+ };
16349
+ }
16208
16350
  var VISUDO_FILES_MAX = 64;
16209
16351
  var VISUDO_TEXT_MAX = 1024 * 1024;
16210
16352
  function visudoCall(req) {
@@ -16606,6 +16748,61 @@ var Sudo = class {
16606
16748
  if (parsed2.validate) return this.validate(session, root, mine, call.cwd);
16607
16749
  return this.command(session, root, mine, call);
16608
16750
  }
16751
+ async replay(value2) {
16752
+ const r = replayOf(value2);
16753
+ if ("error" in r) return r;
16754
+ if (this.o.mode !== "enforce") {
16755
+ return failed2("EINVAL", `sudo: users are '${this.o.mode}', so no request needed approval`);
16756
+ }
16757
+ const self = await this.o.users.lookup(r.user);
16758
+ if (!self) return failed2("ENOENT", `sudo: unknown user ${r.user}`);
16759
+ if (self.uid !== r.uid) return failed2("EPERM", `sudo: ${self.name} is no longer uid ${r.uid}`);
16760
+ const target = await this.lookup(r.target);
16761
+ if (!target) return failed2("ENOENT", `sudo: unknown user ${r.target}`);
16762
+ const cred = credOf(self);
16763
+ const [word = "", ...args] = r.argv;
16764
+ const argv = [r.path, ...args];
16765
+ if (!await this.mayAsk(self, target, r, argv)) {
16766
+ const quoted = argv.map(quoteWord).join(" ");
16767
+ return failed2(
16768
+ "EACCES",
16769
+ `sudo: ${self.name} may no longer ask to run ${quoted} as ${target.name}`
16770
+ );
16771
+ }
16772
+ if (await this.canonical(r.cwd) !== r.cwd) {
16773
+ return failed2("EACCES", `sudo: ${r.cwd} changed since it was requested`);
16774
+ }
16775
+ if (!await this.reachable(cred, r.cwd)) {
16776
+ return failed2(
16777
+ "EACCES",
16778
+ `sudo: cannot run in ${r.cwd}: it isn't a directory ${self.name} can enter`
16779
+ );
16780
+ }
16781
+ const path = await this.locate(word, r.cwd);
16782
+ if (!path) return failed2("ENOENT", `sudo: ${word}: command not found`);
16783
+ if (path !== r.path) return failed2("EPERM", `sudo: ${word} now runs ${path}, not ${r.path}`);
16784
+ const bound3 = await this.bound(path, r.cwd);
16785
+ if (!bound3) return failed2("ENOENT", `sudo: ${word}: command not found`);
16786
+ const changed = changedFile(r.bound, bound3);
16787
+ if (changed !== void 0) return failed2("EPERM", requestedText(changed));
16788
+ const now = await this.rootOnly(r.cwd, args);
16789
+ const flipped = Object.keys(r.rootOnly).find((at) => r.rootOnly[at] && now[at] !== true);
16790
+ if (flipped !== void 0) return failed2("EPERM", `sudo: ${flipped} is no longer root-only`);
16791
+ const { grant } = this.grant(self, target, cred, r.argv, path, r.cwd, void 0, bound3);
16792
+ (this.o.warn ?? console.warn)(
16793
+ `sudo: ${self.name} : TTY=unknown ; PWD=${quoteWord(r.cwd)} ; USER=${target.name} ; REPLAY=${r.id} ; COMMAND=${argv.map(quoteWord).join(" ")}`
16794
+ );
16795
+ return { grant, caller: self, target, tags: r.tags };
16796
+ }
16797
+ async mayAsk(self, target, r, argv) {
16798
+ if (self.uid === 0) return true;
16799
+ const cred = credOf(self);
16800
+ const { rules } = await this.policyRules({ cred, pid: 0, stdin: r.stdin });
16801
+ const applies = await this.applies(self, cred);
16802
+ return rules.some(
16803
+ (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)
16804
+ );
16805
+ }
16609
16806
  async command(s, root, mine, call) {
16610
16807
  const { caller, self, target, parsed: parsed2 } = s;
16611
16808
  const { cred } = caller;
@@ -16785,8 +16982,8 @@ ${lines2}
16785
16982
  const st = await fs.lstat(path).catch(() => void 0);
16786
16983
  if (st && !await this.trusted(path)) return false;
16787
16984
  const old = st ? await fs.readFile(path) : "";
16788
- const words2 = recordedCommand(argv);
16789
- const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words2}
16985
+ const words3 = recordedCommand(argv);
16986
+ const line = `NOPASSWD ${self.name} (${target.name}) Cwd ${quoteWord(cwd)} Stdin none ${sha256Text(bound3)}Cmnd ${words3}
16790
16987
  `;
16791
16988
  const text = old === "" || old.endsWith("\n") ? `${old}${line}` : `${old}
16792
16989
  ${line}`;
@@ -18091,8 +18288,8 @@ function ipv6(host) {
18091
18288
  const head = groups(halves[0]);
18092
18289
  const back = halves.length === 2 ? groups(halves[1]) : [];
18093
18290
  if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
18094
- const words2 = [...head, ...back].map((g) => Number.parseInt(g, 16));
18095
- const missing6 = 8 - words2.length - tail2.length;
18291
+ const words3 = [...head, ...back].map((g) => Number.parseInt(g, 16));
18292
+ const missing6 = 8 - words3.length - tail2.length;
18096
18293
  if (halves.length === 2 ? missing6 < 1 : missing6 !== 0) return void 0;
18097
18294
  const all = [
18098
18295
  ...head.map((g) => Number.parseInt(g, 16)),
@@ -21576,7 +21773,7 @@ function selectFlag(req) {
21576
21773
  throw new KernelError("EINVAL", "ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS: alpn with select");
21577
21774
  return TLS_SERVER_SELECT;
21578
21775
  }
21579
- function flagsOf(req) {
21776
+ function flagsOf2(req) {
21580
21777
  return certFlags(req) | selectFlag(req) | (req.keylog ? TLS_KEYLOG : 0);
21581
21778
  }
21582
21779
  async function engineOf(proc, { select, keylog }) {
@@ -21627,7 +21824,7 @@ async function serverOf(proc, req) {
21627
21824
  let mine;
21628
21825
  try {
21629
21826
  mine = req.ca && req.ca.length > 0 ? storeOf(engine, req.ca) : void 0;
21630
- const flags2 = flagsOf(req);
21827
+ const flags2 = flagsOf2(req);
21631
21828
  const store = mine ?? (flags2 & CERT_FLAGS ? await defaultStore(engine, supportOf(proc.net).roots) : void 0);
21632
21829
  return {
21633
21830
  engine,
@@ -23516,7 +23713,7 @@ var WRITE_ACCESS = 3;
23516
23713
  var KEYBOARD = /* @__PURE__ */ new Set([SIG.INT, SIG.QUIT, SIG.TSTP]);
23517
23714
  var STUCK_ESCAPES = /* @__PURE__ */ new Set([SIG.INT, SIG.TERM, SIG.QUIT, SIG.HUP]);
23518
23715
  var DEFAULT_UMASK2 = 18;
23519
- function words(value2) {
23716
+ function words2(value2) {
23520
23717
  if (!Array.isArray(value2) || !value2.every((w) => typeof w === "string")) {
23521
23718
  throw new KernelError("EINVAL");
23522
23719
  }
@@ -24969,7 +25166,7 @@ var WasmProcess = class {
24969
25166
  if (!sudo) throw new KernelError("ENOSYS");
24970
25167
  const call = {
24971
25168
  op: "proc-sudo",
24972
- args: words(req.args),
25169
+ args: words2(req.args),
24973
25170
  cwd: typeof req.cwd === "string" && req.cwd.startsWith("/") ? req.cwd : "/",
24974
25171
  ...typeof req.term === "string" ? { term: req.term } : {}
24975
25172
  };
@@ -25973,6 +26170,9 @@ var JobTable = class {
25973
26170
  idOf(pid) {
25974
26171
  return this.members.get(pid)?.shownAs ?? pid;
25975
26172
  }
26173
+ execParentOf(pid) {
26174
+ return this.members.get(pid)?.execParent;
26175
+ }
25976
26176
  byId(id2) {
25977
26177
  for (const member of this.members.values()) if (member.shownAs === id2) return member;
25978
26178
  return this.member(id2);
@@ -28255,8 +28455,8 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
28255
28455
  function noticeOf(data) {
28256
28456
  if (Array.isArray(data)) return { paths: data.filter((p) => typeof p === "string"), trees: [] };
28257
28457
  const notice = data ?? {};
28258
- const strings = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
28259
- return { paths: strings(notice.paths), trees: strings(notice.trees), outside: true };
28458
+ const strings2 = (list) => Array.isArray(list) ? list.filter((p) => typeof p === "string") : [];
28459
+ return { paths: strings2(notice.paths), trees: strings2(notice.trees), outside: true };
28260
28460
  }
28261
28461
 
28262
28462
  // src/fs/opfs.ts
@@ -32569,7 +32769,40 @@ function describe(req, tty) {
32569
32769
  }
32570
32770
  function refuseChanged(approved, now) {
32571
32771
  const changed = changedFile(approved, now);
32572
- if (changed !== void 0) throw new ChangedError(changedText(changed));
32772
+ if (changed !== void 0) throw new ChangedError(changedText(changed), changed);
32773
+ }
32774
+ var O_WRONLY2 = 1;
32775
+ var OUTPUT_MODE = 384;
32776
+ var ERRNO_TEXT = {
32777
+ EACCES: "Permission denied",
32778
+ EISDIR: "Is a directory",
32779
+ ENOENT: "No such file or directory",
32780
+ ENOTDIR: "Not a directory",
32781
+ EROFS: "Read-only file system"
32782
+ };
32783
+ function sudoFailure(errno3, message3) {
32784
+ return Object.assign(new Error(message3), { code: errno3 });
32785
+ }
32786
+ function sudoCause(err) {
32787
+ const code = codeOf8(err);
32788
+ if (typeof code !== "string") throw err;
32789
+ return code;
32790
+ }
32791
+ function requested(err) {
32792
+ if (err instanceof ChangedError) throw sudoFailure(err.code, requestedText(err.file));
32793
+ throw err;
32794
+ }
32795
+ var MAX_SPAWN_TAGS = 8;
32796
+ function tagList(value2) {
32797
+ if (!Array.isArray(value2)) {
32798
+ tagOption(value2);
32799
+ return value2 === void 0 ? [] : [value2];
32800
+ }
32801
+ if (value2.length > MAX_SPAWN_TAGS) {
32802
+ throw new TypeError(`a spawn adds at most ${MAX_SPAWN_TAGS} tags`);
32803
+ }
32804
+ for (const each of value2) tagOption(each ?? 0);
32805
+ return value2;
32573
32806
  }
32574
32807
  function linksOf2(req) {
32575
32808
  return req.links === void 0 ? {} : { links: req.links };
@@ -32695,27 +32928,27 @@ function appendedRoots(given) {
32695
32928
  }
32696
32929
  return [...roots];
32697
32930
  }
32698
- function plainEnv(words2) {
32699
- const next3 = words2[1];
32700
- if (baseName2(words2[0] ?? "") !== "env" || next3 === void 0) return false;
32931
+ function plainEnv(words3) {
32932
+ const next3 = words3[1];
32933
+ if (baseName2(words3[0] ?? "") !== "env" || next3 === void 0) return false;
32701
32934
  return next3 === "-S" || !next3.startsWith("-") && !next3.includes("=");
32702
32935
  }
32703
- function shebangCommand(words2) {
32704
- if (!plainEnv(words2)) {
32705
- const arg2 = words2.slice(1).join(" ");
32706
- return { interp: words2[0], args: arg2 ? [arg2] : [] };
32936
+ function shebangCommand(words3) {
32937
+ if (!plainEnv(words3)) {
32938
+ const arg2 = words3.slice(1).join(" ");
32939
+ return { interp: words3[0], args: arg2 ? [arg2] : [] };
32707
32940
  }
32708
- const split3 = words2[1] === "-S";
32709
- const [interp, ...rest2] = words2.slice(split3 ? 2 : 1);
32941
+ const split3 = words3[1] === "-S";
32942
+ const [interp, ...rest2] = words3.slice(split3 ? 2 : 1);
32710
32943
  const arg = rest2.join(" ");
32711
32944
  return { interp, args: split3 ? rest2 : arg ? [arg] : [] };
32712
32945
  }
32713
32946
  function interpreter(head) {
32714
- const words2 = shebang(head);
32715
- if (!words2) return "node";
32716
- if (baseName2(words2[0]) === "env") words2.shift();
32717
- if (words2[0] === "-S") words2.shift();
32718
- return baseName2(words2[0] ?? "");
32947
+ const words3 = shebang(head);
32948
+ if (!words3) return "node";
32949
+ if (baseName2(words3[0]) === "env") words3.shift();
32950
+ if (words3[0] === "-S") words3.shift();
32951
+ return baseName2(words3[0] ?? "");
32719
32952
  }
32720
32953
  function expandDefaults(defaults2, env) {
32721
32954
  const out = {};
@@ -33490,9 +33723,9 @@ var Launcher = class {
33490
33723
  const head = await this.head(script);
33491
33724
  if (!head) return void 0;
33492
33725
  const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
33493
- const words2 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
33494
- if (!words2) return void 0;
33495
- const { interp, args } = shebangCommand(words2);
33726
+ const words3 = shebang(head) ?? (binfmt ? [binfmt] : void 0);
33727
+ if (!words3) return void 0;
33728
+ const { interp, args } = shebangCommand(words3);
33496
33729
  const passed = [...args, command ? script : file, ...argv.slice(1)];
33497
33730
  if (!interp) return void 0;
33498
33731
  const found = await this.resolve(interp, interp, cwd, scope);
@@ -33723,7 +33956,7 @@ var Launcher = class {
33723
33956
  const hex4 = pinned2[real];
33724
33957
  if (hex4 !== void 0) return { [real]: hex4 };
33725
33958
  if (await this.sudoer.trusted(real)) return {};
33726
- throw new ChangedError(changedText(real));
33959
+ throw new ChangedError(changedText(real), real);
33727
33960
  }
33728
33961
  async pinnedBytes(pin, bytes) {
33729
33962
  const [real = ""] = Object.keys(pin);
@@ -34002,6 +34235,10 @@ var Launcher = class {
34002
34235
  }
34003
34236
  async launch(asked, req) {
34004
34237
  const cwd = await physicalPathAsync(this.base, req.cwd);
34238
+ if (req.exactCwd && cwd !== req.cwd) {
34239
+ await req.fds.closeAll();
34240
+ throw sudoFailure("EACCES", `sudo: ${req.cwd} changed since it was requested`);
34241
+ }
34005
34242
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
34006
34243
  await this.pnpmReady();
34007
34244
  let loaded;
@@ -34180,10 +34417,12 @@ var Launcher = class {
34180
34417
  });
34181
34418
  const fit = await (handle.ready ?? true);
34182
34419
  if (fit) {
34420
+ this.traceAdmitted(handle.pid, true);
34183
34421
  await fds.closeAll();
34184
34422
  return handle;
34185
34423
  }
34186
34424
  await handle.exited;
34425
+ this.traceAdmitted(handle.pid, false);
34187
34426
  await this.memories.refused().catch(async (err) => {
34188
34427
  await fds.closeAll();
34189
34428
  throw err;
@@ -34314,10 +34553,12 @@ var Launcher = class {
34314
34553
  if (req.group === "lead") this.jobs.lead(pid);
34315
34554
  if (req.group === "session") this.jobs.setsid(pid);
34316
34555
  this.described.set(pid, describe(req, terminal?.name ?? null));
34556
+ this.traceStarted(pid, req);
34317
34557
  handle.onState(
34318
34558
  (state) => state === "stopped" ? this.stopped.add(pid) : this.stopped.delete(pid)
34319
34559
  );
34320
- void handle.exited.then(() => {
34560
+ void handle.exited.then((status) => {
34561
+ this.traceEnded(pid, handle, status);
34321
34562
  this.cwds.release(this.cwdSlots.get(pid));
34322
34563
  this.cwdSlots.delete(pid);
34323
34564
  this.cwdPaths.delete(pid);
@@ -34432,6 +34673,71 @@ var Launcher = class {
34432
34673
  imageTimes = /* @__PURE__ */ new Map();
34433
34674
  childTimes = /* @__PURE__ */ new Map();
34434
34675
  stopped = /* @__PURE__ */ new Set();
34676
+ traces = new TraceHub();
34677
+ traceHeld = /* @__PURE__ */ new Map();
34678
+ replacedImages = /* @__PURE__ */ new Set();
34679
+ traceStarted(pid, req) {
34680
+ const replaces = req.exec ? req.ppid : void 0;
34681
+ if (req.quietOom) this.traceHeld.set(pid, { replaces, events: [] });
34682
+ else if (replaces !== void 0) this.replacedImages.add(replaces);
34683
+ this.traceEmit(pid, () => {
34684
+ const { pid: shown2, ppid } = this.identityOf(pid);
34685
+ const described2 = this.described.get(pid);
34686
+ const { argv, started, tty: _tty, exeId: _exeId, ...exe } = described2;
34687
+ return {
34688
+ type: req.fork ? "fork" : "exec",
34689
+ pid: shown2,
34690
+ ppid,
34691
+ pgid: this.jobs.pgidOf(pid),
34692
+ argv: [...argv],
34693
+ ...exe,
34694
+ uid: this.credOf(pid).euid,
34695
+ at: started,
34696
+ ...req.exec ? { replaced: true } : {}
34697
+ };
34698
+ });
34699
+ }
34700
+ traceEnded(pid, handle, status) {
34701
+ if (this.replacedImages.delete(pid)) return;
34702
+ this.traceEmit(pid, () => ({
34703
+ type: "exit",
34704
+ pid: this.jobs.idOf(pid),
34705
+ status,
34706
+ ...signalField(handle.termsig()),
34707
+ ...this.chainTimes(pid),
34708
+ at: Date.now()
34709
+ }));
34710
+ }
34711
+ traceEmit(pid, build) {
34712
+ if (this.traces.idle) return;
34713
+ const tags = this.tagsOf(pid);
34714
+ const cred = this.credOf(pid);
34715
+ if (!this.traces.wants(tags, cred)) return;
34716
+ const held = this.traceHeld.get(pid);
34717
+ if (held) held.events.push({ tags, cred: copyCred(cred), draft: build() });
34718
+ else this.traces.publish(tags, cred, build());
34719
+ }
34720
+ traceAdmitted(pid, fit) {
34721
+ const held = this.traceHeld.get(pid);
34722
+ if (!held) return;
34723
+ this.traceHeld.delete(pid);
34724
+ if (!fit) return;
34725
+ if (held.replaces !== void 0) this.replacedImages.add(held.replaces);
34726
+ for (const { tags, cred, draft } of held.events) this.traces.publish(tags, cred, draft);
34727
+ }
34728
+ chainTimes(pid) {
34729
+ let utime = 0;
34730
+ let stime = 0;
34731
+ let timed2 = false;
34732
+ for (let at = pid; at !== void 0; at = this.jobs.execParentOf(at)) {
34733
+ const t = this.timesAt(at);
34734
+ if (!t) continue;
34735
+ timed2 = true;
34736
+ utime += t.utime;
34737
+ stime += t.stime;
34738
+ }
34739
+ return timed2 ? { utime, stime } : {};
34740
+ }
34435
34741
  stateOf(pid) {
34436
34742
  if (!this.processes.has(pid)) return "Z";
34437
34743
  return this.stopped.has(pid) ? "T" : "S";
@@ -35628,6 +35934,96 @@ var Launcher = class {
35628
35934
  const signal = handle.termsig();
35629
35935
  return { status, ...signal ? { signal } : {}, stdout: concat7(out), stderr: concat7(err) };
35630
35936
  }
35937
+ async sudoRun(replay, options = {}) {
35938
+ const added = tagList(options.tag);
35939
+ const replayed = await this.sudoer.replay(replay);
35940
+ if ("error" in replayed) throw sudoFailure(replayed.errno, replayed.error);
35941
+ const { grant, caller, target } = replayed;
35942
+ let tags = withTag(void 0, options.base);
35943
+ for (const tag of [...replayed.tags, ...added]) tags = withTag(tags, tag);
35944
+ const tagged = { tags, tagBase: options.base === void 0 ? 0 : 1 };
35945
+ await this.swaps.settled();
35946
+ this.resetCatalogs();
35947
+ const req = { file: grant.file, argv: grant.argv, env: grant.env, cwd: grant.cwd };
35948
+ const ctx = { ppid: 0, cred: grant.cred, scope: "" };
35949
+ const planned = await this.planOne(req, ctx).catch((err2) => {
35950
+ const code = sudoCause(err2);
35951
+ const word = grant.argv[0] ?? grant.file;
35952
+ if (code === "ENOENT") throw sudoFailure(code, `sudo: ${word}: command not found`);
35953
+ throw sudoFailure(code, `sudo: ${grant.file}: ${ERRNO_TEXT[code] ?? code}`);
35954
+ });
35955
+ const pin = await this.unchanged(planned, grant.file, grant.cwd, grant).catch(requested);
35956
+ const fds = await this.sudoOutputs(options, caller, this.stackFor(tags, tagged.tagBase));
35957
+ const err = fds.get(2).file;
35958
+ const report = (message3) => void Promise.resolve(err.write?.(encoder10.encode(`${message3}
35959
+ `))).catch(() => void 0);
35960
+ const replan = () => this.planOne(req, ctx);
35961
+ if (!await this.grantedCwd(grant.caller, grant.cwd)) {
35962
+ await fds.closeAll();
35963
+ throw sudoFailure("EACCES", `sudo: ${grant.cwd} changed since it was requested`);
35964
+ }
35965
+ const handle = await this.launch(
35966
+ { ...planned, replan },
35967
+ {
35968
+ ...pin,
35969
+ env: grant.env,
35970
+ cwd: grant.cwd,
35971
+ fds,
35972
+ report,
35973
+ ...this.identity(target, tagged, options.session),
35974
+ ...caller.umask !== void 0 ? { umask: caller.umask } : {},
35975
+ links: grant.cred.euid,
35976
+ exactCwd: true
35977
+ }
35978
+ ).catch(requested);
35979
+ const ended = handle.exited.then((status) => {
35980
+ const signal = handle.termsig();
35981
+ return { status, ...signal ? { signal } : {} };
35982
+ });
35983
+ return { pid: handle.pid, ended };
35984
+ }
35985
+ async sudoOutputs(options, caller, stack) {
35986
+ const fds = new FdTable();
35987
+ const opened2 = /* @__PURE__ */ new Map();
35988
+ fds.installAt(0, deviceFile("null"));
35989
+ try {
35990
+ fds.installAt(1, await this.sudoOutput(options.stdout, caller, stack, opened2));
35991
+ fds.installAt(2, await this.sudoOutput(options.stderr, caller, stack, opened2));
35992
+ } catch (err) {
35993
+ await fds.closeAll();
35994
+ throw err;
35995
+ }
35996
+ return fds;
35997
+ }
35998
+ async sudoOutput(path, caller, stack, opened2) {
35999
+ if (path === void 0) return deviceFile("null");
36000
+ if (typeof path !== "string" || !path.startsWith("/")) {
36001
+ throw sudoFailure("EINVAL", `sudo: ${String(path)} is not an absolute path`);
36002
+ }
36003
+ const cred = credOf(caller);
36004
+ const walked = await walk(this.fs, cred, normalizePath(path), true, caller.uid);
36005
+ const real = walked.path;
36006
+ const same2 = opened2.get(real);
36007
+ if (same2) return same2.retain();
36008
+ try {
36009
+ if (walked.denied !== void 0) throw fsError("EACCES", path);
36010
+ await writable({ ...this.execGuard(cred, 0), links: caller.uid }, "open", real);
36011
+ const owner = { uid: caller.uid, gid: caller.gid, mode: OUTPUT_MODE };
36012
+ await stack.noLinks.writeFile(real, new Uint8Array(0), owner);
36013
+ } catch (err) {
36014
+ const code = sudoCause(err);
36015
+ throw sudoFailure(code, `sudo: ${path}: ${ERRNO_TEXT[code] ?? code}`);
36016
+ }
36017
+ const file = vfsFile(stack.fs, { path: real, flags: O_WRONLY2, position: 0 }, stack.nodes);
36018
+ const again = await walk(this.fs, cred, real, true, caller.uid);
36019
+ const st = await stack.noLinks.lstat(real).catch(() => void 0);
36020
+ if (again.path !== real || again.denied !== void 0 || !st?.isFile) {
36021
+ await file.release();
36022
+ throw sudoFailure("EACCES", `sudo: ${path} changed while it was opened`);
36023
+ }
36024
+ opened2.set(real, file);
36025
+ return file;
36026
+ }
35631
36027
  };
35632
36028
  function cronLocks() {
35633
36029
  return globalThis.navigator?.locks;
@@ -36112,7 +36508,7 @@ function serveTransport(peer, transport, {
36112
36508
  }
36113
36509
 
36114
36510
  // src/client/protocol.ts
36115
- var PROTOCOL = [1, 14];
36511
+ var PROTOCOL = [1, 16];
36116
36512
  var FS_METHODS = [
36117
36513
  "readFile",
36118
36514
  "writeFile",
@@ -36268,9 +36664,9 @@ async function accountOf(l, user) {
36268
36664
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
36269
36665
  return found;
36270
36666
  }
36271
- async function runAs(l, self, requested) {
36272
- if (requested === void 0) return self;
36273
- const target = await accountOf(l, requested);
36667
+ async function runAs(l, self, requested2) {
36668
+ if (requested2 === void 0) return self;
36669
+ const target = await accountOf(l, requested2);
36274
36670
  if (self.uid !== 0 && target.uid !== self.uid) {
36275
36671
  throw fsError("EPERM", `${self.name} may not run as ${target.name}`);
36276
36672
  }
@@ -36280,8 +36676,8 @@ function clientCred(self) {
36280
36676
  return self ? credOf(self) : copyCred(ROOT);
36281
36677
  }
36282
36678
  async function runTagged(l, self, options, tag, session = -1) {
36283
- tagOption(options.tag);
36284
- const tags = withTag(withTag(void 0, tag), options.tag);
36679
+ let tags = withTag(void 0, tag);
36680
+ for (const added of tagList(options.tag)) tags = withTag(tags, added);
36285
36681
  return {
36286
36682
  user: await runAs(l, self, options.user),
36287
36683
  tag,
@@ -36412,6 +36808,34 @@ async function watchOp(l, req, watches, send2, g) {
36412
36808
  watches.set(req.id, unwatch);
36413
36809
  return { result: req.id };
36414
36810
  }
36811
+ function traceRegistry(send2) {
36812
+ const traces = /* @__PURE__ */ new Map();
36813
+ return {
36814
+ answer(l, self, req) {
36815
+ if (req.op === "untrace") {
36816
+ traces.get(req.trace)?.close();
36817
+ return { result: traces.delete(req.trace) };
36818
+ }
36819
+ if (typeof req.tag !== "string" || req.tag === "") {
36820
+ throw fsError("EINVAL", "trace: a tag is a non-empty string");
36821
+ }
36822
+ const limit = traceLimit(req.limit);
36823
+ const id2 = req.id;
36824
+ const deliver = ({ events, dropped: dropped2 }) => send2({ trace: id2, events, ...dropped2 > 0 ? { dropped: dropped2 } : {} });
36825
+ traces.set(id2, l.traces.subscribe(req.tag, credOf(self), deliver, limit));
36826
+ return { result: id2 };
36827
+ },
36828
+ ack(data) {
36829
+ if (typeof data.traceAck !== "number") return false;
36830
+ traces.get(data.traceAck)?.ack();
36831
+ return true;
36832
+ },
36833
+ close() {
36834
+ for (const trace of traces.values()) trace.close();
36835
+ traces.clear();
36836
+ }
36837
+ };
36838
+ }
36415
36839
  var TRAITS_WATCH = -1;
36416
36840
  function traitsWatch(launcher, transport, send2) {
36417
36841
  return launcher.onTraits(() => send2({ traits: transport.traits }));
@@ -36438,6 +36862,35 @@ function requestBinding(send2, port) {
36438
36862
  close: () => unbind?.()
36439
36863
  };
36440
36864
  }
36865
+ async function sudoRunAs(l, self, host, req, reply) {
36866
+ if (self.uid !== 0) throw sudoFailure("EPERM", "sudo: only a root client may run a sudo request");
36867
+ const { stdout, stderr, tag } = req.options;
36868
+ const running = await l.sudoRun(req.replay, {
36869
+ stdout,
36870
+ stderr,
36871
+ tag,
36872
+ base: host.tag,
36873
+ session: host.session
36874
+ });
36875
+ reply(req.id, { started: running.pid });
36876
+ const { status, signal } = await running.ended;
36877
+ return { result: status, ...signalField(signal) };
36878
+ }
36879
+ function answered(pending, req, reply, detach) {
36880
+ pending.then(
36881
+ ({ result, signal, transfer }) => {
36882
+ reply(req.id, { result, ...signal ? { signal } : {} }, transfer);
36883
+ if (req.op === "detach") detach(req.kill === true);
36884
+ },
36885
+ (err) => {
36886
+ const code = err?.code;
36887
+ reply(req.id, {
36888
+ error: err instanceof Error ? err.message : String(err),
36889
+ ...typeof code === "string" ? { code } : {}
36890
+ });
36891
+ }
36892
+ );
36893
+ }
36441
36894
  function serveClient(port, host) {
36442
36895
  let state = "new";
36443
36896
  let self;
@@ -36453,6 +36906,7 @@ function serveClient(port, host) {
36453
36906
  };
36454
36907
  const reply = (id2, body, transfer) => send2({ id: id2, ...body }, transfer);
36455
36908
  const hosts = hostRegistries(send2, ended.promise, port);
36909
+ const traces = traceRegistry(send2);
36456
36910
  function detach(kill = false, bye = "the slicc-kernel detached this client") {
36457
36911
  if (state === "closed") return;
36458
36912
  send2({ bye });
@@ -36460,6 +36914,7 @@ function serveClient(port, host) {
36460
36914
  net?.close();
36461
36915
  for (const unwatch of watches.values()) unwatch();
36462
36916
  watches.clear();
36917
+ traces.close();
36463
36918
  hosts.close();
36464
36919
  for (const session of terminals.values()) session.close();
36465
36920
  terminals.clear();
@@ -36567,6 +37022,11 @@ function serveClient(port, host) {
36567
37022
  case "serve-facade":
36568
37023
  case "unserve-facade":
36569
37024
  return { result: hosts.answer(l, req) };
37025
+ case "trace":
37026
+ case "untrace":
37027
+ return traces.answer(l, self, req);
37028
+ case "sudo-run":
37029
+ return sudoRunAs(l, self, host, req, reply);
36570
37030
  case "detach":
36571
37031
  return { result: true };
36572
37032
  default:
@@ -36590,20 +37050,9 @@ function serveClient(port, host) {
36590
37050
  net?.answer(data);
36591
37051
  return;
36592
37052
  }
37053
+ if (traces.ack(data)) return;
36593
37054
  const req = data;
36594
- handle(req).then(
36595
- ({ result, signal, transfer }) => {
36596
- reply(req.id, { result, ...signal ? { signal } : {} }, transfer);
36597
- if (req.op === "detach") detach(req.kill === true);
36598
- },
36599
- (err) => {
36600
- const code = err?.code;
36601
- reply(req.id, {
36602
- error: err instanceof Error ? err.message : String(err),
36603
- ...typeof code === "string" ? { code } : {}
36604
- });
36605
- }
36606
- );
37055
+ answered(handle(req), req, reply, detach);
36607
37056
  });
36608
37057
  port.addEventListener("close", () => detach());
36609
37058
  port.start?.();
@@ -36683,16 +37132,16 @@ function placeOf(address) {
36683
37132
  const kind = isIP3(plain3);
36684
37133
  if (kind === 4) return ipv4Place(plain3);
36685
37134
  if (kind !== 6) return void 0;
36686
- const words2 = hextets2(plain3);
36687
- const embedded2 = words2.slice(0, 5).every((w) => w === 0) && (words2[5] === 65535 || words2[5] === 0);
36688
- if (embedded2 && (words2[5] === 65535 || words2[6] !== 0)) return ipv4Place(ipv4Of(words2));
36689
- if (words2.slice(0, 7).every((w) => w === 0) && (words2[7] ?? 0) <= 1) return "loopback";
36690
- if (((words2[0] ?? 0) & 65472) === 65152) return "link-local";
36691
- if (words2.join(":") === METADATA_V6) return "link-local";
37135
+ const words3 = hextets2(plain3);
37136
+ const embedded2 = words3.slice(0, 5).every((w) => w === 0) && (words3[5] === 65535 || words3[5] === 0);
37137
+ if (embedded2 && (words3[5] === 65535 || words3[6] !== 0)) return ipv4Place(ipv4Of(words3));
37138
+ if (words3.slice(0, 7).every((w) => w === 0) && (words3[7] ?? 0) <= 1) return "loopback";
37139
+ if (((words3[0] ?? 0) & 65472) === 65152) return "link-local";
37140
+ if (words3.join(":") === METADATA_V6) return "link-local";
36692
37141
  return void 0;
36693
37142
  }
36694
- function ipv4Of(words2) {
36695
- const [high = 0, low = 0] = words2.slice(6);
37143
+ function ipv4Of(words3) {
37144
+ const [high = 0, low = 0] = words3.slice(6);
36696
37145
  return [high >> 8, high & 255, low >> 8, low & 255].join(".");
36697
37146
  }
36698
37147
  function hextets2(address) {
@@ -37532,6 +37981,7 @@ var Channel = class {
37532
37981
  failure;
37533
37982
  pending = /* @__PURE__ */ new Map();
37534
37983
  watching = /* @__PURE__ */ new Map();
37984
+ tracing = /* @__PURE__ */ new Map();
37535
37985
  cdpHooks = /* @__PURE__ */ new Map();
37536
37986
  cdpOpen = /* @__PURE__ */ new Set();
37537
37987
  facadeHooks = /* @__PURE__ */ new Map();
@@ -37599,6 +38049,10 @@ var Channel = class {
37599
38049
  this.watching.get(data.watch)?.(data.change);
37600
38050
  return;
37601
38051
  }
38052
+ if (data.trace !== void 0 && data.id === void 0) {
38053
+ this.traced(data.trace, data.events ?? [], data.dropped ?? 0);
38054
+ return;
38055
+ }
37602
38056
  if (data.bye !== void 0) {
37603
38057
  this.end(new KernelGoneError(data.bye));
37604
38058
  return;
@@ -37627,6 +38081,23 @@ var Channel = class {
37627
38081
  if (reply.error !== void 0) call.reject(new KernelCallError(reply.error, reply.code));
37628
38082
  else call.resolve(reply.result);
37629
38083
  }
38084
+ traced(trace, events, dropped2) {
38085
+ const listener = this.tracing.get(trace);
38086
+ if (!listener) return;
38087
+ const ack = () => {
38088
+ if (this.tracing.has(trace)) this.post({ traceAck: trace });
38089
+ };
38090
+ let consumed;
38091
+ try {
38092
+ consumed = listener({ events, dropped: dropped2 });
38093
+ } finally {
38094
+ if (typeof consumed?.then === "function") {
38095
+ void Promise.resolve(consumed).then(ack, ack);
38096
+ } else {
38097
+ ack();
38098
+ }
38099
+ }
38100
+ }
37630
38101
  openCdp({ cdpOpen: { registration, ...request }, port }) {
37631
38102
  const hook = registration === void 0 ? void 0 : this.cdpHooks.get(registration);
37632
38103
  if (!hook || this.failure) {
@@ -37689,6 +38160,29 @@ async function serveCdpOn(channel, hook, options) {
37689
38160
  }
37690
38161
  };
37691
38162
  }
38163
+ async function traceOn(channel, tag, onEvents, options) {
38164
+ if (typeof onEvents !== "function") throw new TypeError("trace: onEvents must be a function");
38165
+ await since(channel, 15, "trace", async () => {
38166
+ });
38167
+ const { id: id2, done: done4 } = channel.request({
38168
+ op: "trace",
38169
+ tag,
38170
+ ...options.limit !== void 0 ? { limit: options.limit } : {}
38171
+ });
38172
+ channel.tracing.set(id2, onEvents);
38173
+ try {
38174
+ await done4;
38175
+ } catch (error) {
38176
+ channel.tracing.delete(id2);
38177
+ throw error;
38178
+ }
38179
+ return {
38180
+ close: () => {
38181
+ if (!channel.tracing.delete(id2)) return;
38182
+ void channel.call({ op: "untrace", trace: id2 }).catch(() => void 0);
38183
+ }
38184
+ };
38185
+ }
37692
38186
  async function dialOn(channel, { port, host }) {
37693
38187
  const req = { op: "dial", port, ...host ? { host } : {} };
37694
38188
  return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
@@ -37702,6 +38196,8 @@ async function spawnOn(channel, argv, opts) {
37702
38196
  });
37703
38197
  if (opts.tag !== void 0) await since(channel, 12, "a tag per spawn", async () => {
37704
38198
  });
38199
+ if (Array.isArray(opts.tag)) await since(channel, 15, "several tags per spawn", async () => {
38200
+ });
37705
38201
  if (opts.cwdMissing === "error") await since(channel, 14, "cwdMissing: 'error'", async () => {
37706
38202
  });
37707
38203
  const started = Promise.withResolvers();
@@ -37726,6 +38222,19 @@ async function spawnOn(channel, argv, opts) {
37726
38222
  signal: async (name2 = "SIGTERM") => void await channel.call({ op: "kill", pid: -pgid, signal: name2 })
37727
38223
  };
37728
38224
  }
38225
+ async function sudoRunOn(channel, replay, { stdout, stderr, tag }) {
38226
+ await since(channel, 16, "sudoRun", async () => {
38227
+ });
38228
+ const started = Promise.withResolvers();
38229
+ let endedBy;
38230
+ const { done: done4 } = channel.request(
38231
+ { op: "sudo-run", replay, options: { stdout, stderr, tag } },
38232
+ { started: started.resolve, ended: (signal) => endedBy = signal }
38233
+ );
38234
+ done4.catch(started.reject);
38235
+ const pid = await started.promise;
38236
+ return { pid, pgid: pid, ended: endedOf(done4, () => endedBy) };
38237
+ }
37729
38238
  function endedOf(done4, signal) {
37730
38239
  return quiet(
37731
38240
  done4.then((status) => {
@@ -37763,6 +38272,10 @@ async function terminalOn(channel, argv, opts) {
37763
38272
  });
37764
38273
  if (opts.tag !== void 0) await since(channel, 12, "a tag per terminal", async () => {
37765
38274
  });
38275
+ if (Array.isArray(opts.tag)) {
38276
+ await since(channel, 15, "several tags per terminal", async () => {
38277
+ });
38278
+ }
37766
38279
  if (opts.cwdMissing === "error") await since(channel, 14, "cwdMissing: 'error'", async () => {
37767
38280
  });
37768
38281
  let listener = onData ?? null;
@@ -37922,6 +38435,8 @@ async function attachKernel(port, options = {}) {
37922
38435
  channel.traitsListeners.add(listener);
37923
38436
  return () => channel.traitsListeners.delete(listener);
37924
38437
  },
38438
+ trace: (tag, onEvents, options2 = {}) => traceOn(channel, tag, onEvents, options2),
38439
+ sudoRun: (replay, options2 = {}) => sudoRunOn(channel, replay, options2),
37925
38440
  async close(o = {}) {
37926
38441
  if (channel.failure) return;
37927
38442
  await channel.call({ op: "detach", ...o.kill ? { kill: true } : {} }).catch(() => void 0);
@@ -38236,6 +38751,30 @@ function checkedRequests(hook) {
38236
38751
  const kinds = kindsOf(hook);
38237
38752
  return { kinds, handle: (request) => hook.handle(request) };
38238
38753
  }
38754
+ function nodeAccounts(launcher, guard2) {
38755
+ return {
38756
+ users: {
38757
+ mode: launcher.usersMode,
38758
+ add: (user) => guard2(() => launcher.users.add(user)),
38759
+ remove: (name2, options) => guard2(() => launcher.users.remove(name2, options)),
38760
+ list: () => guard2(() => launcher.users.list())
38761
+ },
38762
+ groups: {
38763
+ list: () => guard2(() => launcher.users.groups()),
38764
+ add: (name2, options) => guard2(() => launcher.users.addGroup(name2, options?.gid)),
38765
+ addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
38766
+ removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
38767
+ }
38768
+ };
38769
+ }
38770
+ async function nodeSudoRun(launcher, guard2, replay, options) {
38771
+ const running = await guard2(() => launcher.sudoRun(replay, options));
38772
+ const ended = guard2(async () => {
38773
+ const { status, signal } = await running.ended;
38774
+ return { status, ...signalField(signal) };
38775
+ });
38776
+ return { pid: running.pid, pgid: running.pid, ended: quiet(ended) };
38777
+ }
38239
38778
  async function createNodeKernel(options = {}) {
38240
38779
  const hooks = sealedHooks(options.sealed);
38241
38780
  const timeZone = systemTimeZone(options.timeZone, clockZone(options.cron?.clock));
@@ -38364,18 +38903,8 @@ async function createNodeKernel(options = {}) {
38364
38903
  mounts: async () => launcher.mounts.listed(),
38365
38904
  setRoutes: (routes) => guard2(async () => launcher.setRoutes(routes)),
38366
38905
  updateTransportTraits: () => guard2(async () => launcher.updateTransportTraits()),
38367
- users: {
38368
- mode: launcher.usersMode,
38369
- add: (user) => guard2(() => launcher.users.add(user)),
38370
- remove: (name2, options2) => guard2(() => launcher.users.remove(name2, options2)),
38371
- list: () => guard2(() => launcher.users.list())
38372
- },
38373
- groups: {
38374
- list: () => guard2(() => launcher.users.groups()),
38375
- add: (name2, options2) => guard2(() => launcher.users.addGroup(name2, options2?.gid)),
38376
- addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
38377
- removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
38378
- },
38906
+ ...nodeAccounts(launcher, guard2),
38907
+ sudoRun: (replay, sudoOptions = {}) => nodeSudoRun(launcher, guard2, replay, sudoOptions),
38379
38908
  async connect(connectOptions = {}) {
38380
38909
  if (terminated) throw new Error(TERMINATED);
38381
38910
  const { user } = connectOptions;