@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/README.md +15 -1
- package/dist/index.js +56 -8
- package/dist/index.js.map +4 -4
- package/dist/kernel-worker.js +345 -36
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +183 -34
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +323 -47
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +183 -34
- package/dist/process-worker.js.map +3 -3
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -783,6 +783,7 @@ function syncAccessHandles(scope = globalThis) {
|
|
|
783
783
|
function isHandle(child) {
|
|
784
784
|
return "kind" in child;
|
|
785
785
|
}
|
|
786
|
+
var FILE_TIMES_MS = 100;
|
|
786
787
|
var OpfsFs = class {
|
|
787
788
|
root;
|
|
788
789
|
meta;
|
|
@@ -1053,8 +1054,50 @@ var OpfsFs = class {
|
|
|
1053
1054
|
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
1054
1055
|
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
1055
1056
|
}
|
|
1057
|
+
id = crypto.randomUUID();
|
|
1056
1058
|
dirTimes = /* @__PURE__ */ new Map();
|
|
1057
1059
|
dirDirty = /* @__PURE__ */ new Set();
|
|
1060
|
+
fileTimes = /* @__PURE__ */ new Map();
|
|
1061
|
+
written(target) {
|
|
1062
|
+
this.committed(target);
|
|
1063
|
+
if (!this.ranged) return;
|
|
1064
|
+
this.fileTimes.set(target, Date.now());
|
|
1065
|
+
this.scheduleFileTimes();
|
|
1066
|
+
}
|
|
1067
|
+
scheduleFileTimes() {
|
|
1068
|
+
if (this.flushing.file || this.fileTimes.size === 0) return;
|
|
1069
|
+
this.flushing.file = setTimeout(() => {
|
|
1070
|
+
void this.flushFileTimes().catch(() => void 0).finally(() => {
|
|
1071
|
+
this.flushing.file = void 0;
|
|
1072
|
+
this.scheduleFileTimes();
|
|
1073
|
+
});
|
|
1074
|
+
}, FILE_TIMES_MS);
|
|
1075
|
+
this.flushing.file.unref?.();
|
|
1076
|
+
}
|
|
1077
|
+
async flushFileTimes() {
|
|
1078
|
+
for (const [path, ms] of [...this.fileTimes]) {
|
|
1079
|
+
const mtimeFor = await this.modified(path).catch(() => 0);
|
|
1080
|
+
await this.meta.update(
|
|
1081
|
+
path,
|
|
1082
|
+
(entry) => this.fileTimes.get(path) !== ms || this.explicitWins(entry, ms) ? entry : { ...entry, path, mtimeMs: ms, mtimeFor, ctimeMs: ms }
|
|
1083
|
+
);
|
|
1084
|
+
if (this.fileTimes.get(path) === ms) this.fileTimes.delete(path);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
timesOf(path) {
|
|
1088
|
+
return this.dirTimes.get(path) ?? this.fileTimes.get(path);
|
|
1089
|
+
}
|
|
1090
|
+
explicitWins(entry, ms) {
|
|
1091
|
+
if (entry?.timesAt === void 0 || entry.timesBy === this.id) return false;
|
|
1092
|
+
return entry.timesAt >= ms;
|
|
1093
|
+
}
|
|
1094
|
+
movePending(from, to) {
|
|
1095
|
+
for (const [path, ms] of [...this.fileTimes]) {
|
|
1096
|
+
if (!within2(path, from)) continue;
|
|
1097
|
+
this.fileTimes.delete(path);
|
|
1098
|
+
if (to !== void 0) this.fileTimes.set(to + path.slice(from.length), ms);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1058
1101
|
flushing = {};
|
|
1059
1102
|
touch(...dirs) {
|
|
1060
1103
|
const now = Date.now();
|
|
@@ -1078,11 +1121,10 @@ var OpfsFs = class {
|
|
|
1078
1121
|
if (this.dirTimes.get(path) === ms) this.dirTimes.delete(path);
|
|
1079
1122
|
}
|
|
1080
1123
|
}
|
|
1081
|
-
async statOf(path, handle, meta) {
|
|
1124
|
+
async statOf(path, handle, meta, touched) {
|
|
1082
1125
|
const directory = handle.kind === "directory";
|
|
1083
1126
|
const file = directory ? void 0 : await handle.getFile();
|
|
1084
1127
|
const modified = file?.lastModified ?? 0;
|
|
1085
|
-
const touched = directory ? this.dirTimes.get(path) : void 0;
|
|
1086
1128
|
const kept = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
1087
1129
|
const mtime = touched ?? kept;
|
|
1088
1130
|
return {
|
|
@@ -1114,13 +1156,14 @@ var OpfsFs = class {
|
|
|
1114
1156
|
}
|
|
1115
1157
|
async describe(found) {
|
|
1116
1158
|
if (found.link) return this.owned(this.linkStat(found.link), found.path, found.link);
|
|
1159
|
+
const touched = this.timesOf(found.path);
|
|
1117
1160
|
if (!this.owners) {
|
|
1118
1161
|
const [meta2] = await this.meta.get([found.path]);
|
|
1119
|
-
return this.statOf(found.path, found.handle, meta2);
|
|
1162
|
+
return this.statOf(found.path, found.handle, meta2, touched);
|
|
1120
1163
|
}
|
|
1121
1164
|
const entries = await this.meta.get(chain(found.path));
|
|
1122
1165
|
const meta = entries.pop();
|
|
1123
|
-
const stat = await this.statOf(found.path, found.handle, meta);
|
|
1166
|
+
const stat = await this.statOf(found.path, found.handle, meta, touched);
|
|
1124
1167
|
return { ...stat, ...ownOf(meta) ?? inherit(entries) };
|
|
1125
1168
|
}
|
|
1126
1169
|
async stat(path) {
|
|
@@ -1177,13 +1220,14 @@ var OpfsFs = class {
|
|
|
1177
1220
|
async pwrite(path, offset, bytes) {
|
|
1178
1221
|
const [handle, , target] = await this.fileFor(path);
|
|
1179
1222
|
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
1180
|
-
this.
|
|
1223
|
+
this.written(target);
|
|
1181
1224
|
}
|
|
1182
1225
|
async truncate(path, size) {
|
|
1183
1226
|
const handle = await this.existing(path, true);
|
|
1184
|
-
const
|
|
1227
|
+
const resolve = this.ranged || this.remote?.watched();
|
|
1228
|
+
const target = resolve ? (await this.locate(path, true)).path : path;
|
|
1185
1229
|
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
1186
|
-
this.
|
|
1230
|
+
this.written(target);
|
|
1187
1231
|
}
|
|
1188
1232
|
async writeSession(path, truncate) {
|
|
1189
1233
|
const [handle, , target] = await this.fileFor(path);
|
|
@@ -1273,13 +1317,16 @@ var OpfsFs = class {
|
|
|
1273
1317
|
async readdirStat(path) {
|
|
1274
1318
|
const [dir, children2] = await this.listing(path);
|
|
1275
1319
|
const base = dir === "/" ? "" : dir;
|
|
1320
|
+
const touched = children2.map(([name2]) => this.timesOf(`${base}/${name2}`));
|
|
1276
1321
|
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
1277
1322
|
const handed2 = this.owners ? await this.handedTo(dir) : void 0;
|
|
1278
1323
|
const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
|
|
1279
1324
|
return Promise.all(
|
|
1280
1325
|
children2.map(async ([name2, child], i) => {
|
|
1281
1326
|
if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
|
|
1282
|
-
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(
|
|
1327
|
+
const stat = await this.statOf(`${base}/${name2}`, child, metas[i], touched[i]).catch(
|
|
1328
|
+
() => null
|
|
1329
|
+
);
|
|
1283
1330
|
return [name2, stat && owned4(stat, metas[i])];
|
|
1284
1331
|
})
|
|
1285
1332
|
);
|
|
@@ -1337,6 +1384,7 @@ var OpfsFs = class {
|
|
|
1337
1384
|
throw translate(err, path);
|
|
1338
1385
|
}
|
|
1339
1386
|
this.changed(found.path);
|
|
1387
|
+
this.movePending(found.path);
|
|
1340
1388
|
}
|
|
1341
1389
|
await this.meta.remove([found.path]);
|
|
1342
1390
|
this.touch(parentOf3(found.path));
|
|
@@ -1400,6 +1448,7 @@ var OpfsFs = class {
|
|
|
1400
1448
|
const moved2 = await this.place(source.handle, dir, staging);
|
|
1401
1449
|
if (swap) await this.rm(destination, { recursive: true });
|
|
1402
1450
|
await this.meta.move(source.path, destination, carry);
|
|
1451
|
+
this.movePending(source.path, destination);
|
|
1403
1452
|
if (!moved2) await this.rm(source.path, { recursive: true });
|
|
1404
1453
|
let copied = !moved2;
|
|
1405
1454
|
if (swap) {
|
|
@@ -1521,6 +1570,7 @@ var OpfsFs = class {
|
|
|
1521
1570
|
const handle = found.handle;
|
|
1522
1571
|
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
1523
1572
|
this.dirTimes.delete(found.path);
|
|
1573
|
+
this.fileTimes.delete(found.path);
|
|
1524
1574
|
const ctimeMs = Date.now();
|
|
1525
1575
|
await this.meta.update(found.path, (entry) => ({
|
|
1526
1576
|
...entry,
|
|
@@ -1529,7 +1579,8 @@ var OpfsFs = class {
|
|
|
1529
1579
|
mtimeMs: mtime.getTime(),
|
|
1530
1580
|
mtimeFor: file?.lastModified ?? 0,
|
|
1531
1581
|
ctimeMs,
|
|
1532
|
-
timesAt: ctimeMs
|
|
1582
|
+
timesAt: ctimeMs,
|
|
1583
|
+
timesBy: this.id
|
|
1533
1584
|
}));
|
|
1534
1585
|
}
|
|
1535
1586
|
async reconcile() {
|
|
@@ -2255,9 +2306,11 @@ var KernelPipe = class {
|
|
|
2255
2306
|
// src/kernel/fd-table.ts
|
|
2256
2307
|
var KernelError = class extends Error {
|
|
2257
2308
|
code;
|
|
2258
|
-
|
|
2309
|
+
detail;
|
|
2310
|
+
constructor(code, detail) {
|
|
2259
2311
|
super(code);
|
|
2260
2312
|
this.code = code;
|
|
2313
|
+
if (detail !== void 0) this.detail = detail;
|
|
2261
2314
|
}
|
|
2262
2315
|
};
|
|
2263
2316
|
function pollFile(file) {
|
|
@@ -2400,6 +2453,7 @@ function kernelFdKind(file) {
|
|
|
2400
2453
|
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
2401
2454
|
if (file.tty) return "tty";
|
|
2402
2455
|
if (file.sealed) return "sealed";
|
|
2456
|
+
if (file.clipboard) return "clipboard";
|
|
2403
2457
|
return file.seek ? "file" : "stream";
|
|
2404
2458
|
}
|
|
2405
2459
|
var FdTable = class _FdTable {
|
|
@@ -4190,6 +4244,203 @@ function serveClient(port, host) {
|
|
|
4190
4244
|
return { detach, closed: ended.promise };
|
|
4191
4245
|
}
|
|
4192
4246
|
|
|
4247
|
+
// src/kernel/clipboard.ts
|
|
4248
|
+
var CLIPBOARD_MAX_BYTES = 1024 * 1024;
|
|
4249
|
+
var CLIPBOARD_TIMEOUT_MS = 1e4;
|
|
4250
|
+
var encoder = new TextEncoder();
|
|
4251
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
4252
|
+
var READY = { readable: true, writable: true, hangup: false };
|
|
4253
|
+
function lineOf(err) {
|
|
4254
|
+
const text = err instanceof Error ? err.message : String(err);
|
|
4255
|
+
return (text.split("\n")[0] ?? "").slice(0, 200);
|
|
4256
|
+
}
|
|
4257
|
+
function clipboardFailure(err) {
|
|
4258
|
+
if (err instanceof KernelError) return err;
|
|
4259
|
+
const name2 = err?.name;
|
|
4260
|
+
return new KernelError(name2 === "NotAllowedError" ? "EACCES" : "EIO", lineOf(err));
|
|
4261
|
+
}
|
|
4262
|
+
function joined2(chunks, size) {
|
|
4263
|
+
if (chunks.length === 1) return chunks[0];
|
|
4264
|
+
const out = new Uint8Array(size);
|
|
4265
|
+
let at = 0;
|
|
4266
|
+
for (const chunk2 of chunks) {
|
|
4267
|
+
out.set(chunk2, at);
|
|
4268
|
+
at += chunk2.length;
|
|
4269
|
+
}
|
|
4270
|
+
return out;
|
|
4271
|
+
}
|
|
4272
|
+
async function interruptible(work, signal) {
|
|
4273
|
+
if (!signal) return work;
|
|
4274
|
+
if (signal.aborted) throw new KernelError("EINTR");
|
|
4275
|
+
let stop = () => {
|
|
4276
|
+
};
|
|
4277
|
+
const interrupted2 = new Promise((_, reject) => {
|
|
4278
|
+
stop = () => reject(new KernelError("EINTR"));
|
|
4279
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
4280
|
+
});
|
|
4281
|
+
try {
|
|
4282
|
+
return await Promise.race([work, interrupted2]);
|
|
4283
|
+
} finally {
|
|
4284
|
+
signal.removeEventListener("abort", stop);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
var ClipboardChannel = class {
|
|
4288
|
+
stamp;
|
|
4289
|
+
access;
|
|
4290
|
+
broker;
|
|
4291
|
+
snapshot;
|
|
4292
|
+
taken;
|
|
4293
|
+
offset = 0;
|
|
4294
|
+
chunks = [];
|
|
4295
|
+
size = 0;
|
|
4296
|
+
wrote = false;
|
|
4297
|
+
failed = false;
|
|
4298
|
+
constructor(broker, access2, stamp) {
|
|
4299
|
+
this.broker = broker;
|
|
4300
|
+
this.access = access2;
|
|
4301
|
+
this.stamp = stamp;
|
|
4302
|
+
}
|
|
4303
|
+
get unread() {
|
|
4304
|
+
return this.taken === void 0 && this.snapshot === void 0;
|
|
4305
|
+
}
|
|
4306
|
+
get settled() {
|
|
4307
|
+
return this.taken !== void 0;
|
|
4308
|
+
}
|
|
4309
|
+
async read(max, signal) {
|
|
4310
|
+
if (this.access === "write") throw new KernelError("EBADF");
|
|
4311
|
+
if (!this.taken) {
|
|
4312
|
+
if (!this.snapshot) {
|
|
4313
|
+
const stamp = this.stamp;
|
|
4314
|
+
const asked = this.broker.ask(async (hook) => {
|
|
4315
|
+
const text = await hook.read(stamp);
|
|
4316
|
+
if (typeof text !== "string") throw new KernelError("EIO", "the clipboard gave no text");
|
|
4317
|
+
return encoder.encode(text);
|
|
4318
|
+
});
|
|
4319
|
+
this.snapshot = asked;
|
|
4320
|
+
asked.then(
|
|
4321
|
+
(bytes) => {
|
|
4322
|
+
if (this.snapshot === asked) this.taken = bytes;
|
|
4323
|
+
},
|
|
4324
|
+
() => {
|
|
4325
|
+
if (this.snapshot === asked) this.snapshot = void 0;
|
|
4326
|
+
}
|
|
4327
|
+
);
|
|
4328
|
+
}
|
|
4329
|
+
this.taken = await interruptible(this.snapshot, signal);
|
|
4330
|
+
}
|
|
4331
|
+
const out = this.taken.subarray(this.offset, this.offset + max);
|
|
4332
|
+
this.offset += out.length;
|
|
4333
|
+
return out;
|
|
4334
|
+
}
|
|
4335
|
+
write(bytes) {
|
|
4336
|
+
if (this.access === "read") throw new KernelError("EBADF");
|
|
4337
|
+
this.wrote = true;
|
|
4338
|
+
if (this.failed) throw new KernelError("EFBIG");
|
|
4339
|
+
if (this.size + bytes.length > CLIPBOARD_MAX_BYTES) {
|
|
4340
|
+
this.failed = true;
|
|
4341
|
+
this.chunks = [];
|
|
4342
|
+
this.size = 0;
|
|
4343
|
+
throw new KernelError("EFBIG");
|
|
4344
|
+
}
|
|
4345
|
+
if (bytes.length > 0) this.chunks.push(bytes.slice());
|
|
4346
|
+
this.size += bytes.length;
|
|
4347
|
+
return bytes.length;
|
|
4348
|
+
}
|
|
4349
|
+
poll() {
|
|
4350
|
+
return READY;
|
|
4351
|
+
}
|
|
4352
|
+
async close() {
|
|
4353
|
+
this.snapshot = void 0;
|
|
4354
|
+
this.taken = void 0;
|
|
4355
|
+
const commits = this.access === "write" || this.access === "both" && this.wrote;
|
|
4356
|
+
if (!commits || this.failed) return;
|
|
4357
|
+
const bytes = joined2(this.chunks, this.size);
|
|
4358
|
+
this.chunks = [];
|
|
4359
|
+
this.size = 0;
|
|
4360
|
+
let text;
|
|
4361
|
+
try {
|
|
4362
|
+
text = decoder.decode(bytes);
|
|
4363
|
+
} catch {
|
|
4364
|
+
throw new KernelError("EILSEQ", "the text is not valid UTF-8");
|
|
4365
|
+
}
|
|
4366
|
+
const stamp = this.stamp;
|
|
4367
|
+
await this.broker.ask(async (hook) => {
|
|
4368
|
+
await hook.write(text, stamp);
|
|
4369
|
+
});
|
|
4370
|
+
}
|
|
4371
|
+
};
|
|
4372
|
+
var ClipboardBroker = class {
|
|
4373
|
+
hook;
|
|
4374
|
+
users;
|
|
4375
|
+
timeoutMs;
|
|
4376
|
+
tagOf;
|
|
4377
|
+
constructor(hook, users, timeoutMs = CLIPBOARD_TIMEOUT_MS, tagOf = () => void 0) {
|
|
4378
|
+
this.hook = hook;
|
|
4379
|
+
this.users = users;
|
|
4380
|
+
this.timeoutMs = timeoutMs;
|
|
4381
|
+
this.tagOf = tagOf;
|
|
4382
|
+
}
|
|
4383
|
+
async ask(work) {
|
|
4384
|
+
const hook = this.hook;
|
|
4385
|
+
if (!hook) throw new KernelError("ENOTSUP", "no clipboard in this embedder");
|
|
4386
|
+
let timer;
|
|
4387
|
+
const late = new Promise((_, reject) => {
|
|
4388
|
+
timer = setTimeout(
|
|
4389
|
+
() => reject(new KernelError("EIO", "clipboard timed out")),
|
|
4390
|
+
this.timeoutMs
|
|
4391
|
+
);
|
|
4392
|
+
});
|
|
4393
|
+
const asked = Promise.resolve().then(() => work(hook)).catch((err) => {
|
|
4394
|
+
throw clipboardFailure(err);
|
|
4395
|
+
});
|
|
4396
|
+
try {
|
|
4397
|
+
return await Promise.race([asked, late]);
|
|
4398
|
+
} finally {
|
|
4399
|
+
clearTimeout(timer);
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
open(access2, stamp) {
|
|
4403
|
+
const channel = new ClipboardChannel(this, access2, stamp);
|
|
4404
|
+
return new OpenFile({
|
|
4405
|
+
read: (max, signal) => channel.read(max, signal),
|
|
4406
|
+
write: async (bytes) => channel.write(bytes),
|
|
4407
|
+
poll: () => channel.poll(),
|
|
4408
|
+
close: () => channel.close(),
|
|
4409
|
+
clipboard: channel
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
|
|
4414
|
+
// src/clipboard-relay.ts
|
|
4415
|
+
function clipboardAsker(port) {
|
|
4416
|
+
const asks = /* @__PURE__ */ new Map();
|
|
4417
|
+
let next2 = 0;
|
|
4418
|
+
const ask = (op, stamp, text) => {
|
|
4419
|
+
const id2 = ++next2;
|
|
4420
|
+
const waiting = Promise.withResolvers();
|
|
4421
|
+
asks.set(id2, waiting);
|
|
4422
|
+
const timer = setTimeout(() => {
|
|
4423
|
+
if (asks.delete(id2)) waiting.reject(new Error("clipboard timed out"));
|
|
4424
|
+
}, CLIPBOARD_TIMEOUT_MS + 1e3);
|
|
4425
|
+
port.postMessage({ clipboardAsk: { id: id2, op, stamp, ...text !== void 0 ? { text } : {} } });
|
|
4426
|
+
return waiting.promise.finally(() => clearTimeout(timer));
|
|
4427
|
+
};
|
|
4428
|
+
const hook = {
|
|
4429
|
+
read: async (stamp) => await ask("read", stamp) ?? "",
|
|
4430
|
+
write: async (text, stamp) => {
|
|
4431
|
+
await ask("write", stamp, text);
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
const answer = ({ id: id2, text, error }) => {
|
|
4435
|
+
const waiting = asks.get(id2);
|
|
4436
|
+
if (!waiting) return;
|
|
4437
|
+
asks.delete(id2);
|
|
4438
|
+
if (error) waiting.reject(Object.assign(new Error(error.message), { name: error.name }));
|
|
4439
|
+
else waiting.resolve(text);
|
|
4440
|
+
};
|
|
4441
|
+
return { hook, answer };
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4193
4444
|
// src/kernel/net/loopback-names.ts
|
|
4194
4445
|
var HOST_LOOPBACK = "host.slicc.internal";
|
|
4195
4446
|
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
@@ -9191,20 +9442,20 @@ var SAB_DEFAULT_WINDOW_BYTES = 1024 * 1024;
|
|
|
9191
9442
|
var SAB_MIN_BYTES = SAB_HEADER_BYTES + 4096;
|
|
9192
9443
|
var SYNC_SAB_REQ_MSG = "sync-sab-req";
|
|
9193
9444
|
var SYNC_SAB_NEXT_MSG = "sync-sab-next";
|
|
9194
|
-
var
|
|
9195
|
-
var
|
|
9445
|
+
var encoder2 = new TextEncoder();
|
|
9446
|
+
var decoder2 = new TextDecoder();
|
|
9196
9447
|
function encodeSabResult(result) {
|
|
9197
9448
|
if (!result.ok) {
|
|
9198
9449
|
return {
|
|
9199
9450
|
status: SAB_STATUS_ERRNO,
|
|
9200
|
-
payload:
|
|
9451
|
+
payload: encoder2.encode(JSON.stringify({ errno: result.errno, message: result.message }))
|
|
9201
9452
|
};
|
|
9202
9453
|
}
|
|
9203
9454
|
switch (result.kind) {
|
|
9204
9455
|
case "bytes":
|
|
9205
9456
|
return { status: SAB_STATUS_BYTES, payload: result.bytes };
|
|
9206
9457
|
case "json":
|
|
9207
|
-
return { status: SAB_STATUS_JSON, payload:
|
|
9458
|
+
return { status: SAB_STATUS_JSON, payload: encoder2.encode(JSON.stringify(result.json)) };
|
|
9208
9459
|
default:
|
|
9209
9460
|
return { status: SAB_STATUS_VOID, payload: new Uint8Array(0) };
|
|
9210
9461
|
}
|
|
@@ -10678,7 +10929,7 @@ function defaultTermios() {
|
|
|
10678
10929
|
c_cc: cCc
|
|
10679
10930
|
};
|
|
10680
10931
|
}
|
|
10681
|
-
var
|
|
10932
|
+
var encoder3 = new TextEncoder();
|
|
10682
10933
|
function isContinuation(byte) {
|
|
10683
10934
|
return (byte & 192) === 128;
|
|
10684
10935
|
}
|
|
@@ -10800,7 +11051,7 @@ var KernelTty = class {
|
|
|
10800
11051
|
this.line.push(byte);
|
|
10801
11052
|
this.readable.push(Uint8Array.from(this.line));
|
|
10802
11053
|
this.line = [];
|
|
10803
|
-
if (echo) this.output(
|
|
11054
|
+
if (echo) this.output(encoder3.encode("\n"));
|
|
10804
11055
|
} else {
|
|
10805
11056
|
this.line.push(byte);
|
|
10806
11057
|
if (echo) this.echo(byte);
|
|
@@ -10825,7 +11076,7 @@ var KernelTty = class {
|
|
|
10825
11076
|
const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
|
|
10826
11077
|
const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
|
|
10827
11078
|
if (cells > 0 && this.termios.c_lflag & ECHO) {
|
|
10828
|
-
this.output(
|
|
11079
|
+
this.output(encoder3.encode("\b \b".repeat(cells)), false);
|
|
10829
11080
|
}
|
|
10830
11081
|
}
|
|
10831
11082
|
echo(byte) {
|
|
@@ -11314,7 +11565,7 @@ async function socketSyscall(req, proc) {
|
|
|
11314
11565
|
dialing?.catch(() => void 0);
|
|
11315
11566
|
throw new KernelError("EINPROGRESS");
|
|
11316
11567
|
}
|
|
11317
|
-
if (dialing !== void 0) await
|
|
11568
|
+
if (dialing !== void 0) await interruptible2(dialing, proc.blocking());
|
|
11318
11569
|
return ok();
|
|
11319
11570
|
}
|
|
11320
11571
|
case "sock-shutdown":
|
|
@@ -11338,7 +11589,7 @@ async function socketSyscall(req, proc) {
|
|
|
11338
11589
|
}
|
|
11339
11590
|
}
|
|
11340
11591
|
}
|
|
11341
|
-
function
|
|
11592
|
+
function interruptible2(dialing, signal) {
|
|
11342
11593
|
return new Promise((resolve, reject) => {
|
|
11343
11594
|
const abort = () => reject(new KernelError("EINTR"));
|
|
11344
11595
|
signal.addEventListener("abort", abort, { once: true });
|
|
@@ -12005,6 +12256,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
|
|
|
12005
12256
|
"fd-tty-names",
|
|
12006
12257
|
"fd-tty-owner",
|
|
12007
12258
|
"fd-open-sealed",
|
|
12259
|
+
"fd-open-clipboard",
|
|
12008
12260
|
"tty-get",
|
|
12009
12261
|
"tty-set",
|
|
12010
12262
|
"tty-winsz",
|
|
@@ -12247,7 +12499,7 @@ var WasmProcess = class {
|
|
|
12247
12499
|
return await this.procSyscall(req, cancelled);
|
|
12248
12500
|
} catch (e) {
|
|
12249
12501
|
if (e instanceof KernelError || e instanceof SpawnError) {
|
|
12250
|
-
return { ok: false, errno: e.code, message: e.code };
|
|
12502
|
+
return { ok: false, errno: e.code, message: e.detail ?? e.code };
|
|
12251
12503
|
}
|
|
12252
12504
|
const code = e?.code;
|
|
12253
12505
|
if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
|
|
@@ -12290,6 +12542,7 @@ var WasmProcess = class {
|
|
|
12290
12542
|
const read = req.peek ? file.peek : file.read;
|
|
12291
12543
|
if (!read) throw new KernelError(req.peek && file.read ? "EOPNOTSUPP" : "EBADF");
|
|
12292
12544
|
if (file.tty) this.checkForeground(file.tty);
|
|
12545
|
+
if (file.clipboard) return this.readClipboard(file.clipboard, req, cancelled);
|
|
12293
12546
|
if (req.max <= 0) return new Uint8Array(0);
|
|
12294
12547
|
if (file instanceof KernelSocket && file.unconnected) throw new KernelError("ENOTCONN");
|
|
12295
12548
|
const ready2 = pollFile(file).readable;
|
|
@@ -12297,6 +12550,13 @@ var WasmProcess = class {
|
|
|
12297
12550
|
const signal = ready2 ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
12298
12551
|
return read.call(file, Math.max(0, Math.min(req.max, MAX_READ)), signal);
|
|
12299
12552
|
}
|
|
12553
|
+
async readClipboard(channel, req, cancelled) {
|
|
12554
|
+
if (channel.access === "write") throw new KernelError("EBADF");
|
|
12555
|
+
if (req.max <= 0) return new Uint8Array(0);
|
|
12556
|
+
if (channel.unread) channel.stamp = await this.clipboardStamp();
|
|
12557
|
+
const signal = channel.settled ? this.interrupt.signal : this.blockingSignal(cancelled);
|
|
12558
|
+
return channel.read(Math.min(req.max, MAX_READ), signal);
|
|
12559
|
+
}
|
|
12300
12560
|
async write(fd, body, nonblock = false, cancelled) {
|
|
12301
12561
|
const file = this.fds.get(fd).file;
|
|
12302
12562
|
if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
|
|
@@ -12400,6 +12660,36 @@ var WasmProcess = class {
|
|
|
12400
12660
|
if (!broker) throw new KernelError("ENOENT");
|
|
12401
12661
|
return this.fds.install(broker.open(), 3);
|
|
12402
12662
|
}
|
|
12663
|
+
async openClipboard(flags2) {
|
|
12664
|
+
const broker = this.options.clipboard;
|
|
12665
|
+
if (!broker) throw new KernelError("ENOENT");
|
|
12666
|
+
const mode = flags2 & WRITE_ACCESS;
|
|
12667
|
+
const access2 = mode === 0 ? "read" : mode === 1 ? "write" : "both";
|
|
12668
|
+
return this.fds.install(broker.open(access2, await this.clipboardStamp()), 3);
|
|
12669
|
+
}
|
|
12670
|
+
async clipboardStamp() {
|
|
12671
|
+
const shown = await this.options.identity?.();
|
|
12672
|
+
const broker = this.options.clipboard;
|
|
12673
|
+
const tag = broker.tagOf(this.pid);
|
|
12674
|
+
return {
|
|
12675
|
+
pid: shown?.pid ?? this.pid,
|
|
12676
|
+
uid: this.cred.euid,
|
|
12677
|
+
gid: this.cred.egid,
|
|
12678
|
+
users: broker.users,
|
|
12679
|
+
...tag !== void 0 ? { tag } : {}
|
|
12680
|
+
};
|
|
12681
|
+
}
|
|
12682
|
+
async stampClipboards() {
|
|
12683
|
+
if (!this.options.clipboard) return;
|
|
12684
|
+
const open = this.fds.numbers().filter((fd) => this.fds.get(fd).file.clipboard);
|
|
12685
|
+
if (open.length === 0) return;
|
|
12686
|
+
const stamp = await this.clipboardStamp().catch(() => void 0);
|
|
12687
|
+
if (!stamp) return;
|
|
12688
|
+
for (const fd of open) {
|
|
12689
|
+
const channel = this.fds.get(fd).file.clipboard;
|
|
12690
|
+
if (channel) channel.stamp = stamp;
|
|
12691
|
+
}
|
|
12692
|
+
}
|
|
12403
12693
|
async sealedCaller() {
|
|
12404
12694
|
const shown = await this.options.identity?.();
|
|
12405
12695
|
const broker = this.options.sealed;
|
|
@@ -12474,10 +12764,13 @@ var WasmProcess = class {
|
|
|
12474
12764
|
kind: "json",
|
|
12475
12765
|
json: await this.write(req.fd, req.body, req.nonblock, cancelled)
|
|
12476
12766
|
};
|
|
12477
|
-
case "fd-close":
|
|
12767
|
+
case "fd-close": {
|
|
12478
12768
|
this.options.locks?.closed(this.pid, req.fd);
|
|
12769
|
+
const channel = this.fds.get(req.fd).file.clipboard;
|
|
12770
|
+
if (channel) channel.stamp = await this.clipboardStamp();
|
|
12479
12771
|
await Promise.resolve(this.fds.close(req.fd));
|
|
12480
12772
|
return { ok: true, kind: "void" };
|
|
12773
|
+
}
|
|
12481
12774
|
case "fd-pipe": {
|
|
12482
12775
|
const pipe = openPipe();
|
|
12483
12776
|
const read = this.fds.install(pipe.read, 3);
|
|
@@ -12535,6 +12828,8 @@ var WasmProcess = class {
|
|
|
12535
12828
|
return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
|
|
12536
12829
|
case "fd-open-sealed":
|
|
12537
12830
|
return { ok: true, kind: "json", json: this.openSealed() };
|
|
12831
|
+
case "fd-open-clipboard":
|
|
12832
|
+
return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
|
|
12538
12833
|
case "fd-tty-owner":
|
|
12539
12834
|
return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
|
|
12540
12835
|
case "fd-select": {
|
|
@@ -12986,6 +13281,7 @@ var WasmProcess = class {
|
|
|
12986
13281
|
this.options.locks?.release(this.pid);
|
|
12987
13282
|
this.asyncOps.close();
|
|
12988
13283
|
for (const pid of this.children.pids()) this.options.onReap?.(pid);
|
|
13284
|
+
await this.stampClipboards();
|
|
12989
13285
|
await this.fds.closeAll();
|
|
12990
13286
|
if (!this.options.nodes) this.options.openFiles?.delete(this.nodes);
|
|
12991
13287
|
await this.options.http?.closeAll();
|
|
@@ -13168,6 +13464,7 @@ function processOptions(opts, header3, hooks, created3) {
|
|
|
13168
13464
|
...inherited(opts),
|
|
13169
13465
|
...opts.owners ? { owners: true } : {},
|
|
13170
13466
|
...opts.sealed ? { sealed: opts.sealed } : {},
|
|
13467
|
+
...opts.clipboard ? { clipboard: opts.clipboard } : {},
|
|
13171
13468
|
...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
|
|
13172
13469
|
...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
|
|
13173
13470
|
...opts.identity ? { identity: opts.identity } : {},
|
|
@@ -13672,14 +13969,14 @@ var SEALED_OPS = /* @__PURE__ */ new Set([
|
|
|
13672
13969
|
"cancel",
|
|
13673
13970
|
"materialize"
|
|
13674
13971
|
]);
|
|
13675
|
-
var
|
|
13676
|
-
var
|
|
13972
|
+
var encoder4 = new TextEncoder();
|
|
13973
|
+
var decoder3 = new TextDecoder("utf-8", { fatal: true });
|
|
13677
13974
|
function parseSealed(bytes) {
|
|
13678
13975
|
try {
|
|
13679
13976
|
if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
|
|
13680
13977
|
let parsed2;
|
|
13681
13978
|
try {
|
|
13682
|
-
parsed2 = JSON.parse(
|
|
13979
|
+
parsed2 = JSON.parse(decoder3.decode(bytes));
|
|
13683
13980
|
} catch {
|
|
13684
13981
|
throw new KernelError("EINVAL");
|
|
13685
13982
|
}
|
|
@@ -13705,13 +14002,13 @@ function serialized(reply) {
|
|
|
13705
14002
|
const text = JSON.stringify(reply);
|
|
13706
14003
|
plain = text === void 0 ? void 0 : JSON.parse(text);
|
|
13707
14004
|
} catch (err) {
|
|
13708
|
-
return JSON.stringify(errorReply("EIO",
|
|
14005
|
+
return JSON.stringify(errorReply("EIO", lineOf2(err)));
|
|
13709
14006
|
}
|
|
13710
14007
|
if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
|
|
13711
14008
|
const { v: _, ...rest2 } = plain;
|
|
13712
14009
|
return JSON.stringify({ v: 1, ...rest2 });
|
|
13713
14010
|
}
|
|
13714
|
-
function
|
|
14011
|
+
function lineOf2(err) {
|
|
13715
14012
|
const text = err instanceof Error ? err.message : String(err);
|
|
13716
14013
|
return (text.split("\n")[0] ?? "").slice(0, 200);
|
|
13717
14014
|
}
|
|
@@ -13741,7 +14038,7 @@ var SealedChannel = class {
|
|
|
13741
14038
|
gone.removeEventListener("abort", withdraw);
|
|
13742
14039
|
if (this.inflight !== flight) return;
|
|
13743
14040
|
this.inflight = void 0;
|
|
13744
|
-
if (reply) this.reply =
|
|
14041
|
+
if (reply) this.reply = encoder4.encode(serialized(reply));
|
|
13745
14042
|
});
|
|
13746
14043
|
this.inflight = flight;
|
|
13747
14044
|
return length2;
|
|
@@ -13755,7 +14052,7 @@ var SealedChannel = class {
|
|
|
13755
14052
|
if (signal.aborted) return void 0;
|
|
13756
14053
|
const asked = Promise.resolve().then(() => hook(req, { signal })).then(
|
|
13757
14054
|
(reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
|
|
13758
|
-
(err) => errorReply("EIO",
|
|
14055
|
+
(err) => errorReply("EIO", lineOf2(err))
|
|
13759
14056
|
);
|
|
13760
14057
|
const reply = await Promise.race([asked, stopped]);
|
|
13761
14058
|
if (!signal.aborted) return reply;
|
|
@@ -14478,13 +14775,13 @@ var HostfsClient = class {
|
|
|
14478
14775
|
const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
|
|
14479
14776
|
onOpen();
|
|
14480
14777
|
const reader = response.body.getReader();
|
|
14481
|
-
const
|
|
14778
|
+
const decoder4 = new TextDecoder();
|
|
14482
14779
|
let buffered = "";
|
|
14483
14780
|
try {
|
|
14484
14781
|
for (; ; ) {
|
|
14485
14782
|
const { done: done4, value } = await reader.read();
|
|
14486
14783
|
if (done4) return;
|
|
14487
|
-
buffered +=
|
|
14784
|
+
buffered += decoder4.decode(value, { stream: true });
|
|
14488
14785
|
let end = buffered.indexOf("\n");
|
|
14489
14786
|
while (end >= 0) {
|
|
14490
14787
|
const line = buffered.slice(0, end).trim();
|
|
@@ -15863,7 +16160,7 @@ var LAYOUT = [
|
|
|
15863
16160
|
];
|
|
15864
16161
|
var LOAD_SAMPLE_MS = 5e3;
|
|
15865
16162
|
var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
|
|
15866
|
-
var
|
|
16163
|
+
var encoder5 = new TextEncoder();
|
|
15867
16164
|
var USERS_MODES = ["off", "report", "enforce"];
|
|
15868
16165
|
function usersMode(mode = "off") {
|
|
15869
16166
|
if (USERS_MODES.includes(mode)) return mode;
|
|
@@ -16020,6 +16317,7 @@ var Launcher = class {
|
|
|
16020
16317
|
usersMode;
|
|
16021
16318
|
dac;
|
|
16022
16319
|
sealed;
|
|
16320
|
+
clipboard;
|
|
16023
16321
|
rootFs;
|
|
16024
16322
|
fileWatches;
|
|
16025
16323
|
nextPid = 1e3;
|
|
@@ -16030,6 +16328,12 @@ var Launcher = class {
|
|
|
16030
16328
|
this.rootFs = options.fs;
|
|
16031
16329
|
this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
|
|
16032
16330
|
if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
|
|
16331
|
+
this.clipboard = new ClipboardBroker(
|
|
16332
|
+
options.clipboard,
|
|
16333
|
+
this.usersMode,
|
|
16334
|
+
void 0,
|
|
16335
|
+
(pid) => this.tagOf(pid)
|
|
16336
|
+
);
|
|
16033
16337
|
this.sealed = new SealedBroker(
|
|
16034
16338
|
options.sealed,
|
|
16035
16339
|
this.usersMode,
|
|
@@ -16446,6 +16750,7 @@ var Launcher = class {
|
|
|
16446
16750
|
cred: req.cred,
|
|
16447
16751
|
...this.owners ? { owners: true } : {},
|
|
16448
16752
|
sealed: this.sealed,
|
|
16753
|
+
clipboard: this.clipboard,
|
|
16449
16754
|
...this.dac ? { dac: this.dac } : {},
|
|
16450
16755
|
program: req.program,
|
|
16451
16756
|
argv0: req.argv0,
|
|
@@ -17206,7 +17511,7 @@ var Launcher = class {
|
|
|
17206
17511
|
},
|
|
17207
17512
|
account
|
|
17208
17513
|
);
|
|
17209
|
-
const report = (message3) => options.onData(
|
|
17514
|
+
const report = (message3) => options.onData(encoder5.encode(`${message3}\r
|
|
17210
17515
|
`));
|
|
17211
17516
|
const handle = await this.launch(planned, {
|
|
17212
17517
|
env,
|
|
@@ -17243,7 +17548,7 @@ var Launcher = class {
|
|
|
17243
17548
|
};
|
|
17244
17549
|
const stdout = collect(out, options.onStdout);
|
|
17245
17550
|
const stderr = collect(err, options.onStderr);
|
|
17246
|
-
const report = (message3) => stderr(
|
|
17551
|
+
const report = (message3) => stderr(encoder5.encode(`${message3}
|
|
17247
17552
|
`));
|
|
17248
17553
|
if (!planned) {
|
|
17249
17554
|
report(`${file}: command not found`);
|
|
@@ -17431,13 +17736,14 @@ function usersOp(l, req) {
|
|
|
17431
17736
|
if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
|
|
17432
17737
|
return l.users.list();
|
|
17433
17738
|
}
|
|
17434
|
-
function hostOptions(req, port, sealed) {
|
|
17739
|
+
function hostOptions(req, port, sealed, clipboard) {
|
|
17435
17740
|
return {
|
|
17436
17741
|
...req.hostname !== void 0 ? { hostname: req.hostname } : {},
|
|
17437
17742
|
...req.users !== void 0 ? { users: req.users } : {},
|
|
17438
17743
|
...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
|
|
17439
17744
|
...req.protect ? { protect: protectInit(req.protect) } : {},
|
|
17440
|
-
...req.sealed ? { sealed } : {}
|
|
17745
|
+
...req.sealed ? { sealed } : {},
|
|
17746
|
+
...req.clipboard ? { clipboard } : {}
|
|
17441
17747
|
};
|
|
17442
17748
|
}
|
|
17443
17749
|
function policyAsker(port) {
|
|
@@ -17467,6 +17773,7 @@ function serveKernel(port, deps) {
|
|
|
17467
17773
|
const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
|
|
17468
17774
|
const clients = /* @__PURE__ */ new Set();
|
|
17469
17775
|
const sealed = sealedAsker(port);
|
|
17776
|
+
const clipboard = clipboardAsker(port);
|
|
17470
17777
|
const approvals = sudoApprovals(port);
|
|
17471
17778
|
const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
|
|
17472
17779
|
let holding;
|
|
@@ -17531,7 +17838,7 @@ function serveKernel(port, deps) {
|
|
|
17531
17838
|
...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
|
|
17532
17839
|
...req.modules ? { modules: req.modules } : {},
|
|
17533
17840
|
...req.env ? { env: req.env } : {},
|
|
17534
|
-
...hostOptions(req, port, sealed.hook),
|
|
17841
|
+
...hostOptions(req, port, sealed.hook, clipboard.hook),
|
|
17535
17842
|
...req.approve ? { approve: approvals.ask } : {},
|
|
17536
17843
|
...net,
|
|
17537
17844
|
...media ? { media } : {},
|
|
@@ -17600,6 +17907,8 @@ function serveKernel(port, deps) {
|
|
|
17600
17907
|
port.addEventListener("message", (event) => {
|
|
17601
17908
|
const sealedReply = event.data.sealedReply;
|
|
17602
17909
|
if (sealedReply) return sealed.answer(sealedReply);
|
|
17910
|
+
const clipboardReply = event.data.clipboardReply;
|
|
17911
|
+
if (clipboardReply) return clipboard.answer(clipboardReply);
|
|
17603
17912
|
const grant = event.data.hostfsGrant;
|
|
17604
17913
|
if (grant) return granted2(grant);
|
|
17605
17914
|
if (allowed(event.data)) return;
|