@ai-ecoverse/slicc-bios 1.51.3 → 1.51.5

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
@@ -105,7 +105,7 @@ A kernel process can still write to `/os/` and `/node_modules/` (any process whi
105
105
  `os/` is `<slicc-app>` from `@ai-ecoverse/slicc-spectrum/ui` on `createKernelModel` from `@ai-ecoverse/slicc-spectrum/kernel`, with the agent spread over it once it's up. The page offers six sections:
106
106
 
107
107
  - **Agents:** the rail of cones and their scoops, with each one's status (spectrum ≥ 1.23.0). Selecting a scoop opens its chat, and **Drop** stops it after a confirm. The header picker lists the cones and creates new ones with **New cone**. A cone starts scoops with the `agent` and `subagent` commands that slicc-agent installs in `/usr/local/bin`, mode 755, on every user's `PATH` (slicc-agent ≥ 3.35.0; it removes the copies earlier versions put in `$PNPM_HOME/bin` once the new ones are in place): `agent --prompt …` runs a scoop until it answers, and `agent --async …` starts one that reports back to its cone as a lick.
108
- - **Chat:** SLICC's agent, a chat per cone or scoop, once it's installed (see [Agent](#agent)). Its tab joins the terminal's group without taking focus. The model is AWS Bedrock (`us.anthropic.claude-sonnet-5-5` in `us-west-2`) with a Bedrock API key entered under **Settings → Accounts**. The key stays in the agent worker's encrypted credential store. Bedrock sends no CORS headers, so chat needs a local proxy or the extension (see [Network](#network)).
108
+ - **Chat:** SLICC's agent, a chat per cone or scoop, once it's installed (see [Agent](#agent)). Its tab joins the terminal's group without taking focus. The model is AWS Bedrock (`us.anthropic.claude-sonnet-5-5` in `us-west-2`) with a Bedrock API key entered under **Settings → Accounts**. The key stays in the agent worker's encrypted credential store. Bedrock sends no CORS headers, so chat needs slicc-extension or a linked `slicc` CLI as the exit (see [Network](#network)).
109
109
  - **Terminal:** `bash -i` sessions on the kernel, starting in `/home`, as many as you open with **+**.
110
110
  - **Files:** the OPFS root as a tree. `/.slicc` (the agent's own state, mode 0700 from slicc-agent 3.35.0) and `/.slicc-unlinked` (where the kernel keeps a deleted file that a program still has open, slicc-kernel ≥ 1.37.1) are hidden (`hide`, spectrum ≥ 1.9.0); `/node_modules`, `/opt/agent/node_modules`, pnpm's store and `/home/.cache` are listed but not scanned. The tree follows what the terminal writes: a rescan every 2 s, and on `FileSystemObserver` records. From spectrum 1.85.1, records only under hidden or unscanned trees cause no rescan, and the rest cause at most one every 2 s, so an agent install into `/opt/agent/node_modules` no longer rescans the whole tree per record. Files open in tabs, and **Edit** turns a tab into a text area that **Save** or `Mod+S` writes back to OPFS. Mounted folders show in the tree too (see [Folder mounts](#folder-mounts)).
111
111
  - **Memory:** what the agent remembers (slicc-agent ≥ 3.14.0, spectrum ≥ 1.28.0). Each scope is one `MEMORY.md`: `/home/.pi/agent/memory/` for everyone and one per cone, `/home/.pi/agent/agent-memory/<path>/` for roles. An entry is a `###` title under a `##` section, with an optional `tag:` line before its body. The agent writes them with `memory_write`, the panel's **Save** and **Forget** write the same files, and a file edited by hand shows up in the panel.
@@ -115,12 +115,12 @@ A kernel process can still write to `/os/` and `/node_modules/` (any process whi
115
115
  - **Keeping seven's files:** at the first chat send in a page session, `os/storage.js` calls `navigator.storage.persist()`, unless storage is already persisted. If Chrome declines, every chat panel shows a warning notice (`model.notices`, spectrum ≥ 1.43.0, from `os/notices.js`): *Seven’s files may be cleared*, saying Chrome may clear seven's files and chats when disk space runs low. Chrome grants persistence by engagement, not by a setting, and installing seven as an app is the reliable way. So when Chrome offers to install (`beforeinstallprompt`), the notice says to install seven as an app and offers **Install**, which opens Chrome's install prompt. Otherwise it says to bookmark seven. **Try again** asks again. After `appinstalled`, seven asks by itself and drops the notice once storage is kept. Dismissing the notice is remembered in `localStorage`. The folder-mount request (see [Folder mounts](#folder-mounts)) stays as it is.
116
116
  - **Installing as an app:** [`src/manifest.json`](src/manifest.json) makes seven installable. Its name is *SLICC seven* and its launcher label *seven*. It opens standalone, its `id` and `scope` are `/` and its `start_url` is the BIOS's folder (`./`), and its icons in `src/icons/` come as `any` and `maskable`, relative to the manifest. The UI links `manifest.json`, which the service worker redirects to the BIOS's. The app starts at the BIOS, which checks the packages and goes on to `os/` as in a tab, so the service worker, COOP/COEP and the boot path are the same. Both pages link the manifest and set `theme-color` (`#f8f8f8` light, `#1b1b1b` dark); `os.css` gives `html` the same background, so nothing flashes before the UI paints. Each `<branch>.sliccy.ai` is its own origin, so a preview installs as an app of its own.
117
117
  - **Adobe sign-in:** **Connect** on the Adobe account signs in to Adobe IMS. IMS only redirects to `www.sliccy.ai/auth/callback`, and seven is cross-origin isolated, so the popup has no opener to report back to. Which path seven uses depends on where it runs:
118
- - **On `https://<label>.sliccy.ai`:** seven asks the relay for its own origin (state `{ source: 'origin', origin, nonce }`). The relay navigates the popup to `<origin>/auth/callback`. The service worker serves that page from `os/callback.html`. Its first, inline script keeps the address in memory and replaces it with the bare path before anything loads, and the page sends no referrer. It then hands the nonce and the grant's fields (`access_token`, `expires_in`, `error`, `error_description`) to the tab over the same-origin `BroadcastChannel('slicc-sign-in')`, and closes. Neither slicc-node nor the extension is needed.
118
+ - **On `https://<label>.sliccy.ai`:** seven asks the relay for its own origin (state `{ source: 'origin', origin, nonce }`). The relay navigates the popup to `<origin>/auth/callback`. The service worker serves that page from `os/callback.html`. Its first, inline script keeps the address in memory and replaces it with the bare path before anything loads, and the page sends no referrer. It then hands the nonce and the grant's fields (`access_token`, `expires_in`, `error`, `error_description`) to the tab over the same-origin `BroadcastChannel('slicc-sign-in')`, and closes. The extension isn't needed.
119
119
  - **On `https://www.sliccy.ai`,** where seven shares the origin with the relay page (see [Next to SLICC 6](#next-to-slicc-6-wwwsliccyai7)): seven asks the relay for its `opener` delivery (state `{ source: 'opener', nonce }`). The relay page then posts the redirect to every tab of the origin over `BroadcastChannel('slicc-oauth-relay')`, which seven listens on.
120
120
  - **Elsewhere, with slicc-extension installed:** the extension signs in with `chrome.identity`.
121
- - **Elsewhere, through slicc-node:** seven registers a nonce with `POST /api/oauth-state`, sends IMS to the relay's `local` source, and polls `GET /api/oauth-result` once a second.
121
+ - **Elsewhere, without slicc-extension:** seven can't sign in, and says to install slicc-extension or open seven on its `sliccy.ai` address.
122
122
 
123
- Setting `localStorage['slicc-os.sign-in'] = 'relay'` forces the first path on a dev origin; the chat test does that. Seven checks the nonce on every path. While it waits, the Adobe row in Settings › Accounts reads *Signing in…* with **Cancel** (slicc-agent ≥ 3.19.0, spectrum ≥ 1.34.0). The agent calls `login(providerId, options, { signal })`, and Cancel aborts the signal. Cancel, or the 10-minute timeout, stops the wait. On the slicc-node path, Cancel and a `404` also drop the nonce. The token goes to the agent worker's credential store, and the header shows the weekly budget as *Budget N%* (`#usage`), a plain status while seven offers no Monitor panel.
123
+ Setting `localStorage['slicc-os.sign-in'] = 'relay'` forces the first path on a dev origin; the chat test does that. Seven checks the nonce on every path. While it waits, the Adobe row in Settings › Accounts reads *Signing in…* with **Cancel** (slicc-agent ≥ 3.19.0, spectrum ≥ 1.34.0). The agent calls `login(providerId, options, { signal })`, and Cancel aborts the signal. Cancel, or the 10-minute timeout, stops the wait. The token goes to the agent worker's credential store, and the header shows the weekly budget as *Budget N%* (`#usage`), a plain status while seven offers no Monitor panel.
124
124
 
125
125
  Each screen class has its own layout, kept in `localStorage` (`slicc-os.layout.<screen>`). On desktop and tablet chat and the terminal share the middle, the agents and files are on the left, and file tabs open between them. On a phone it's chat and the terminal, with chat, the agents and files in the bottom rail. Every panel keeps its rail icon while it's open; while it's visible the icon shows selected (`.selected`, `data-visible`, labelled "{Title}, shown", spectrum ≥ 1.53.0), and a click brings it to the front. The header's tray connection (`#chip`) opens Network at its **Sync** section: the tray's state, the float's name, the join link with **Copy**, the followers, and **Disconnect** (after a confirm) or **Reconnect**. The agent's tray port is a placeholder today (offline, no join link), so the section reads offline; seven's own tray leader shows under Linked devices (see [Network](#network)). Memory opens from the View menu. Changes, the browser and the monitor come back as their backends land. Adobe Clean comes from `/fonts/`, which the sliccy-ai worker serves on every host.
126
126
 
@@ -155,7 +155,7 @@ A local folder can be mounted into seven as on Linux ([#73](https://github.com/a
155
155
  - **From the Files panel** (spectrum ≥ 1.27.0), **Mount a folder…** picks a folder and mounts it at `/mnt/<name>`, then selects it in the tree. A **Mounted** strip lists every folder mount, shell mounts included, each with **Eject**. The files port behind them has `mountFolder(path?)`, `mounts()`, `needsFolder()` and `eject(path)`. `needsFolder()` lists the mount points waiting for a folder, and `mountFolder(path)` inserts one there. The port emits `mounts` when either list changes, then `files`, then `file` for each path that went away, so a tab open on an ejected file notices.
156
156
  - **Remembered:** `hostfs` mounts, and `fsa` mounts where the profile allows it (see below), are kept in `localStorage` (`slicc-os.mounts`) and mounted again on the next load. The kernel keeps each folder's handle in IndexedDB, so a folder whose permission still holds comes back at once, and one that needs permission again waits in the Files panel, where **Insert folder…** asks for it.
157
157
  - **Off the record, failing safe:** Chrome 153 crashes the browser when a folder handle goes through IndexedDB in an off-the-record profile (Incognito, or a CDP browser context such as the test harness's), as soon as an `fsa` mount touches the kernel's handle store. The page remembers folders only with positive evidence that the profile isn't off the record: `navigator.storage.persisted()` is granted and the storage quota is at least twice the JavaScript heap limit. Otherwise it gives the kernel `media: false` (slicc-kernel ≥ 1.18.0): folder handles stay in memory and the kernel never opens its `:media` database, while POSIX metadata and the kernel's TLS CA still persist, shared with seven's other kernels. It also neither remembers nor remounts `fsa` mounts then. The kernels seven starts to install the agent never mount folders, so they always get `media: false`. In that session mode a folder is asked for again after a reload. Chrome grants persistent storage by engagement, so ordinary profiles can start in session mode too: **Insert folder…** also calls `navigator.storage.persist()`, and when that is granted and the quota check passes, the session switches to remembered. Folders inserted before the switch are asked for once more after the next reload, after which their handles are kept. `document.documentElement.dataset.folders` says which mode is on: `remembered` or `session`.
158
- - **Host folders:** with a local proxy that exports folders (`npx @ai-ecoverse/slicc-node --mount <path>[:<name>][:ro]`, or slicc-swift), `mount -t hostfs <name> /mnt/h` mounts one with no picker. The page asks the proxy for a token scoped to that folder (`POST /api/hostfs/grant` with the proxy key); the kernel gets only the token, and neither the key nor the token shows up in `/proc/mounts`. A name that is neither exported by the proxy (`POST /api/hostfs/mounts`) nor a linked CLI's folder (`<device>/<name>`) is refused at once (exit 32, permission denied), with or without a proxy.
158
+ - **Host folders:** `mount -t hostfs <device>/<name> /mnt/h` mounts a folder a linked CLI shares (see [Linked devices](#tailscale-proof-of-concept)) with no picker. A name that isn't a linked CLI's folder is refused at once (exit 32, permission denied).
159
159
  - **Deleted but open files:** the kernel keeps a file that a program still has open after it's deleted in `.slicc-unlinked/` at the mount's root until it's closed (slicc-kernel ≥ 1.37.1). In a mounted folder that directory is visible on the host, so a repository there may want it in `.gitignore`.
160
160
  - **Owners** (slicc-kernel ≥ 1.61.0): every mount bios makes, a folder picked in the Files panel and each remembered mount it mounts again, gets its owner as `uid=` and `gid=` from one function, `owned()` in `os/mounts.js`. While the kernel's users are off, that's root (`uid=0,gid=0`), and `/proc/mounts` shows it. When agents run as their own users, it gives the user's ids, so their programs see the folder as their own.
161
161
  - **No modes or symlinks** in an `fsa` folder: the File System Access API has neither, so `chmod` there succeeds and changes nothing (`stat` keeps `644`), and symlinks can't be made there.
@@ -181,7 +181,7 @@ Several tabs of one origin share one kernel and one agent (spectrum ≥ 1.46.0 f
181
181
 
182
182
  **The owner** is the tab holding the Web Lock `slicc-agent`. `os.js` asks for it with `ifAvailable` before it starts a kernel. The owner does everything a single tab did before, and only the owner does it:
183
183
  - it runs the kernel and the agent worker (`startAgent({ held: true })`);
184
- - it handles folder mounts, Tailscale, the tunnel, and the installs and update checks.
184
+ - it handles folder mounts, Tailscale, and the installs and update checks.
185
185
 
186
186
  **Every other tab is a follower.** It starts no kernel. It shows the full UI:
187
187
  - the chat, through its own pi-protocol connection to the owner's agent worker;
@@ -378,18 +378,19 @@ Programs ask seven's page for what only a page can do through the kernel's `/dev
378
378
 
379
379
  Before the kernel starts, `os/transport.js` picks how programs reach the network. The first of these that applies wins:
380
380
 
381
- 1. **A local proxy:** [slicc-node](https://github.com/ai-ecoverse/slicc-node) (`npx @ai-ecoverse/slicc-node`) or slicc-swift. The launcher opens `https://seven.sliccy.ai/#proxy=<url>&key=<key>`. A small script at the top of the page takes both out of the address before anything else loads. It keeps them in local storage under a fresh id and leaves only `#handoff=<id>` in the address, and the boot reads them from there (the same goes for `#tailscale=`, its `exit=` and `#slicc-pair=`). Once seven has read them, and also when the boot fails, the entry and the id are removed, together with any entry older than 10 minutes. If they can't be stored, the address is still tidied and the values are dropped. It accepts only a plain `http://` origin on `127.0.0.1`, `localhost` or `[::1]`, so a link can't send traffic elsewhere. It keeps them in IndexedDB (`slicc-os`, store `transport`), so a reload keeps using the proxy. Each load checks the proxy with `checkLocalProxy`. If the proxy refuses the key (every proxy process mints a new one), isn't a proxy, or doesn't answer although the page may reach loopback, the page forgets it and falls through. It keeps the proxy, and still falls through, when Chrome's Local Network Access permission is denied, or when nothing answered while the permission is undecided (Chrome asks only once it has a connection, so a dismissed prompt and a stopped proxy look the same).
382
- 2. **[slicc-extension](https://github.com/ai-ecoverse/slicc-extension):** when the extension defines `globalThis.sliccExtension`, the kernel uses `fetchTransport({ fetch: sliccExtension.fetch })`.
383
- 3. **The page's own `fetch`:** `fetchTransport()`, bound by CORS. A request CORS stops is answered `502` with a hint to run `npx sliccy@next` (the slicc CLI, until `sliccy`'s `latest` is the new CLI) or install slicc-extension, which `curl` prints.
381
+ Seven reads no local proxy: a `#proxy=` or `#key=` in the address is taken out before anything loads and never used or stored, and a proxy an older seven kept in IndexedDB (`slicc-os`, store `transport`) is deleted. A small script at the top of the page also takes `#tailscale=`, its `exit=` and `#slicc-pair=` out of the address before anything else loads. It keeps them in local storage under a fresh id and leaves only `#handoff=<id>` in the address, and the boot reads them from there. Once seven has read them, and also when the boot fails, the entry and the id are removed, together with any entry older than 10 minutes. If they can't be stored, the address is still tidied and the values are dropped.
384
382
 
385
- A response body that sends nothing for 120 s on the extension or the page fetch fails with `ETIMEDOUT`, so a stalled download doesn't hang. ntfy (ntfy.sh and the tray's ntfy gateway) sends a keepalive every 45 s, so after one missed keepalive the next byte comes at 90 s plus the network's latency; 120 s leaves 30 s of slack beyond that, so a quiet long-lived stream (`ntfy sub`, server-sent events, a long poll) survives it. The bound isn't lifted, because a body that Chrome stops handing over under load must still end. The installer's own idle detector, which re-fetches a stalled tarball, stays at 30 s. On the local proxy that window is 5 minutes, the kernel's default, because a server-sent stream there, such as an MCP tool call working without progress, may stay quiet for a long time.
383
+ 1. **[slicc-extension](https://github.com/ai-ecoverse/slicc-extension):** when the extension defines `globalThis.sliccExtension`, the kernel uses `fetchTransport({ fetch: sliccExtension.fetch })`, with `traits.crossOrigin: 'any'`: the extension's fetch isn't bound by CORS, so the agent offers the providers that need a CORS-free transport (Kimi, Cerebras) there too.
384
+ 2. **The page's own `fetch`:** `fetchTransport()`, bound by CORS. A request CORS stops is answered `502` with a hint to run `npx sliccy@next` (the slicc CLI, until `sliccy`'s `latest` is the new CLI) or install slicc-extension, which `curl` prints.
386
385
 
387
- The choice is in `document.documentElement.dataset.transport`: `local-proxy`, `extension` or `page`. `os/network.js` is spectrum's `NetworkPort` (`model.network`, spectrum ≥ 1.32.0), behind the header's **Network** indicator and panel:
386
+ A response body that sends nothing for 120 s on the extension or the page fetch fails with `ETIMEDOUT`, so a stalled download doesn't hang. ntfy (ntfy.sh and the tray's ntfy gateway) sends a keepalive every 45 s, so after one missed keepalive the next byte comes at 90 s plus the network's latency; 120 s leaves 30 s of slack beyond that, so a quiet long-lived stream (`ntfy sub`, server-sent events, a long poll) survives it. The bound isn't lifted, because a body that Chrome stops handing over under load must still end. The installer's own idle detector, which re-fetches a stalled tarball, stays at 30 s.
388
387
 
389
- - **Route and health:** `proxy` or `extension` is green (`ok`) and turns red (`failing`) when its last request failed, until one gets through. `page` is yellow (`limited`).
390
- - **Detail:** one line, next to spectrum's own description of the route. It names the local proxy. When a local proxy was asked for but isn't used, it says why (blocked by the permission, not answering, or refusing the key) and what the page uses instead. When the route stops answering, it says so and what to do. It is empty on the extension and the page fetch while there is nothing to add.
388
+ The choice is in `document.documentElement.dataset.transport`: `extension` or `page`. `os/network.js` is spectrum's `NetworkPort` (`model.network`, spectrum ≥ 1.32.0), behind the header's **Network** indicator and panel:
389
+
390
+ - **Route and health:** `extension` is green (`ok`) and turns red (`failing`) when its last request failed, until one gets through. `page` is yellow (`limited`).
391
+ - **Detail:** one line, next to spectrum's own description of the route. When the route stops answering, it says so and what to do. It is empty while there is nothing to add.
391
392
  - **Failures:** the last 20 requests that failed in the transport, newest first, with URL, error and time.
392
- - **Check again** (`check()`) probes the local proxy again: on the proxy route it updates the health, and otherwise it reloads the page onto the proxy once it answers.
393
+ - **Check again** (`check()`) checks a Tailscale that failed again, and refreshes the panel.
393
394
  - **Get the whole web** links slicc-extension in the Chrome Web Store (`extensionUrl`).
394
395
 
395
396
  ## Browser automation
@@ -397,9 +398,8 @@ The choice is in `document.documentElement.dataset.transport`: `local-proxy`, `e
397
398
  Programs in the kernel drive a browser over the Chrome DevTools Protocol: `playwright-cli` and `curlwright` ([slicc-cdp](https://github.com/ai-ecoverse/slicc-cdp), in the base set) or anything else that reads `SLICC_CDP_URL`. The kernel's facade on `127.0.0.1:9222` takes connections only from programs inside the kernel. `playwright-cli route`, `route-list` and `unroute` (slicc-cdp ≥ 1.10.0) fulfil or change a tab's requests through `Fetch` on that tab's session while a command runs. A pattern that names a protected host is refused, and a request to a protected origin is never routed. slicc-cdp ≥ 1.11.2 keeps the protected origins out of reach on its own side too: `tab-list` leaves protected tabs out, a command that names one with `--tab` exits 1, `open` and `goto` refuse protected URLs, and a tab it touches can't load a protected document (a script, link, meta refresh or redirect into one gets Chrome's error page). It hands each one to the `cdp` hook that `os/cdp.js` gives `createKernel`, and the hook connects it to a host:
398
399
 
399
400
  1. **[slicc-extension](https://github.com/ai-ecoverse/slicc-extension):** when the extension defines `sliccExtension.cdp`, its `chrome.debugger` bridge drives the Chrome that seven runs in. Every program connection shares the page's one extension port. The hook gives each connection only the answers to its own commands and the events of the sessions it attached, and on close it detaches every session the connection attached.
400
- 2. **The local proxy:** used when it is the transport (see [Network](#network)) and its probe says `cdp: 1`. The hook opens `ws://<proxy>/cdp` and offers the subprotocols `slicc.cdp.v1` and `slicc.key.<key>`, so the key never appears in a URL. It accepts the socket only when the proxy selects `slicc.cdp.v1`. The proxy checks the key and the page's `Origin` before the upgrade. slicc-node and slicc-swift keep one page client per `/cdp`, so every program connection shares one socket: the hook gives each command an id of its own, answers each connection with its own ids, and routes events by the sessions it attached (browser-level events go to every connection). When the last connection closes, so does the socket. When the proxy hands the socket to another page (`4001`) or loses its browser (`4002`), or closes it otherwise, every open connection is closed with a message saying which, and the next one opens a new socket.
401
401
 
402
- **Seven's own pages and the vault are out of reach.** Every connection, whether through the extension, the local proxy or a linked browser, goes through a guard (`guardProtected` in `os/guard.js`), so no program in the kernel can drive, read or close a protected tab. What is protected comes from [slicc-browsers](https://github.com/ai-ecoverse/slicc-browsers#protected-origins)' `protectedOrigins` (in the base set): every `sliccy.ai` host, the vault (`vault.sliccy.ai`), `blob:`, `filesystem:` and `view-source:` URLs by the URL inside them, and `devtools:`, plus seven's own origin. A blank popup or frame (`about:blank`, `about:srcdoc`) is protected when its opener is protected or unknown, or when it was protected before. Seven's own pages can show files from OPFS that the user or the agent wrote, and the vault holds the user's secrets.
402
+ **Seven's own pages and the vault are out of reach.** Every connection, whether through the extension or a linked browser, goes through a guard (`guardProtected` in `os/guard.js`), so no program in the kernel can drive, read or close a protected tab. What is protected comes from [slicc-browsers](https://github.com/ai-ecoverse/slicc-browsers#protected-origins)' `protectedOrigins` (in the base set): every `sliccy.ai` host, the vault (`vault.sliccy.ai`), `blob:`, `filesystem:` and `view-source:` URLs by the URL inside them, and `devtools:`, plus seven's own origin. A blank popup or frame (`about:blank`, `about:srcdoc`) is protected when its opener is protected or unknown, or when it was protected before. Seven's own pages can show files from OPFS that the user or the agent wrote, and the vault holds the user's secrets.
403
403
  - **Hidden targets:** `Target.getTargets` and the target events leave protected targets out.
404
404
  - **Refused commands** answer an error: any command whose `targetId` or `frameId` is a protected target (the guard asks for the target's current URL first) (`Target.attachToTarget`, `activateTarget`, `closeTarget`, `getTargetInfo`, `Browser.getWindowForTarget` and the rest, with or without a session); any command whose `url`, origin (`origin`, `securityOrigin`, `ownerOrigin`, `topLevelSite`, `scopeURL` and the like), `storageKey`, `cacheId` or cookie `domain` is protected, at any depth of its parameters, which covers navigation, `IndexedDB.*`, `CacheStorage.*`, `DOMStorage.*`, `Storage.*`, `ServiceWorker.*` and cookie writes; `Network.clearBrowserCookies`, `Storage.clearCookies`, `ServiceWorker.stopAllWorkers`, `BackgroundService.clearEvents`, `Storage.getPrivateVerificationTokens` and `Tracing.start`, which reach every origin; `Network.loadNetworkResource`, which follows redirects the guard can't see; `IO.read` and `IO.close` on a stream the guard didn't hand out in an answer; a `ServiceWorker` command for a registration or version the guard hasn't shown; `Target.detachFromTarget` of a session the connection didn't attach; and every `Fetch.*` command without a tab session, so nothing can pause the whole browser. Free-form values such as `expression` or `headers` aren't searched.
405
405
  - **Filtered answers and events:** cookie lists leave out cookies whose domain is protected and partitioned cookies under a protected site. `Storage`, `DOMStorage`, `IndexedDB`, `CacheStorage` and `BackgroundService` events that name a protected origin are dropped, and so are service worker registrations and versions on one.
@@ -409,20 +409,20 @@ Programs in the kernel drive a browser over the Chrome DevTools Protocol: `playw
409
409
  - **Flat sessions only:** `Target.attachToTarget` and `Target.setAutoAttach` need `flatten: true`. `Target.sendMessageToTarget`, `Target.attachToBrowserTarget` and `Target.exposeDevToolsProtocol` are refused, so no command can be tunnelled past the guard.
410
410
 
411
411
  **Sign-in tabs are held for life (#754).** A sign-in popup shows its token or code in its URL and on the IdP's pages, and the guard can't scrub a page's own content. So the owner tab holds every sign-in tab from the moment it opens. A held tab is never listed, attached, auto-attached or reported to a program on any origin, until it closes. Its events, `urlFragment` included, never reach the program.
412
- - **Seven's own sign-in:** the popup opens on `about:blank#slicc-sign-in-<nonce>`. bios asks the owner over the `slicc-held` BroadcastChannel to hold it. The owner finds that target by its marker URL on each browser host, the extension and slicc-node, holds it, and only then is the popup sent to the IdP. The owner answers `held` once it has held the tab on every listed host, `none` when no host is listed, and `unheld` otherwise, including when a listed host can't be opened. On `unheld`, on `none` while bios sees a host, or on no answer within 6 s while a host is connected, the sign-in stops before the IdP and says so; it never goes on unheld. On `none`, the sign-in goes on, and the owner keeps it as in flight for 11 minutes (in `localStorage`, `slicc-os.sign-in-flight`, so a reload keeps it). Before any program gets a browser connection, the owner sweeps every host: it holds sign-in tabs still on `about:blank`, and while a sign-in is in flight, every page a protected page opened. So a host that connects in the middle of a sign-in never sees its tab.
412
+ - **Seven's own sign-in:** the popup opens on `about:blank#slicc-sign-in-<nonce>`. bios asks the owner over the `slicc-held` BroadcastChannel to hold it. The owner finds that target by its marker URL on each browser host (slicc-extension), holds it, and only then is the popup sent to the IdP. The owner answers `held` once it has held the tab on every listed host, `none` when no host is listed, and `unheld` otherwise, including when a listed host can't be opened. On `unheld`, on `none` while bios sees a host, or on no answer within 6 s while a host is connected, the sign-in stops before the IdP and says so; it never goes on unheld. On `none`, the sign-in goes on, and the owner keeps it as in flight for 11 minutes (in `localStorage`, `slicc-os.sign-in-flight`, so a reload keeps it). Before any program gets a browser connection, the owner sweeps every host: it holds sign-in tabs still on `about:blank`, and while a sign-in is in flight, every page a protected page opened. So a host that connects in the middle of a sign-in never sees its tab.
413
413
  - **The guard decides by itself, too:** a page that a held tab opened is held, on any URL, and so are its own popups in turn, and so are the frames and workers of a held tab (`parentId`). A page seven (or another protected page) opened with opener access (`canAccessOpener`), or that is still on `about:blank`, is held the first time any guard sees it.
414
- - **Other sign-in tabs:** a watcher in the owner tab holds every page that a protected page (seven or the vault) opened and that is still on `about:blank`, and a popup that a held tab opens, such as the IdP's own. **Through slicc-node** it connects when the owner boots and takes target events, so it holds an unmarked popup from its creation, before any program has connected. **Through slicc-extension** it only polls every 250 ms once a program or a sign-in needs the browser, and the extension reports no opener access, so only marked popups are held reliably there: an unmarked popup that leaves `about:blank` before the next poll can be missed, and a program could then list it, URL and fragment included (a known gap). Seven's own Adobe sign-in carries the marker. The agent's MCP OAuth and device-code popups carry it from slicc-agent 6.19.15 (#550), which seven pins: they ask on `slicc-held` and navigate only on `held` or `none`, so on both hosts they are held before they leave `about:blank`, and with no holder answering the agent's sign-in fails.
414
+ - **Other sign-in tabs:** a watcher in the owner tab holds every page that a protected page (seven or the vault) opened and that is still on `about:blank`, and a popup that a held tab opens, such as the IdP's own. It goes through slicc-extension and only polls every 250 ms once a program or a sign-in needs the browser, and the extension reports no opener access, so only marked popups are held reliably there: an unmarked popup that leaves `about:blank` before the next poll can be missed, and a program could then list it, URL and fragment included (a known gap). Seven's own Adobe sign-in carries the marker. The agent's MCP OAuth and device-code popups carry it from slicc-agent 6.19.15 (#550), which seven pins: they ask on `slicc-held` and navigate only on `held` or `none`, so they are held before they leave `about:blank`, and with no holder answering the agent's sign-in fails.
415
415
  - **Kept:** held tab ids are kept in `localStorage` (`slicc-os.held-targets`, the 32 most recently held or seen again), so a reload of the owner keeps them.
416
- - **The watcher's connections:** on slicc-extension it connects only once a program or a sign-in needs the browser. On slicc-node it connects when the owner boots, so an owner boot takes slicc-node's browser from another page driving it through the same slicc-node (a known, functional only). If another page then takes it back, the watcher doesn't fight it, and reconnects when the next program connects. Before any program gets a browser connection, the owner sweeps every host; if that listing fails, the program gets no connection, and no host connection is opened for it.
416
+ - **The watcher's connections:** it connects to slicc-extension only once a program or a sign-in needs the browser. Before any program gets a browser connection, the owner sweeps every host; if that listing fails, the program gets no connection, and no host connection is opened for it.
417
417
  - **A tab only lent seven's protection** is one that seven opened with no opener access, or that inherited it only from about:blank. It becomes drivable once it commits an ordinary `http:`, `https:` or `data:` page, whether the program saw it at creation or later. Real protected origins stay protected for life, a `blob:` of seven's origin included.
418
418
 
419
419
  A tab a program creates through the guard opens in the background, behind seven, unless it passes `background: false`. A program that needs seven to keep rendering should open tabs, the default, and not `newWindow`: even unfocused, a new window can cover seven's window, and Chrome then throttles seven ([slicc-extension#105](https://github.com/ai-ecoverse/slicc-extension/issues/105)).
420
420
 
421
- A program picks a host by adding `runtime=extension` or `runtime=proxy` to the query of the URL it opens (`playwright-cli --runtime proxy`). With no runtime, the extension goes first. The hook never derives a URL from the request: it reaches only the extension's page global and the proxy the page already checked.
421
+ A program picks a host by adding `runtime=extension`, or a linked browser's `<device>/<browser>`, to the query of the URL it opens (`playwright-cli --runtime extension`). With no runtime, the extension goes first, then a linked browser. The hook never derives a URL from the request: it reaches only the extension's page global and the linked browsers.
422
422
 
423
- **No consent dialog.** A connection goes straight through once the hook has found a host; seven doesn't ask first. The once-per-session confirm from #138 was removed by Lars's decision W10. The Network panel shows the host in its browser automation line (`browser` on the network port, from `browserVia`): through the extension, through slicc-node, or not available.
423
+ **No consent dialog.** A connection goes straight through once the hook has found a host; seven doesn't ask first. The once-per-session confirm from #138 was removed by Lars's decision W10. The Network panel shows the host in its browser automation line (`browser` on the network port, from `browserVia`): through the extension, through a linked browser, or not available.
424
424
 
425
- When no host can drive a browser, `/json/list` and the WebSocket handshake answer `502` with `CDP host: no browser to drive: install slicc-extension, or run npx @ai-ecoverse/slicc-node`. A runtime that isn't `extension` or `proxy` gets `CDP host: unknown runtime "<name>" (extension, proxy)`. `/json/version` answers either way, since a hook is always offered.
425
+ When no host can drive a browser, `/json/list` and the WebSocket handshake answer `502` with `CDP host: no browser to drive: install slicc-extension, or link a computer with npx sliccy@next`. A runtime that isn't `extension` or a linked browser gets `CDP host: unknown runtime "<name>" (extension, …)`, listing the linked browsers. `/json/version` answers either way, since a hook is always offered.
426
426
 
427
427
  ### Tailscale (proof of concept)
428
428
 
@@ -439,10 +439,10 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
439
439
  `document.documentElement.dataset.tailscale` shows the backend state, or `off`. While an exit node is in use, the panel's Connection block reports `route: 'tailnet'` (spectrum ≥ 1.41.0), with health `ok` and a note that this computer's own services still go through the base route. Without one, it reports the base route.
440
440
  - **Joining:** **Sign in to Tailscale** opens Tailscale's login in a new tab. Or paste an auth key into the panel's field. A key is held in memory for that one login and is never stored, put in a URL or logged. An auth key in the fragment is dropped with a warning. The integration test hands its key to the page in `globalThis.sliccTailscaleAuthKey` through an init script, and the page takes it once and deletes it. The tailnet object is exposed as `globalThis.sliccTailscale` only when an init script sets `globalThis.sliccTailscaleDebug`, as the integration test does.
441
441
  - **Nothing inbound:** the node runs with shields up, so tailnet peers can't open connections to it. The build has no peerapi server, and the page listens on nothing. Peers can't reach the kernel's services, `*.kernel.localhost` or the CDP facade.
442
- - **Where the wasm comes from:** shown as **Tailscale (/opt/tailscale)** in [Install / Update](#install--update). [`@ai-ecoverse/wasm-tailscale`](https://www.npmjs.com/package/@ai-ecoverse/wasm-tailscale) (built in [ai-ecoverse/wasm-tailscale](https://github.com/ai-ecoverse/wasm-tailscale), certified, 1.104.1-11, which drops hop-by-hop headers (Proxy-Authorization, Proxy-Authenticate, TE and any header named in Connection) before a request reaches a tailnet server through `ipn.fetch` or `ipn.websocket` (wasm-tailscale#12), handles oversized text (#10), and no longer drops unread messages silently when a WebSocket closes (#11)) is a pnpm project of its own, [`src/packages/tailscale/`](src/packages/tailscale/), installed like the agent, never at boot. The first time Tailscale is turned on, `os/tailscale.js` writes the deployed manifest and lockfile to `/opt/tailscale` and runs `pnpm install --frozen-lockfile --trust-lockfile` there under the Web Lock `slicc-tailscale`. The panel shows the download progress. The lockfile is recorded in `var/lib/slicc/tailscale/pnpm-lock.yaml`, so later starts skip the install, and a new deployed lockfile installs the new version on the next start. The page then reads `main.wasm` and `wasm_exec.js` through the kernel. A host folder mounted at `/mnt/tailscale`, or a folder named in the stored setting's `from`, (`npx @ai-ecoverse/slicc-node --mount <dist>:tailscale:ro`, then `mount -t hostfs tailscale /mnt/tailscale`) wins over the package, for trying a local build.
443
- - **Routing:** the kernel keeps the transport [picked above](#network), and Tailscale is layered over it. While an exit node is in use, every request goes through the tailnet, except what [never goes out](#network) (loopback keeps `host.slicc.internal` on the local proxy). Without one, only tailnet addresses (`100.64.0.0/10`, `fd7a:115c:a1e0::/48`) and MagicDNS names go there.
442
+ - **Where the wasm comes from:** shown as **Tailscale (/opt/tailscale)** in [Install / Update](#install--update). [`@ai-ecoverse/wasm-tailscale`](https://www.npmjs.com/package/@ai-ecoverse/wasm-tailscale) (built in [ai-ecoverse/wasm-tailscale](https://github.com/ai-ecoverse/wasm-tailscale), certified, 1.104.1-11, which drops hop-by-hop headers (Proxy-Authorization, Proxy-Authenticate, TE and any header named in Connection) before a request reaches a tailnet server through `ipn.fetch` or `ipn.websocket` (wasm-tailscale#12), handles oversized text (#10), and no longer drops unread messages silently when a WebSocket closes (#11)) is a pnpm project of its own, [`src/packages/tailscale/`](src/packages/tailscale/), installed like the agent, never at boot. The first time Tailscale is turned on, `os/tailscale.js` writes the deployed manifest and lockfile to `/opt/tailscale` and runs `pnpm install --frozen-lockfile --trust-lockfile` there under the Web Lock `slicc-tailscale`. The panel shows the download progress. The lockfile is recorded in `var/lib/slicc/tailscale/pnpm-lock.yaml`, so later starts skip the install, and a new deployed lockfile installs the new version on the next start. The page then reads `main.wasm` and `wasm_exec.js` through the kernel. A host folder mounted at `/mnt/tailscale`, or a folder named in the stored setting's `from`, (`npx sliccy@next --mount <dist>:tailscale:ro`, then `mount -t hostfs tailscale /mnt/tailscale`) wins over the package, for trying a local build.
443
+ - **Routing:** the kernel keeps the transport [picked above](#network), and Tailscale is layered over it. While an exit node is in use, every request goes through the tailnet, except what [never goes out](#network) (loopback keeps `host.slicc.internal` on the CLI linked on this computer). Without one, only tailnet addresses (`100.64.0.0/10`, `fd7a:115c:a1e0::/48`) and MagicDNS names go there.
444
444
  - **Exits:** `os/exits.js` routes traffic over any number of exits: Tailscale, and linked devices.
445
- - **Linked devices:** `os/link.js` links a `slicc` CLI to the page over WebRTC, as one exit per link. It speaks the [slicc link protocol](https://github.com/ai-ecoverse/slicc-cli): slicc-node's tunnel frames over data channels, with one channel per stream and a 256 KiB credit window per stream and direction.
445
+ - **Linked devices:** `os/link.js` links a `slicc` CLI to the page over WebRTC, as one exit per link. It speaks the [slicc link protocol](https://github.com/ai-ecoverse/slicc-cli): tunnel frames over data channels, with one channel per stream and a 256 KiB credit window per stream and direction.
446
446
  - **Setup:** a CLI that holds CDP to this browser calls `globalThis.sliccLinkReceive` with a `hello` and its nonce, and the page answers through the CLI's `sliccLinkSend` binding: the offer, then its ICE candidates. The CLI checks that the call came from seven's main frame. CDP carries only the setup; the data goes over WebRTC. Every `hello` names its CLI process (`node`): a new `hello` from the same process replaces its link (after a reload, say), and one from another process (`slicc prompt` next to `slicc`) adds a second link.
447
447
  - **Pairing without CDP (`slicc open`, `slicc pair`):** `os/pair.js` is seven's side of the link spec's §1.3, for a seven in an everyday browser that no CLI controls. It needs slicc ≥ 1.39.0; the tests run the pinned `@ai-ecoverse/slicc` devDependency. `slicc open` opens `<page>#slicc-pair=<port>.<token>.<node-fp>`, and `slicc pair` prints it for the **Link this computer** field under Linked devices (`pairComputer(url)`, spectrum's pairing block, spectrum ≥ 1.88.1). Seven strips the fragment at once, opens the Network panel, and asks *Link SLICC on this computer?* with the node's short fingerprint (`ab12…ef`), which slicc prints too. Only **Link** (`confirmPairing()`) sends a loopback request: one `POST http://127.0.0.1:<port>/slicc/v1/pair` with the token and a non-trickled offer (ICE gathering waits 10 s at most, then the offer goes with what it has), from a page certificate (ECDSA P-256) kept per origin in IndexedDB (`slicc-follow`, key `loopback`). Seven uses the answer only if its fingerprint is the URL's, then pins `{ port, name }` by that fingerprint in `localStorage` (`slicc-os.paired`). It never pairs over ports 9222–9229 or below 1024. Every loopback `POST` gets 15 s (2 minutes while Chrome's Local Network Access prompt is open, which shows as `links.permission`), and a missed deadline reads as nothing answering. A tab that isn't running SLICC keeps the link until it follows the tab that is, then hands the attempt over; under a version skew between the tabs it says so instead and never pairs on its own. A tab that takes over reads the pins again. Refusals read as a sentence in the block: a wrong, used or expired link (`403`), nothing answering (or an origin slicc refuses), a browser that blocks local network access, and an impostor's fingerprint.
448
448
  - **Browsers:** Chrome, Edge and Firefox reach `http://127.0.0.1` from an https page. WebKit (Safari, and every browser on iOS) blocks it as mixed content, so there an attempt fails at once with "Safari doesn’t let an https page reach slicc on this computer…", which points to the join command (the tray path) or another browser.
@@ -451,8 +451,8 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
451
451
  - **Before HELLO:** each stream channel is its own SCTP stream, so a node's first OPEN can reach the page ahead of its HELLO on the control channel, the order a `slicc prompt`, `abort` or `watch` sometimes arrives in. The page holds up to 64 such frames, 256 KiB in all (one stream's credit window), and serves them in order once the HELLO arrives, as the CLI does for the page's; frames held on a channel that closed meanwhile are dropped. A frame on the control channel before HELLO, a 65th held frame or byte past 256 KiB, a held frame that turns out malformed, and no HELLO within 10 s still close the link: before its HELLO a peer is anonymous.
452
452
  - **Half-open links:** a link that hasn't said HELLO within 30 s of its offer (a follower that gave up on a bootstrap, say) is closed, and its peer connection with it. A device's row follows its link that said HELLO, never a newer half-open one; with only a half-open link left, a known device shows as away. Until a link says HELLO its row shows only that it's connecting, under its bootstrap id: no *Asks to follow*, no Allow and no switches, because its device isn't known yet. So Allow, which keeps the grant with the device's certificate, always finds the device it was clicked for. Unlinking a device drops every link it has and tells the ones that said HELLO.
453
453
  - **Names:** each link gets an address from `198.18.57.0/24` (the first is `.11`) and the name `<host>.slicc.internal`, from the CLI's hostname, made DNS-safe and unique (`lars-mbp-2`). A link keeps its name when it reconnects, also after a reload and in any order, while the name is free: the page remembers each CLI process's name (by device certificate and process id) in `localStorage` (`slicc-os.link-labels`). A CLI that restarts is a new process, so it may come back as `lars-mbp-2` while its old link is still closing; once that link is gone, the new one takes the name back (and its SSH host key is written under it). Port 22 there is the CLI's built-in SSH server, and any other port is the CLI machine's loopback, under the CLI's policy.
454
- - **Into the page (`kernel-in`):** the page's HELLO offers `kernel-in`, so a local CLI serves `http://<port>.kernel.localhost` on its own loopback, on the port its HELLO names as `kernel.port` (80 when it names none; see [Kernel servers](#kernel-servers)) and carries each connection into the page as `OPEN { kind: 'kernel', port }`. The page answers with `kernel.dial({ port })` and pipes both ways with half-close under the same credit window. It refuses port 9222, the CDP facade, with `EACCES`, and anything but a port 1–65535 with `EINVAL`. A port nothing listens on gets `ECONNREFUSED` (the CLI answers 502). Every other node-opened kind is `EOPNOTSUPP`, and an OPEN without a JSON `kind` is `EPROTO`. This replaces slicc-node's kernel tunnel for a linked CLI.
455
- - **Browsers (`cdp`):** a CLI with `cdp` lists its browsers in HELLO, and later in `OPEN { kind: 'browsers' }` updates (the page offers cap `browsers`, answers OPENED and END, and replaces the list). Each browser is a runtime of the 9222 facade named `<device>/<browser>`, so `playwright-cli --runtime lars-mbp/chrome …`, or `/json/list?runtime=lars-mbp/chrome`, drives it. Each facade connection opens its own `OPEN { kind: 'cdp', browser }` stream, framed as a `u32` length plus one JSON message (at most 64 MiB, otherwise the stream is reset with `EMSGSIZE`). Without a runtime, the facade still prefers slicc-extension, then slicc-node, then a linked browser that hosts this seven (`seven: true`), then the first linked one. The Network panel lists each device's browsers with their runtimes (`devices[].browsers`), and browser automation reads `via: 'link'` when linked browsers are what it drives. Seven shares the browsers only of a slicc CLI at **1.47.3 or later** (it relies on slicc-cli #325, #331, #335, #376 and #388), read from its HELLO's `version`: `dev` passes, and otherwise only the numeric `major.minor.patch` counts, so a `-suffix` is ignored (a Go pseudo-version such as `1.47.4-0.2026…-abc123` passes). A HELLO without `version`, or with one that doesn't parse, counts as older. An older CLI that offers `cdp` lists no browsers, on its device row or on the facade, its `OPEN { kind: 'browsers' }` updates are ignored and a `cdp` open to it is refused with `EACCES`; its row says "Update slicc to 1.47.3 or later to share this computer's browsers." Everything else it offers (`net`, `http`, `ssh`, `ws`, `udp`, its folders and `kernel-in`) stays.
454
+ - **Into the page (`kernel-in`):** the page's HELLO offers `kernel-in`, so a local CLI serves `http://<port>.kernel.localhost` on its own loopback, on the port its HELLO names as `kernel.port` (80 when it names none; see [Kernel servers](#kernel-servers)) and carries each connection into the page as `OPEN { kind: 'kernel', port }`. The page answers with `kernel.dial({ port })` and pipes both ways with half-close under the same credit window. It refuses port 9222, the CDP facade, with `EACCES`, and anything but a port 1–65535 with `EINVAL`. A port nothing listens on gets `ECONNREFUSED` (the CLI answers 502). Every other node-opened kind is `EOPNOTSUPP`, and an OPEN without a JSON `kind` is `EPROTO`.
455
+ - **Browsers (`cdp`):** a CLI with `cdp` lists its browsers in HELLO, and later in `OPEN { kind: 'browsers' }` updates (the page offers cap `browsers`, answers OPENED and END, and replaces the list). Each browser is a runtime of the 9222 facade named `<device>/<browser>`, so `playwright-cli --runtime lars-mbp/chrome …`, or `/json/list?runtime=lars-mbp/chrome`, drives it. Each facade connection opens its own `OPEN { kind: 'cdp', browser }` stream, framed as a `u32` length plus one JSON message (at most 64 MiB, otherwise the stream is reset with `EMSGSIZE`). Without a runtime, the facade still prefers slicc-extension, then a linked browser that hosts this seven (`seven: true`), then the first linked one. The Network panel lists each device's browsers with their runtimes (`devices[].browsers`), and browser automation reads `via: 'link'` when linked browsers are what it drives. Seven shares the browsers only of a slicc CLI at **1.47.3 or later** (it relies on slicc-cli #325, #331, #335, #376 and #388), read from its HELLO's `version`: `dev` passes, and otherwise only the numeric `major.minor.patch` counts, so a `-suffix` is ignored (a Go pseudo-version such as `1.47.4-0.2026…-abc123` passes). A HELLO without `version`, or with one that doesn't parse, counts as older. An older CLI that offers `cdp` lists no browsers, on its device row or on the facade, its `OPEN { kind: 'browsers' }` updates are ignored and a `cdp` open to it is refused with `EACCES`; its row says "Update slicc to 1.47.3 or later to share this computer's browsers." Everything else it offers (`net`, `http`, `ssh`, `ws`, `udp`, its folders and `kernel-in`) stays.
456
456
  - **Sync:** the Network panel's Sync section (spectrum's `model.tray`) shows this tray as `os/tray.js` sees it:
457
457
  - **Name and role:** seven's name, `leader` while it leads the tray (`none` when disconnected), and `hosted`.
458
458
  - **Connection:** Live, Connecting, Reconnecting, Error or Offline.
@@ -472,10 +472,10 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
472
472
  - A CLI that goes away ends its stream but never aborts the run; aborting is explicit. A `watch` the CLI half-closes right after OPENED, as slicc does, still ends at once when it is later reset or its link closes, so **Watching** clears without waiting for the agent's next event.
473
473
  - Linked devices lists every device that has linked, offline ones too, with its control setting, and shows **Prompting** or **Watching** while a CLI does, also in the header.
474
474
  - A device that reconnects replaces only its own process's link, so `slicc <join> prompt` doesn't drop that machine's `follow`. Control-only links (`caps: []`) get no `.slicc.internal` name and no exit.
475
- - **Folders (`fs`):** a CLI started with `--mount <path>[:<name>][:ro]` lists its folders in HELLO, and later in `OPEN { kind: 'mounts' }` updates; the page offers cap `mounts`. Each is a `hostfs` source named `<device>/<name>`, so `mount -t hostfs lars-mbp/proj /mnt/proj` mounts it, and nothing is mounted unless someone asks. A bare source with no `<device>/`, as slicc-node named its folders (`mount -t hostfs project /mnt/p`, or one remembered from then), is the folder of that name on the CLI linked on this computer, when exactly one local CLI shares one. The device's row in Linked devices lists its folders (`folders`, with a Read-only badge; spectrum ≥ 1.58.0): **Mount** (`mountFolder(source)`) mounts one at `/mnt/<device>/<name>` (a folder named `.` or `..` is never listed), and **Eject** (`ejectFolder(source)`) unmounts it. The Files panel's Mounted strip shows each `hostfs` mount's source (`mountInfo()`), so `/mnt/proj · lars-mbp/proj` reads as another computer's folder. A mounted linked folder stays with the CLI process it was first granted from (`slicc-os.link-sources`), even if that process comes back under another name. If that process isn't linked, the folder goes to whichever process has the name now, but only when no other process with the same host name shares a folder of that name; otherwise it waits. So a remembered `lars-mbp/proj` never lands on another process's `proj`. A remembered linked folder that is mounted again before its CLI is back stays `nomedium`, and fills in when the CLI links. The kernel's `hostfs` hook answers such a source with a `MessagePort` (slicc-kernel ≥ 1.54.9), and the page serves it with `serveHostfsPort` over `OPEN { kind: 'fs', mount, op, … }` streams: `list` collects its NDJSON, a read over 64 KiB and every write get their own channel, and `watch` streams the CLI's change events. A read-only kernel mount, or a read-only export, sends `readonly: true` on every OPEN. A link that drops makes the mount `nomedium` until the CLI is back; files that were open reopen by path and continue if the host didn't change them.
475
+ - **Folders (`fs`):** a CLI started with `--mount <path>[:<name>][:ro]` lists its folders in HELLO, and later in `OPEN { kind: 'mounts' }` updates; the page offers cap `mounts`. Each is a `hostfs` source named `<device>/<name>`, so `mount -t hostfs lars-mbp/proj /mnt/proj` mounts it, and nothing is mounted unless someone asks. A bare source with no `<device>/` (`mount -t hostfs project /mnt/p`, or one remembered from an older seven), is the folder of that name on the CLI linked on this computer, when exactly one local CLI shares one. The device's row in Linked devices lists its folders (`folders`, with a Read-only badge; spectrum ≥ 1.58.0): **Mount** (`mountFolder(source)`) mounts one at `/mnt/<device>/<name>` (a folder named `.` or `..` is never listed), and **Eject** (`ejectFolder(source)`) unmounts it. The Files panel's Mounted strip shows each `hostfs` mount's source (`mountInfo()`), so `/mnt/proj · lars-mbp/proj` reads as another computer's folder. A mounted linked folder stays with the CLI process it was first granted from (`slicc-os.link-sources`), even if that process comes back under another name. If that process isn't linked, the folder goes to whichever process has the name now, but only when no other process with the same host name shares a folder of that name; otherwise it waits. So a remembered `lars-mbp/proj` never lands on another process's `proj`. A remembered linked folder that is mounted again before its CLI is back stays `nomedium`, and fills in when the CLI links. The kernel's `hostfs` hook answers such a source with a `MessagePort` (slicc-kernel ≥ 1.54.9), and the page serves it with `serveHostfsPort` over `OPEN { kind: 'fs', mount, op, … }` streams: `list` collects its NDJSON, a read over 64 KiB and every write get their own channel, and `watch` streams the CLI's change events. A read-only kernel mount, or a read-only export, sends `readonly: true` on every OPEN. A link that drops makes the mount `nomedium` until the CLI is back; files that were open reopen by path and continue if the host didn't change them.
476
476
  - **SSH host key:** the CLI's HELLO carries its host key, and the page writes `<host>.slicc.internal,<address> <key>` into the system-wide `/etc/ssh/ssh_known_hosts`, replacing older entries for that name or address, so `ssh` never trusts a key on first sight, whichever user runs it. Seven manages these keys, not the user, and OpenSSH looks up a user's own file under the passwd home, which isn't always `$HOME`.
477
477
  - **Tests:** `test/integration/remote.test.mjs` runs the real `slicc watch`, `slicc prompt` and `slicc abort` with `--signal stdio` against seven's real agent and a fake Bedrock. It also leads a fake tray hub and sends a `webhook.event`, which reaches the agent as a webhook lick (without its `authorization` header) and starts a run. `test/integration/tray.test.mjs` leads a fake tray hub and links a follower in the page through it, then rotates and unlinks; a remote follower that names kernel port 17180 leaves `SLICC_PAGE_LOOPBACK`, the stored port and the profile script alone. `test/integration/link.test.mjs` links a fake node in the page over a real `RTCPeerConnection`, with a HELLO that names no kernel port, port 80 or 17180, and checks `SLICC_PAGE_LOOPBACK` in login shells and, after a reload, in every program (one that offers `cdp` as version `1.47.2` or without a version shares no browsers while its net and folders still work; `1.47.3`, `dev` and a pseudo-version above share them), and, with `SLICC_CLI` set, the real `slicc attach --signal stdio`, relaying its NDJSON through the CDP binding: raw TCP and the http kind; kernel `ssh` with strict host-key checking against `--runner sh -c`, after `pnpm add -g @ai-ecoverse/wasix-openssh`; and, with the link as the exit, a name from the CLI's `--hosts` file, then refused under `--deny`; and `<port>.kernel.localhost` through the CLI's listener into a kernel server (`httptest`), with 502 for a closed port and the facade refused; a folder shared with `--mount`, mounted as `ref-node/proj`, read, written, seen changing on the host, and refused for writes when mounted `ro`; a second headless Chromium shared with `--cdp-extra`, listed and driven by `playwright-cli --runtime ref-node/<id>`; and a program WebSocket from kernel `bash` through the proxy to a `ws` echo server on the CLI's loopback: text, binary, the subprotocol, a close from each end, and `EACCES` under `--deny`; kernel `websocat`, installed from the catalog, to a `ws` echo on the CLI's loopback and, without `-k`, to `wss://echo.sliccy.ai/` (SLICC's echo, behind Cloudflare's certificate), so its own TLS checks the chain over raw TCP through the link, then failing at once under Directly and with no exit; and a kernel `python3` UDP socket (after `pnpm add -g @ai-ecoverse/wasix-python`) to a UDP echo server on the CLI's loopback, unconnected and connected, and `EACCES` on a connected socket under `--deny`, as with the fake node's own refusal. CI runs the CLI from npm, the pinned `@ai-ecoverse/slicc` devDependency.
478
- - **As the exit:** a CLI linked on this computer (`npx sliccy@next`) is the exit by itself until the user chooses one, so the kernel's HTTP and raw TCP get past CORS at once, as slicc-node's proxy did; a Tailscale exit node that is on comes first. A remote device that offers to be a default exit carries other traffic only once it's chosen. The Network panel's **Internet traffic** picker (`setExit`) is one choice across Directly, Tailscale exit nodes and linked devices: choosing a device turns off Tailscale's exit node, and choosing Tailscale clears the device. The choice of a device is kept in `localStorage` (`slicc-os.exit`), and while that device isn't connected, after a reload or while it reconnects, internet traffic **waits**: raw connections get `ENETUNREACH` and HTTP fails, rather than going out directly. Choosing Directly sends it directly again and is kept (`direct`), so a local CLI doesn't take it over, while a Tailscale exit node turned on later still does; unlinking the chosen exit device goes back to the automatic choice.
478
+ - **As the exit:** a CLI linked on this computer (`npx sliccy@next`) is the exit by itself until the user chooses one, so the kernel's HTTP and raw TCP get past CORS at once; a Tailscale exit node that is on comes first. A remote device that offers to be a default exit carries other traffic only once it's chosen. The Network panel's **Internet traffic** picker (`setExit`) is one choice across Directly, Tailscale exit nodes and linked devices: choosing a device turns off Tailscale's exit node, and choosing Tailscale clears the device. The choice of a device is kept in `localStorage` (`slicc-os.exit`), and while that device isn't connected, after a reload or while it reconnects, internet traffic **waits**: raw connections get `ENETUNREACH` and HTTP fails, rather than going out directly. Choosing Directly sends it directly again and is kept (`direct`), so a local CLI doesn't take it over, while a Tailscale exit node turned on later still does; unlinking the chosen exit device goes back to the automatic choice.
479
479
  - **What the agent sees:** the transport's `traits` name the default exit while there is one, so the agent can say how its fetches go. `traits.exit` is `{ kind, caps }`:
480
480
  - **`kind`:** `local` (a CLI on this computer), `remote` (a chosen linked device) or `tailnet` (a Tailscale exit node);
481
481
  - **`caps`:** what it carries, such as `['net', 'http', 'ws']`;
@@ -566,7 +566,7 @@ A server running inside the kernel (vite, `python -m http.server`, impeccable `l
566
566
 
567
567
  On CI (`CI` set), or with `TAILSCALE_E2E=1`, these tests run with every `npm test`: on Linux (x64 or arm64) `test/integration/tailnet.mjs` downloads headscale 0.29.4 and tailscale 1.104.1, checks each against its pinned sha256 and keeps them in `node_modules/.cache/tailnet/`; elsewhere set `HEADSCALE`, `TAILSCALED` and `TAILSCALE`, or they skip. To run just these: `TAILSCALE_E2E=1 node --test --test-global-setup=test/integration/global.mjs --test-name-pattern 'stage B' test/integration/tailscale.test.mjs` (`TS_SHOTS=<dir>` also saves screenshots of the Network panel and the Install / Update row).
568
568
  - **Plain `localhost` and `127.0.0.1` stay the real machine.** The service worker passes them through untouched. Inside the kernel it's the other way round: `localhost` is the kernel loopback, and `host.slicc.internal` reaches the machine. The kernel exports the page-visible name as `SLICC_PAGE_LOOPBACK` (`kernel.localhost`) and `SLICC_PAGE_LOOPBACK_URL` (`http://{port}.kernel.localhost`), so tools can print the right URL. With a kernel port (below) other than 80 they carry it: `kernel.localhost:17180` and `http://{port}.kernel.localhost:17180`.
569
- - **Why the port goes in the name.** `kernel.localhost:8400` would resolve to the machine's real port 8400 whenever the service worker doesn't answer, as for a navigation or a WebSocket, and reach the wrong server. `8400.kernel.localhost` lands on the kernel port, 80 by default (below), which is closed or answered by a local slicc's kernel listener or [slicc-node](https://github.com/ai-ecoverse/slicc-node)'s kernel tunnel, and it gives every kernel port its own origin. The service worker routes by the name alone and ignores the URL's port, so `http://8400.kernel.localhost:17180/` reaches the same kernel server.
569
+ - **Why the port goes in the name.** `kernel.localhost:8400` would resolve to the machine's real port 8400 whenever the service worker doesn't answer, as for a navigation or a WebSocket, and reach the wrong server. `8400.kernel.localhost` lands on the kernel port, 80 by default (below), which is closed or answered by a local slicc's kernel listener, and it gives every kernel port its own origin. The service worker routes by the name alone and ignores the URL's port, so `http://8400.kernel.localhost:17180/` reaches the same kernel server.
570
570
  - **How a request gets there.** A seven page's request to `*.kernel.localhost`, with any method, reaches the service worker. `*.localhost` is potentially trustworthy, so an `https` page loads it without a mixed-content block. The service worker answers it itself, so nothing goes to the network, no Local Network Access prompt is shown, and there's no CORS preflight. It hands the request to the `/os/` tab: the requesting one, or else the most recently focused. That tab calls `kernel.loopbackFetch(request, { port })` and transfers the streamed response body back.
571
571
  - **The response.** Status, headers and body pass through. Bodies stream, so `EventSource` and long-poll work, and closing the `EventSource` closes the kernel socket. seven's pages are cross-origin isolated, so where the server sent none, the service worker adds `access-control-allow-origin: <seven's origin>` with `access-control-allow-credentials: true`, and `cross-origin-resource-policy: cross-origin`. That lets a plain `<script src>` or `fetch` load from a server that knows nothing about CORS.
572
572
  - **When it fails.**
@@ -582,21 +582,11 @@ What doesn't work through the service worker:
582
582
  - **Opening a kernel server as a page.** A navigation to `http://<port>.kernel.localhost/` is another origin, which seven's service worker doesn't control.
583
583
  - **Cookies.** The responses are built in the page, so `Set-Cookie` is dropped.
584
584
 
585
- **The kernel port.** A local slicc names the port its kernel listener uses in its HELLO, as `"kernel": { "port": <n> }`: 80 on macOS and Windows, a spare port in 17180–17189 where it can't have 80 (Linux for a normal user, or 80 taken), or its `--kernel-port`. A HELLO without the field (an older slicc, slicc-node) means 80. Kernel URLs then carry any port but 80: `http://8400.kernel.localhost:17180/`. seven keeps the last port a local node named, in `localStorage` (`slicc-kernel-port`); a remote device's or another seven's HELLO never changes it. It then:
585
+ **The kernel port.** A local slicc names the port its kernel listener uses in its HELLO, as `"kernel": { "port": <n> }`: 80 on macOS and Windows, a spare port in 17180–17189 where it can't have 80 (Linux for a normal user, or 80 taken), or its `--kernel-port`. A HELLO without the field (an older slicc) means 80. Kernel URLs then carry any port but 80: `http://8400.kernel.localhost:17180/`. seven keeps the last port a local node named, in `localStorage` (`slicc-kernel-port`); a remote device's or another seven's HELLO never changes it. It then:
586
586
 
587
587
  - writes it to `/etc/profile.d/slicc-kernel.sh`, which sets `SLICC_PAGE_LOOPBACK` and `SLICC_PAGE_LOOPBACK_URL`, so every login shell opened after the HELLO gets it. Every HELLO checks the script itself and rewrites it when it names another port, one HELLO at a time, and the port is kept only once the script is written;
588
588
  - hands it to the kernel's `env` when the kernel starts, so every program gets it after a reload. A shell that is already open keeps what it started with.
589
589
 
590
- **slicc-node's kernel tunnel** carries what the service worker can't. slicc-node names no kernel port, so it's 80: it listens on `127.0.0.1:80`, or on the port `--kernel-port` gives it, and the URLs then carry that port: `http://8400.kernel.localhost:8080/`. It hands every connection to `*.kernel.localhost` to the page through a WebSocket, as raw bytes, so navigations, keep-alive and WebSocket upgrades (vite HMR) work. A page opened this way is its own origin, isolated from seven's.
591
-
592
- - **Opening the tunnel.** When the local proxy's probe reports `kernelTunnel`, `os/tunnel.js` opens `ws://<proxy>/api/kernel-tunnel`, with the proxy key as a WebSocket subprotocol.
593
- - **Serving streams.** For every stream slicc-node opens, the page calls `kernel.dial({ port })` and pipes the bytes both ways. Each direction may have at most 256 KiB in flight before the other side credits it, so a slow stream doesn't hold up the others.
594
- - **A dial that fails** is reset with its error code. slicc-node then answers `502 nothing listening on kernel port <port>`.
595
- - **Reconnecting.** A dropped tunnel reconnects after 1, 2, 5, 10, then every 30 seconds.
596
- - **Several tabs.** slicc-node gives new streams to the most recently connected tunnel. A seven tab reconnects when it gets focus. When another seven tab's tunnel opens, for example after slicc-node restarts, a tab that has focus reconnects too, over the BroadcastChannel `slicc-kernel-tunnel`. So the focused tab's kernel takes new streams, and an old tunnel closes once its open streams have ended.
597
-
598
- The contract is in slicc-node's README, under [Kernel services](https://github.com/ai-ecoverse/slicc-node#kernel-services).
599
-
600
590
  ## Updates
601
591
 
602
592
  A running install updates itself from the deployment it was booted from, package by package, without a reload:
@@ -666,14 +656,14 @@ npm run lint
666
656
 
667
657
  CPU profiling is off by default, in CI too: V8's sampler interrupts every page and worker thread every 100 µs with a signal, and that is where the renderer crashes in [#114](https://github.com/ai-ecoverse/slicc-bios/issues/114) faulted. Run `SLICC_PROFILE=1 npm test` to get a `.cpuprofile` per page and worker for every document and test, the kernel's workers included (open them in the DevTools Performance panel or [speedscope](https://www.speedscope.app)). `hotspots.md` then lists the functions in `src/` with the most self time across all profiles. The profiles are recorded with coverage switched on, so absolute timings run high. Use them to compare functions with each other rather than as real-world numbers.
668
658
 
669
- No test calls a real model. Every test serves an empty `packages/agent/` project (`emptyAgent` in `test/integration/chrome.mjs`), so only the chat test installs the agent in the background. In it, `test/integration/fake-proxy.mjs` plays a local proxy and answers Bedrock's `converse-stream` with a scripted event stream, so the turn runs through the real agent worker, pi-ai's Bedrock client and the kernel's transport. The test also signs in to Adobe against a fake IMS (`--host-resolver-rules` maps `ims-na1.adobelogin.com` to a local HTTPS server) and the fake proxy's `oauth-state`/`oauth-result` endpoints, cancelling once first.
659
+ No test calls a real model. Every test serves an empty `packages/agent/` project (`emptyAgent` in `test/integration/chrome.mjs`), so only the chat test installs the agent in the background. In it, `test/integration/fake-llm.mjs` answers Bedrock's `converse-stream` with a scripted event stream at the network level, through the harness's own CDP `Fetch` interception, so the turn runs through the real agent worker, pi-ai's Bedrock client and the kernel's page-fetch transport. The test also signs in to Adobe against a fake IMS (`--host-resolver-rules` maps `ims-na1.adobelogin.com` to a local HTTPS server) through the relay, cancelling once first, after checking that without slicc-extension a sign-in off sliccy.ai says to use it.
670
660
 
671
661
  ### CI
672
662
 
673
663
  [`.github/workflows/ci.yml`](.github/workflows/ci.yml) is one caller of [slicc-shared-web's pipeline](https://github.com/ai-ecoverse/slicc-shared-web#pipeline), pinned to the release `@ai-ecoverse/slicc-shared-web` has in `devDependencies` (Renovate moves both in one PR). It runs on pull requests, the merge queue and pushes to `main`:
674
664
 
675
665
  - **Pull requests:** after lint, the pipeline's `deploy` job publishes a `bb/**` branch's preview (see [Hosting](#hosting)); the tests and reviewers get its URL as `STAGING_URL`.
676
- - **Pull requests and the merge queue:** lint, then every `test/integration/*.test.mjs` split by the last runs' JUnit timings over 12 pool shards (`ci / integration`), then on a pull request the Claude and Codex reviews (`ci / review`) with this repository's `review-prompt`. The shared guard vectors run as one file per path (extension, proxy), target group (`seven`, `vault`, `vault-x`, `apex` and the rest, `test/integration/guard-groups.mjs`) and detach variant (`lagging` or the others), 20 files of 2 to about 12 minutes, so the split can balance them. The slicc CLI tests use `node_modules/.bin/slicc` (or `SLICC_CLI`).
666
+ - **Pull requests and the merge queue:** lint, then every `test/integration/*.test.mjs` split by the last runs' JUnit timings over 12 pool shards (`ci / integration`), then on a pull request the Claude and Codex reviews (`ci / review`) with this repository's `review-prompt`. The shared guard vectors run as one file per path (extension, link), target group (`seven`, `vault`, `vault-x`, `apex` and the rest, `test/integration/guard-groups.mjs`) and detach variant (`lagging` or the others), 20 files of 2 to about 12 minutes, so the split can balance them. The slicc CLI tests use `node_modules/.bin/slicc` (or `SLICC_CLI`).
677
667
  - **`main`:** semantic-release publishes the npm package (see [The npm package](#the-npm-package)). When it releases, the pipeline's `after-release` publishes that version to seven, in the `production` environment (see [Hosting](#hosting)).
678
668
 
679
669
  Require `ci / integration` and `ci / review`. There is no retry: a flaky test fails the run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-bios",
3
- "version": "1.51.3",
3
+ "version": "1.51.5",
4
4
  "description": "Slim loader that boots SLICC into OPFS and serves the UI from there",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -51,8 +51,7 @@
51
51
  "devDependencies": {
52
52
  "@ai-ecoverse/slicc": "1.49.27",
53
53
  "@ai-ecoverse/slicc-browsers": "1.18.16",
54
- "@ai-ecoverse/slicc-cdp": "1.42.2",
55
- "@ai-ecoverse/slicc-node": "2.5.1",
54
+ "@ai-ecoverse/slicc-cdp": "1.42.3",
56
55
  "@ai-ecoverse/slicc-shared-web": "2.13.3",
57
56
  "@ai-ecoverse/slicc-spectrum": "1.107.2",
58
57
  "@ai-ecoverse/slicc-vault": "1.17.0",
@@ -11,7 +11,7 @@
11
11
  "@ai-ecoverse/slicc-capture": "1.4.1",
12
12
  "@ai-ecoverse/slicc-kernel": "1.168.1",
13
13
  "@ai-ecoverse/slicc-spectrum": "1.107.2",
14
- "@ai-ecoverse/wasi-fnox": "1.40.0-1",
14
+ "@ai-ecoverse/wasi-fnox": "1.41.0-1",
15
15
  "@ai-ecoverse/wasi-pnpm": "12.9.1-21",
16
16
  "@ai-ecoverse/wasm-bash": "5.3.0-23",
17
17
  "@ai-ecoverse/wasm-coreutils": "9.12.0-15",
@@ -155,9 +155,9 @@
155
155
  }
156
156
  },
157
157
  "node_modules/@ai-ecoverse/wasi-fnox": {
158
- "version": "1.40.0-1",
159
- "resolved": "https://registry.npmjs.org/@ai-ecoverse/wasi-fnox/-/wasi-fnox-1.40.0-1.tgz",
160
- "integrity": "sha512-/1c/ueKiqih0NfgQdQ96Re/LKYQx6vWz31Od7V3YWAgvAxGr9+BBfWhseM/VP7+bUzSPw0ZmD+CqAQ/vJhihog==",
158
+ "version": "1.41.0-1",
159
+ "resolved": "https://registry.npmjs.org/@ai-ecoverse/wasi-fnox/-/wasi-fnox-1.41.0-1.tgz",
160
+ "integrity": "sha512-76b3bd5lKTbKtCXIHklOLU0nrKQvKqw3r7gyotZQrjSj5HuJ3YxUODViOIav5cxGH+u/C45/Fo9S97tshlOEeg==",
161
161
  "license": "MIT",
162
162
  "engines": {
163
163
  "slicc-kernel": ">=1.83.0"
@@ -8,7 +8,7 @@
8
8
  "@ai-ecoverse/slicc-capture": "1.4.1",
9
9
  "@ai-ecoverse/slicc-kernel": "1.168.1",
10
10
  "@ai-ecoverse/slicc-spectrum": "1.107.2",
11
- "@ai-ecoverse/wasi-fnox": "1.40.0-1",
11
+ "@ai-ecoverse/wasi-fnox": "1.41.0-1",
12
12
  "@ai-ecoverse/wasi-pnpm": "12.9.1-21",
13
13
  "@ai-ecoverse/wasm-bash": "5.3.0-23",
14
14
  "@ai-ecoverse/wasm-coreutils": "9.12.0-15",
@@ -24,8 +24,8 @@ importers:
24
24
  specifier: 1.107.2
25
25
  version: 1.107.2
26
26
  '@ai-ecoverse/wasi-fnox':
27
- specifier: 1.40.0-1
28
- version: 1.40.0-1
27
+ specifier: 1.41.0-1
28
+ version: 1.41.0-1
29
29
  '@ai-ecoverse/wasi-pnpm':
30
30
  specifier: 12.9.1-21
31
31
  version: 12.9.1-21
@@ -109,8 +109,8 @@ packages:
109
109
  resolution: {integrity: sha512-zN4kW/Ss8X9sAbszs2uwv1dO4UpSfqWutd8Sc4BD3EfmUekYJcPSsqVuKzlkhPtIBlWDVpQu/pgLDYPzQ3r+PA==}
110
110
  engines: {node: '>=24'}
111
111
 
112
- '@ai-ecoverse/wasi-fnox@1.40.0-1':
113
- resolution: {integrity: sha512-/1c/ueKiqih0NfgQdQ96Re/LKYQx6vWz31Od7V3YWAgvAxGr9+BBfWhseM/VP7+bUzSPw0ZmD+CqAQ/vJhihog==}
112
+ '@ai-ecoverse/wasi-fnox@1.41.0-1':
113
+ resolution: {integrity: sha512-76b3bd5lKTbKtCXIHklOLU0nrKQvKqw3r7gyotZQrjSj5HuJ3YxUODViOIav5cxGH+u/C45/Fo9S97tshlOEeg==}
114
114
  engines: {slicc-kernel: '>=1.83.0'}
115
115
 
116
116
  '@ai-ecoverse/wasi-pnpm@12.9.1-21':
@@ -272,7 +272,7 @@ snapshots:
272
272
 
273
273
  '@ai-ecoverse/slicc-spectrum@1.107.2': {}
274
274
 
275
- '@ai-ecoverse/wasi-fnox@1.40.0-1': {}
275
+ '@ai-ecoverse/wasi-fnox@1.41.0-1': {}
276
276
 
277
277
  '@ai-ecoverse/wasi-pnpm@12.9.1-21': {}
278
278