@ai-ecoverse/slicc-kernel 1.54.8 → 1.55.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.
@@ -661,7 +661,7 @@ var OpfsFs = class {
661
661
  const parent = await this.locate(parentOf2(path), true);
662
662
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
663
663
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
664
- this.touch(parent.path);
664
+ this.touch(parent.path, `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`);
665
665
  }
666
666
  async rm(path, options = {}) {
667
667
  let found;
@@ -725,7 +725,7 @@ var OpfsFs = class {
725
725
  const swap = targetStat?.isDirectory === true;
726
726
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
727
727
  this.forget(destination);
728
- const timed = await this.explicitTimes(source.path);
728
+ const timed2 = await this.explicitTimes(source.path);
729
729
  const moved2 = await this.place(source.handle, dir, staging);
730
730
  if (swap) await this.rm(destination, { recursive: true });
731
731
  await this.meta.move(source.path, destination, carry);
@@ -737,7 +737,7 @@ var OpfsFs = class {
737
737
  if (!placed) await dir.removeEntry(staging, { recursive: true });
738
738
  copied ||= !placed;
739
739
  }
740
- if (copied) await this.restamp(destination, timed);
740
+ if (copied) await this.restamp(destination, timed2);
741
741
  this.changed(source.path, destination);
742
742
  this.touch(parentOf2(source.path), parent.path);
743
743
  }
@@ -746,16 +746,16 @@ var OpfsFs = class {
746
746
  return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
747
747
  }
748
748
  async explicitTimes(path) {
749
- const timed = [];
749
+ const timed2 = [];
750
750
  for (const entry of await this.meta.under(path)) {
751
751
  if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
752
752
  const current = await this.modified(entry.path).catch(() => void 0);
753
- if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
753
+ if (current === entry.mtimeFor) timed2.push(entry.path.slice(path.length));
754
754
  }
755
- return timed;
755
+ return timed2;
756
756
  }
757
- async restamp(path, timed) {
758
- for (const rest2 of timed) {
757
+ async restamp(path, timed2) {
758
+ for (const rest2 of timed2) {
759
759
  const mtimeFor = await this.modified(path + rest2);
760
760
  await this.meta.update(path + rest2, (entry) => entry && { ...entry, mtimeFor });
761
761
  }
@@ -2385,6 +2385,7 @@ var LAST_UID = 6e4;
2385
2385
  var USERS_GROUP = "users";
2386
2386
  var HOME_DIR = /^\/home\/[^/]+$/;
2387
2387
  var SYSTEM_PNPM_HOME = "/usr/local/share/pnpm";
2388
+ var PNPM_CONFIG = "minimumReleaseAge: 1440\n";
2388
2389
  var isId = (text) => text !== void 0 && /^\d+$/.test(text);
2389
2390
  function lines(text) {
2390
2391
  return text.split("\n").filter((line, i, all) => line !== "" || i < all.length - 1);
@@ -2440,6 +2441,7 @@ function userEnv(account, system) {
2440
2441
  USER: account.name,
2441
2442
  LOGNAME: account.name,
2442
2443
  PNPM_HOME: pnpm,
2444
+ NPM_CONFIG_PREFIX: account.uid === 0 ? "/usr/local" : `${account.home}/.local`,
2443
2445
  PATH: `${pnpm}/bin:/usr/local/bin:/usr/bin:/bin`
2444
2446
  };
2445
2447
  }
@@ -2464,6 +2466,18 @@ var UserDb = class {
2464
2466
  group: parseGroup(await this.read(GROUP, DEFAULT_GROUP))
2465
2467
  };
2466
2468
  }
2469
+ async provision(row) {
2470
+ const pnpm = `${row.home}/.local/share/pnpm`;
2471
+ if (!await this.fs.exists(pnpm)) {
2472
+ await this.fs.mkdir(pnpm, { recursive: true });
2473
+ await this.fs.chmod(pnpm, row.name.startsWith("scoop-") ? 488 : 448);
2474
+ }
2475
+ const config = `${row.home}/.config/pnpm/config.yaml`;
2476
+ if (!await this.fs.exists(config)) {
2477
+ await this.fs.mkdir(`${row.home}/.config/pnpm`, { recursive: true });
2478
+ await this.fs.writeFile(config, PNPM_CONFIG);
2479
+ }
2480
+ }
2467
2481
  async commit(passwd, group, then) {
2468
2482
  const before = [await this.read(PASSWD, DEFAULT_PASSWD), await this.read(GROUP, DEFAULT_GROUP)];
2469
2483
  try {
@@ -2537,6 +2551,7 @@ var UserDb = class {
2537
2551
  const groups = parsed(group);
2538
2552
  const found = users.find((r) => r.name === name2);
2539
2553
  if (found) {
2554
+ if (found.uid >= FIRST_UID && found.uid <= LAST_UID) await this.provision(found);
2540
2555
  remember();
2541
2556
  return this.account(found, groups);
2542
2557
  }
@@ -2555,6 +2570,7 @@ var UserDb = class {
2555
2570
  const fresh = !await this.fs.exists(home);
2556
2571
  await this.fs.mkdir(home, { recursive: true });
2557
2572
  try {
2573
+ await this.provision(row);
2558
2574
  await this.fs.mkdir("/etc", { recursive: true });
2559
2575
  await this.commit(formatPasswd(passwd), formatGroup(group));
2560
2576
  } catch (err) {
@@ -5558,17 +5574,22 @@ async function followLinks(fs, path) {
5558
5574
  }
5559
5575
  return current;
5560
5576
  }
5561
- function withCommandDirs(fs, names) {
5577
+ function withCommandDirs(fs, names, userDir = async () => void 0) {
5562
5578
  async function children2(path) {
5563
5579
  if (path === "/") return ["bin", "usr"];
5564
5580
  if (path === "/usr") return ["bin"];
5565
5581
  if (path === "/bin" || path === "/usr/bin") return [...await names()];
5566
- return void 0;
5582
+ const own = await userDir(path);
5583
+ return own?.size ? [...own] : void 0;
5567
5584
  }
5568
5585
  async function virtual(path) {
5569
5586
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
5570
5587
  const name2 = COMMAND.exec(path)?.[1];
5571
- return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
5588
+ if (name2 !== void 0) return (await names()).has(name2) ? synthetic(false, path) : void 0;
5589
+ if ((await userDir(path))?.size) return synthetic(true, path);
5590
+ const slash = path.lastIndexOf("/");
5591
+ const own = await userDir(path.slice(0, slash));
5592
+ return own?.has(path.slice(slash + 1)) ? synthetic(false, path) : void 0;
5572
5593
  }
5573
5594
  async function linked(path) {
5574
5595
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -11818,11 +11839,24 @@ var HostfsClient = class {
11818
11839
  get maxIo() {
11819
11840
  return this.capabilities.maxIo ?? HOSTFS_IO;
11820
11841
  }
11821
- async send(path, init, handle = false, retried = false) {
11842
+ retired;
11843
+ stop = new AbortController();
11844
+ get gone() {
11845
+ return this.retired !== void 0;
11846
+ }
11847
+ retire(why) {
11848
+ this.retired ??= why;
11849
+ this.stop.abort();
11850
+ }
11851
+ async send(path, init, handle = false, retried = false, cleanup = false) {
11852
+ if (this.retired && !cleanup) throw fsError2("ENOTCONN", this.retired);
11853
+ const stop = cleanup ? void 0 : this.stop.signal;
11854
+ const signal = init.signal && stop ? AbortSignal.any([init.signal, stop]) : init.signal ?? stop;
11822
11855
  let response;
11823
11856
  try {
11824
11857
  response = await this.fetch(`${this.grant.url}${path}`, {
11825
11858
  ...init,
11859
+ ...signal ? { signal } : {},
11826
11860
  credentials: "omit",
11827
11861
  headers: {
11828
11862
  ...init.headers,
@@ -11859,6 +11893,20 @@ var HostfsClient = class {
11859
11893
  );
11860
11894
  return await response.json();
11861
11895
  }
11896
+ async cleanup(op, body) {
11897
+ const response = await this.send(
11898
+ "/api/hostfs",
11899
+ {
11900
+ method: "POST",
11901
+ headers: { "Content-Type": "application/json" },
11902
+ body: JSON.stringify({ op, ...body })
11903
+ },
11904
+ true,
11905
+ false,
11906
+ true
11907
+ );
11908
+ await response.body?.cancel();
11909
+ }
11862
11910
  async read(fh, offset, size, ifMatch) {
11863
11911
  const response = await this.send(
11864
11912
  "/api/hostfs",
@@ -11926,6 +11974,11 @@ function hostfsHandlers(current) {
11926
11974
  return fresh(remote);
11927
11975
  };
11928
11976
  const call = (op, body = {}) => current().call(op, body);
11977
+ const settle3 = (client, restore) => client.call("setattr", restore).catch((err) => {
11978
+ if (!client.gone || !client.cleanup) throw err;
11979
+ void client.cleanup("setattr", restore).catch(() => {
11980
+ });
11981
+ });
11929
11982
  async function writable(client, path, flags) {
11930
11983
  const { ifMatch: _, ...asked } = flags;
11931
11984
  const opening = () => client.call("open", { path: rel(path), ...asked });
@@ -11936,9 +11989,9 @@ function hostfsHandlers(current) {
11936
11989
  const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
11937
11990
  if (mode & 128) throw err;
11938
11991
  const restore = { path: rel(path), mode };
11939
- await client.call("setattr", { path: rel(path), mode: mode | 128 });
11940
- const opened2 = await opening().catch(async (again) => {
11941
- await client.call("setattr", restore);
11992
+ const opened2 = await client.call("setattr", { path: rel(path), mode: mode | 128 }).then(opening).catch(async (again) => {
11993
+ await settle3(client, restore).catch(() => {
11994
+ });
11942
11995
  throw again;
11943
11996
  });
11944
11997
  return { opened: opened2, restore };
@@ -11993,7 +12046,7 @@ function hostfsHandlers(current) {
11993
12046
  try {
11994
12047
  await fresh(remote).client.call("release", { fh: remote.fh }, true);
11995
12048
  } finally {
11996
- if (remote.restore) await current().call("setattr", remote.restore);
12049
+ if (remote.restore) await settle3(current(), remote.restore);
11997
12050
  }
11998
12051
  },
11999
12052
  mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
@@ -12123,6 +12176,73 @@ function invalidation(event) {
12123
12176
  return event.paths ?? true;
12124
12177
  }
12125
12178
  var PORT_REQUEST_MS = 6e4;
12179
+ var HOSTFS_CALL_MS = 2e4;
12180
+ var PORT_BEAT_MS = 1e3;
12181
+ var PORT_DEAD_MS = 1e4;
12182
+ var PORT_LINGER_MS = 1e3;
12183
+ function timed(transport, ms) {
12184
+ const inflight = /* @__PURE__ */ new Set();
12185
+ const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
12186
+ let expired = false;
12187
+ const running = run2();
12188
+ inflight.add(running);
12189
+ const done4 = () => inflight.delete(running);
12190
+ running.then(done4, done4);
12191
+ const timer = setTimeout(() => {
12192
+ expired = true;
12193
+ const why = `the host folder did not answer ${name2} within ${ms} ms`;
12194
+ transport.retire?.(why);
12195
+ expire?.(running);
12196
+ reject(fsError2("ENOTCONN", why));
12197
+ }, ms);
12198
+ running.then(
12199
+ (value) => {
12200
+ clearTimeout(timer);
12201
+ if (expired) late?.(value);
12202
+ resolve(value);
12203
+ },
12204
+ (err) => {
12205
+ clearTimeout(timer);
12206
+ reject(err);
12207
+ }
12208
+ );
12209
+ });
12210
+ const cleanup = async (op, body, skip) => {
12211
+ await Promise.allSettled([...inflight].filter((work) => work !== skip));
12212
+ await transport.cleanup?.(op, body);
12213
+ };
12214
+ const release = (opened2) => {
12215
+ const fh = opened2?.fh;
12216
+ if (typeof fh === "number") void cleanup("release", { fh }).catch(() => {
12217
+ });
12218
+ };
12219
+ return {
12220
+ get capabilities() {
12221
+ return transport.capabilities;
12222
+ },
12223
+ get token() {
12224
+ return transport.token;
12225
+ },
12226
+ get maxIo() {
12227
+ return transport.maxIo;
12228
+ },
12229
+ get gone() {
12230
+ return transport.gone;
12231
+ },
12232
+ retire: (why) => transport.retire?.(why),
12233
+ ...transport.cleanup ? { cleanup } : {},
12234
+ call: (op, body, handle) => guard(
12235
+ op,
12236
+ () => transport.call(op, body, handle),
12237
+ op === "open" ? release : void 0,
12238
+ op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
12239
+ }) : void 0
12240
+ ),
12241
+ read: (fh, offset, size, ifMatch) => guard("read", () => transport.read(fh, offset, size, ifMatch)),
12242
+ write: (fh, offset, bytes) => guard("write", () => transport.write(fh, offset, bytes)),
12243
+ watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
12244
+ };
12245
+ }
12126
12246
  var PORT_MS_PER_MIB = 1e3;
12127
12247
  var nextPortToken = 0;
12128
12248
  var PortTransport = class {
@@ -12133,13 +12253,25 @@ var PortTransport = class {
12133
12253
  waiting = /* @__PURE__ */ new Map();
12134
12254
  next = 0;
12135
12255
  dead;
12136
- constructor(grant, requestMs = PORT_REQUEST_MS) {
12256
+ beatMs;
12257
+ deadMs;
12258
+ lingerMs;
12259
+ beat;
12260
+ answers = false;
12261
+ pinged;
12262
+ constructor(grant, liveness = {}) {
12137
12263
  this.port = grant.port;
12138
- this.requestMs = requestMs;
12264
+ this.requestMs = liveness.requestMs ?? PORT_REQUEST_MS;
12265
+ this.beatMs = liveness.beatMs ?? PORT_BEAT_MS;
12266
+ this.deadMs = liveness.deadMs ?? PORT_DEAD_MS;
12267
+ this.lingerMs = liveness.lingerMs ?? PORT_LINGER_MS;
12139
12268
  this.capabilities = grant.capabilities ?? {};
12140
12269
  this.token = grant.token ?? `port-${++nextPortToken}`;
12141
12270
  this.port.addEventListener("message", (event) => {
12142
- this.waiting.get(event.data?.id)?.(event.data);
12271
+ if (event.data?.pong) {
12272
+ this.answers = true;
12273
+ this.pinged = void 0;
12274
+ } else this.waiting.get(event.data?.id)?.(event.data);
12143
12275
  });
12144
12276
  this.port.addEventListener("close", () => this.fail("the folder's port closed"));
12145
12277
  this.port.start();
@@ -12150,8 +12282,51 @@ var PortTransport = class {
12150
12282
  get gone() {
12151
12283
  return this.dead !== void 0;
12152
12284
  }
12285
+ retire(why) {
12286
+ this.fail(why);
12287
+ }
12288
+ ping() {
12289
+ if (this.pinged !== void 0) return;
12290
+ this.pinged = Date.now();
12291
+ try {
12292
+ this.port.postMessage({ id: 0, op: "ping" });
12293
+ } catch {
12294
+ }
12295
+ }
12296
+ watchBeat() {
12297
+ if (this.beat || this.dead) return;
12298
+ this.ping();
12299
+ this.beat = setInterval(() => {
12300
+ if (this.waiting.size === 0) {
12301
+ clearInterval(this.beat);
12302
+ this.beat = void 0;
12303
+ return;
12304
+ }
12305
+ if (this.answers && this.pinged !== void 0 && Date.now() - this.pinged > this.deadMs) {
12306
+ this.fail("the folder's port stopped answering");
12307
+ return;
12308
+ }
12309
+ this.ping();
12310
+ }, this.beatMs);
12311
+ }
12312
+ async cleanup(op, body) {
12313
+ if (this.closed) throw fsError2("ENOTCONN", "the folder's port is closed");
12314
+ this.port.postMessage({ id: 0, op: "cleanup", name: op, body });
12315
+ }
12316
+ closed = false;
12153
12317
  fail(why) {
12154
- if (this.dead === void 0) this.port.close();
12318
+ if (this.dead === void 0) {
12319
+ setTimeout(() => {
12320
+ try {
12321
+ this.port.postMessage({ id: 0, op: "bye" });
12322
+ } catch {
12323
+ }
12324
+ this.closed = true;
12325
+ this.port.close();
12326
+ }, this.lingerMs);
12327
+ }
12328
+ clearInterval(this.beat);
12329
+ this.beat = void 0;
12155
12330
  this.dead ??= why;
12156
12331
  for (const settle3 of [...this.waiting.values()])
12157
12332
  settle3({ id: 0, error: { code: "ENOTCONN", message: why } });
@@ -12168,6 +12343,7 @@ var PortTransport = class {
12168
12343
  },
12169
12344
  this.requestMs + Math.ceil(bytes / 1048576) * PORT_MS_PER_MIB
12170
12345
  );
12346
+ this.watchBeat();
12171
12347
  this.waiting.set(id2, (reply) => {
12172
12348
  clearTimeout(timer);
12173
12349
  this.waiting.delete(id2);
@@ -12214,6 +12390,7 @@ var PortTransport = class {
12214
12390
  }
12215
12391
  resolve();
12216
12392
  };
12393
+ this.watchBeat();
12217
12394
  this.waiting.set(id2, (reply) => {
12218
12395
  if (reply.open) return onOpen();
12219
12396
  if (reply.event) return onEvent(reply.event);
@@ -12231,10 +12408,10 @@ var PortTransport = class {
12231
12408
  }).finally(() => this.fail("the folder's watch ended"));
12232
12409
  }
12233
12410
  };
12234
- function grantConnect(hook, fetch2, requestMs) {
12411
+ function grantConnect(hook, fetch2, liveness = {}) {
12235
12412
  return async (source, options) => {
12236
12413
  const first = await hook(source, options);
12237
- if ("port" in first) return new PortTransport(first, requestMs);
12414
+ if ("port" in first) return new PortTransport(first, liveness);
12238
12415
  const grant = async () => {
12239
12416
  const again = await hook(source, options);
12240
12417
  if ("port" in again)
@@ -12254,7 +12431,10 @@ async function openHostfs(spec, connect, timing = {}) {
12254
12431
  const stop = new AbortController();
12255
12432
  let active;
12256
12433
  const handlers = hostfsHandlers(() => active);
12257
- const ranged2 = () => active?.capabilities.ranges === true ? { ranges: true } : {};
12434
+ const ranged2 = () => ({
12435
+ ...active?.capabilities.ranges === true ? { ranges: true } : {},
12436
+ ...active ? { maxIo: Math.min(active.maxIo, HOSTFS_IO) } : {}
12437
+ });
12258
12438
  const served = serveFilesystem(
12259
12439
  port2,
12260
12440
  { ...slot.handlers, mount: ranged2 },
@@ -12270,7 +12450,7 @@ async function openHostfs(spec, connect, timing = {}) {
12270
12450
  {
12271
12451
  connect: () => connect(spec.source, { readonly: readonly2 }),
12272
12452
  up(client) {
12273
- active = client;
12453
+ active = timed(client, timing.callMs ?? HOSTFS_CALL_MS);
12274
12454
  slot.insert(client, handlers);
12275
12455
  served.invalidate(true);
12276
12456
  },
@@ -12760,8 +12940,11 @@ var Launcher = class {
12760
12940
  createWorker;
12761
12941
  modulesDir;
12762
12942
  env;
12763
- catalog;
12764
- binfmts;
12943
+ catalogs = /* @__PURE__ */ new Map();
12944
+ ownCatalogs = /* @__PURE__ */ new Map();
12945
+ binfmts = /* @__PURE__ */ new Map();
12946
+ scopes;
12947
+ unwatchScopes = () => void 0;
12765
12948
  warned = /* @__PURE__ */ new Set();
12766
12949
  compiled = /* @__PURE__ */ new Map();
12767
12950
  processes = /* @__PURE__ */ new Map();
@@ -12841,7 +13024,11 @@ var Launcher = class {
12841
13024
  ca: this.ca
12842
13025
  });
12843
13026
  enableCdp(this.net, this.cdp);
12844
- const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
13027
+ const fs = withCommandDirs(
13028
+ this.base,
13029
+ async () => new Set((await this.commands()).keys()),
13030
+ (dir) => this.userCommandDir(dir)
13031
+ );
12845
13032
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
12846
13033
  this.nodes.keepsOnDisk = true;
12847
13034
  this.openFiles.add(this.nodes);
@@ -12859,35 +13046,99 @@ var Launcher = class {
12859
13046
  this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
12860
13047
  this.users = new UserDb(this.fs);
12861
13048
  void this.unlinked.sweep("/");
12862
- this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
12863
- this.catalog = void 0;
12864
- this.binfmts = void 0;
13049
+ this.watchers.watch(
13050
+ [this.modulesDir, this.pnpmHome],
13051
+ { recursive: true },
13052
+ () => this.resetCatalogs()
13053
+ );
13054
+ this.watchers.watch([PASSWD], { recursive: false }, () => {
13055
+ this.scopes = void 0;
13056
+ this.resetCatalogs();
12865
13057
  });
12866
13058
  }
12867
13059
  setRoutes(table) {
12868
13060
  this.routes.set(table);
12869
13061
  }
12870
- commands() {
12871
- this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
12872
- return this.catalog;
13062
+ commands(scope = "") {
13063
+ let found = this.catalogs.get(scope);
13064
+ if (!found) {
13065
+ found = this.roots(scope).then((roots) => scanCommands(this.base, roots));
13066
+ this.catalogs.set(scope, found);
13067
+ }
13068
+ return found;
12873
13069
  }
12874
- interpreters() {
12875
- this.binfmts ??= this.roots().then(
12876
- (roots) => scanBinfmts(this.base, roots, (message3) => {
12877
- if (this.warned.has(message3)) return;
12878
- this.warned.add(message3);
12879
- console.warn(message3);
12880
- })
12881
- );
12882
- return this.binfmts;
13070
+ ownCommands(scope) {
13071
+ let found = this.ownCatalogs.get(scope);
13072
+ if (!found) {
13073
+ found = pnpmGlobalRoots(this.base, scope).then((roots) => scanCommands(this.base, roots)).then((commands) => new Set(commands.keys()));
13074
+ this.ownCatalogs.set(scope, found);
13075
+ }
13076
+ return found;
13077
+ }
13078
+ interpreters(scope = "") {
13079
+ let found = this.binfmts.get(scope);
13080
+ if (!found) {
13081
+ found = this.roots(scope).then(
13082
+ (roots) => scanBinfmts(this.base, roots, (message3) => {
13083
+ if (this.warned.has(message3)) return;
13084
+ this.warned.add(message3);
13085
+ console.warn(message3);
13086
+ })
13087
+ );
13088
+ this.binfmts.set(scope, found);
13089
+ }
13090
+ return found;
12883
13091
  }
12884
- async roots() {
12885
- return [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
13092
+ async roots(scope = "") {
13093
+ const system = [this.modulesDir, ...await pnpmGlobalRoots(this.base, this.pnpmHome)];
13094
+ return scope ? [...await pnpmGlobalRoots(this.base, scope), ...system] : system;
13095
+ }
13096
+ resetCatalogs() {
13097
+ this.catalogs.clear();
13098
+ this.ownCatalogs.clear();
13099
+ this.binfmts.clear();
13100
+ }
13101
+ userScopes() {
13102
+ this.scopes ??= this.users.list().then(
13103
+ (accounts) => {
13104
+ const users = accounts.filter((account) => account.uid !== 0);
13105
+ const homes = users.map((account) => pnpmHomeOf(account, this.pnpmHome));
13106
+ this.unwatchScopes();
13107
+ this.unwatchScopes = this.watchers.watch(
13108
+ homes,
13109
+ { recursive: true },
13110
+ () => this.resetCatalogs()
13111
+ );
13112
+ return {
13113
+ byUid: new Map(users.map((account, i) => [account.uid, homes[i]])),
13114
+ byBin: new Map(homes.map((home) => [`${home}/bin`, home]))
13115
+ };
13116
+ },
13117
+ () => ({ byUid: /* @__PURE__ */ new Map(), byBin: /* @__PURE__ */ new Map() })
13118
+ );
13119
+ return this.scopes;
13120
+ }
13121
+ async userCommandDir(dir) {
13122
+ const scope = (await this.userScopes()).byBin.get(dir);
13123
+ return scope === void 0 ? void 0 : this.ownCommands(scope);
13124
+ }
13125
+ async scopeOf(uid) {
13126
+ return (await this.userScopes()).byUid.get(uid) ?? "";
13127
+ }
13128
+ async commandAt(file, scope) {
13129
+ const system = COMMAND2.exec(file)?.[1];
13130
+ if (system !== void 0) return (await this.commands()).get(system);
13131
+ if (!file.includes("/")) return (await this.commands(scope)).get(file);
13132
+ const slash = file.lastIndexOf("/");
13133
+ const owner = (await this.userScopes()).byBin.get(file.slice(0, slash));
13134
+ if (owner === void 0 || !(await this.ownCommands(owner)).has(file.slice(slash + 1))) {
13135
+ return void 0;
13136
+ }
13137
+ return (await this.commands(owner)).get(file.slice(slash + 1));
12886
13138
  }
12887
- async resolve(file, argv0, cwd) {
12888
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
12889
- if (name2 !== void 0) {
12890
- const command = (await this.commands()).get(name2);
13139
+ async resolve(file, argv0, cwd, scope = "") {
13140
+ if (!file.includes("/") || COMMAND2.test(file)) {
13141
+ const command = await this.commandAt(file, scope);
12891
13142
  return command && !command.script ? targetOf(command) : void 0;
12892
13143
  }
12893
13144
  const path = this.fs.resolvePath(cwd, file);
@@ -12898,6 +13149,8 @@ var Launcher = class {
12898
13149
  const byPath = !COMMAND2.test(path) && (await this.commands()).get(linked)?.argv0Path === true;
12899
13150
  return target && byPath ? { ...target, argv0: path } : target;
12900
13151
  }
13152
+ const own = await this.commandAt(glue, scope);
13153
+ if (own) return own.script ? void 0 : targetOf(own);
12901
13154
  const head = await this.head(glue);
12902
13155
  if (!head) return void 0;
12903
13156
  if (isWasm(head)) {
@@ -12920,45 +13173,45 @@ var Launcher = class {
12920
13173
  return void 0;
12921
13174
  }
12922
13175
  }
12923
- async scriptCommand(file) {
12924
- const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
12925
- if (name2 === void 0) return void 0;
12926
- const command = (await this.commands()).get(name2);
13176
+ async scriptCommand(file, cwd, scope) {
13177
+ const direct = !file.includes("/") || COMMAND2.test(file);
13178
+ const at = direct ? file : await followLinks(this.base, this.fs.resolvePath(cwd, file));
13179
+ const command = await this.commandAt(at, scope);
12927
13180
  return command?.script ? command : void 0;
12928
13181
  }
12929
- async interpreted(file, argv, cwd, depth = 0) {
12930
- const command = await this.scriptCommand(file);
13182
+ async interpreted(file, argv, cwd, scope, depth = 0) {
13183
+ const command = await this.scriptCommand(file, cwd, scope);
12931
13184
  const script = command?.script ?? this.fs.resolvePath(cwd, file);
12932
13185
  const head = await this.head(script);
12933
13186
  if (!head) return void 0;
12934
- const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(), script);
13187
+ const binfmt = isWasm(head) ? void 0 : binfmtOf(await this.interpreters(scope), script);
12935
13188
  const words = shebang(head) ?? (binfmt ? [binfmt] : void 0);
12936
13189
  if (!words) return void 0;
12937
13190
  if (baseName(words[0]) === "env") words.shift();
12938
13191
  const [interp, ...rest2] = words;
12939
13192
  const arg = rest2.join(" ");
12940
13193
  const passed = [...arg ? [arg] : [], command ? script : file, ...argv.slice(1)];
12941
- const found = interp ? await this.resolve(interp, interp, cwd) : void 0;
13194
+ const found = interp ? await this.resolve(interp, interp, cwd, scope) : void 0;
12942
13195
  if (!found) {
12943
- if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp)) {
13196
+ if (!interp || depth >= MAX_INTERPRETERS || !await this.scriptCommand(interp, cwd, scope)) {
12944
13197
  return void 0;
12945
13198
  }
12946
- const inner = await this.interpreted(interp, [interp, ...passed], cwd, depth + 1);
13199
+ const inner = await this.interpreted(interp, [interp, ...passed], cwd, scope, depth + 1);
12947
13200
  return inner && { ...inner, target: withScriptEnv(inner.target, command) };
12948
13201
  }
12949
13202
  const target = withScriptEnv(found, command);
12950
13203
  return { target, args: [...target.prefix ?? [], ...passed] };
12951
13204
  }
12952
- async plan(file, argv, cwd) {
12953
- const direct = await this.resolve(file, argv[0] ?? file, cwd);
13205
+ async plan(file, argv, cwd, scope) {
13206
+ const direct = await this.resolve(file, argv[0] ?? file, cwd, scope);
12954
13207
  if (direct) return { target: direct, args: [...direct.prefix ?? [], ...argv.slice(1)] };
12955
- return this.interpreted(file, argv, cwd);
13208
+ return this.interpreted(file, argv, cwd, scope);
12956
13209
  }
12957
- async unrunnable(file, cwd) {
13210
+ async unrunnable(file, cwd, scope) {
12958
13211
  const path = this.fs.resolvePath(cwd, file);
12959
13212
  const head = await this.base.readFileBuffer(path).catch(() => null);
12960
13213
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
12961
- return binfmtOf(await this.interpreters(), path) ? "ENOENT" : "ENOEXEC";
13214
+ return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
12962
13215
  }
12963
13216
  module(path) {
12964
13217
  return this.base.stat(path).then((st) => {
@@ -13168,10 +13421,11 @@ var Launcher = class {
13168
13421
  }
13169
13422
  spawner(ppid, report) {
13170
13423
  return async (req, fds) => {
13171
- const planned = await this.plan(req.file, req.argv, req.cwd);
13424
+ const scope = await this.scopeOf(this.credOf(ppid).euid);
13425
+ const planned = await this.plan(req.file, req.argv, req.cwd, scope);
13172
13426
  if (!planned) {
13173
13427
  await fds.closeAll();
13174
- throw new SpawnError(await this.unrunnable(req.file, req.cwd));
13428
+ throw new SpawnError(await this.unrunnable(req.file, req.cwd, scope));
13175
13429
  }
13176
13430
  const exec = req.exec ? { exec: true } : {};
13177
13431
  const deciding = req.exec ? void 0 : this.undecided(ppid);
@@ -13290,11 +13544,7 @@ var Launcher = class {
13290
13544
  throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
13291
13545
  return openHostfs(
13292
13546
  spec,
13293
- grantConnect(
13294
- hostfs,
13295
- hostfsFetch ?? ((url, init) => fetch(url, init)),
13296
- hostfsTiming?.requestMs
13297
- ),
13547
+ grantConnect(hostfs, hostfsFetch ?? ((url, init) => fetch(url, init)), hostfsTiming),
13298
13548
  hostfsTiming
13299
13549
  );
13300
13550
  }
@@ -13477,18 +13727,18 @@ var Launcher = class {
13477
13727
  ...account.umask !== void 0 ? { umask: account.umask } : {}
13478
13728
  };
13479
13729
  }
13480
- async starting(argv, dir) {
13481
- this.catalog = void 0;
13482
- this.binfmts = void 0;
13730
+ async starting(argv, dir, account) {
13731
+ this.resetCatalogs();
13732
+ const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
13483
13733
  const start2 = dir ?? "/";
13484
13734
  const cwd = await physicalPathAsync(this.base, start2.startsWith("/") ? start2 : `/${start2}`);
13485
13735
  const [file = ""] = argv;
13486
13736
  await this.base.mkdir(cwd, { recursive: true });
13487
- return { cwd, file, planned: await this.plan(file, argv, cwd) };
13737
+ return { cwd, file, planned: await this.plan(file, argv, cwd, scope) };
13488
13738
  }
13489
13739
  async openTerminal(argv, options) {
13490
13740
  const account = await this.account(options.user);
13491
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13741
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13492
13742
  if (!planned) throw new Error(`${file}: command not found`);
13493
13743
  let leader = 0;
13494
13744
  const tty = new KernelTty(
@@ -13531,7 +13781,7 @@ var Launcher = class {
13531
13781
  }
13532
13782
  async run(argv, options = {}) {
13533
13783
  const account = await this.account(options.user);
13534
- const { cwd, file, planned } = await this.starting(argv, options.cwd);
13784
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
13535
13785
  const env = this.environment(cwd, options.env, account);
13536
13786
  const out = [];
13537
13787
  const err = [];