@ai-ecoverse/slicc-bios 1.15.1 → 1.16.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
@@ -316,7 +316,7 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
316
316
  - **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.
317
317
  - **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.
318
318
  - **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.
319
- - **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. Unlinking a device drops every link it has and tells the ones that said HELLO.
319
+ - **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.
320
320
  - **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.
321
321
  - **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 (port 80, or `--kernel-port`) 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.
322
322
  - **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.
@@ -367,11 +367,11 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
367
367
  - **The Tailscale exit** carries UDP too, with one wasm-tailscale `dial('udp', …)` per destination (at most 256 per socket, the oldest closed first), so tailnet addresses and, with an exit node, public ones.
368
368
  - **Network panel:** `status().links.devices` lists the linked devices (name, `<host>.slicc.internal`, address, mode, what they offer, the CLI's policy summary, whether they can be the exit, and their state), `status().exit` is the current choice, and `unlink(id)` drops a device. On `*.sliccy.ai` the panel also has the join URL, `rotateJoinUrl` and `retryLinks`.
369
369
  - **Remote devices (the tray hub):** on `*.sliccy.ai`, `os/tray.js` makes seven a **tray leader** on SLICC's tray hub (`slicc-tray-hub`), through the sliccy-ai worker's same-origin proxy for `POST /tray`, `/controller/<token>` and the leader WebSocket, which drops cookies. The hub mints the join URL on `www.sliccy.ai`, and the panel shows `npx sliccy@next <join-url> follow` (`@next` until `sliccy`'s `latest` is the new CLI; `latest` is still six's). Followers then link the same way as a local CLI: seven offers, and the offer, answer and candidates go over the hub's bootstrap messages. TURN comes from the hub.
370
- - **Who may link:** only followers with runtime `slicc-link/1` and full trust. A device **is its DTLS certificate**: its id is the SHA-256 fingerprint in its answer. The CLI keeps one certificate per hub and mints a new `controllerId` for every connection, which is only a routing id. A device that reconnects replaces its old link and gets its address back, and a new certificate is a new device. Known devices (name, label, address, what they offer) are kept in `localStorage` (`slicc-os.devices`), keyed by fingerprint, so a device that's away shows as reconnecting. The exit choice is keyed the same way.
370
+ - **Who may link:** only followers with runtime `slicc-link/1` or `slicc-seven/1` and full trust, and guests on a seat (see Guests below), which never become devices. A device **is its DTLS certificate**: its id is the SHA-256 fingerprint in its answer. The CLI keeps one certificate per hub and mints a new `controllerId` for every connection, which is only a routing id. A device that reconnects replaces its old link and gets its address back, and a new certificate is a new device. Known devices (name, label, address, what they offer) are kept in `localStorage` (`slicc-os.devices`), keyed by fingerprint, so a device that's away shows as reconnecting. The exit choice is keyed the same way.
371
371
  - **Unlink:** a connected device is told `OPEN { kind: 'unlink' }` and forgets its join URL and identity. Its fingerprint is refused from then on: an answer that carries it fails the bootstrap with `UNLINKED`. Running the join command again mints a new certificate, so it links as a new device.
372
372
  - **New join URL:** seven leads a new tray, tells every connected device that supports it the new URL (`OPEN { kind: 'join', url }`), and only then stops leading the old one, so a leaked URL stops working. A device that was away has to be linked again.
373
373
  - **Followers (seven on another device):** Link a device also offers **Open on another device**, `https://<this origin>/#follow=<join-url>` as a link and a QR code (spectrum ≥ 1.52.0, `links.followUrl`). A seven opened that way is a **follower**: it runs neither a kernel nor an agent of its own and shows the owner's chat and terminals over the link instead.
374
- - **The page:** `os/start.js` picks `os/follower.js` over `os/os.js` when there is a follow URL. The page reads `#follow` and removes it from the address bar. It accepts only a join URL on the tray hub, `www.sliccy.ai` (or the hub `slicc-os.tray-hub` names, or loopback for tests), and it never follows a tray that moves to another hub. A new link asks first: *Follow a SLICC on another computer?* says that every SLICC tab in this browser then shows the other SLICC until **Leave**. **Cancel** goes back to this browser's own SLICC. Only **Follow** keeps the join URL in `localStorage` (`slicc-os.follow`), so the page reconnects after a reload until Leave. It offers the Agents, Chat, Terminal and Settings panels only, and `model.follow` (spectrum's follow port) says whom it follows (`owner`), the link (`connecting`, `connected`, `reconnecting`, `unlinked`, `failed`) and what it may do (`access`).
374
+ - **The page:** `os/start.js` picks `os/follower.js` over `os/os.js` when there is a follow URL. The page reads `#follow` and removes it from the address bar. It accepts only a join URL on the tray hub, `www.sliccy.ai` (or the hub `slicc-os.tray-hub` names, or loopback for tests), and it never follows a tray that moves to another hub. A new link asks first: *Follow a SLICC on another computer?* says that every SLICC tab in this browser then shows the other SLICC until **Leave**. **Cancel** goes back to this browser's own SLICC. Only **Follow** keeps the join URL in `localStorage` (`slicc-os.follow`), so the page reconnects after a reload until Leave. It offers the Agents, Chat, Terminal, Network and Settings panels only (Network shows only the owner's Guests, from the agent's `model.guests`, since a follower has no network port of its own), and `model.follow` (spectrum's follow port) says whom it follows (`owner`), the link (`connecting`, `connected`, `reconnecting`, `unlinked`, `failed`) and what it may do (`access`).
375
375
  - **Joining:** the follower attaches to the join URL as runtime `slicc-seven/1` through the sliccy-ai worker's same-origin `/join/<token>` proxy, polls the bootstrap, answers seven's offer and trickles its candidates. It keeps polling until HELLO, so an `UNLINKED` refusal still reaches it, and follows a moved tray (in the body, `Link` or `x-tray-location`) at most five times per dial. Each dial has a fresh `controllerId`, and a dropped link dials again after 1, 2, 5, 10, then every 30 seconds.
376
376
  - **Identity:** one persistent DTLS certificate per hub origin (`RTCPeerConnection.generateCertificate`, ECDSA P-256 for a year, in the IndexedDB database `slicc-follow`), so the device and its access survive reloads. Unlinking deletes it.
377
377
  - **The link:** the follower answers as the link's node: its HELLO says `role: 'node'`, `kind: 'seven'`, caps `join` and `permissions`, and its tab as `node`, and it opens even stream ids. The owner treats a link as a follower because the hub said `slicc-seven/1`, and closes it if HELLO disagrees. A follower gets no `.slicc.internal` name, no exit, and none of the CLI kinds: the owner refuses `kernel`, `agent` and `browsers` from it with `EOPNOTSUPP`.
@@ -389,6 +389,12 @@ seven can also join a tailnet, with a patched build of Tailscale's wasm client (
389
389
  - **Refusals:** a join URL on a hub seven doesn't link through, a forged or expired one, a disconnected leader and an unlink are `EACCES`, so `slicc` exits 3 with the reason. An unlink the owner tab finds while nothing waits is told to the next run, and only the run after that re-links. A tray that can't be reached in time is `ENOTCONN`, which `watch` retries.
390
390
  - **Leaders in the Network panel:** `status().leaders` lists each kept leader by its join key (`id`), its name (`slicc` shown as `SLICC`, with the start of the key when two share a name), its hub's host and its state: `connecting`, `connected`, `reconnecting`, `failed` with the reason (a forged or expired join URL, or a tray that moved to another hub), `disconnected`, or `unlinked` once, in the status that reports it. The status never carries a join URL. **Retry** dials a failed leader again. **Disconnect** closes the link and keeps the record and the certificate: the leader stays disconnected across reloads, seven never dials it, and `slicc` to it is refused until **Reconnect**, which dials it with the same certificate. **Forget** drops the record, join URL included, and leaves only the re-link mark: the hub's certificate stays, so the other sevens on the hub keep their grants, and the next run to it mints the new one, as after an unlink. Other tabs act on them through the owner tab.
391
391
  - **Tests:** `test/integration/follow-slicc.test.mjs` runs the real `slicc` from the catalog as a cone in seven's terminal against a second seven's real agent over a fake tray hub: refused until Allow remote control, then prompt, watch and abort; a vault value scrubbed on the way out; a forged URL, another hub, a scoop and an unlinked device refused; and a fake leader whose `unlink`, `join`, `agent` and `kernel` OPENs are refused while it is followed. `test/integration/leaders.test.mjs` has a fake leader unlink this seven while a real one keeps recognising it after a reload, with its grant, until the re-link makes a new device. Re-links that fail while that leader doesn't answer mint that new certificate once, not once per retry. `test/integration/follow-leaders.test.mjs` drives the Leaders section against a real seven and a fake one on one hub: both connected, Disconnect holding across a reload with no dial while the other is dialled again at boot, Reconnect with the same certificate, an expired tray failing with Retry, and Forget, after which the join URL is nowhere in `localStorage`, IndexedDB or OPFS, the real seven keeps its grant, and the next run to the fake one is a new device.
392
+ - **Guests (guest seats, slicc-agent ≥ 6.0):** the Network panel's **Guests** (spectrum's `GuestPort`, from the agent's `createAgentModel`) invites a person to one live cone through a seat link, lists the seats with their expiry and whether they're connected, and revokes them. The agent owns the seats (`slicc.guests`), their gates and the owner-changeable guest policy; bios only mints and revokes the hub's seat, through the `guests: { mint, revoke }` it gives `createAgentModel` in every tab of the owner's browser.
393
+ - **Minting:** `mint({ label, expiresAt, agentId })` posts `{ label, unitJid: agentId, ttlMs }` to the hub's `POST /api/tray/<trayId>/biscotto` with the tray's controller token as the bearer (no `ttlMs` for a seat without expiry), and returns the seat's id and `https://<this origin>/#seat=<the hub's /join/<token> URL>`, shown once and never kept. `revoke(id)` drops the seat's links here and posts `{ id }` to `POST /api/tray/<trayId>/biscotto/stop` (a 404 counts as done). On `*.sliccy.ai` both go through the sliccy-ai worker's same-origin proxy (sliccy-ai#43), like the other tray routes. Followers list and revoke seats through the agent but can't mint ("Inviting guests needs the tray hub.").
394
+ - **Joining:** the hub announces a seat's join as `follower.join_requested` with `trust: 'biscotto'` and the seat's `biscotto: { id, label }`. The owner offers a guest link of its own, kept apart from linked devices: no device record, no fingerprint, no `.slicc.internal` name or exit, and nothing in `localStorage`. Its HELLO offers `pi` only, and every other kind (`terminal`, `kernel`, `fs`, `agent`, `mounts`, `browsers`, `tcp`, `http`, …) is refused with `EOPNOTSUPP`. One `pi` stream at a time (`EBUSY` otherwise) is bridged to `owner.port({ guest: { id, label } })`, so the worker checks the seat before Chord sees the link and the guest gets only the shared cone's transcript and a composer: no `decide`, no control, settings or panels. A seat keeps at most four links; a fifth drops the oldest.
395
+ - **Revoking:** the hub's `biscotto.revoked` (sent for a revoke from anywhere) and this page's own `revoke(id)` close every link of the seat at once; the agent ends the seat's Chord links and leaves nothing behind, and the hub refuses its token from then on (`INVALID_JOIN_CAPABILITY`). Seven doesn't rely on the hub for that: both also put the seat's id in a local deny list (`localStorage['slicc-os.revoked-seats']`, id → until), kept until the seat's own expiry, or 7 days when seven doesn't know it, and a join on a denied seat is refused with `UNTRUSTED` ("this guest seat was revoked") even if the hub let it through. When the hub's stop fails, `revoke` rejects with "Seven refuses this guest from now on, but the tray hub didn’t revoke the link: …", and the tab shows a warning notice (`guest-seat-stop:<id>`) with **Try again**, which retries the stop and goes once the hub takes it. The deny list stays either way.
396
+ - **The guest's page:** opening the seat link boots the BIOS as usual (the base set and `/opt/agent` go into that browser's OPFS), then the BIOS goes straight to `os/join.html` for a `#seat` (and `os/start.js` sends one there too), a page that opts into the cross-document view transition inline, like `index.html`. That page is `<slicc-chat>` alone inside `.swc-theme` and `<sp-theme system="spectrum-two">`, with spectrum's stylesheet (`@ai-ecoverse/slicc-spectrum/swc.css`, spectrum ≥ 1.86.0: the pinned `@adobe/spectrum-wc` stylesheet with its tokens inlined), following the system's light or dark. It takes the seat URL from the fragment, removes it from the address bar and keeps it in `sessionStorage` (`slicc-os.seat`) for reloads of that tab only, installs `/opt/agent` like a follower, and dials the hub as `slicc-seven/1` with a fresh certificate each time, so a guest is never a device. It opens `pi`, calls slicc-agent's `connectGuest`, and shows `createAgentModel(guest)`, whose `seat` puts the chat in guest mode (with the assistant messages' `model` left out, since a guest gets no model list and the chat would show the raw id): the host's messages read "From the host", and the guest's messages wait for the seat's review. A dropped link reconnects. A revoked or expired seat, a refused port (`EACCES`) or another agent version (`EVERSION`) ends it: the page drops the chat, forgets the seat and says why ("This guest link isn’t valid any more.").
397
+ - **Tests:** `test/integration/follow-guests.test.mjs` invites a guest from a real owner's Network panel against the fake hub, joins from a second browser, delivers an approved guest message, checks that a guest can't stop, abort, steer or reconfigure the cone (its message lands as a follow-up after the owner's run, never in it), can't reach sudo decisions, `connectAgent` or any other stream kind, that a follower sees the seat, and that Revoke closes the link at once and the link is refused after.
392
398
  - **The tray:** its capabilities are kept in `localStorage` (`slicc-os.tray`), so the join URL survives reloads. A hub that forgot the tray gets a new one. The leader socket pings every 30 s and reconnects with backoff. Elsewhere (`localhost`, tests) the tray starts only when `localStorage['slicc-os.tray-hub']` names a hub.
393
399
  - **Kernel side:** the kernel gets one uplink and one route table, the union of what the active exits offer, pushed with `kernel.setRoutes`.
394
400
  - **Choosing an exit:** for each destination, an exit that claims it (a tailnet address or MagicDNS name) wins. Otherwise the default exit takes it: the one that offers a default route, for example Tailscale with an exit node. Otherwise HTTP stays on the picked transport, and raw connections are `ENETUNREACH`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-bios",
3
- "version": "1.15.1",
3
+ "version": "1.16.1",
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",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@ai-ecoverse/slicc": "1.44.0",
46
- "@ai-ecoverse/slicc-browsers": "1.7.10",
46
+ "@ai-ecoverse/slicc-browsers": "1.8.3",
47
47
  "@ai-ecoverse/slicc-node": "2.5.1",
48
48
  "@ai-ecoverse/slicc-shared-web": "1.23.1",
49
49
  "@ai-ecoverse/slicc-spectrum": "1.86.3",
@@ -6,7 +6,7 @@
6
6
  "": {
7
7
  "name": "slicc-bios-packages",
8
8
  "dependencies": {
9
- "@ai-ecoverse/slicc-browsers": "1.7.10",
9
+ "@ai-ecoverse/slicc-browsers": "1.8.3",
10
10
  "@ai-ecoverse/slicc-cdp": "1.26.2",
11
11
  "@ai-ecoverse/slicc-clipboard": "1.0.2",
12
12
  "@ai-ecoverse/slicc-kernel": "1.124.2",
@@ -37,9 +37,9 @@
37
37
  }
38
38
  },
39
39
  "node_modules/@ai-ecoverse/slicc-browsers": {
40
- "version": "1.7.10",
41
- "resolved": "https://registry.npmjs.org/@ai-ecoverse/slicc-browsers/-/slicc-browsers-1.7.10.tgz",
42
- "integrity": "sha512-D0UZs7HCX1V43fb6hPafaFwNCOE7DTbVsDraIoJ2mER14LN2tOraSuLomcSENWaeEQl+m+KeKSPOewnKHEc4xw==",
40
+ "version": "1.8.3",
41
+ "resolved": "https://registry.npmjs.org/@ai-ecoverse/slicc-browsers/-/slicc-browsers-1.8.3.tgz",
42
+ "integrity": "sha512-1mRSTf6nBz3BHcYf8C2LuQPGg5MoMTSJpoDHyzqyXLQ/kxCrDCcKWkxfUPGXOWuGSnBY96QbT6o1qkijZUoQmw==",
43
43
  "license": "Apache-2.0",
44
44
  "engines": {
45
45
  "node": ">=24"
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "description": "Packages the BIOS installs from npm into OPFS, pinned by package-lock.json and pnpm-lock.yaml",
5
5
  "dependencies": {
6
- "@ai-ecoverse/slicc-browsers": "1.7.10",
6
+ "@ai-ecoverse/slicc-browsers": "1.8.3",
7
7
  "@ai-ecoverse/slicc-cdp": "1.26.2",
8
8
  "@ai-ecoverse/slicc-clipboard": "1.0.2",
9
9
  "@ai-ecoverse/slicc-kernel": "1.124.2",
@@ -9,8 +9,8 @@ importers:
9
9
  .:
10
10
  dependencies:
11
11
  '@ai-ecoverse/slicc-browsers':
12
- specifier: 1.7.10
13
- version: 1.7.10
12
+ specifier: 1.8.3
13
+ version: 1.8.3
14
14
  '@ai-ecoverse/slicc-cdp':
15
15
  specifier: 1.26.2
16
16
  version: 1.26.2
@@ -95,8 +95,8 @@ importers:
95
95
 
96
96
  packages:
97
97
 
98
- '@ai-ecoverse/slicc-browsers@1.7.10':
99
- resolution: {integrity: sha512-D0UZs7HCX1V43fb6hPafaFwNCOE7DTbVsDraIoJ2mER14LN2tOraSuLomcSENWaeEQl+m+KeKSPOewnKHEc4xw==}
98
+ '@ai-ecoverse/slicc-browsers@1.8.3':
99
+ resolution: {integrity: sha512-1mRSTf6nBz3BHcYf8C2LuQPGg5MoMTSJpoDHyzqyXLQ/kxCrDCcKWkxfUPGXOWuGSnBY96QbT6o1qkijZUoQmw==}
100
100
  engines: {node: '>=24'}
101
101
 
102
102
  '@ai-ecoverse/slicc-cdp@1.26.2':
@@ -200,7 +200,7 @@ packages:
200
200
 
201
201
  snapshots:
202
202
 
203
- '@ai-ecoverse/slicc-browsers@1.7.10': {}
203
+ '@ai-ecoverse/slicc-browsers@1.8.3': {}
204
204
 
205
205
  '@ai-ecoverse/slicc-cdp@1.26.2': {}
206
206