@ai-ecoverse/slicc-kernel 1.15.0 → 1.16.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.
@@ -76,9 +76,9 @@ var IndexedDbMeta = class {
76
76
  if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
77
77
  }
78
78
  }
79
- changed(invalidation) {
80
- this.drop(invalidation);
81
- this.channel.postMessage(invalidation);
79
+ changed(invalidation2) {
80
+ this.drop(invalidation2);
81
+ this.channel.postMessage(invalidation2);
82
82
  }
83
83
  transaction(mode) {
84
84
  const transaction = this.db.transaction(META_STORE, mode);
@@ -2952,9 +2952,9 @@ var RemoteTransport = class {
2952
2952
  var NAME = /^[A-Za-z0-9._+-]+$/;
2953
2953
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
2954
2954
  var PACKAGE = /\$\{package\}/g;
2955
- function inside(pkg, rel) {
2956
- if (typeof rel !== "string" || rel === "") return void 0;
2957
- const clean = rel.replace(/^\.\//, "");
2955
+ function inside(pkg, rel2) {
2956
+ if (typeof rel2 !== "string" || rel2 === "") return void 0;
2957
+ const clean = rel2.replace(/^\.\//, "");
2958
2958
  if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
2959
2959
  return `${pkg}/${clean}`;
2960
2960
  }
@@ -6159,6 +6159,57 @@ function serveFilesystem(port, handlers, capabilities = {}) {
6159
6159
  return { invalidate: (paths) => reply({ invalidate: paths }) };
6160
6160
  }
6161
6161
 
6162
+ // src/mount/medium.ts
6163
+ var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
6164
+ var absent = () => fsError2("ENOMEDIUM", "No medium found");
6165
+ function mediumSlot(lost, onLost) {
6166
+ let current;
6167
+ function wrap(pick) {
6168
+ return async (...args) => {
6169
+ const medium = current;
6170
+ if (!medium) throw absent();
6171
+ try {
6172
+ return await pick(medium.handlers)(...args);
6173
+ } catch (err) {
6174
+ if (!await lost(err, medium.key)) throw err;
6175
+ if (current === medium) {
6176
+ current = void 0;
6177
+ onLost(medium.key);
6178
+ }
6179
+ throw absent();
6180
+ }
6181
+ };
6182
+ }
6183
+ const getattr = wrap((h) => h.getattr);
6184
+ const readdir = wrap((h) => h.readdir);
6185
+ const statfs = wrap((h) => async () => await h.statfs?.() ?? null);
6186
+ return {
6187
+ handlers: {
6188
+ getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6189
+ readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6190
+ open: wrap((h) => h.open),
6191
+ read: wrap((h) => h.read),
6192
+ write: wrap((h) => h.write),
6193
+ release: wrap((h) => h.release),
6194
+ mkdir: wrap((h) => h.mkdir),
6195
+ rmdir: wrap((h) => h.rmdir),
6196
+ unlink: wrap((h) => h.unlink),
6197
+ rename: wrap((h) => h.rename),
6198
+ symlink: wrap((h) => h.symlink),
6199
+ readlink: wrap((h) => h.readlink),
6200
+ setattr: wrap((h) => h.setattr),
6201
+ statfs: () => current ? statfs() : Promise.resolve(null)
6202
+ },
6203
+ present: () => current !== void 0,
6204
+ insert(key, handlers) {
6205
+ current = { key, handlers };
6206
+ },
6207
+ eject() {
6208
+ current = void 0;
6209
+ }
6210
+ };
6211
+ }
6212
+
6162
6213
  // src/mount/fsa.ts
6163
6214
  var FSA_CAPABILITIES = { listingStats: true };
6164
6215
  var codeOf = (err) => err?.code;
@@ -6246,7 +6297,6 @@ function kernelFsHandlers(fs) {
6246
6297
  }
6247
6298
  };
6248
6299
  }
6249
- var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16749 };
6250
6300
  function granted(handle) {
6251
6301
  if (!handle.queryPermission) return Promise.resolve(true);
6252
6302
  return handle.queryPermission({ mode: "readwrite" }).then(
@@ -6255,48 +6305,345 @@ function granted(handle) {
6255
6305
  );
6256
6306
  }
6257
6307
  function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
6258
- let current;
6259
- const absent = () => fsError2("ENOMEDIUM", "No medium found");
6260
- function wrap(pick) {
6261
- return async (...args) => {
6262
- const medium = current;
6263
- if (!medium) throw absent();
6264
- try {
6265
- return await pick(medium.handlers)(...args);
6266
- } catch (err) {
6267
- if (!denied(err) || await granted(medium.handle)) throw err;
6268
- if (current === medium) {
6269
- current = void 0;
6270
- onLost(medium.handle);
6308
+ const slot = mediumSlot(
6309
+ async (err, handle) => denied(err) && !await granted(handle),
6310
+ onLost
6311
+ );
6312
+ return {
6313
+ handlers: slot.handlers,
6314
+ present: slot.present,
6315
+ insert: (handle) => slot.insert(handle, kernelFsHandlers(open(handle))),
6316
+ eject: slot.eject
6317
+ };
6318
+ }
6319
+
6320
+ // src/mount/hostfs.ts
6321
+ var HOSTFS_IO = 16 * 1024 * 1024;
6322
+ var codeOf2 = (err) => err?.code;
6323
+ var unreachable = (err) => codeOf2(err) === "ENOTCONN";
6324
+ function rel(path) {
6325
+ return path.replace(/^\/+/, "");
6326
+ }
6327
+ var HostfsClient = class {
6328
+ grant;
6329
+ regrant;
6330
+ fetch;
6331
+ constructor(grant, regrant, fetch2) {
6332
+ this.grant = grant;
6333
+ this.regrant = regrant;
6334
+ this.fetch = fetch2;
6335
+ }
6336
+ get capabilities() {
6337
+ return this.grant.capabilities ?? {};
6338
+ }
6339
+ get token() {
6340
+ return this.grant.token;
6341
+ }
6342
+ get maxIo() {
6343
+ return this.capabilities.maxIo ?? HOSTFS_IO;
6344
+ }
6345
+ async send(path, init, handle = false, retried = false) {
6346
+ let response;
6347
+ try {
6348
+ response = await this.fetch(`${this.grant.url}${path}`, {
6349
+ ...init,
6350
+ credentials: "omit",
6351
+ headers: {
6352
+ ...init.headers,
6353
+ "X-Hostfs-Token": this.grant.token
6271
6354
  }
6272
- throw absent();
6355
+ });
6356
+ } catch (err) {
6357
+ throw fsError2("ENOTCONN", `the host proxy is unreachable: ${err.message}`);
6358
+ }
6359
+ if (response.ok) return response;
6360
+ if (response.headers.get("X-Proxy-Error")) {
6361
+ await response.body?.cancel();
6362
+ if (retried || response.status !== 403) {
6363
+ throw fsError2("EACCES", `the host proxy refused this folder (${response.status})`);
6273
6364
  }
6274
- };
6365
+ this.grant = await this.regrant();
6366
+ if (handle)
6367
+ throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
6368
+ return this.send(path, init, false, true);
6369
+ }
6370
+ const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
6371
+ const body = await response.json().catch(() => ({}));
6372
+ throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
6373
+ }
6374
+ async call(op, body = {}, handle = false) {
6375
+ const response = await this.send(
6376
+ "/api/hostfs",
6377
+ {
6378
+ method: "POST",
6379
+ headers: { "Content-Type": "application/json" },
6380
+ body: JSON.stringify({ op, ...body })
6381
+ },
6382
+ handle
6383
+ );
6384
+ return await response.json();
6385
+ }
6386
+ async read(fh, offset, size, ifMatch) {
6387
+ const response = await this.send(
6388
+ "/api/hostfs",
6389
+ {
6390
+ method: "POST",
6391
+ headers: { "Content-Type": "application/json" },
6392
+ body: JSON.stringify({ op: "read", fh, offset, size, ...ifMatch ? { ifMatch } : {} })
6393
+ },
6394
+ true
6395
+ );
6396
+ return new Uint8Array(await response.arrayBuffer());
6397
+ }
6398
+ async write(fh, offset, bytes) {
6399
+ const response = await this.send(
6400
+ "/api/hostfs/write",
6401
+ {
6402
+ method: "PUT",
6403
+ headers: {
6404
+ "Content-Type": "application/octet-stream",
6405
+ "X-Hostfs-Request": JSON.stringify({ fh, offset })
6406
+ },
6407
+ body: bytes
6408
+ },
6409
+ true
6410
+ );
6411
+ await response.body?.cancel();
6412
+ }
6413
+ async watch(signal, onEvent, onOpen = () => void 0) {
6414
+ const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
6415
+ onOpen();
6416
+ const reader = response.body.getReader();
6417
+ const decoder2 = new TextDecoder();
6418
+ let buffered = "";
6419
+ try {
6420
+ for (; ; ) {
6421
+ const { done: done4, value } = await reader.read();
6422
+ if (done4) return;
6423
+ buffered += decoder2.decode(value, { stream: true });
6424
+ let end = buffered.indexOf("\n");
6425
+ while (end >= 0) {
6426
+ const line = buffered.slice(0, end).trim();
6427
+ buffered = buffered.slice(end + 1);
6428
+ if (line) onEvent(JSON.parse(line));
6429
+ end = buffered.indexOf("\n");
6430
+ }
6431
+ }
6432
+ } catch (err) {
6433
+ if (signal.aborted) return;
6434
+ throw fsError2("ENOTCONN", `the host proxy's watch broke: ${err.message}`);
6435
+ } finally {
6436
+ reader.releaseLock();
6437
+ }
6438
+ }
6439
+ };
6440
+ function hostfsHandlers(current) {
6441
+ const handles = /* @__PURE__ */ new Map();
6442
+ let nextFh = 0;
6443
+ const fresh = (remote) => {
6444
+ if (remote.client.token === remote.token) return remote;
6445
+ throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
6446
+ };
6447
+ const live = (fh) => {
6448
+ const remote = handles.get(fh);
6449
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6450
+ return fresh(remote);
6451
+ };
6452
+ const call = (op, body = {}) => current().call(op, body);
6453
+ async function writable(client, path, flags) {
6454
+ const opening = () => client.call("open", { path: rel(path), ...flags });
6455
+ try {
6456
+ return { opened: await opening(), restore: void 0 };
6457
+ } catch (err) {
6458
+ if (!flags.write || codeOf2(err) !== "EACCES") throw err;
6459
+ const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
6460
+ if (mode & 128) throw err;
6461
+ const restore = { path: rel(path), mode };
6462
+ await client.call("setattr", { path: rel(path), mode: mode | 128 });
6463
+ const opened2 = await opening().catch(async (again) => {
6464
+ await client.call("setattr", restore);
6465
+ throw again;
6466
+ });
6467
+ return { opened: opened2, restore };
6468
+ }
6275
6469
  }
6276
- const getattr = wrap((h) => h.getattr);
6277
- const readdir = wrap((h) => h.readdir);
6278
6470
  return {
6279
- handlers: {
6280
- getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6281
- readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6282
- open: wrap((h) => h.open),
6283
- read: wrap((h) => h.read),
6284
- write: wrap((h) => h.write),
6285
- release: wrap((h) => h.release),
6286
- mkdir: wrap((h) => h.mkdir),
6287
- rmdir: wrap((h) => h.rmdir),
6288
- unlink: wrap((h) => h.unlink),
6289
- rename: wrap((h) => h.rename),
6290
- symlink: wrap((h) => h.symlink),
6291
- readlink: wrap((h) => h.readlink),
6292
- setattr: wrap((h) => h.setattr)
6471
+ getattr: async (path) => await call("stat", { path: rel(path) }),
6472
+ async readdir(path) {
6473
+ const { entries } = await call(
6474
+ "list",
6475
+ { path: rel(path) }
6476
+ );
6477
+ return entries.map(({ name: name2, attr }) => ({ name: name2, kind: attr.kind, attr }));
6293
6478
  },
6294
- present: () => current !== void 0,
6295
- insert(handle) {
6296
- current = { handle, handlers: kernelFsHandlers(open(handle)) };
6479
+ async open(path, flags) {
6480
+ const client = current();
6481
+ const token = client.token;
6482
+ const { opened: opened2, restore } = await writable(client, path, flags);
6483
+ handles.set(++nextFh, {
6484
+ client,
6485
+ token,
6486
+ fh: opened2.fh,
6487
+ etag: flags.write ? void 0 : opened2.attr?.etag,
6488
+ restore
6489
+ });
6490
+ return nextFh;
6297
6491
  },
6298
- eject() {
6299
- current = void 0;
6492
+ async read(fh, offset, size) {
6493
+ const remote = live(fh);
6494
+ return remote.client.read(
6495
+ remote.fh,
6496
+ offset,
6497
+ Math.min(size, remote.client.maxIo),
6498
+ remote.etag
6499
+ );
6500
+ },
6501
+ async write(fh, offset, bytes) {
6502
+ const remote = live(fh);
6503
+ const most = remote.client.maxIo;
6504
+ for (let at = 0; at < bytes.length; at += most) {
6505
+ await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
6506
+ }
6507
+ },
6508
+ async release(fh) {
6509
+ const remote = handles.get(fh);
6510
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6511
+ handles.delete(fh);
6512
+ try {
6513
+ await fresh(remote).client.call("release", { fh: remote.fh }, true);
6514
+ } finally {
6515
+ if (remote.restore) await current().call("setattr", remote.restore);
6516
+ }
6517
+ },
6518
+ mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
6519
+ rmdir: async (path) => void await call("rmdir", { path: rel(path) }),
6520
+ unlink: async (path) => void await call("unlink", { path: rel(path) }),
6521
+ rename: async (from, to) => void await call("rename", { from: rel(from), to: rel(to) }),
6522
+ symlink: async (target, path) => void await call("symlink", { target, path: rel(path) }),
6523
+ readlink: async (path) => (await call("readlink", { path: rel(path) })).target,
6524
+ setattr: async (path, change) => void await call("setattr", { path: rel(path), ...change }),
6525
+ statfs: () => call("statfs")
6526
+ };
6527
+ }
6528
+ function sleep(ms, signal) {
6529
+ return new Promise((resolve) => {
6530
+ const done4 = () => {
6531
+ clearTimeout(timer);
6532
+ signal.removeEventListener("abort", done4);
6533
+ resolve();
6534
+ };
6535
+ const timer = setTimeout(done4, ms);
6536
+ signal.addEventListener("abort", done4);
6537
+ });
6538
+ }
6539
+ function keepWatching(loop, signal) {
6540
+ const silenceMs = loop.silenceMs ?? 45e3;
6541
+ const [first, most] = loop.backoffMs ?? [1e3, 3e4];
6542
+ const ready2 = Promise.withResolvers();
6543
+ let wait = first;
6544
+ let up = false;
6545
+ let current;
6546
+ async function session() {
6547
+ const session2 = new AbortController();
6548
+ current = session2;
6549
+ const stop = () => session2.abort();
6550
+ signal.addEventListener("abort", stop);
6551
+ let timer;
6552
+ const quiet = () => {
6553
+ clearTimeout(timer);
6554
+ timer = setTimeout(stop, silenceMs);
6555
+ };
6556
+ let opened2 = false;
6557
+ try {
6558
+ const client = await loop.connect();
6559
+ await client.watch(
6560
+ session2.signal,
6561
+ (event) => {
6562
+ quiet();
6563
+ if (!event.ping) loop.event(event);
6564
+ },
6565
+ () => {
6566
+ quiet();
6567
+ opened2 = true;
6568
+ up = true;
6569
+ loop.up(client);
6570
+ ready2.resolve();
6571
+ }
6572
+ );
6573
+ return opened2;
6574
+ } catch {
6575
+ return opened2;
6576
+ } finally {
6577
+ clearTimeout(timer);
6578
+ signal.removeEventListener("abort", stop);
6579
+ }
6580
+ }
6581
+ void (async () => {
6582
+ while (!signal.aborted) {
6583
+ const began = Date.now();
6584
+ const opened2 = await session();
6585
+ if (signal.aborted) return;
6586
+ if (opened2 && Date.now() - began >= first) {
6587
+ wait = first;
6588
+ continue;
6589
+ }
6590
+ if (!opened2 && up) {
6591
+ up = false;
6592
+ loop.down();
6593
+ }
6594
+ ready2.resolve();
6595
+ await sleep(wait, signal);
6596
+ wait = Math.min(wait * 2, most);
6597
+ }
6598
+ })();
6599
+ return { ready: ready2.promise, kick: () => current?.abort() };
6600
+ }
6601
+ function invalidation(event) {
6602
+ return event.paths ?? true;
6603
+ }
6604
+ async function openHostfs(spec, hook, fetch2, timing = {}) {
6605
+ const readonly = spec.options?.ro !== void 0;
6606
+ const { port1, port2 } = new MessageChannel();
6607
+ const slot = mediumSlot(
6608
+ async (err) => unreachable(err),
6609
+ () => watching.kick()
6610
+ );
6611
+ const stop = new AbortController();
6612
+ const grant = () => hook(spec.source, { readonly });
6613
+ let active;
6614
+ const handlers = hostfsHandlers(() => active);
6615
+ const served = serveFilesystem(port2, slot.handlers, {
6616
+ symlinks: true,
6617
+ chmod: true,
6618
+ listingStats: true,
6619
+ maxIo: HOSTFS_IO,
6620
+ ...readonly ? { readonly: true } : {}
6621
+ });
6622
+ const watching = keepWatching(
6623
+ {
6624
+ connect: async () => new HostfsClient(await grant(), grant, fetch2),
6625
+ up(client) {
6626
+ active = client;
6627
+ slot.insert(client, handlers);
6628
+ served.invalidate(true);
6629
+ },
6630
+ event: (event) => served.invalidate(invalidation(event)),
6631
+ down() {
6632
+ slot.eject();
6633
+ served.invalidate(true);
6634
+ },
6635
+ ...timing
6636
+ },
6637
+ stop.signal
6638
+ );
6639
+ await watching.ready;
6640
+ return {
6641
+ port: port1,
6642
+ present: slot.present,
6643
+ dispose() {
6644
+ stop.abort();
6645
+ port1.close();
6646
+ port2.close();
6300
6647
  }
6301
6648
  };
6302
6649
  }
@@ -6396,6 +6743,7 @@ var DriverConnection = class _DriverConnection {
6396
6743
 
6397
6744
  // src/mount/mount-fs.ts
6398
6745
  var MAX_LINKS4 = 40;
6746
+ var STALE_RETRIES = 3;
6399
6747
  var DEFAULT_IO = 8 * 1024 * 1024;
6400
6748
  var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
6401
6749
  function parseSize(text) {
@@ -6462,8 +6810,8 @@ var MountTable = class {
6462
6810
  best = mount;
6463
6811
  }
6464
6812
  if (!best) return void 0;
6465
- const rel = best.target === "/" ? path : path.slice(best.target.length);
6466
- return { mount: best, rel: rel || "/" };
6813
+ const rel2 = best.target === "/" ? path : path.slice(best.target.length);
6814
+ return { mount: best, rel: rel2 || "/" };
6467
6815
  }
6468
6816
  async mount(spec, fs) {
6469
6817
  const target = normalizePath(spec.target);
@@ -6568,20 +6916,20 @@ var MountTable = class {
6568
6916
  mount.lists.delete(parentOf4(path));
6569
6917
  for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
6570
6918
  }
6571
- async getattr(mount, path, rel) {
6919
+ async getattr(mount, path, rel2) {
6572
6920
  if (mount.conn.failure) throw mount.conn.failure;
6573
6921
  const cached = mount.attrs.get(path);
6574
6922
  if (cached && cached.expires > Date.now()) return cached.value;
6575
- const attr = await mount.conn.call({ op: "getattr", path: rel });
6923
+ const attr = await mount.conn.call({ op: "getattr", path: rel2 });
6576
6924
  const ttl = mount.caps.attrTtl ?? 1e3;
6577
6925
  if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
6578
6926
  return attr;
6579
6927
  }
6580
- async readdir(mount, path, rel) {
6928
+ async readdir(mount, path, rel2) {
6581
6929
  if (mount.conn.failure) throw mount.conn.failure;
6582
6930
  const cached = mount.lists.get(path);
6583
6931
  if (cached && cached.expires > Date.now()) return cached.value;
6584
- const entries = await mount.conn.call({ op: "readdir", path: rel });
6932
+ const entries = await mount.conn.call({ op: "readdir", path: rel2 });
6585
6933
  const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
6586
6934
  if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
6587
6935
  return entries;
@@ -6626,10 +6974,10 @@ var MountFs = class {
6626
6974
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6627
6975
  return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
6628
6976
  }
6629
- async open(mount, rel, write) {
6977
+ async open(mount, rel2, write) {
6630
6978
  return await mount.conn.call({
6631
6979
  op: "open",
6632
- path: rel,
6980
+ path: rel2,
6633
6981
  write,
6634
6982
  create: write,
6635
6983
  truncate: write,
@@ -6646,13 +6994,22 @@ var MountFs = class {
6646
6994
  const real = await this.follow(path);
6647
6995
  const found = this.at(real);
6648
6996
  if (!found) return this.base.readFileBuffer(real);
6649
- const { mount, rel } = found;
6650
- const attr = await this.table.getattr(mount, real, rel);
6997
+ for (let attempt = 1; ; attempt++) {
6998
+ try {
6999
+ return await this.readWhole(found.mount, real, found.rel);
7000
+ } catch (err) {
7001
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
7002
+ this.table.forget(found.mount, real);
7003
+ }
7004
+ }
7005
+ }
7006
+ async readWhole(mount, real, rel2) {
7007
+ const attr = await this.table.getattr(mount, real, rel2);
6651
7008
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
6652
7009
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
6653
7010
  throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
6654
7011
  }
6655
- const fh = await this.open(mount, rel, false);
7012
+ const fh = await this.open(mount, rel2, false);
6656
7013
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6657
7014
  const chunks = [];
6658
7015
  let total = 0;
@@ -6678,7 +7035,7 @@ var MountFs = class {
6678
7035
  const real = await this.follow(path, 0, true);
6679
7036
  const found = this.at(real);
6680
7037
  if (!found) return this.base.writeFile(real, content);
6681
- const { mount, rel } = found;
7038
+ const { mount, rel: rel2 } = found;
6682
7039
  this.writable(mount, real);
6683
7040
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
6684
7041
  if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
@@ -6687,8 +7044,17 @@ var MountFs = class {
6687
7044
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
6688
7045
  );
6689
7046
  }
7047
+ for (let attempt = 1; ; attempt++) {
7048
+ try {
7049
+ return await this.writeWhole(mount, real, rel2, bytes);
7050
+ } catch (err) {
7051
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
7052
+ }
7053
+ }
7054
+ }
7055
+ async writeWhole(mount, real, rel2, bytes) {
6690
7056
  this.table.forget(mount, real);
6691
- const fh = await this.open(mount, rel, true);
7057
+ const fh = await this.open(mount, rel2, true);
6692
7058
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6693
7059
  try {
6694
7060
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -6755,16 +7121,16 @@ var MountFs = class {
6755
7121
  async mkdir(path, options = {}) {
6756
7122
  const found = this.at(path);
6757
7123
  if (!found) return this.base.mkdir(path, options);
6758
- const { mount, rel } = found;
7124
+ const { mount, rel: rel2 } = found;
6759
7125
  const real = normalizePath(path);
6760
7126
  this.writable(mount, real);
6761
7127
  if (!options.recursive) {
6762
- if (rel === "/") throw errnoError("EEXIST", real);
7128
+ if (rel2 === "/") throw errnoError("EEXIST", real);
6763
7129
  this.table.forget(mount, real);
6764
- await mount.conn.call({ op: "mkdir", path: rel });
7130
+ await mount.conn.call({ op: "mkdir", path: rel2 });
6765
7131
  return;
6766
7132
  }
6767
- const parts = rel.split("/").filter(Boolean);
7133
+ const parts = rel2.split("/").filter(Boolean);
6768
7134
  for (let n = 1; n <= parts.length; n++) {
6769
7135
  const sub = `/${parts.slice(0, n).join("/")}`;
6770
7136
  const full = normalizePath(`${mount.target}${sub}`);
@@ -6778,25 +7144,25 @@ var MountFs = class {
6778
7144
  async rm(path, options = {}) {
6779
7145
  const found = this.at(path);
6780
7146
  if (!found) return this.base.rm(path, options);
6781
- const { mount, rel } = found;
7147
+ const { mount, rel: rel2 } = found;
6782
7148
  const real = normalizePath(path);
6783
- if (rel === "/") throw errnoError("EBUSY", `${real} is a mount point`);
7149
+ if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
6784
7150
  this.writable(mount, real);
6785
7151
  let attr;
6786
7152
  try {
6787
- attr = await this.table.getattr(mount, real, rel);
7153
+ attr = await this.table.getattr(mount, real, rel2);
6788
7154
  } catch (err) {
6789
7155
  if (options.force && err.code === "ENOENT") return;
6790
7156
  throw err;
6791
7157
  }
6792
7158
  this.table.forget(mount, real);
6793
7159
  if (attr.kind === "directory" && options.recursive) {
6794
- for (const entry of await this.table.readdir(mount, real, rel)) {
7160
+ for (const entry of await this.table.readdir(mount, real, rel2)) {
6795
7161
  await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
6796
7162
  }
6797
7163
  this.table.forget(mount, real);
6798
7164
  }
6799
- await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel });
7165
+ await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel2 });
6800
7166
  this.table.forget(mount, real);
6801
7167
  }
6802
7168
  async rename(from, to) {
@@ -7258,6 +7624,7 @@ var Launcher = class {
7258
7624
  media;
7259
7625
  onMountPending;
7260
7626
  removable = /* @__PURE__ */ new Map();
7627
+ hostfs;
7261
7628
  inserted = /* @__PURE__ */ new Map();
7262
7629
  openFiles = /* @__PURE__ */ new Set();
7263
7630
  held = /* @__PURE__ */ new Set();
@@ -7275,6 +7642,7 @@ var Launcher = class {
7275
7642
  this.createDriverWorker = options.createDriverWorker;
7276
7643
  this.media = options.media ?? memoryMedia();
7277
7644
  this.onMountPending = options.onMountPending;
7645
+ this.hostfs = options;
7278
7646
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7279
7647
  this.modulesDir = options.modules ?? "/node_modules";
7280
7648
  this.env = { ...networkEnv(), ...options.env };
@@ -7540,6 +7908,17 @@ var Launcher = class {
7540
7908
  }
7541
7909
  async driver(type, spec) {
7542
7910
  if (type === "fsa") return this.fsaDriver(spec);
7911
+ if (type === "hostfs") {
7912
+ const { hostfs, hostfsFetch, hostfsTiming } = this.hostfs;
7913
+ if (!hostfs)
7914
+ throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
7915
+ return openHostfs(
7916
+ spec,
7917
+ hostfs,
7918
+ hostfsFetch ?? ((url, init) => fetch(url, init)),
7919
+ hostfsTiming
7920
+ );
7921
+ }
7543
7922
  if (type === "tmpfs") {
7544
7923
  const { port1, port2 } = new MessageChannel();
7545
7924
  serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
@@ -7796,6 +8175,7 @@ function serveKernel(port, deps) {
7796
8175
  ...remote ? { transport: remote } : {},
7797
8176
  ...media ? { media } : {},
7798
8177
  onMountPending: (medium) => port.postMessage({ medium }),
8178
+ ...req.hostfs ? { hostfs: askGrant } : {},
7799
8179
  caStore: caStore(deps.metadata ? name2 : false)
7800
8180
  });
7801
8181
  await started.prepare();
@@ -7829,7 +8209,25 @@ function serveKernel(port, deps) {
7829
8209
  onStderr: (bytes) => reply(req.id, { fd: 2, bytes })
7830
8210
  });
7831
8211
  }
8212
+ const grants = /* @__PURE__ */ new Map();
8213
+ let nextGrant = 0;
8214
+ const askGrant = (source, options) => {
8215
+ const id = ++nextGrant;
8216
+ const waiting = Promise.withResolvers();
8217
+ grants.set(id, waiting);
8218
+ port.postMessage({ hostfs: { id, source, ...options } });
8219
+ return waiting.promise;
8220
+ };
8221
+ function granted2({ id, grant, error }) {
8222
+ const waiting = grants.get(id);
8223
+ if (!waiting) return;
8224
+ grants.delete(id);
8225
+ if (grant) waiting.resolve(grant);
8226
+ else waiting.reject(Object.assign(new Error(error ?? "no grant"), { code: "EACCES" }));
8227
+ }
7832
8228
  port.addEventListener("message", (event) => {
8229
+ const grant = event.data.hostfsGrant;
8230
+ if (grant) return granted2(grant);
7833
8231
  if (event.data.net !== void 0) {
7834
8232
  remote?.receive(event.data);
7835
8233
  return;