@ai-ecoverse/slicc-kernel 1.74.0 → 1.75.1

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
@@ -736,9 +736,11 @@ var KernelPipe = class {
736
736
  // src/kernel/fd-table.ts
737
737
  var KernelError = class extends Error {
738
738
  code;
739
- constructor(code) {
739
+ detail;
740
+ constructor(code, detail) {
740
741
  super(code);
741
742
  this.code = code;
743
+ if (detail !== void 0) this.detail = detail;
742
744
  }
743
745
  };
744
746
  function pollFile(file) {
@@ -881,6 +883,7 @@ function kernelFdKind(file) {
881
883
  if (file.heldMeta && "device" in file.heldMeta) return "device";
882
884
  if (file.tty) return "tty";
883
885
  if (file.sealed) return "sealed";
886
+ if (file.clipboard) return "clipboard";
884
887
  return file.seek ? "file" : "stream";
885
888
  }
886
889
  var FdTable = class _FdTable {
@@ -3425,6 +3428,7 @@ async function positioned(handle, op) {
3425
3428
  function isHandle(child) {
3426
3429
  return "kind" in child;
3427
3430
  }
3431
+ var FILE_TIMES_MS = 100;
3428
3432
  var OpfsFs = class {
3429
3433
  root;
3430
3434
  meta;
@@ -3695,8 +3699,50 @@ var OpfsFs = class {
3695
3699
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
3696
3700
  return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
3697
3701
  }
3702
+ id = crypto.randomUUID();
3698
3703
  dirTimes = /* @__PURE__ */ new Map();
3699
3704
  dirDirty = /* @__PURE__ */ new Set();
3705
+ fileTimes = /* @__PURE__ */ new Map();
3706
+ written(target) {
3707
+ this.committed(target);
3708
+ if (!this.ranged) return;
3709
+ this.fileTimes.set(target, Date.now());
3710
+ this.scheduleFileTimes();
3711
+ }
3712
+ scheduleFileTimes() {
3713
+ if (this.flushing.file || this.fileTimes.size === 0) return;
3714
+ this.flushing.file = setTimeout(() => {
3715
+ void this.flushFileTimes().catch(() => void 0).finally(() => {
3716
+ this.flushing.file = void 0;
3717
+ this.scheduleFileTimes();
3718
+ });
3719
+ }, FILE_TIMES_MS);
3720
+ this.flushing.file.unref?.();
3721
+ }
3722
+ async flushFileTimes() {
3723
+ for (const [path, ms] of [...this.fileTimes]) {
3724
+ const mtimeFor = await this.modified(path).catch(() => 0);
3725
+ await this.meta.update(
3726
+ path,
3727
+ (entry) => this.fileTimes.get(path) !== ms || this.explicitWins(entry, ms) ? entry : { ...entry, path, mtimeMs: ms, mtimeFor, ctimeMs: ms }
3728
+ );
3729
+ if (this.fileTimes.get(path) === ms) this.fileTimes.delete(path);
3730
+ }
3731
+ }
3732
+ timesOf(path) {
3733
+ return this.dirTimes.get(path) ?? this.fileTimes.get(path);
3734
+ }
3735
+ explicitWins(entry, ms) {
3736
+ if (entry?.timesAt === void 0 || entry.timesBy === this.id) return false;
3737
+ return entry.timesAt >= ms;
3738
+ }
3739
+ movePending(from, to) {
3740
+ for (const [path, ms] of [...this.fileTimes]) {
3741
+ if (!within2(path, from)) continue;
3742
+ this.fileTimes.delete(path);
3743
+ if (to !== void 0) this.fileTimes.set(to + path.slice(from.length), ms);
3744
+ }
3745
+ }
3700
3746
  flushing = {};
3701
3747
  touch(...dirs) {
3702
3748
  const now = Date.now();
@@ -3720,11 +3766,10 @@ var OpfsFs = class {
3720
3766
  if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
3721
3767
  }
3722
3768
  }
3723
- async statOf(path, handle, meta) {
3769
+ async statOf(path, handle, meta, touched) {
3724
3770
  const directory = handle.kind === "directory";
3725
3771
  const file = directory ? void 0 : await handle.getFile();
3726
3772
  const modified = file?.lastModified ?? 0;
3727
- const touched = directory ? this.dirTimes.get(path) : void 0;
3728
3773
  const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
3729
3774
  const mtime = touched ?? kept;
3730
3775
  return {
@@ -3756,13 +3801,14 @@ var OpfsFs = class {
3756
3801
  }
3757
3802
  async describe(found) {
3758
3803
  if (found.link) return this.owned(this.linkStat(found.link), found.path, found.link);
3804
+ const touched = this.timesOf(found.path);
3759
3805
  if (!this.owners) {
3760
3806
  const [meta2] = await this.meta.get([found.path]);
3761
- return this.statOf(found.path, found.handle, meta2);
3807
+ return this.statOf(found.path, found.handle, meta2, touched);
3762
3808
  }
3763
3809
  const entries = await this.meta.get(chain(found.path));
3764
3810
  const meta = entries.pop();
3765
- const stat = await this.statOf(found.path, found.handle, meta);
3811
+ const stat = await this.statOf(found.path, found.handle, meta, touched);
3766
3812
  return { ...stat, ...ownOf(meta) ?? inherit(entries) };
3767
3813
  }
3768
3814
  async stat(path) {
@@ -3819,13 +3865,14 @@ var OpfsFs = class {
3819
3865
  async pwrite(path, offset, bytes) {
3820
3866
  const [handle, , target] = await this.fileFor(path);
3821
3867
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
3822
- this.committed(target);
3868
+ this.written(target);
3823
3869
  }
3824
3870
  async truncate(path, size) {
3825
3871
  const handle = await this.existing(path, true);
3826
- const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
3872
+ const resolve = this.ranged || this.remote?.watched();
3873
+ const target = resolve ? (await this.locate(path, true)).path : path;
3827
3874
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
3828
- this.committed(target);
3875
+ this.written(target);
3829
3876
  }
3830
3877
  async writeSession(path, truncate) {
3831
3878
  const [handle, , target] = await this.fileFor(path);
@@ -3915,13 +3962,16 @@ var OpfsFs = class {
3915
3962
  async readdirStat(path) {
3916
3963
  const [dir, children2] = await this.listing(path);
3917
3964
  const base = dir === "/" ? "" : dir;
3965
+ const touched = children2.map(([name2]) => this.timesOf(`${base}/${name2}`));
3918
3966
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
3919
3967
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
3920
3968
  const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
3921
3969
  return Promise.all(
3922
3970
  children2.map(async ([name2, child], i) => {
3923
3971
  if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
3924
- const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
3972
+ const stat = await this.statOf(`${base}/${name2}`, child, metas[i], touched[i]).catch(
3973
+ () => null
3974
+ );
3925
3975
  return [name2, stat && owned4(stat, metas[i])];
3926
3976
  })
3927
3977
  );
@@ -3979,6 +4029,7 @@ var OpfsFs = class {
3979
4029
  throw translate(err, path);
3980
4030
  }
3981
4031
  this.changed(found.path);
4032
+ this.movePending(found.path);
3982
4033
  }
3983
4034
  await this.meta.remove([found.path]);
3984
4035
  this.touch(parentOf4(found.path));
@@ -4042,6 +4093,7 @@ var OpfsFs = class {
4042
4093
  const moved2 = await this.place(source.handle, dir, staging);
4043
4094
  if (swap) await this.rm(destination, { recursive: true });
4044
4095
  await this.meta.move(source.path, destination, carry);
4096
+ this.movePending(source.path, destination);
4045
4097
  if (!moved2) await this.rm(source.path, { recursive: true });
4046
4098
  let copied = !moved2;
4047
4099
  if (swap) {
@@ -4163,6 +4215,7 @@ var OpfsFs = class {
4163
4215
  const handle = found.handle;
4164
4216
  const file = handle.kind === "file" ? await handle.getFile() : void 0;
4165
4217
  this.dirTimes.delete(found.path);
4218
+ this.fileTimes.delete(found.path);
4166
4219
  const ctimeMs = Date.now();
4167
4220
  await this.meta.update(found.path, (entry) => ({
4168
4221
  ...entry,
@@ -4171,7 +4224,8 @@ var OpfsFs = class {
4171
4224
  mtimeMs: mtime.getTime(),
4172
4225
  mtimeFor: file?.lastModified ?? 0,
4173
4226
  ctimeMs,
4174
- timesAt: ctimeMs
4227
+ timesAt: ctimeMs,
4228
+ timesBy: this.id
4175
4229
  }));
4176
4230
  }
4177
4231
  async reconcile() {
@@ -6070,6 +6124,173 @@ var ChildTable = class {
6070
6124
  }
6071
6125
  };
6072
6126
 
6127
+ // src/kernel/clipboard.ts
6128
+ var CLIPBOARD_MAX_BYTES = 1024 * 1024;
6129
+ var CLIPBOARD_TIMEOUT_MS = 1e4;
6130
+ var encoder = new TextEncoder();
6131
+ var decoder = new TextDecoder("utf-8", { fatal: true });
6132
+ var READY = { readable: true, writable: true, hangup: false };
6133
+ function lineOf(err) {
6134
+ const text = err instanceof Error ? err.message : String(err);
6135
+ return (text.split("\n")[0] ?? "").slice(0, 200);
6136
+ }
6137
+ function clipboardFailure(err) {
6138
+ if (err instanceof KernelError) return err;
6139
+ const name2 = err?.name;
6140
+ return new KernelError(name2 === "NotAllowedError" ? "EACCES" : "EIO", lineOf(err));
6141
+ }
6142
+ function joined2(chunks2, size) {
6143
+ if (chunks2.length === 1) return chunks2[0];
6144
+ const out = new Uint8Array(size);
6145
+ let at = 0;
6146
+ for (const chunk2 of chunks2) {
6147
+ out.set(chunk2, at);
6148
+ at += chunk2.length;
6149
+ }
6150
+ return out;
6151
+ }
6152
+ async function interruptible(work, signal) {
6153
+ if (!signal) return work;
6154
+ if (signal.aborted) throw new KernelError("EINTR");
6155
+ let stop = () => {
6156
+ };
6157
+ const interrupted2 = new Promise((_, reject) => {
6158
+ stop = () => reject(new KernelError("EINTR"));
6159
+ signal.addEventListener("abort", stop, { once: true });
6160
+ });
6161
+ try {
6162
+ return await Promise.race([work, interrupted2]);
6163
+ } finally {
6164
+ signal.removeEventListener("abort", stop);
6165
+ }
6166
+ }
6167
+ var ClipboardChannel = class {
6168
+ stamp;
6169
+ access;
6170
+ broker;
6171
+ snapshot;
6172
+ taken;
6173
+ offset = 0;
6174
+ chunks = [];
6175
+ size = 0;
6176
+ wrote = false;
6177
+ failed = false;
6178
+ constructor(broker, access2, stamp) {
6179
+ this.broker = broker;
6180
+ this.access = access2;
6181
+ this.stamp = stamp;
6182
+ }
6183
+ get unread() {
6184
+ return this.taken === void 0 && this.snapshot === void 0;
6185
+ }
6186
+ get settled() {
6187
+ return this.taken !== void 0;
6188
+ }
6189
+ async read(max, signal) {
6190
+ if (this.access === "write") throw new KernelError("EBADF");
6191
+ if (!this.taken) {
6192
+ if (!this.snapshot) {
6193
+ const stamp = this.stamp;
6194
+ const asked = this.broker.ask(async (hook) => {
6195
+ const text = await hook.read(stamp);
6196
+ if (typeof text !== "string") throw new KernelError("EIO", "the clipboard gave no text");
6197
+ return encoder.encode(text);
6198
+ });
6199
+ this.snapshot = asked;
6200
+ asked.then(
6201
+ (bytes) => {
6202
+ if (this.snapshot === asked) this.taken = bytes;
6203
+ },
6204
+ () => {
6205
+ if (this.snapshot === asked) this.snapshot = void 0;
6206
+ }
6207
+ );
6208
+ }
6209
+ this.taken = await interruptible(this.snapshot, signal);
6210
+ }
6211
+ const out = this.taken.subarray(this.offset, this.offset + max);
6212
+ this.offset += out.length;
6213
+ return out;
6214
+ }
6215
+ write(bytes) {
6216
+ if (this.access === "read") throw new KernelError("EBADF");
6217
+ this.wrote = true;
6218
+ if (this.failed) throw new KernelError("EFBIG");
6219
+ if (this.size + bytes.length > CLIPBOARD_MAX_BYTES) {
6220
+ this.failed = true;
6221
+ this.chunks = [];
6222
+ this.size = 0;
6223
+ throw new KernelError("EFBIG");
6224
+ }
6225
+ if (bytes.length > 0) this.chunks.push(bytes.slice());
6226
+ this.size += bytes.length;
6227
+ return bytes.length;
6228
+ }
6229
+ poll() {
6230
+ return READY;
6231
+ }
6232
+ async close() {
6233
+ this.snapshot = void 0;
6234
+ this.taken = void 0;
6235
+ const commits = this.access === "write" || this.access === "both" && this.wrote;
6236
+ if (!commits || this.failed) return;
6237
+ const bytes = joined2(this.chunks, this.size);
6238
+ this.chunks = [];
6239
+ this.size = 0;
6240
+ let text;
6241
+ try {
6242
+ text = decoder.decode(bytes);
6243
+ } catch {
6244
+ throw new KernelError("EILSEQ", "the text is not valid UTF-8");
6245
+ }
6246
+ const stamp = this.stamp;
6247
+ await this.broker.ask(async (hook) => {
6248
+ await hook.write(text, stamp);
6249
+ });
6250
+ }
6251
+ };
6252
+ var ClipboardBroker = class {
6253
+ hook;
6254
+ users;
6255
+ timeoutMs;
6256
+ tagOf;
6257
+ constructor(hook, users, timeoutMs = CLIPBOARD_TIMEOUT_MS, tagOf = () => void 0) {
6258
+ this.hook = hook;
6259
+ this.users = users;
6260
+ this.timeoutMs = timeoutMs;
6261
+ this.tagOf = tagOf;
6262
+ }
6263
+ async ask(work) {
6264
+ const hook = this.hook;
6265
+ if (!hook) throw new KernelError("ENOTSUP", "no clipboard in this embedder");
6266
+ let timer;
6267
+ const late = new Promise((_, reject) => {
6268
+ timer = setTimeout(
6269
+ () => reject(new KernelError("EIO", "clipboard timed out")),
6270
+ this.timeoutMs
6271
+ );
6272
+ });
6273
+ const asked = Promise.resolve().then(() => work(hook)).catch((err) => {
6274
+ throw clipboardFailure(err);
6275
+ });
6276
+ try {
6277
+ return await Promise.race([asked, late]);
6278
+ } finally {
6279
+ clearTimeout(timer);
6280
+ }
6281
+ }
6282
+ open(access2, stamp) {
6283
+ const channel = new ClipboardChannel(this, access2, stamp);
6284
+ return new OpenFile({
6285
+ read: (max, signal) => channel.read(max, signal),
6286
+ write: async (bytes) => channel.write(bytes),
6287
+ poll: () => channel.poll(),
6288
+ close: () => channel.close(),
6289
+ clipboard: channel
6290
+ });
6291
+ }
6292
+ };
6293
+
6073
6294
  // src/mount/protocol.ts
6074
6295
  var DRIVER_PROTOCOL = [1, 2];
6075
6296
  function driverVersionError(theirs) {
@@ -7607,20 +7828,20 @@ var SAB_DEFAULT_WINDOW_BYTES = 1024 * 1024;
7607
7828
  var SAB_MIN_BYTES = SAB_HEADER_BYTES + 4096;
7608
7829
  var SYNC_SAB_REQ_MSG = "sync-sab-req";
7609
7830
  var SYNC_SAB_NEXT_MSG = "sync-sab-next";
7610
- var encoder = new TextEncoder();
7611
- var decoder = new TextDecoder();
7831
+ var encoder2 = new TextEncoder();
7832
+ var decoder2 = new TextDecoder();
7612
7833
  function encodeSabResult(result) {
7613
7834
  if (!result.ok) {
7614
7835
  return {
7615
7836
  status: SAB_STATUS_ERRNO,
7616
- payload: encoder.encode(JSON.stringify({ errno: result.errno, message: result.message }))
7837
+ payload: encoder2.encode(JSON.stringify({ errno: result.errno, message: result.message }))
7617
7838
  };
7618
7839
  }
7619
7840
  switch (result.kind) {
7620
7841
  case "bytes":
7621
7842
  return { status: SAB_STATUS_BYTES, payload: result.bytes };
7622
7843
  case "json":
7623
- return { status: SAB_STATUS_JSON, payload: encoder.encode(JSON.stringify(result.json)) };
7844
+ return { status: SAB_STATUS_JSON, payload: encoder2.encode(JSON.stringify(result.json)) };
7624
7845
  default:
7625
7846
  return { status: SAB_STATUS_VOID, payload: new Uint8Array(0) };
7626
7847
  }
@@ -10442,7 +10663,7 @@ function defaultTermios() {
10442
10663
  c_cc: cCc
10443
10664
  };
10444
10665
  }
10445
- var encoder2 = new TextEncoder();
10666
+ var encoder3 = new TextEncoder();
10446
10667
  function isContinuation(byte) {
10447
10668
  return (byte & 192) === 128;
10448
10669
  }
@@ -10564,7 +10785,7 @@ var KernelTty = class {
10564
10785
  this.line.push(byte);
10565
10786
  this.readable.push(Uint8Array.from(this.line));
10566
10787
  this.line = [];
10567
- if (echo) this.output(encoder2.encode("\n"));
10788
+ if (echo) this.output(encoder3.encode("\n"));
10568
10789
  } else {
10569
10790
  this.line.push(byte);
10570
10791
  if (echo) this.echo(byte);
@@ -10589,7 +10810,7 @@ var KernelTty = class {
10589
10810
  const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
10590
10811
  const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
10591
10812
  if (cells > 0 && this.termios.c_lflag & ECHO) {
10592
- this.output(encoder2.encode("\b \b".repeat(cells)), false);
10813
+ this.output(encoder3.encode("\b \b".repeat(cells)), false);
10593
10814
  }
10594
10815
  }
10595
10816
  echo(byte) {
@@ -11078,7 +11299,7 @@ async function socketSyscall(req, proc) {
11078
11299
  dialing?.catch(() => void 0);
11079
11300
  throw new KernelError("EINPROGRESS");
11080
11301
  }
11081
- if (dialing !== void 0) await interruptible(dialing, proc.blocking());
11302
+ if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
11082
11303
  return ok();
11083
11304
  }
11084
11305
  case "sock-shutdown":
@@ -11102,7 +11323,7 @@ async function socketSyscall(req, proc) {
11102
11323
  }
11103
11324
  }
11104
11325
  }
11105
- function interruptible(dialing, signal) {
11326
+ function interruptible2(dialing, signal) {
11106
11327
  return new Promise((resolve, reject) => {
11107
11328
  const abort = () => reject(new KernelError("EINTR"));
11108
11329
  signal.addEventListener("abort", abort, { once: true });
@@ -11769,6 +11990,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11769
11990
  "fd-tty-names",
11770
11991
  "fd-tty-owner",
11771
11992
  "fd-open-sealed",
11993
+ "fd-open-clipboard",
11772
11994
  "tty-get",
11773
11995
  "tty-set",
11774
11996
  "tty-winsz",
@@ -12011,7 +12233,7 @@ var WasmProcess = class {
12011
12233
  return await this.procSyscall(req, cancelled);
12012
12234
  } catch (e) {
12013
12235
  if (e instanceof KernelError || e instanceof SpawnError) {
12014
- return { ok: false, errno: e.code, message: e.code };
12236
+ return { ok: false, errno: e.code, message: e.detail ?? e.code };
12015
12237
  }
12016
12238
  const code = e?.code;
12017
12239
  if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
@@ -12054,6 +12276,7 @@ var WasmProcess = class {
12054
12276
  const read = req.peek ? file.peek : file.read;
12055
12277
  if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
12056
12278
  if (file.tty) this.checkForeground(file.tty);
12279
+ if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
12057
12280
  if (req.max <= 0) return new Uint8Array(0);
12058
12281
  if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
12059
12282
  const ready2 = pollFile(file).readable;
@@ -12061,6 +12284,13 @@ var WasmProcess = class {
12061
12284
  const signal = ready2 ? this.interrupt.signal : this.blockingSignal(cancelled);
12062
12285
  return read.call(file, Math.max(0, Math.min(req.max, MAX_READ)), signal);
12063
12286
  }
12287
+ async readClipboard(channel, req, cancelled) {
12288
+ if (channel.access === "write") throw new KernelError("EBADF");
12289
+ if (req.max <= 0) return new Uint8Array(0);
12290
+ if (channel.unread) channel.stamp = await this.clipboardStamp();
12291
+ const signal = channel.settled ? this.interrupt.signal : this.blockingSignal(cancelled);
12292
+ return channel.read(Math.min(req.max, MAX_READ), signal);
12293
+ }
12064
12294
  async write(fd, body, nonblock = false, cancelled) {
12065
12295
  const file = this.fds.get(fd).file;
12066
12296
  if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
@@ -12164,6 +12394,36 @@ var WasmProcess = class {
12164
12394
  if (!broker) throw new KernelError("ENOENT");
12165
12395
  return this.fds.install(broker.open(), 3);
12166
12396
  }
12397
+ async openClipboard(flags2) {
12398
+ const broker = this.options.clipboard;
12399
+ if (!broker) throw new KernelError("ENOENT");
12400
+ const mode = flags2 & WRITE_ACCESS;
12401
+ const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
12402
+ return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
12403
+ }
12404
+ async clipboardStamp() {
12405
+ const shown = await this.options.identity?.();
12406
+ const broker = this.options.clipboard;
12407
+ const tag = broker.tagOf(this.pid);
12408
+ return {
12409
+ pid: shown?.pid ?? this.pid,
12410
+ uid: this.cred.euid,
12411
+ gid: this.cred.egid,
12412
+ users: broker.users,
12413
+ ...tag !== void 0 ? { tag } : {}
12414
+ };
12415
+ }
12416
+ async stampClipboards() {
12417
+ if (!this.options.clipboard) return;
12418
+ const open = this.fds.numbers().filter((fd) => this.fds.get(fd).file.clipboard);
12419
+ if (open.length === 0) return;
12420
+ const stamp = await this.clipboardStamp().catch(() => void 0);
12421
+ if (!stamp) return;
12422
+ for (const fd of open) {
12423
+ const channel = this.fds.get(fd).file.clipboard;
12424
+ if (channel) channel.stamp = stamp;
12425
+ }
12426
+ }
12167
12427
  async sealedCaller() {
12168
12428
  const shown = await this.options.identity?.();
12169
12429
  const broker = this.options.sealed;
@@ -12238,10 +12498,13 @@ var WasmProcess = class {
12238
12498
  kind: "json",
12239
12499
  json: await this.write(req.fd, req.body, req.nonblock, cancelled)
12240
12500
  };
12241
- case "fd-close":
12501
+ case "fd-close": {
12242
12502
  this.options.locks?.closed(this.pid, req.fd);
12503
+ const channel = this.fds.get(req.fd).file.clipboard;
12504
+ if (channel) channel.stamp = await this.clipboardStamp();
12243
12505
  await Promise.resolve(this.fds.close(req.fd));
12244
12506
  return { ok: true, kind: "void" };
12507
+ }
12245
12508
  case "fd-pipe": {
12246
12509
  const pipe = openPipe();
12247
12510
  const read = this.fds.install(pipe.read, 3);
@@ -12299,6 +12562,8 @@ var WasmProcess = class {
12299
12562
  return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
12300
12563
  case "fd-open-sealed":
12301
12564
  return { ok: true, kind: "json", json: this.openSealed() };
12565
+ case "fd-open-clipboard":
12566
+ return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
12302
12567
  case "fd-tty-owner":
12303
12568
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
12304
12569
  case "fd-select": {
@@ -12750,6 +13015,7 @@ var WasmProcess = class {
12750
13015
  this.options.locks?.release(this.pid);
12751
13016
  this.asyncOps.close();
12752
13017
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
13018
+ await this.stampClipboards();
12753
13019
  await this.fds.closeAll();
12754
13020
  if (!this.options.nodes) this.options.openFiles?.delete(this.nodes);
12755
13021
  await this.options.http?.closeAll();
@@ -12932,6 +13198,7 @@ function processOptions(opts, header4, hooks, created3) {
12932
13198
  ...inherited(opts),
12933
13199
  ...opts.owners ? { owners: true } : {},
12934
13200
  ...opts.sealed ? { sealed: opts.sealed } : {},
13201
+ ...opts.clipboard ? { clipboard: opts.clipboard } : {},
12935
13202
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
12936
13203
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12937
13204
  ...opts.identity ? { identity: opts.identity } : {},
@@ -13436,14 +13703,14 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
13436
13703
  "cancel",
13437
13704
  "materialize"
13438
13705
  ]);
13439
- var encoder3 = new TextEncoder();
13440
- var decoder2 = new TextDecoder("utf-8", { fatal: true });
13706
+ var encoder4 = new TextEncoder();
13707
+ var decoder3 = new TextDecoder("utf-8", { fatal: true });
13441
13708
  function parseSealed(bytes) {
13442
13709
  try {
13443
13710
  if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
13444
13711
  let parsed2;
13445
13712
  try {
13446
- parsed2 = JSON.parse(decoder2.decode(bytes));
13713
+ parsed2 = JSON.parse(decoder3.decode(bytes));
13447
13714
  } catch {
13448
13715
  throw new KernelError("EINVAL");
13449
13716
  }
@@ -13469,13 +13736,13 @@ function serialized(reply) {
13469
13736
  const text = JSON.stringify(reply);
13470
13737
  plain = text === void 0 ? void 0 : JSON.parse(text);
13471
13738
  } catch (err) {
13472
- return JSON.stringify(errorReply("EIO", lineOf(err)));
13739
+ return JSON.stringify(errorReply("EIO", lineOf2(err)));
13473
13740
  }
13474
13741
  if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
13475
13742
  const { v: _, ...rest2 } = plain;
13476
13743
  return JSON.stringify({ v: 1, ...rest2 });
13477
13744
  }
13478
- function lineOf(err) {
13745
+ function lineOf2(err) {
13479
13746
  const text = err instanceof Error ? err.message : String(err);
13480
13747
  return (text.split("\n")[0] ?? "").slice(0, 200);
13481
13748
  }
@@ -13505,7 +13772,7 @@ var SealedChannel = class {
13505
13772
  gone.removeEventListener("abort", withdraw);
13506
13773
  if (this.inflight !== flight) return;
13507
13774
  this.inflight = void 0;
13508
- if (reply) this.reply = encoder3.encode(serialized(reply));
13775
+ if (reply) this.reply = encoder4.encode(serialized(reply));
13509
13776
  });
13510
13777
  this.inflight = flight;
13511
13778
  return length2;
@@ -13519,7 +13786,7 @@ var SealedChannel = class {
13519
13786
  if (signal.aborted) return void 0;
13520
13787
  const asked = Promise.resolve().then(() => hook(req, { signal })).then(
13521
13788
  (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
13522
- (err) => errorReply("EIO", lineOf(err))
13789
+ (err) => errorReply("EIO", lineOf2(err))
13523
13790
  );
13524
13791
  const reply = await Promise.race([asked, stopped]);
13525
13792
  if (!signal.aborted) return reply;
@@ -14242,13 +14509,13 @@ var HostfsClient = class {
14242
14509
  const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
14243
14510
  onOpen();
14244
14511
  const reader = response.body.getReader();
14245
- const decoder3 = new TextDecoder();
14512
+ const decoder4 = new TextDecoder();
14246
14513
  let buffered = "";
14247
14514
  try {
14248
14515
  for (; ; ) {
14249
14516
  const { done: done4, value } = await reader.read();
14250
14517
  if (done4) return;
14251
- buffered += decoder3.decode(value, { stream: true });
14518
+ buffered += decoder4.decode(value, { stream: true });
14252
14519
  let end = buffered.indexOf("\n");
14253
14520
  while (end >= 0) {
14254
14521
  const line = buffered.slice(0, end).trim();
@@ -15730,7 +15997,7 @@ var LAYOUT = [
15730
15997
  ];
15731
15998
  var LOAD_SAMPLE_MS = 5e3;
15732
15999
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
15733
- var encoder4 = new TextEncoder();
16000
+ var encoder5 = new TextEncoder();
15734
16001
  var USERS_MODES = ["off", "report", "enforce"];
15735
16002
  function usersMode(mode = "off") {
15736
16003
  if (USERS_MODES.includes(mode)) return mode;
@@ -15887,6 +16154,7 @@ var Launcher = class {
15887
16154
  usersMode;
15888
16155
  dac;
15889
16156
  sealed;
16157
+ clipboard;
15890
16158
  rootFs;
15891
16159
  fileWatches;
15892
16160
  nextPid = 1e3;
@@ -15897,6 +16165,12 @@ var Launcher = class {
15897
16165
  this.rootFs = options.fs;
15898
16166
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15899
16167
  if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
16168
+ this.clipboard = new ClipboardBroker(
16169
+ options.clipboard,
16170
+ this.usersMode,
16171
+ void 0,
16172
+ (pid) => this.tagOf(pid)
16173
+ );
15900
16174
  this.sealed = new SealedBroker(
15901
16175
  options.sealed,
15902
16176
  this.usersMode,
@@ -16313,6 +16587,7 @@ var Launcher = class {
16313
16587
  cred: req.cred,
16314
16588
  ...this.owners ? { owners: true } : {},
16315
16589
  sealed: this.sealed,
16590
+ clipboard: this.clipboard,
16316
16591
  ...this.dac ? { dac: this.dac } : {},
16317
16592
  program: req.program,
16318
16593
  argv0: req.argv0,
@@ -17073,7 +17348,7 @@ var Launcher = class {
17073
17348
  },
17074
17349
  account
17075
17350
  );
17076
- const report = (message3) => options.onData(encoder4.encode(`${message3}\r
17351
+ const report = (message3) => options.onData(encoder5.encode(`${message3}\r
17077
17352
  `));
17078
17353
  const handle = await this.launch(planned, {
17079
17354
  env,
@@ -17110,7 +17385,7 @@ var Launcher = class {
17110
17385
  };
17111
17386
  const stdout = collect(out, options.onStdout);
17112
17387
  const stderr = collect(err, options.onStderr);
17113
- const report = (message3) => stderr(encoder4.encode(`${message3}
17388
+ const report = (message3) => stderr(encoder5.encode(`${message3}
17114
17389
  `));
17115
17390
  if (!planned) {
17116
17391
  report(`${file}: command not found`);
@@ -17647,7 +17922,7 @@ function dialStream(port, onClose) {
17647
17922
 
17648
17923
  // src/client/loopback-fetch.ts
17649
17924
  var OWN_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
17650
- var encoder5 = new TextEncoder();
17925
+ var encoder6 = new TextEncoder();
17651
17926
  var latin12 = new TextDecoder("latin1");
17652
17927
  var ByteSource = class {
17653
17928
  buffered = new Uint8Array(0);
@@ -17658,14 +17933,14 @@ var ByteSource = class {
17658
17933
  async more() {
17659
17934
  const { done: done4, value } = await this.reader.read();
17660
17935
  if (done4) return false;
17661
- const joined2 = new Uint8Array(this.buffered.length + value.length);
17662
- joined2.set(this.buffered);
17663
- joined2.set(value, this.buffered.length);
17664
- this.buffered = joined2;
17936
+ const joined3 = new Uint8Array(this.buffered.length + value.length);
17937
+ joined3.set(this.buffered);
17938
+ joined3.set(value, this.buffered.length);
17939
+ this.buffered = joined3;
17665
17940
  return true;
17666
17941
  }
17667
17942
  async until(marker) {
17668
- const needle = encoder5.encode(marker);
17943
+ const needle = encoder6.encode(marker);
17669
17944
  for (; ; ) {
17670
17945
  const at = indexOf(this.buffered, needle);
17671
17946
  if (at >= 0) return this.take(at + needle.length);
@@ -17773,7 +18048,7 @@ async function requestBytes(request) {
17773
18048
  });
17774
18049
  if (body) lines2.push(`Content-Length: ${body.length}`);
17775
18050
  lines2.push("Connection: close", "", "");
17776
- const head = encoder5.encode(lines2.join("\r\n"));
18051
+ const head = encoder6.encode(lines2.join("\r\n"));
17777
18052
  const out = new Uint8Array(head.length + (body?.length ?? 0));
17778
18053
  out.set(head);
17779
18054
  if (body) out.set(body, head.length);
@@ -17827,8 +18102,8 @@ var KernelCallError = class extends Error {
17827
18102
  this.code = code;
17828
18103
  }
17829
18104
  };
17830
- var encoder6 = new TextEncoder();
17831
- var bytesOf2 = (data) => typeof data === "string" ? encoder6.encode(data) : data;
18105
+ var encoder7 = new TextEncoder();
18106
+ var bytesOf2 = (data) => typeof data === "string" ? encoder7.encode(data) : data;
17832
18107
  var UNAVAILABLE = {
17833
18108
  manualRedirects: false,
17834
18109
  encodedBodies: false,
@@ -18246,8 +18521,8 @@ function nodeWorker(file, live, closed) {
18246
18521
  }
18247
18522
  function textOf(on) {
18248
18523
  if (!on) return void 0;
18249
- const decoder3 = new TextDecoder();
18250
- return (bytes) => on(decoder3.decode(bytes, { stream: true }));
18524
+ const decoder4 = new TextDecoder();
18525
+ return (bytes) => on(decoder4.decode(bytes, { stream: true }));
18251
18526
  }
18252
18527
  async function createNodeKernel(options = {}) {
18253
18528
  const root = options.root ?? memoryRoot();
@@ -18282,6 +18557,7 @@ async function createNodeKernel(options = {}) {
18282
18557
  ...options.onDenial ? { onDenial: options.onDenial } : {},
18283
18558
  ...options.protect ? { protect: options.protect } : {},
18284
18559
  ...options.sealed ? { sealed: options.sealed } : {},
18560
+ ...options.clipboard ? { clipboard: options.clipboard } : {},
18285
18561
  ...options.approve ? { approve: options.approve } : {}
18286
18562
  });
18287
18563
  await launcher.prepare();
@@ -18290,7 +18566,7 @@ async function createNodeKernel(options = {}) {
18290
18566
  pending.add(reject);
18291
18567
  work().then(resolve, reject).finally(() => pending.delete(reject));
18292
18568
  });
18293
- const decoder3 = new TextDecoder();
18569
+ const decoder4 = new TextDecoder();
18294
18570
  return {
18295
18571
  root,
18296
18572
  async run(argv, opts = {}) {
@@ -18307,8 +18583,8 @@ async function createNodeKernel(options = {}) {
18307
18583
  );
18308
18584
  return {
18309
18585
  status: result.status,
18310
- stdout: decoder3.decode(result.stdout),
18311
- stderr: decoder3.decode(result.stderr)
18586
+ stdout: decoder4.decode(result.stdout),
18587
+ stderr: decoder4.decode(result.stderr)
18312
18588
  };
18313
18589
  },
18314
18590
  async openTerminal(argv, opts = {}) {