@ai-ecoverse/slicc-kernel 1.160.0 → 1.160.2
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 +4 -3
- package/dist/kernel-worker.js +33 -7
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-driver-worker.js +8 -0
- package/dist/node-driver-worker.js.map +2 -2
- package/dist/node-process-worker.js +51 -22
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +41 -7
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +38 -22
- package/dist/process-worker.js.map +2 -2
- package/dist/types/kernel/protocol.d.ts +1 -0
- package/dist/types/launcher.d.ts +1 -0
- package/dist/types/node.d.ts +5 -0
- package/dist/types/process/js/js-kernel.d.ts +3 -0
- package/dist/types/realm/sync-fs-wire.d.ts +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -359,7 +359,7 @@ A WASI command can name an ES module of its package that provides imports of its
|
|
|
359
359
|
|
|
360
360
|
### JS programs
|
|
361
361
|
|
|
362
|
-
A command can be an ES module instead of a wasm program: `"abi": "js"` and `module` name the module in the package, which must be one self-contained file (no relative or bare imports), and `args`, `argv0` and `env` work as for other commands. An entry with `"abi": "js"` but no `module` (or one outside the package) is not a command, like a wasm entry without its module, so running it fails with 127. Every run gets its own process worker, so each process evaluates the module afresh: module-level state never carries over from one run to the next. The kernel keeps an importable URL per module source (a `blob:` URL in the browser
|
|
362
|
+
A command can be an ES module instead of a wasm program: `"abi": "js"` and `module` name the module in the package, which must be one self-contained file (no relative or bare imports), and `args`, `argv0` and `env` work as for other commands. An entry with `"abi": "js"` but no `module` (or one outside the package) is not a command, like a wasm entry without its module, so running it fails with 127. Every run gets its own process worker, so each process evaluates the module afresh: module-level state never carries over from one run to the next. The kernel keeps an importable URL per module source (a `blob:` URL in the browser; under Node a short `slicc-module:<sha-256>` id that the process worker is handed together with the source, so no base64 is made or decoded, [#1596](https://github.com/ai-ecoverse/slicc-kernel/issues/1596)) for the 16 sources it ran last, so a command run in a loop doesn't encode its module again each time ([#846](https://github.com/ai-ecoverse/slicc-kernel/issues/846)). A changed file is a new source and gets a new URL. An evicted URL is revoked once every run it was handed to has exited, so a run that is still loading its module never loses it.
|
|
363
363
|
|
|
364
364
|
```json
|
|
365
365
|
{ "slicc": { "commands": { "jstool": { "abi": "js", "module": "dist/jstool.mjs" } } } }
|
|
@@ -591,9 +591,9 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
591
591
|
|
|
592
592
|
A program that throws, or leaves an error or a rejected promise unhandled with no listener for it, ends with status 1 and the message on stderr; one whose module cannot be loaded, or exports no function, ends with 126. A write to a pipe whose reader is gone ends the program with 141, unless it ignores or catches `SIGPIPE` (an ignore inherited from its parent counts), in which case the write fails with `EPIPE`. Errors carry a POSIX `code`. Reads and writes of pipes, terminals and sockets go through the kernel's asynchronous operations, so a program blocked reading stdin can still write and handle signals; other calls of one process are made one at a time. A JS program cannot `fork`, and threads it starts itself are not the kernel's.
|
|
593
593
|
|
|
594
|
-
The module has the trust of an Emscripten program's glue or a WASI program's imports module: it comes from a package that was installed, and it runs in the worker of its own process. The kernel withholds the worker's own ways out before it loads the module: `XMLHttpRequest`, `WebSocketStream`, `WebTransport`, `EventSource`, `indexedDB`, `caches`, `cookieStore`, `SharedWorker`, `close`, Chrome's old file system API (`webkitRequestFileSystem`, `webkitRequestFileSystemSync`, `webkitResolveLocalFileSystemURL` and `webkitResolveLocalFileSystemSyncURL`, which reach the origin's OPFS), and `navigator.storage`, `navigator.storageBuckets`, `navigator.locks`, `navigator.serviceWorker`, `navigator.usb`, `navigator.hid` and `navigator.serial` are `undefined`, also through their prototypes, and a worker where one of them cannot be removed refuses to run the program (126). `fetch`, `WebSocket`, `BroadcastChannel`, `importScripts`, `Worker` and `postMessage` are `undefined` too, but stay writable and configurable, so a program can install its own over `ctx.fetch` and `ctx.websocket`, a `BroadcastChannel` within the program (as slicc-jsh does), or the globals napi-rs's wasm32-wasi worker template assigns (`Object.assign(globalThis, { self, require, Worker, importScripts, postMessage })`); the worker's own are gone before the program runs, so deleting the program's brings back `undefined`. `self` stays the global, but as an own writable slot of the scope, so a program can assign its own (the template does) without reaching the worker's. The platform's `BroadcastChannel`, which would reach the origin's other contexts around the kernel, is gone from the scope and its prototypes before the program runs, so deleting the program's own brings back `undefined`, not it; a worker where it can't be removed refuses to run the program (126), as for the others. A program's files are the kernel's, through the context; it reaches the kernel only through its own process's system calls, as the context makes them, and no other process's. This is a guard against programs going around the kernel, not a sandbox: `import()` of a URL is a request the kernel cannot withhold. In a browser, every process worker gets the same lockdown before any program code runs, so an Emscripten program's glue and a WASI program's imports module (any user can write either) see the same globals `undefined`, and a worker where one cannot be removed ends the process with 126. Under the Node entry, every process worker gets the same lockdown before any program code runs: an Emscripten program's glue, a WASI program's imports module and a package driver all see `require`, `process`, `module`, `__dirname` and every Node builtin `undefined`. An Emscripten program's worker is also shaped like a dedicated worker's, as it is in a browser, with `WorkerGlobalScope`, `self` (the global) and `location.href` (the glue's own path in the kernel as a `file:` URL, never the host's install path) as writable slots, so a glue built with `-sENVIRONMENT=web,worker,node` and assertions (`emcc -O0`) reads it as a worker, not a JS shell (#1238); JS and WASI programs don't get them (`require`, `module`, `exports`, `__filename` and `__dirname` writable and configurable, as `process` is, so a program can define its own, never Node's), and `import` of a builtin or a non-`data:` URL fails with `ERR_ACCESS_DENIED
|
|
594
|
+
The module has the trust of an Emscripten program's glue or a WASI program's imports module: it comes from a package that was installed, and it runs in the worker of its own process. The kernel withholds the worker's own ways out before it loads the module: `XMLHttpRequest`, `WebSocketStream`, `WebTransport`, `EventSource`, `indexedDB`, `caches`, `cookieStore`, `SharedWorker`, `close`, Chrome's old file system API (`webkitRequestFileSystem`, `webkitRequestFileSystemSync`, `webkitResolveLocalFileSystemURL` and `webkitResolveLocalFileSystemSyncURL`, which reach the origin's OPFS), and `navigator.storage`, `navigator.storageBuckets`, `navigator.locks`, `navigator.serviceWorker`, `navigator.usb`, `navigator.hid` and `navigator.serial` are `undefined`, also through their prototypes, and a worker where one of them cannot be removed refuses to run the program (126). `fetch`, `WebSocket`, `BroadcastChannel`, `importScripts`, `Worker` and `postMessage` are `undefined` too, but stay writable and configurable, so a program can install its own over `ctx.fetch` and `ctx.websocket`, a `BroadcastChannel` within the program (as slicc-jsh does), or the globals napi-rs's wasm32-wasi worker template assigns (`Object.assign(globalThis, { self, require, Worker, importScripts, postMessage })`); the worker's own are gone before the program runs, so deleting the program's brings back `undefined`. `self` stays the global, but as an own writable slot of the scope, so a program can assign its own (the template does) without reaching the worker's. The platform's `BroadcastChannel`, which would reach the origin's other contexts around the kernel, is gone from the scope and its prototypes before the program runs, so deleting the program's own brings back `undefined`, not it; a worker where it can't be removed refuses to run the program (126), as for the others. A program's files are the kernel's, through the context; it reaches the kernel only through its own process's system calls, as the context makes them, and no other process's. This is a guard against programs going around the kernel, not a sandbox: `import()` of a URL is a request the kernel cannot withhold. In a browser, every process worker gets the same lockdown before any program code runs, so an Emscripten program's glue and a WASI program's imports module (any user can write either) see the same globals `undefined`, and a worker where one cannot be removed ends the process with 126. Under the Node entry, every process worker gets the same lockdown before any program code runs: an Emscripten program's glue, a WASI program's imports module and a package driver all see `require`, `process`, `module`, `__dirname` and every Node builtin `undefined`. An Emscripten program's worker is also shaped like a dedicated worker's, as it is in a browser, with `WorkerGlobalScope`, `self` (the global) and `location.href` (the glue's own path in the kernel as a `file:` URL, never the host's install path) as writable slots, so a glue built with `-sENVIRONMENT=web,worker,node` and assertions (`emcc -O0`) reads it as a worker, not a JS shell (#1238); JS and WASI programs don't get them (`require`, `module`, `exports`, `__filename` and `__dirname` writable and configurable, as `process` is, so a program can define its own, never Node's), and `import` of a builtin or a non-`data:` URL fails with `ERR_ACCESS_DENIED`, as does any `slicc-module:` id other than the one the kernel handed that worker.
|
|
595
595
|
|
|
596
|
-
Under the Node entry, the kernel also withholds the host's Node from the program before its module loads ([#297](https://github.com/ai-ecoverse/slicc-kernel/issues/297)). `localStorage` and `sessionStorage` are `undefined` for good. `require`, `module`, `exports`, `__filename`, `__dirname`, `process`, `global`, `setImmediate` and `clearImmediate` are `undefined` too, but stay writable and configurable, so a program can install its own (as slicc-jsh and napi-rs's wasi worker template do); Node's are gone before the program runs, so deleting the program's brings back `undefined`. `Buffer` stays Node's own, since Node's `Response`, `Request`, `Blob` and stream bodies read it when they run, and a program can replace it. Process workers share Node's memory allocator, so its `allocUnsafe` and `allocUnsafeSlow` zero-fill and its pool is off (`Buffer.poolSize` is 0): a program never sees memory another process freed. A host can also start Node with `--zero-fill-buffers`. Imports of Node's builtin modules (`node:fs`, `fs`, `node:worker_threads`, …) and of anything but a `data:` URL, `file:` included, fail with `ERR_ACCESS_DENIED`, whether static, dynamic or through `import.meta.resolve`. A process worker that exits or crashes on its own ends its process with 70. This is the same guard, not a sandbox: a host that runs code it does not trust, such as an agent's, needs its own isolation around the Node process as well.
|
|
596
|
+
Under the Node entry, the kernel also withholds the host's Node from the program before its module loads ([#297](https://github.com/ai-ecoverse/slicc-kernel/issues/297)). `localStorage` and `sessionStorage` are `undefined` for good. `require`, `module`, `exports`, `__filename`, `__dirname`, `process`, `global`, `setImmediate` and `clearImmediate` are `undefined` too, but stay writable and configurable, so a program can install its own (as slicc-jsh and napi-rs's wasi worker template do); Node's are gone before the program runs, so deleting the program's brings back `undefined`. `Buffer` stays Node's own, since Node's `Response`, `Request`, `Blob` and stream bodies read it when they run, and a program can replace it. Process workers share Node's memory allocator, so its `allocUnsafe` and `allocUnsafeSlow` zero-fill and its pool is off (`Buffer.poolSize` is 0): a program never sees memory another process freed. A host can also start Node with `--zero-fill-buffers`. Imports of Node's builtin modules (`node:fs`, `fs`, `node:worker_threads`, …) and of anything but a `data:` URL or the worker's own `slicc-module:` id, `file:` included, fail with `ERR_ACCESS_DENIED`, whether static, dynamic or through `import.meta.resolve`. A process worker that exits or crashes on its own ends its process with 70. This is the same guard, not a sandbox: a host that runs code it does not trust, such as an agent's, needs its own isolation around the Node process as well.
|
|
597
597
|
|
|
598
598
|
## Network
|
|
599
599
|
|
|
@@ -1156,6 +1156,7 @@ printf '{"kind":"open.url","url":"%s"}\n' "$url" | cat > /dev/embedder
|
|
|
1156
1156
|
|
|
1157
1157
|
- **The request:** the first line is a JSON object with a `kind` (`a.b-c`: lower-case words with dots, at most 64 bytes) and options (every other key), at most 64 KiB. Everything written after it is the body, streamed to the page as it is written: a write waits while 1 MiB of it is unread by the page. A bad header fails that `write()` with `EINVAL`, and the description stays failed. A page that answers with bytes, a string or nothing, or cancels the body, before it has read all of it gets no more: the rest the program writes is dropped ([#1536](https://github.com/ai-ecoverse/slicc-kernel/issues/1536)), so a writer never waits on a body nobody reads. A page that answers with a stream keeps the body until that stream ends, so it must read or cancel the body: until it does, a writer past the 1 MiB window waits. The request goes to the page when its header line ends.
|
|
1158
1158
|
- **The reply:** reads return the page's reply in order, pulled as the program reads, and its end is end-of-file. Opened `O_RDWR`, the first `read()` ends the body. Opened `O_WRONLY` (nobody reads), the last close ends the body and waits for the page to finish, so `close()` returns its error (an Emscripten program's standard output follows `/dev/clipboard`'s exit rule, so `printf … | cat > /dev/embedder` fails with `cat: write error: …`). There is no timeout: a reply may wait for a prompt or a model download; run `timeout` for one.
|
|
1159
|
+
- **Who waits:** the process that does the last close waits for the reply, and only a signal to that process interrupts it. So put the open inside the command that `timeout` bounds: `timeout 12 sh -c 'cat request > /dev/embedder'`. In `timeout 12 cat request > /dev/embedder` the redirection is `timeout`'s own. `cat` writes the header and exits at once, so the timer never fires, and then `timeout` waits in its own last close, on its way out (coreutils closes its standard output), until the page answers.
|
|
1159
1160
|
- **Cancel:** a signal during a blocked read, write or `O_WRONLY` close fails it with `EINTR`, and the page's `signal` aborts with reason `interrupted`. Closing the description before the end of the reply aborts it with reason `closed`, and that is no error: it is how `hear` stops recording. A process that ends during a request aborts it too.
|
|
1160
1161
|
- **Errors:** the hook's error gives the errno: `NotAllowedError` or `AbortError` (a dismissed prompt, a refused permission) `EACCES`; `NotSupportedError` `ENOTSUP`; `NotFoundError` `ENOENT`; anything else `EIO`. An Emscripten or WASI program first gets `<argv0>: /dev/embedder: <first line of the message>` on standard error, as for `/dev/clipboard`; a JS program has it in the error. `request.progress(line)` from the page writes `<argv0>: <line>` to standard error the same way, at most 4 lines a second.
|
|
1161
1162
|
- **Who answers:** the hook of the client the process's session was started through, inherited by its children, forks and `exec`s: `createKernel({ requests })` (and `createNodeKernel`) for the page's own `run` and `openTerminal`, the hook a port was made with (`connect({ requests })`), or the hook its attached client brought (`connect({ requests: 'attach' })`, then `attachKernel(port, { requests })`). Nothing falls back to another client's hook, so a request from another tab's terminal never opens in this one. With no hook, or a kind the hook doesn't list, the request is `ENOTSUP` before the page sees it. A program has no way to register, replace or see a hook.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -8236,7 +8236,7 @@ function satisfies(version, range2) {
|
|
|
8236
8236
|
}
|
|
8237
8237
|
|
|
8238
8238
|
// src/version.ts
|
|
8239
|
-
var KERNEL_VERSION = true ? "1.160.
|
|
8239
|
+
var KERNEL_VERSION = true ? "1.160.2" : "0.0.0-dev";
|
|
8240
8240
|
var OS_RELEASE = "6.0.0-slicc";
|
|
8241
8241
|
function procVersion() {
|
|
8242
8242
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -14932,7 +14932,8 @@ async function dispatchSyncFs(req) {
|
|
|
14932
14932
|
const entry = resolveSyncFsToken(req.token);
|
|
14933
14933
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
14934
14934
|
if (req.identity && req.op === "create") return created2(entry, req);
|
|
14935
|
-
if (!req.identity || !IDENTITY_OPS.has(req.op))
|
|
14935
|
+
if (!req.identity || !IDENTITY_OPS.has(req.op))
|
|
14936
|
+
return devsOf(entry, req, await dispatchOne(entry, req));
|
|
14936
14937
|
const { fs, cwd } = entry;
|
|
14937
14938
|
const path = fs.resolvePath(cwd, req.path);
|
|
14938
14939
|
const identified = async (held) => {
|
|
@@ -14950,6 +14951,30 @@ async function dispatchSyncFs(req) {
|
|
|
14950
14951
|
return toErrno(err);
|
|
14951
14952
|
}
|
|
14952
14953
|
}
|
|
14954
|
+
var DEV_PROBES = /* @__PURE__ */ new Set(["stat", "lstat", "read", "exists"]);
|
|
14955
|
+
async function devsOf(entry, req, result) {
|
|
14956
|
+
if (req.devs !== true || !DEV_PROBES.has(req.op)) return result;
|
|
14957
|
+
const absent2 = result.ok ? result.kind === "json" && result.json === false : result.errno === "ENOENT";
|
|
14958
|
+
if (!absent2) return result;
|
|
14959
|
+
const dev = await devTarget(entry, req, entry.fs.resolvePath(entry.cwd, req.path));
|
|
14960
|
+
if (dev === void 0) return result;
|
|
14961
|
+
return result.ok ? json2({ dev }) : { ...result, dev };
|
|
14962
|
+
}
|
|
14963
|
+
async function devTarget(entry, req, path) {
|
|
14964
|
+
const look = async (op, at) => {
|
|
14965
|
+
const r = await dispatchPath(entry, { token: req.token, op, path: at });
|
|
14966
|
+
if (!r.ok) throw new Error(r.errno);
|
|
14967
|
+
return r.json;
|
|
14968
|
+
};
|
|
14969
|
+
const resolved2 = await physicalPathAsync(
|
|
14970
|
+
{
|
|
14971
|
+
lstat: async (p) => await look("lstat", p),
|
|
14972
|
+
readlink: async (p) => String(await look("readlink", p))
|
|
14973
|
+
},
|
|
14974
|
+
path
|
|
14975
|
+
);
|
|
14976
|
+
return resolved2 !== path && (resolved2 === "/dev" || resolved2.startsWith("/dev/")) ? resolved2 : void 0;
|
|
14977
|
+
}
|
|
14953
14978
|
async function created2(entry, req) {
|
|
14954
14979
|
const result = await dispatchOne(entry, req);
|
|
14955
14980
|
if (!result.ok) return result;
|
|
@@ -15136,7 +15161,8 @@ function encodeSabResult(result) {
|
|
|
15136
15161
|
JSON.stringify({
|
|
15137
15162
|
errno: result.errno,
|
|
15138
15163
|
message: result.message,
|
|
15139
|
-
...result.direct ? { direct: true } : {}
|
|
15164
|
+
...result.direct ? { direct: true } : {},
|
|
15165
|
+
...result.dev !== void 0 ? { dev: result.dev } : {}
|
|
15140
15166
|
})
|
|
15141
15167
|
)
|
|
15142
15168
|
};
|
|
@@ -31796,7 +31822,7 @@ var Launcher = class {
|
|
|
31796
31822
|
this.moduleUrls.delete(source);
|
|
31797
31823
|
this.moduleUrls.set(source, known);
|
|
31798
31824
|
known.users++;
|
|
31799
|
-
return known.made
|
|
31825
|
+
return known.made;
|
|
31800
31826
|
}
|
|
31801
31827
|
const held = {
|
|
31802
31828
|
made: (this.moduleUrl ?? dataModuleUrl)(source),
|
|
@@ -31811,7 +31837,7 @@ var Launcher = class {
|
|
|
31811
31837
|
entry.evicted = true;
|
|
31812
31838
|
this.releaseUrl(entry);
|
|
31813
31839
|
}
|
|
31814
|
-
return held.made
|
|
31840
|
+
return held.made;
|
|
31815
31841
|
}
|
|
31816
31842
|
heldUrl(program) {
|
|
31817
31843
|
const held = program.abi === "js" && program.url ? this.urlsInUse.get(program.url) : void 0;
|
|
@@ -31892,8 +31918,8 @@ var Launcher = class {
|
|
|
31892
31918
|
}
|
|
31893
31919
|
const { asked } = target;
|
|
31894
31920
|
if (target.abi === "js") {
|
|
31895
|
-
const url = this.urlFor(await this.text(target.wasm, caller, asked));
|
|
31896
|
-
return { abi: "js", glue: "", url, path: target.wasm };
|
|
31921
|
+
const { url, source } = this.urlFor(await this.text(target.wasm, caller, asked));
|
|
31922
|
+
return { abi: "js", glue: "", url, path: target.wasm, ...source ? { source } : {} };
|
|
31897
31923
|
}
|
|
31898
31924
|
if (target.abi !== "wasi") {
|
|
31899
31925
|
const [glue, { module: module2 }] = await Promise.all([
|