@ai-ecoverse/slicc-kernel 1.26.0 → 1.26.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 CHANGED
@@ -144,7 +144,7 @@ A WASI program runs in a process worker of its own like an Emscripten one, again
144
144
 
145
145
  - **Threads**: `wasi.thread-spawn` (wasm32-wasip1-threads) and WASIX `thread_spawn_v2` start a worker per thread on the process's shared memory, sized from the module's own import (a 2 GiB maximum when the engine cannot reserve more). The threads share one descriptor table; `exit` or a trap in any thread ends the process. At most 64 threads per process; `SLICC_WASM_THREADS` sets another cap, up to 256.
146
146
  - **WASIX** (a module importing `wasix_32v1`): fork and setjmp/longjmp through Asyncify (the module is built with `wasm-opt --asyncify`), exec, `posix_spawn`, `waitpid`, pipes, `dup2`, signal handlers and interval timers, terminal modes, and dynamic linking: a position-independent main module loads side modules with `dlopen` from `LD_LIBRARY_PATH`, its runtime path, `/lib`, `/usr/lib` and `/usr/local/lib`.
147
- - **Sockets**: the WASIX socket calls (`sock_open`, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers loopback names and literal addresses.
147
+ - **Sockets**: the WASIX socket calls (`sock_open`, `bind`, `listen`, `connect`, `accept`, options, local and peer names) are the kernel's loopback sockets, and preview1's `sock_recv` / `sock_send` / `sock_shutdown` work on them and on inherited ones. Writing to a socket whose peer is gone fails with `EPIPE`, through `fd_write` as through `sock_send`, as if `MSG_NOSIGNAL` were set, since a WASI program cannot ignore `SIGPIPE`; a pipe or terminal whose reader is gone still ends the program with status 141. HTTP and HTTPS leave through the realm proxy that `https_proxy` names, as for any other program; `resolve` answers loopback names and literal addresses.
148
148
  - **Diagnostics**: a trap ends the program with 134 and its message on stderr; with `SLICC_WASM_BACKTRACE=1` the wasm frames follow, named from the module's name section, or from a sidecar for a module shipped without one (`<module>.names` beside it, or the same path in a `<package>-names` package). `SLICC_WASI_STATS=1` makes a program print its calls, counted and timed, as it ends.
149
149
 
150
150
  ### Program imports
@@ -184,7 +184,7 @@ await kernel.run(['curl', '-sS', 'https://registry.npmjs.org/@ai-ecoverse/wasm-b
184
184
  Programs drive a browser with the Chrome DevTools Protocol over a WebSocket, whichever host actually has the browser: slicc-extension's `chrome.debugger`, slicc-node's or slicc-swift's CDP proxy, or a test harness. The kernel relays; it does not launch a browser.
185
185
 
186
186
  - **Programs** find the endpoint in `SLICC_CDP_URL`, `ws://127.0.0.1:9222/devtools/browser/<id>`, where `<id>` is random for each kernel boot. `http://127.0.0.1:9222/json/version` answers `{ "Browser", "Protocol-Version", "webSocketDebuggerUrl" }` with the same URL (carrying the request's query), and `/json/list` (or `/json`) lists the host's targets, without per-target sockets. Every other path is `404`. The listener starts on the first connection, on the kernel's loopback; a program that binds `9222` first keeps it.
187
- - **Sessions** are Chrome's flattened ones: `Target.attachToTarget` with `flatten: true`, then commands with a top-level `sessionId`. Messages pass unchanged, as text frames up to 256 MiB (a larger one closes the socket with `1009`). Each program WebSocket gets a host connection of its own: closing the socket (or exiting) closes it, which detaches its sessions, and a host that closes is passed on as a close frame (`1011` with its reason). `terminate()` closes them all.
187
+ - **Sessions** are Chrome's flattened ones: `Target.attachToTarget` with `flatten: true`, then commands with a top-level `sessionId`. Messages pass unchanged, as text frames up to 256 MiB (a larger one closes the socket with `1009`). Each program WebSocket gets a host connection of its own: closing the socket (or exiting) closes it, which detaches its sessions, and a host that closes is passed on as a close frame (`1011` with its reason). The kernel then waits up to a second for the program's close frame before it hangs up, as RFC 6455 has it, so a program that answers the close can still report the code. `terminate()` closes them all.
188
188
  - **Runtime**: a program adds `runtime=<name>` to the query of the URL it opens, and the kernel gives it to the host; the kernel does not interpret it.
189
189
  - **No host**: `/json/*` and the WebSocket handshake answer `503` with `slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)`, and a host that refuses (an unknown runtime, say) gives `502` with its message.
190
190
  - **Kernel-only**: port `9222` takes connections only from programs in the kernel. `kernel.dial`, a client's `dial` and `loopbackFetch` (and so `9222.kernel.localhost` and slicc-node's tunnel) are refused with `ECONNREFUSED`, since the facade drives the user's real browser.
@@ -3749,6 +3749,7 @@ Sec-WebSocket-Accept: ${accept2}\r
3749
3749
  // src/cdp/facade.ts
3750
3750
  var MAX_HEAD = 64 * 1024;
3751
3751
  var LIST_TIMEOUT = 1e4;
3752
+ var CLOSE_WAIT_MS = 1e3;
3752
3753
  function respond(status, body, type = "text/plain; charset=utf-8") {
3753
3754
  const bytes = new TextEncoder().encode(body);
3754
3755
  const head = latin1Bytes(
@@ -3894,7 +3895,11 @@ var CdpFacade = class {
3894
3895
  async upgrade(conn, incoming, req, runtime) {
3895
3896
  const accept2 = await acceptKey(req.headers);
3896
3897
  const host = await this.host(runtime);
3897
- await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
3898
+ const limits = {
3899
+ max: this.options.maxMessage ?? MAX_MESSAGE,
3900
+ closeWaitMs: this.options.closeWaitMs ?? CLOSE_WAIT_MS
3901
+ };
3902
+ await new Relay(conn, incoming, host, limits, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
3898
3903
  }
3899
3904
  };
3900
3905
  var Relay = class {
@@ -3904,12 +3909,14 @@ var Relay = class {
3904
3909
  incoming;
3905
3910
  host;
3906
3911
  max;
3912
+ closeWaitMs;
3907
3913
  stop;
3908
- constructor(conn, incoming, host, max, stop) {
3914
+ constructor(conn, incoming, host, limits, stop) {
3909
3915
  this.conn = conn;
3910
3916
  this.incoming = incoming;
3911
3917
  this.host = host;
3912
- this.max = max;
3918
+ this.max = limits.max;
3919
+ this.closeWaitMs = limits.closeWaitMs;
3913
3920
  this.stop = stop;
3914
3921
  }
3915
3922
  send(bytes) {
@@ -3935,8 +3942,15 @@ var Relay = class {
3935
3942
  void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
3936
3943
  else void this.send(frame(OP_TEXT, bytes));
3937
3944
  };
3938
- await Promise.race([this.pump(), hostGone]);
3945
+ const pumping = this.pump();
3946
+ await Promise.race([pumping, hostGone]);
3939
3947
  await this.writes;
3948
+ let timer;
3949
+ const waited = new Promise((resolve) => {
3950
+ timer = setTimeout(resolve, this.closeWaitMs);
3951
+ });
3952
+ await Promise.race([pumping, waited]);
3953
+ clearTimeout(timer);
3940
3954
  }
3941
3955
  async pump() {
3942
3956
  const reader = new WsReader(this.incoming, this.max);