@ai-ecoverse/slicc-kernel 1.161.0 → 1.162.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.
@@ -1005,6 +1005,9 @@ var RemoteWatch = class {
1005
1005
  holdsSent = 0;
1006
1006
  silent = /* @__PURE__ */ new Set();
1007
1007
  holders = /* @__PURE__ */ new Set();
1008
+ instances = /* @__PURE__ */ new Map();
1009
+ instance;
1010
+ keeps;
1008
1011
  listeners = /* @__PURE__ */ new Set();
1009
1012
  waiting = /* @__PURE__ */ new Map();
1010
1013
  locks;
@@ -1124,8 +1127,9 @@ var RemoteWatch = class {
1124
1127
  this.met?.();
1125
1128
  }
1126
1129
  if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
1127
- if (data.tab !== void 0 && data.holds !== void 0) this.heldBy(data);
1130
+ this.holdsHeard(data);
1128
1131
  if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
1132
+ if (data.instance !== void 0) this.instances.set(data.from, data.instance);
1129
1133
  this.acked(data.ack, data.from);
1130
1134
  }
1131
1135
  if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [], content: true });
@@ -1152,6 +1156,7 @@ var RemoteWatch = class {
1152
1156
  this.mourned.delete(peer);
1153
1157
  this.others.delete(peer);
1154
1158
  this.peerHolds.delete(peer);
1159
+ this.keeps?.left(peer);
1155
1160
  this.peers.delete(peer);
1156
1161
  this.ackers.delete(peer);
1157
1162
  this.holders.delete(peer);
@@ -1192,9 +1197,38 @@ var RemoteWatch = class {
1192
1197
  this.rebuild();
1193
1198
  this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
1194
1199
  }
1200
+ holdsHeard(data) {
1201
+ if (data.tab === void 0) return;
1202
+ if (data.holds !== void 0) this.heldBy(data);
1203
+ if (data.to === this.tab && (data.keeping || data.kept)) void this.keepAsked(data);
1204
+ }
1205
+ keepChains = /* @__PURE__ */ new Map();
1206
+ keepAsked(data) {
1207
+ const from = data.tab;
1208
+ const next3 = (this.keepChains.get(from) ?? Promise.resolve()).then(() => this.keepOne(data));
1209
+ this.keepChains.set(from, next3);
1210
+ return next3;
1211
+ }
1212
+ async keepOne({ tab, seq: seq2, keeping, kept }) {
1213
+ const from = tab;
1214
+ const keeps = this.keeps;
1215
+ try {
1216
+ if (keeping) await keeps?.keeping(from, keeping.paths);
1217
+ if (kept)
1218
+ await keeps?.kept(from, kept.items, kept.paths ?? kept.items.map((item) => item.path));
1219
+ } finally {
1220
+ this.channel.postMessage({
1221
+ ack: seq2,
1222
+ from: this.tab,
1223
+ to: from,
1224
+ ...this.instance === void 0 ? {} : { instance: this.instance }
1225
+ });
1226
+ }
1227
+ }
1195
1228
  heldBy({ tab, holds: holds2, seq: seq2 }) {
1196
1229
  const from = tab;
1197
1230
  const { add, drop } = holds2;
1231
+ if (holds2?.instance !== void 0) this.instances.set(from, holds2.instance);
1198
1232
  const held = this.peerHolds.get(from) ?? /* @__PURE__ */ new Map();
1199
1233
  for (const path of drop) held.delete(path);
1200
1234
  for (const [path, fid] of add) held.set(path, fid);
@@ -1236,9 +1270,38 @@ var RemoteWatch = class {
1236
1270
  sendHolds(add, drop) {
1237
1271
  const seq2 = ++this.seq;
1238
1272
  this.holdsSent++;
1239
- this.channel.postMessage({ tab: this.tab, holds: { add: [...add], drop: [...drop] }, seq: seq2 });
1273
+ const instance = this.instance === void 0 ? {} : { instance: this.instance };
1274
+ this.channel.postMessage({
1275
+ tab: this.tab,
1276
+ holds: { add: [...add], drop: [...drop], ...instance },
1277
+ seq: seq2
1278
+ });
1240
1279
  return seq2;
1241
1280
  }
1281
+ heldWithin(target) {
1282
+ const out = [];
1283
+ for (const [tab, held] of this.peerHolds) {
1284
+ for (const [path, fid] of held) {
1285
+ if (path !== target && !path.startsWith(`${target}/`)) continue;
1286
+ out.push({ tab, path, fid, instance: this.instances.get(tab) });
1287
+ }
1288
+ }
1289
+ return out;
1290
+ }
1291
+ ask(to, body, cap) {
1292
+ const seq2 = ++this.seq;
1293
+ this.channel.postMessage({ tab: this.tab, to, seq: seq2, ...body });
1294
+ return new Promise((resolve2) => {
1295
+ const done5 = () => {
1296
+ clearTimeout(timer);
1297
+ this.waiting.delete(seq2);
1298
+ resolve2(left.size === 0);
1299
+ };
1300
+ const left = /* @__PURE__ */ new Set([to]);
1301
+ const timer = setTimeout(done5, cap);
1302
+ this.waiting.set(seq2, { left, done: done5 });
1303
+ });
1304
+ }
1242
1305
  peerHeld(path) {
1243
1306
  const out = [];
1244
1307
  for (const [tab, held] of this.peerHolds)
@@ -1274,6 +1337,7 @@ var RemoteWatch = class {
1274
1337
  if (this.peers.delete(tab)) left = true;
1275
1338
  this.others.delete(tab);
1276
1339
  this.peerHolds.delete(tab);
1340
+ this.keeps?.left(tab);
1277
1341
  this.ackers.delete(tab);
1278
1342
  this.holders.delete(tab);
1279
1343
  }
@@ -1440,6 +1504,7 @@ function translate(err, path, op = "lookup", mapped = true) {
1440
1504
  return error;
1441
1505
  }
1442
1506
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
1507
+ var FILE_ID_TRIES = 3;
1443
1508
  function sidecar(meta) {
1444
1509
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
1445
1510
  const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
@@ -1683,18 +1748,48 @@ var OpfsFs = class {
1683
1748
  this.channel?.postMessage(paths);
1684
1749
  }
1685
1750
  async fileId(path) {
1751
+ for (let tries = 0; tries < FILE_ID_TRIES; tries++) {
1752
+ const fid = await this.assignId(path);
1753
+ if (fid !== void 0) return fid;
1754
+ }
1755
+ throw fsError("EAGAIN", `${path}: moved while its file id was assigned`);
1756
+ }
1757
+ bindId(path) {
1758
+ return this.assignId(path);
1759
+ }
1760
+ async assignId(path) {
1686
1761
  const found = await this.locate(path, true);
1687
1762
  const [entry] = await (this.meta.fresh ?? this.meta.get).call(this.meta, [found.path]);
1688
1763
  if (entry?.fid !== void 0) return entry.fid;
1689
1764
  const fid = crypto.randomUUID();
1765
+ let moved2 = false;
1766
+ await this.meta.update(found.path, (now) => {
1767
+ const vacatedSince = now?.vacated && (!entry?.vacated || now.ino !== entry.ino);
1768
+ if (vacatedSince || entry !== void 0 && now === void 0) {
1769
+ moved2 = true;
1770
+ return now;
1771
+ }
1772
+ return { ...occupied(now), path: found.path, fid: now?.fid ?? fid };
1773
+ });
1774
+ if (moved2) return void 0;
1690
1775
  this.idWrites++;
1691
- await this.meta.update(found.path, (now) => ({
1692
- ...occupied(now),
1693
- path: found.path,
1694
- fid: now?.fid ?? fid
1695
- }));
1696
- const [stored] = await this.meta.get([found.path]);
1697
- return stored?.fid ?? fid;
1776
+ const there = await this.locate(path, true).then(
1777
+ (at) => at.path === found.path,
1778
+ () => false
1779
+ );
1780
+ if (there) return (await this.meta.get([found.path]))[0]?.fid ?? fid;
1781
+ const gone = await this.locate(found.path, true).then(
1782
+ () => false,
1783
+ (err) => missing2(err)
1784
+ );
1785
+ if (gone) {
1786
+ await this.meta.update(found.path, (now) => {
1787
+ if (now?.fid !== fid || now.vacated) return now;
1788
+ const { fid: _, ...rest2 } = now;
1789
+ return rest2;
1790
+ });
1791
+ }
1792
+ return void 0;
1698
1793
  }
1699
1794
  heldUnder = () => [];
1700
1795
  heldBy(held) {
@@ -1720,6 +1815,20 @@ var OpfsFs = class {
1720
1815
  const remote = this.remote;
1721
1816
  return remote && { ...remote.holdCounts(), held: remote.peerHeldPaths() };
1722
1817
  }
1818
+ peerKeeps() {
1819
+ const remote = this.remote;
1820
+ if (!remote) return void 0;
1821
+ return {
1822
+ shared: () => this.shared(),
1823
+ heldWithin: (path) => remote.heldWithin(path),
1824
+ ask: (to, body, cap) => remote.ask(to, body, cap)
1825
+ };
1826
+ }
1827
+ servePeerKeeps(instance, handlers) {
1828
+ if (!this.remote) return;
1829
+ this.remote.instance = instance;
1830
+ this.remote.keeps = handlers;
1831
+ }
1723
1832
  rehold(from, to) {
1724
1833
  this.remote?.rehold(from, to);
1725
1834
  }
@@ -2667,7 +2776,7 @@ var OpfsFs = class {
2667
2776
  }
2668
2777
  }
2669
2778
  async writeFile(path, content, owner) {
2670
- const [file, existed, target] = await this.fileFor(path);
2779
+ const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
2671
2780
  if (!existed) await this.stampNew(target, owner, false);
2672
2781
  if (!existed && content.length === 0) return;
2673
2782
  let writable2;
@@ -8326,7 +8435,7 @@ function satisfies(version, range2) {
8326
8435
  }
8327
8436
 
8328
8437
  // src/version.ts
8329
- var KERNEL_VERSION = true ? "1.161.0" : "0.0.0-dev";
8438
+ var KERNEL_VERSION = true ? "1.162.0" : "0.0.0-dev";
8330
8439
  var OS_RELEASE = "6.0.0-slicc";
8331
8440
  function procVersion() {
8332
8441
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9276,6 +9385,9 @@ var LOOKUP_OPS = [
9276
9385
  var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
9277
9386
  var TOUCHED_MAX = 4096;
9278
9387
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
9388
+ var PEER_RM_MS = 1e3;
9389
+ var PEER_RENAME_MS = 1e4;
9390
+ var PEER_GATE_MS = PEER_RENAME_MS + 5e3;
9279
9391
  var lockName = (instance) => `slicc-unlinked:${instance}`;
9280
9392
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
9281
9393
  var PathGate = class {
@@ -9318,7 +9430,9 @@ var UnlinkedKeeper = class {
9318
9430
  links = /* @__PURE__ */ new Map();
9319
9431
  linked = /* @__PURE__ */ new Map();
9320
9432
  dirOwner;
9433
+ peers;
9321
9434
  constructor(fs, holders, rootOf, options = {}) {
9435
+ this.peers = options.peers;
9322
9436
  this.marks = options.marks ?? new PathMarks();
9323
9437
  const { locks, owner } = options;
9324
9438
  this.dirOwner = options.dirOwner;
@@ -9394,22 +9508,29 @@ var UnlinkedKeeper = class {
9394
9508
  lstat: (path) => unreplaced(path, async () => counted2(await fs.lstat(path))),
9395
9509
  async rm(path, options) {
9396
9510
  const target = fs.resolvePath("/", path);
9397
- await replace(target, async (gated, sealed) => {
9398
- const pinned = await keeper.pin(target, gated, sealed);
9399
- if (pinned.moved.has(target)) pinned.commit();
9400
- else await keeper.settle(pinned, () => fs.rm(path, options));
9401
- });
9511
+ const kept = await keeper.peerKeep(target, false, () => keeper.removable(target, options));
9512
+ if (kept.whole) return;
9513
+ await kept.undoing(
9514
+ () => replace(target, async (gated, sealed) => {
9515
+ const pinned = await keeper.pin(target, gated, sealed);
9516
+ if (pinned.moved.has(target)) pinned.commit();
9517
+ else await keeper.settle(pinned, () => fs.rm(path, options));
9518
+ })
9519
+ );
9402
9520
  },
9403
9521
  async rename(from, to) {
9404
9522
  const target = fs.resolvePath("/", to);
9405
9523
  const source = fs.resolvePath("/", from);
9406
9524
  if (target === source) return fs.rename(from, to);
9407
- await keeper.marks.around(
9408
- [source],
9409
- () => replace(target, async (gated, sealed) => {
9410
- const pinned = await keeper.pin(target, gated, sealed);
9411
- await keeper.settle(pinned, () => fs.rename(from, to));
9412
- })
9525
+ const kept = await keeper.peerKeep(target, true, () => keeper.renamable(source, target));
9526
+ await kept.undoing(
9527
+ () => keeper.marks.around(
9528
+ [source],
9529
+ () => replace(target, async (gated, sealed) => {
9530
+ const pinned = await keeper.pin(target, gated, sealed);
9531
+ await keeper.settle(pinned, () => fs.rename(from, to));
9532
+ })
9533
+ )
9413
9534
  );
9414
9535
  },
9415
9536
  symlink: (target, path, owner) => keeper.marks.around([fs.resolvePath("/", path)], () => fs.symlink(target, path, owner)),
@@ -9554,6 +9675,114 @@ var UnlinkedKeeper = class {
9554
9675
  (err) => err?.code !== "ENOENT" && err?.code !== "ENOTDIR"
9555
9676
  );
9556
9677
  }
9678
+ get id() {
9679
+ return this.instance;
9680
+ }
9681
+ adopt(hidden, refs) {
9682
+ return this.keptFile(hidden, { refs });
9683
+ }
9684
+ async peerKeep(target, over, valid = async () => true) {
9685
+ const none = { whole: false, undoing: (op) => op() };
9686
+ const peers = this.peers;
9687
+ if (!peers?.shared() || this.rootOf(target) !== "/") return none;
9688
+ const held = peers.heldWithin(target).filter((holder) => holder.instance !== void 0);
9689
+ if (held.length === 0 || !await valid()) return none;
9690
+ const tabs = /* @__PURE__ */ new Map();
9691
+ for (const holder of held) tabs.set(holder.tab, [...tabs.get(holder.tab) ?? [], holder]);
9692
+ const asked = [...tabs].map(([tab, holders]) => {
9693
+ const paths = holders.map((holder) => holder.path);
9694
+ return { tab, holders, paths };
9695
+ });
9696
+ const heard = await Promise.all(
9697
+ asked.map(
9698
+ ({ tab, paths }) => peers.ask(tab, { keeping: { paths } }, over ? PEER_RENAME_MS : PEER_RM_MS)
9699
+ )
9700
+ );
9701
+ if (over && heard.includes(false)) {
9702
+ for (const { tab, paths } of asked)
9703
+ void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
9704
+ throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
9705
+ }
9706
+ const { whole, moved: moved2 } = await this.setAside(peers, target, asked, heard);
9707
+ return { whole, undoing: (op) => this.undoing(op, moved2) };
9708
+ }
9709
+ async setAside(peers, target, asked, heard) {
9710
+ const mine = this.heldWithin(target);
9711
+ const taken = /* @__PURE__ */ new Set();
9712
+ let whole = false;
9713
+ const told = [];
9714
+ const moved2 = [];
9715
+ for (const [i, { tab, holders, paths }] of asked.entries()) {
9716
+ const items = [];
9717
+ for (const holder of holders) {
9718
+ const free = !mine.has(holder.path) && !taken.has(holder.path);
9719
+ const hidden = free ? await this.asideFor(holder.path, holder.instance) : void 0;
9720
+ if (hidden !== void 0) {
9721
+ taken.add(holder.path);
9722
+ moved2.push({ tab, path: holder.path, hidden, fid: holder.fid });
9723
+ if (holder.path === target) whole = true;
9724
+ }
9725
+ items.push({ path: holder.path, hidden, fid: holder.fid });
9726
+ }
9727
+ const answer2 = peers.ask(tab, { kept: { items, paths } }, PEER_RM_MS);
9728
+ if (heard[i]) told.push(answer2);
9729
+ }
9730
+ await Promise.all(told);
9731
+ return { whole, moved: moved2 };
9732
+ }
9733
+ async undoing(op, moved2) {
9734
+ try {
9735
+ return await op();
9736
+ } catch (err) {
9737
+ for (const { tab, path, hidden, fid } of moved2) {
9738
+ const back = await this.serial(async () => {
9739
+ if (await this.present(path)) return false;
9740
+ return this.fs.rename(hidden, path).then(
9741
+ () => true,
9742
+ () => false
9743
+ );
9744
+ });
9745
+ if (!back) continue;
9746
+ void this.peers?.ask(
9747
+ tab,
9748
+ { kept: { items: [{ path, hidden: void 0, fid, undo: hidden }], paths: [] } },
9749
+ PEER_RM_MS
9750
+ );
9751
+ }
9752
+ throw err;
9753
+ }
9754
+ }
9755
+ async removable(target, options) {
9756
+ const st = await this.fs.lstat(target).catch(() => void 0);
9757
+ return st !== void 0 && (!st.isDirectory || options?.recursive === true);
9758
+ }
9759
+ async renamable(source, target) {
9760
+ const from = await this.fs.lstat(source);
9761
+ const to = await this.fs.lstat(target).catch(() => void 0);
9762
+ if (!to) return true;
9763
+ if (to.isDirectory && !from.isDirectory) throw fsError("EISDIR", target);
9764
+ if (!to.isDirectory && from.isDirectory) throw fsError("ENOTDIR", target);
9765
+ if (to.isDirectory && (await this.fs.readdir(target)).length > 0)
9766
+ throw fsError("ENOTEMPTY", target);
9767
+ return true;
9768
+ }
9769
+ async asideFor(path, instance) {
9770
+ return await this.hideFor(path, instance) ?? this.hideFor(path, instance);
9771
+ }
9772
+ hideFor(path, instance) {
9773
+ return this.serial(async () => {
9774
+ const dir = dirOf("/");
9775
+ const hidden = `${dir}/${instance}.p${crypto.randomUUID().replaceAll("-", "")}`;
9776
+ try {
9777
+ const owner = this.dirOwner;
9778
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
9779
+ await this.fs.rename(path, hidden);
9780
+ return hidden;
9781
+ } catch {
9782
+ return void 0;
9783
+ }
9784
+ });
9785
+ }
9557
9786
  keptFile(hidden, entry) {
9558
9787
  return {
9559
9788
  hidden,
@@ -9608,7 +9837,7 @@ var UnlinkedKeeper = class {
9608
9837
  }
9609
9838
  async tidy(dir) {
9610
9839
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
9611
- if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
9840
+ if (left.length === 0) await this.fs.rm(dir).catch(() => void 0);
9612
9841
  }
9613
9842
  sweep(root) {
9614
9843
  const query = this.locks?.query?.bind(this.locks);
@@ -10853,6 +11082,11 @@ async function shownTarget(fs, target) {
10853
11082
  const st = await quiet(fs.stat(target));
10854
11083
  return st?.isFile && await readsAs2(fs, NOBODY, target) ? st : void 0;
10855
11084
  }
11085
+ async function madeFor(owner, virtual) {
11086
+ if (!owner?.existing || await virtual() === void 0) return owner;
11087
+ const { existing: _, ...made2 } = owner;
11088
+ return made2;
11089
+ }
10856
11090
  function withCommandDirs(fs, names2, userDir = async () => void 0, owners = false, targetOf2 = async () => void 0) {
10857
11091
  async function nearest(path) {
10858
11092
  const st = await fs.stat(path).catch(() => void 0);
@@ -10950,7 +11184,7 @@ function withCommandDirs(fs, names2, userDir = async () => void 0, owners = fals
10950
11184
  }),
10951
11185
  readFileBuffer: (path) => fs.readFileBuffer(path).catch((err) => content(path, err)),
10952
11186
  commandTarget: async (path) => (await entryTarget(path))?.path,
10953
- writeFile: (path, content2, owner) => fs.writeFile(path, content2, owner),
11187
+ writeFile: async (path, content2, owner) => fs.writeFile(path, content2, await madeFor(owner, () => entryTarget(path))),
10954
11188
  link: (from, to, follow2) => fs.link(from, to, follow2),
10955
11189
  links: (path) => mayLink(fs, path),
10956
11190
  async exists(path) {
@@ -11115,6 +11349,10 @@ function withEtcFiles(fs, files) {
11115
11349
  }
11116
11350
  } : {},
11117
11351
  readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
11352
+ writeFile: async (path, content, owner) => {
11353
+ if (owner?.existing && files.has(path)) await materialize(path);
11354
+ return fs.writeFile(path, content, owner);
11355
+ },
11118
11356
  ...fs.append ? {
11119
11357
  append: async (path, bytes, options) => {
11120
11358
  if (files.has(path)) await materialize(path);
@@ -13073,12 +13311,25 @@ var HeldPaths = class extends Map {
13073
13311
  for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
13074
13312
  return false;
13075
13313
  }
13314
+ boundAt(path, fid) {
13315
+ return this.handlesAt(path).filter(
13316
+ (id2) => !this.keptHandles.has(id2) && this.bound.get(id2)?.fid === fid
13317
+ );
13318
+ }
13319
+ keepBound(ids, path, file) {
13320
+ for (const id2 of ids) this.keptHandles.set(id2, file);
13321
+ this.uses.set(file, ids.length);
13322
+ this.unlinked.set(path, file);
13323
+ }
13076
13324
  keep(path, file) {
13077
13325
  const fresh3 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
13078
13326
  for (const id2 of fresh3) this.keptHandles.set(id2, file);
13079
13327
  this.uses.set(file, Math.max(fresh3.length, 1));
13080
13328
  this.unlinked.set(path, file);
13081
13329
  }
13330
+ unkeepAt(path, hidden) {
13331
+ for (const file of [...this.uses.keys()]) if (file.hidden === hidden) this.unkeep(path, file);
13332
+ }
13082
13333
  unkeep(path, file) {
13083
13334
  for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
13084
13335
  this.uses.delete(file);
@@ -13647,12 +13898,12 @@ var MountFs = class _MountFs {
13647
13898
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
13648
13899
  return this.follow(this.base.resolvePath(parentOf8(real), target), hops + 1, create);
13649
13900
  }
13650
- async open(mount, rel2, write, owner) {
13901
+ async open(mount, rel2, write, owner, create = write) {
13651
13902
  return await mount.conn.call({
13652
13903
  op: "open",
13653
13904
  path: rel2,
13654
13905
  write,
13655
- create: write,
13906
+ create,
13656
13907
  truncate: write,
13657
13908
  exclusive: false,
13658
13909
  ...owner
@@ -13749,10 +14000,10 @@ var MountFs = class _MountFs {
13749
14000
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
13750
14001
  );
13751
14002
  }
13752
- const made2 = await this.driverOwner(found, real, owner, false);
14003
+ const made2 = owner?.existing ? void 0 : await this.driverOwner(found, real, owner, false);
13753
14004
  for (let attempt = 1; ; attempt++) {
13754
14005
  try {
13755
- return await this.writeWhole(mount, real, rel2, bytes, made2);
14006
+ return await this.writeWhole(mount, real, rel2, bytes, made2, owner?.existing !== true);
13756
14007
  } catch (err) {
13757
14008
  if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
13758
14009
  }
@@ -13770,10 +14021,10 @@ var MountFs = class _MountFs {
13770
14021
  this.table.forget(mount, real);
13771
14022
  }
13772
14023
  }
13773
- async writeWhole(mount, real, rel2, bytes, owner) {
14024
+ async writeWhole(mount, real, rel2, bytes, owner, create = true) {
13774
14025
  await this.table.commit(real);
13775
14026
  this.table.forget(mount, real);
13776
- const fh = await this.open(mount, rel2, true, owner);
14027
+ const fh = await this.open(mount, rel2, true, owner, create);
13777
14028
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
13778
14029
  try {
13779
14030
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -14510,6 +14761,11 @@ function ownerFor2(entry, mode) {
14510
14761
  if (!cred) return void 0;
14511
14762
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
14512
14763
  }
14764
+ function writeOwner(entry, req) {
14765
+ const owner = ownerFor2(entry, 438);
14766
+ if (req.handle === void 0 || req.mode !== 1) return owner;
14767
+ return { ...owner ?? {}, existing: true };
14768
+ }
14513
14769
  function plainDirMode(entry, mode) {
14514
14770
  if (entry.cred || mode === void 0) return void 0;
14515
14771
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
@@ -14580,7 +14836,7 @@ async function run(entry, path, req) {
14580
14836
  case "read":
14581
14837
  return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
14582
14838
  case "write":
14583
- await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
14839
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), writeOwner(entry, req));
14584
14840
  return done2;
14585
14841
  case "exists":
14586
14842
  return json2(await fs.exists(path));
@@ -31176,7 +31432,7 @@ var Launcher = class {
31176
31432
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
31177
31433
  owner: options.unlinkOwner,
31178
31434
  marks: this.pathMarks,
31179
- ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
31435
+ ...this.peerOptions(options.fs)
31180
31436
  }
31181
31437
  );
31182
31438
  this.fs = this.processChain(fs, this.nodes, this.unlinked);
@@ -32010,7 +32266,7 @@ var Launcher = class {
32010
32266
  const assign = this.mounts.locate(target.wasm) === void 0 ? this.rootFs.fileId : void 0;
32011
32267
  return this.base.stat(target.wasm).then(
32012
32268
  async (st) => {
32013
- const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm);
32269
+ const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm)?.catch(() => void 0);
32014
32270
  if (fid === void 0) return exeIdOf(fileIdentity(st.ino, st.mtime.getTime()));
32015
32271
  return { exeId: `fid:${fid}:${st.mtime.getTime()}` };
32016
32272
  },
@@ -33172,8 +33428,82 @@ var Launcher = class {
33172
33428
  this.writers ??= this.writerStack();
33173
33429
  return this.writers;
33174
33430
  }
33431
+ peerGates = /* @__PURE__ */ new Map();
33432
+ peerOptions(fs) {
33433
+ const peers = fs.peerKeeps?.();
33434
+ queueMicrotask(() => fs.servePeerKeeps?.(this.unlinked.id, this.peerKept()));
33435
+ return {
33436
+ ...peers ? { peers } : {},
33437
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33438
+ };
33439
+ }
33440
+ peerKept() {
33441
+ const gates = this.peerGates;
33442
+ const gone = /* @__PURE__ */ new Set();
33443
+ const instance = this.unlinked.id;
33444
+ const ours = (hidden) => hidden.startsWith(`/${UNLINKED_DIR}/${instance}.p`) && /^[0-9a-f]+$/.test(hidden.slice(`/${UNLINKED_DIR}/${instance}.p`.length));
33445
+ const open2 = (tab, paths) => {
33446
+ for (const [key, release] of [...gates]) {
33447
+ const [owner, path] = key.split("\0");
33448
+ if (owner !== tab || paths && !paths.includes(path)) continue;
33449
+ gates.delete(key);
33450
+ release();
33451
+ }
33452
+ };
33453
+ return {
33454
+ keeping: async (tab, paths) => {
33455
+ for (const path of paths) {
33456
+ if (gone.has(tab)) return;
33457
+ const release = await this.unlinked.gate.acquire(path);
33458
+ const key = `${tab}\0${path}`;
33459
+ let done5 = false;
33460
+ const once2 = () => {
33461
+ if (done5) return;
33462
+ done5 = true;
33463
+ clearTimeout(timer);
33464
+ release();
33465
+ };
33466
+ const timer = setTimeout(() => {
33467
+ if (gates.get(key) === once2) gates.delete(key);
33468
+ once2();
33469
+ }, PEER_GATE_MS);
33470
+ if (gone.has(tab)) once2();
33471
+ else gates.set(key, once2);
33472
+ }
33473
+ },
33474
+ kept: async (tab, items, paths) => {
33475
+ for (const { path, hidden, fid, undo } of items) {
33476
+ if (undo !== void 0) this.unadopt(path, undo, fid);
33477
+ else this.adoptKept(path, hidden, fid, ours);
33478
+ }
33479
+ open2(tab, paths);
33480
+ },
33481
+ left: (tab) => {
33482
+ gone.add(tab);
33483
+ open2(tab);
33484
+ }
33485
+ };
33486
+ }
33487
+ unadopt(path, undo, fid) {
33488
+ for (const holds2 of this.held) holds2.unkeepAt(path, undo);
33489
+ void this.rootFs.announceHolds?.([[path, fid]], [], false);
33490
+ }
33491
+ adoptKept(path, hidden, fid, ours) {
33492
+ if (hidden === void 0 || !ours(hidden)) {
33493
+ this.held.doubt([path]);
33494
+ return;
33495
+ }
33496
+ const bound3 = [...this.held].map(
33497
+ (holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
33498
+ );
33499
+ const keeping = bound3.filter(([, ids]) => ids.length > 0);
33500
+ const file = this.unlinked.adopt(hidden, Math.max(keeping.length, 1));
33501
+ if (keeping.length === 0) file.release();
33502
+ for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
33503
+ void this.rootFs.announceHolds?.([], [path, hidden], false);
33504
+ }
33175
33505
  rootFileId(path) {
33176
- return this.mounts.locate(path) === void 0 ? this.rootFs.fileId?.(path) : void 0;
33506
+ return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
33177
33507
  }
33178
33508
  flushingPeers(fs) {
33179
33509
  const peers = fs.peers?.();
@@ -33219,6 +33549,7 @@ var Launcher = class {
33219
33549
  busy: (path) => [...this.openFiles].some((open2) => open2.holds(path)),
33220
33550
  owner: this.unlinkOwnerOption,
33221
33551
  marks: this.pathMarks,
33552
+ peers: this.rootFs.peerKeeps?.(),
33222
33553
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33223
33554
  }
33224
33555
  );