@ai-ecoverse/slicc 1.44.8 → 1.46.0

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.
Files changed (2) hide show
  1. package/README.md +87 -31
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `slicc` is SLICC's command line for seven, the SLICC on `*.sliccy.ai`. It is one binary for macOS, Windows and Linux. It launches and links browsers, and shares a machine's network, compute and browsers with a running seven over a WebRTC link. `npx sliccy` and Sliccstart.app run this binary. It replaces slicc-node and slicc-swift (see [Coming from slicc-node](#coming-from-slicc-node)).
4
4
 
5
- **Status:** being built. `slicc` (the default flow), `slicc attach` (with the `cdp` capability), `slicc open`, `slicc pair` and `slicc unpair` (seven in your everyday browser, without CDP), `slicc <join-url> follow`, `slicc tray` (on a machine and inside seven), remote control with `prompt`, `watch` and `abort` (also from inside seven's kernel, see [Inside seven](#inside-seven)), host folders with `--mount`, `slicc browsers`, `slicc launch`, `update`, `install`, `--version` and `--help` work so far, plus a hidden diagnostic, `slicc <join-url> follow --signal-only [--timeout 2m]`. It attaches through the tray hub, prints a summary of the leader's offer (its size, media sections, candidate count and DTLS fingerprint) and the ICE server URLs (credentials are never printed), and exits without answering. The link protocol is specified in [Link protocol](#link-protocol).
5
+ **Status:** being built. `slicc` (the default flow), `slicc attach` (with the `cdp` capability), `slicc open`, `slicc pair` and `slicc unpair` (seven in your everyday browser, without CDP), `slicc <join-url> follow`, `slicc tray` (on a machine and inside seven), remote control with `prompt`, `watch`, `wait` and `abort` (also from inside seven's kernel, see [Inside seven](#inside-seven)), host folders with `--mount`, `slicc browsers`, `slicc launch`, `update`, `install`, `--version` and `--help` work so far, plus a hidden diagnostic, `slicc <join-url> follow --signal-only [--timeout 2m]`. It attaches through the tray hub, prints a summary of the leader's offer (its size, media sections, candidate count and DTLS fingerprint) and the ICE server URLs (credentials are never printed), and exits without answering. The link protocol is specified in [Link protocol](#link-protocol).
6
6
 
7
7
  ## Commands (planned)
8
8
 
@@ -15,8 +15,8 @@
15
15
  | `slicc tray [--json] [-q] [-a]` | list the `slicc` processes on this machine that hold a link: mode, seven, device, state and offers (works now; see [Running links](#running-links-slicc-tray)) |
16
16
  | `slicc launch` | launch a browser with CDP in a separate SLICC profile and print its CDP URL |
17
17
  | `slicc <join-url> follow [--exit] [--runner …] [--cdp <url>] [--mount <path>]` | follow a seven's tray from another machine and offer its network, compute (over SSH), browsers or folders (`--exit`, `--runner`, `--cdp` and `--mount` work now; see [Remote setup](#remote-setup-slicc-join-url-follow)). `-` or `@<file>` in place of `<join-url>` reads it from stdin or a file, out of sight of `ps` |
18
- | `slicc <join-url> prompt <text>`, `watch`, `abort` | send a prompt to seven's agent and stream its answer, follow the agent as it works, or abort its run; `--cdp <url>` instead of a join URL links locally (works now; see [Remote control](#remote-control-slicc-prompt-watch-and-abort)) |
19
- | `slicc <join-url> prompt`, `watch`, `abort` inside seven | the same commands from seven's kernel, as `@ai-ecoverse/wasi-slicc` (works now; see [Inside seven](#inside-seven)) |
18
+ | `slicc <join-url> prompt <text>`, `watch`, `wait`, `abort` | send a prompt to seven's agent and stream its answer, follow the agent as it works, wait until it has nothing left to do, or abort its run; `--cdp <url>` instead of a join URL links locally (works now; see [Remote control](#remote-control-slicc-prompt-watch-wait-and-abort)) |
19
+ | `slicc <join-url> prompt`, `watch`, `wait`, `abort` inside seven | the same commands from seven's kernel, as `@ai-ecoverse/wasi-slicc` (works now; see [Inside seven](#inside-seven)) |
20
20
  | `slicc tray [url \| rotate \| unlink <device> \| retry]` inside seven | this seven's tray: its state and devices, the join URL on request, rotate it, drop a device, reconnect (see [Inside seven](#inside-seven)) |
21
21
  | `slicc browsers --json` | list running CDP endpoints and installed browsers, ranked |
22
22
  | `slicc update [--check]` | replace this binary with the latest release (works now; see [Updates](#updates)) |
@@ -203,7 +203,7 @@ A profile that doesn't exist is an error (`SLICC six's Chrome profile doesn't ex
203
203
 
204
204
  **Output.** `slicc --profile six` skips the picker and prints plain lines (or NDJSON with `--json`). When it attaches, the `foreign-browser` notice and event follow `attached`, with `profile` `six` (or `six:<port>`): `slicc: using Chrome (profile six) at http://127.0.0.1:9222, which slicc didn't launch; opened a new seven tab. …`. A launch reports `launched` with `browser` `six`, `profile` `six` and `profileDir`. `slicc launch --profile six` prints `attached` and `detached` events when it attaches (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)). Errors about six's profile go to telemetry as fixed messages, never with a path.
205
205
 
206
- **In the picker** a `Chrome · SLICC six profile` row (`… six profile <port>` for a port variant) appears when that profile exists, with `SLICC six's own profile, used in place`, below every other row. It is never the best match and never preselected: Enter on it needs the cursor moved there, or the row selected with space. Picking it does the same as `--profile six`. A six endpoint in that profile is shown only through its row.
206
+ **In the picker** a `six` row (`six:<port>` for a port variant) appears under `SLICC six profiles` when that profile exists, after the other sections (unless it was the last launch); the section header says it might lose data. It is never the best match and never preselected: Enter on it needs the cursor moved there, or the row selected with space. Picking it does the same as `--profile six`. A six endpoint in that profile is shown only through its row.
207
207
 
208
208
  ## Verifying a download
209
209
 
@@ -299,7 +299,7 @@ Release builds outside npm and app bundles print a notice on stderr when a newer
299
299
  ## Telemetry
300
300
 
301
301
  Release builds (a plain `X.Y.Z` version, linked in or, for `go install`, from the module version) send operational telemetry through [go-optel](https://github.com/ai-ecoverse/go-optel) with the app id `slicc-cli`, sampled once per process (1 in 100 by default):
302
- - `enter`, with the subcommand as `source`, from a fixed list (`abort`, `attach`, `browsers`, `follow`, `install`, `launch`, `open`, `pair`, `prompt`, `unpair`, `update`, `watch`, `default` for no command (no arguments, or only flags), `join` for a bare join URL, otherwise `unknown`). No other argument is ever sent.
302
+ - `enter`, with the subcommand as `source`, from a fixed list (`abort`, `attach`, `browsers`, `follow`, `install`, `launch`, `open`, `pair`, `prompt`, `unpair`, `update`, `wait`, `watch`, `default` for no command (no arguments, or only flags), `join` for a bare join URL, otherwise `unknown`). No other argument is ever sent.
303
303
  - `error`, for operational failures, from a fixed set of sources (`update`, `signal`, `link`, `browser`, `ssh`, `cdp`). Messages pass through go-optel's sanitizer, which strips URLs and paths.
304
304
 
305
305
  `--version`, and any command line with `--help`, `-h` or `-help` (such as `slicc update --help`), send nothing, and development builds never configure a client.
@@ -369,18 +369,21 @@ The cdp list follows the browsers: an extra that stops answering is taken off th
369
369
  - `--default-browser`: don't look for browsers; link a seven in the default browser instead, exactly as `slicc open` does (see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)).
370
370
  - A flag of SLICC six's points back to six and exits 2 (see [Coming from SLICC six](#coming-from-slicc-six)). Any other flag, or an argument, is an error.
371
371
 
372
- **The picker** comes up when stdin and stdout are both terminals and none of `--yes`, `--browser`, `--plain` or `--json` is given. It lists the ranking best first, one row each, with its state: `CDP :<port>` and `seven open` for a running endpoint, `installed · profile <name>` (or `new SLICC profile <name>` when the profile doesn't exist yet) for a browser slicc would launch, and `installed · Electron` for an Electron app. The first row is marked `best match`. A list longer than the terminal scrolls with the cursor.
372
+ **The picker** comes up when stdin and stdout are both terminals and none of `--yes`, `--browser`, `--plain` or `--json` is given. It shows the ranking in sections, each under a header that says what picking a row does: `Running` (CDP endpoints, attached over CDP), `Launch` (installed browsers slicc launches in a SLICC profile of its own, for testing SLICC seven) and `SLICC six profiles` (re-use existing SLICC profile (might lose data), the one warning, in yellow), in the order their best row ranks. Electron apps and headless browsers follow as folded summary rows (`▸ 15 Electron apps extras only · 1 blocks debugging`, `▸ 2 headless browsers`), opened and closed with Enter or space on the row, or with `e` and `h`; they start open when nothing else is listed. A row shows the name, the version (the major version for Chromium, major.minor otherwise; none on six's rows), its labels and, faint on the right, the port, or the browser a six profile opens in: `best match` on the row Enter takes, `last launched` (green), `seven is open`, `profile "<name>"` or `new profile "<name>"` when that SLICC profile doesn't exist yet, when a six profile was last used (`used 2 days ago`, from its `Local State`), `headless`, `websocket only` and `blocks debugging`. Below the list a detail line starts with what Enter does with the highlighted row (`↵ Opens seven in Google Chrome 155 (create new Google Chrome profile "chrome" for testing SLICC seven)`, `↵ Links the seven tab in Google Chrome at :9222`, `↵ Re-uses existing SLICC profile six:51852 in Google Chrome 155 (might lose data)`), with the profile or user-data path under it; with a selection it adds the extras (`…, with Slack and bb as extra browsers`). A browser that can't be selected says so in faint text below the list (`1Password blocks remote debugging, so it can't be picked`). Selected rows are marked `●1` (hosts seven), `●2`, … in selection order. The keys are listed at the bottom. With colour on, the highlighted row has a background bar, and the palette follows the terminal's light or dark background; on a light one faint text is a grey with at least 4.5:1 contrast. A running endpoint found in the process list is named after the app it runs from: the catalog name for a known browser, also for its helper processes (`Google Chrome for Testing` rather than `Chrome`), otherwise the macOS app bundle or the executable (`chrome-headless-shell`). A headless endpoint is one whose user agent says `HeadlessChrome` or whose process has `--headless`; unless it was named with `--cdp` (then it stays under `Running`, marked `headless`), the picker folds it into the dimmed headless rows after the installed browsers and never makes it the best match while anything else can host seven. `--yes`, `--plain` and `slicc browsers --json` keep the ranking below. The browser or SLICC six profile slicc last launched to host seven comes first, marked `last launched`, with the SLICC profile it used; slicc remembers it in `last-launch.json` next to `seven-profiles`. `--yes`, `--plain` and `--json` don't use it. A list longer than the terminal scrolls with the cursor.
373
373
 
374
374
  | key | what it does |
375
375
  | --- | --- |
376
376
  | ↑ ↓ (`k`, `j`) | move |
377
- | space | select or unselect the row; the first selected hosts seven, and the rest are `extra cdp` browsers, in the order selected |
378
- | Enter | go: with the selected rows, or with the highlighted row when nothing is selected |
377
+ | space | select or unselect the row (`●1` hosts seven, `●2`, … are `extra cdp` browsers, in the order selected); on a folded row, open or close it |
378
+ | Enter | go: with the selected rows, or with the highlighted row when nothing is selected; on a folded row with nothing selected, open or close it |
379
379
  | `p` | choose the highlighted browser's SLICC profile: its default, an existing profile in the profiles dir, or `new profile…` with a name (letters, digits, `.`, `_`, `-`) |
380
+ | `e`, `h` | open or close the Electron apps and the headless browsers |
380
381
  | Esc | back, in the profile chooser |
381
382
  | `q`, Ctrl-C | quit; in the dashboard, close down as described above |
382
383
 
383
- **The dashboard** follows: the linked browsers (launched or attached, their CDP port, hosts seven or extra, and `gone` while one doesn't answer), the seven tab, the link state (`waiting for seven`, `connecting`, `linked` with its uptime, or `reconnecting` after a link is lost, while slicc waits for seven to offer again), the capabilities offered, the policy summary, `compute ssh via <runner>` with `--runner`, the kernel services URL, stream counts per kind (open and total, never contents), the last status lines, and the update notice, which then isn't printed on stderr. `NO_COLOR` turns colors off.
384
+ **The dashboard** follows, in sections: BROWSERS (launched or attached, their CDP port, hosts seven or extra, and `gone` while one doesn't answer); SEVEN (the tab, the link state (`waiting for seven`, `connecting`, `linked` with its uptime, `reconnecting` after a link is lost, while slicc waits for seven to offer again, or `relinking` while new capabilities are applied), the policy summary, and stream counts per kind, open and total, never contents); CAPABILITIES (the network capabilities always offered, then `jerk`, `shell` with `ssh via <runner>`, `kernel` with its URL, or why it is off, and the shared folders); RECENT (the last status lines, links in green, losses in yellow, errors in red); and the update notice, which then isn't printed on stderr. Keys: `c` capabilities, `m` share a folder, `q` quit. `NO_COLOR` turns colors off.
385
+
386
+ **Capabilities at runtime.** `c` opens the capabilities screen: `jerk` (when slicc-jerk is installed), `shell` (the `--runner`; turning it on uses `$SHELL -c`, or `pwsh -Command` / `powershell -Command` on Windows), `kernel` (the `--kernel-port`, or off) and the shared folders, each `path[:name][:ro]` as for `--mount`, with tab completing folders. `space` turns a row on or off, `↵` edits it, `a` adds a folder, `r` makes a folder read-only, `x` removes it. Changes collect as pending until `s` applies them (`u` undoes them; leaving with `esc` asks first). Folder changes go live as a mount update (see [Host folders](#host-folders)) when folders are already shared and seven takes `mounts` updates. Every other change relinks: the node says `bye` with the reason `slicc is relinking`, the browsers and the CDP connection stay, and a new node with the new capabilities sends seven's tab a fresh `hello` with the same `node` id, so seven offers again and replaces the link. Streams open on the old link close. A runner changed in the TUI drops `--shell`, `--eval`, `--eval-quiet` and `--sftp` / `--no-sftp`, which belong to the runner given on the command line.
384
387
 
385
388
  **Plain mode** (no terminal, `--yes`, `--browser` or `--plain`): no picker and no dashboard, just the default, with status lines on stderr (`slicc: <name> is still starting (slow disk?), <n> s so far`, `slicc: launched …`, `slicc: attached to …`, `slicc: offering …; policy: …; compute: ssh via <runner>`, `slicc: linked with "<name>" in <tab>`, `slicc: link lost: …`, `slicc: <name> quit …`, `slicc: <name> is back …`, `slicc: closed <ids>`). It never writes ANSI escapes or cursor codes. `SLICC_DEBUG=1` adds diagnostics.
386
389
 
@@ -390,7 +393,7 @@ The cdp list follows the browsers: an extra that stops answering is taken off th
390
393
  - `attached`: `browser`, `name`, `version?`, `role`, `launched?` (`true` when this run launched it), `cdp`, `webSocketDebuggerUrl?`. For `seven` slicc is connected to it over CDP; an `extra` is listed for `cdp`.
391
394
  - `foreign-browser`: `browser`, `name`, `profile?`, `cdp`, `openedTab` (`true` when slicc opened the seven tab, `false` when one was open). It follows `attached` when the browser hosting seven is an endpoint slicc didn't launch and the user didn't choose it (see above), and always when it is SLICC six's Chrome, with `profile` `six` or `six:<port>`. The same line goes to stderr.
392
395
  - `linked`: `peer?` (the name in seven's HELLO), `tab?` (the seven tab's URL), `caps`, `policy` (the summary).
393
- - `link-lost`: `reason?` when slicc or seven closed the link (`slicc is exiting`, seven's `bye` reason, `replaced by a new offer`), or `message?` for a failure. seven sets the link up again after a reload, and a new `linked` follows.
396
+ - `link-lost`: `reason?` when slicc or seven closed the link (`slicc is exiting`, `slicc is relinking`, seven's `bye` reason, `replaced by a new offer`), or `message?` for a failure. seven sets the link up again after a reload, and a new `linked` follows.
394
397
  - `browser-gone`, `browser-back`: `browser`, `name`, `role`. A browser stopped answering and left the cdp list, or answers again and is back on it.
395
398
  - `browser-slow`: `browser`, `name`, `role`, `elapsed` (seconds). A browser slicc launched hasn't written its `DevToolsActivePort` yet, every 10 s from 10 s on (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)). The dashboard and plain mode show it as `<name> is still starting (slow disk?), <n> s so far`.
396
399
  - `closed`: `browsers`, the ids slicc closed (always a list). It is the last event of every run that got past choosing its browsers.
@@ -734,7 +737,7 @@ Link spec §5.14 (J1). slicc-jerk (`jerk`, [Companion tools](#companion-tools))
734
737
  **Offered** when host jerk is found: of the first `jerk` on `PATH` and `jerk` in the data folder's `bin`, in that order, the first whose `jerk --version` names 0.6.0 or later. 0.6.0 is the first slicc-jerk with the `--via-link` calls below. An older jerk counts as absent: slicc says `jerk: host jerk <version> is older than 0.6.0; run slicc install jerk`, and HELLO has neither cap.
735
738
  - `slicc` and `slicc attach`: caps `jerk` and `jerk-seal`, unless `--no-jerk`.
736
739
  - `follow`: only with `--jerk` (cap `jerk`) or `--jerk-seal` (both).
737
- - `prompt`, `watch` and `abort`: never.
740
+ - `prompt`, `watch`, `wait` and `abort`: never.
738
741
  - slicc says `jerk: offering slicc-jerk <version> (jerk, jerk-seal)`, and HELLO adds `"jerk": { "version" }`. Without the cap an OPEN gets `EOPNOTSUPP`.
739
742
 
740
743
  **OPEN** `{ "kind": "jerk", "op", "provider"?, "scope"?, "lease_id"?, "duration"?, "params"? }`. The page sends END right after it. slicc runs jerk as the user, with no terminal and its stderr dropped:
@@ -791,6 +794,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
791
794
  **Seven's origin is off limits.** The node relays CDP through a filter, so seven's browser control never navigates to, opens or attaches to a target on seven's own origin, and never reads or writes its cookies or storage. That holds for every listed browser, not only the one seven runs in. What is protected comes from [slicc-browsers](https://github.com/ai-ecoverse/slicc-browsers#protected-origins)' `protectedOrigins`: every `sliccy.ai` host (the apex and `www` included, any port, `https` and `http`), `vault.sliccy.ai` by name (the vault is never a CDP target for seven's tools), `blob:`, `filesystem:` and `view-source:` URLs by the URL inside them, `devtools:` URLs, and the extra origins: `--origin` for `attach`, and `--origin` and the `--page` origin for `slicc`. `follow` has none. `*.kernel.localhost` is not protected. URLs are parsed the way the browser parses them (WHATWG), and the node forwards its own re-encoding of each command, so the browser runs exactly what was checked: a key given twice counts once, as its last value, and a message that isn't a JSON object with an integer `id` and a string `method` is refused (or dropped, without an `id`).
792
795
  - **Privileged pages** (slicc-browsers ≥ 1.8.0) are protected the same way, as if they were seven's: every URL whose scheme is in `protectedSchemes` (`chrome:`, `chrome-untrusted:`, `chrome-extension:`, `devtools:`, `file:`, from 1.8.5 also `chrome-search:`, `chrome-native:`, `chrome-distiller:`, `isolated-app:`, `chrome-experimental-site-token-provider:`, `externalfile:` and `content:`, and the UI schemes of the catalog's other Chromium browsers, `edge:`, `brave:`, `opera:`, `vivaldi:` and `arc:`), on any host and path, except the new tab pages in `schemeExceptions` (`chrome://newtab`, `chrome://new-tab-page` and its `chrome-untrusted://new-tab-page` frames, `edge://newtab`, `brave://newtab`, `opera://startpage`), matched by the parsed scheme and exact host, so `chrome-untrusted://new-tab-page.evil/` stays protected. `chrome-error:` is not protected: Chrome's network and frame error pages stay readable, snapshottable, reattachable and navigable away from, like the ordinary pages they stand in for. So `chrome://settings`, `chrome://password-manager`, an extension's pages, frames and workers (a password manager's or a wallet's vault included) and any local file are refused, hidden from auto-attach and detached like a seven tab. A wrapper is judged by what it wraps: `view-source:chrome://settings/` and `view-source:file:///…` by their inner URL, `blob:` and `filesystem:` by their origin. An `about:` URL other than `about:blank` and `about:srcdoc` is judged as the `chrome://` URL Chrome makes of it, so `about:settings` is `chrome://settings`. A target whose `type` is in `targetTypes` (`browser_ui`, Chrome's own UI such as `chrome://omnibox-popup.top-chrome/`) is protected whatever its URL, and hidden altogether (see *Listing*).
793
796
  - **Targets.** Before the stream opens, the node attaches its own session to the browser target and calls `Target.setDiscoverTargets` and `Target.getTargets` on it with ids of its own. Neither the session nor its messages ever reach the page. From then on it knows every target's URL and opener. A target on an inheriting URL (`about:blank`, `about:srcdoc`, none) is protected when it was protected before or when its opener is protected or unknown, so a blank popup that seven opens counts as seven. On a page session, the main frame's `securityOrigin` (`Page.frameNavigated`) and the default context's `origin` (`Runtime.executionContextCreated`) count too.
797
+ - **Extension worlds.** Any other execution context on a protected origin, such as a password manager's content-script isolated world on an ordinary page, is hidden rather than cutting the page. Seven never sees it, and commands, objects and call frames that name it are refused with `slicc: refused: that execution context belongs to a browser extension` ([§5.8](#58-cdp-cap-cdp-page--node)).
794
798
  - **Refused**, with `{"id":…,"sessionId"?:…,"error":{"code":-32000,"message":"slicc: refused: target is on seven's origin"}}`, never forwarded: `Target.createTarget`, `Page.navigate` and anything else whose `url` is protected; `Page.navigateToHistoryEntry` to an entry whose URL is protected (the node looks it up with `Page.getNavigationHistory`); `Target.attachToTarget`, `activateTarget`, `closeTarget`, `autoAttachRelated`, `Browser.getWindowForTarget` and any other command whose `targetId` or `frameId` is a protected target; and every command on a session whose target is protected, or became so. `Target.getTargetInfo` and `Target.detachFromTarget` stay allowed, except `Target.getTargetInfo` on a hidden target (see *Listing*).
795
799
  - **Origins and storage.** A command whose `origin`, `securityOrigin`, `ownerOrigin`, `issuerOrigin`, `topLevelOrigin`, `topLevelSite`, `embeddedOrigin`, `requestingOrigin`, `scopeURL` or `manifestId` is protected, or whose `storageKey` or `cacheId` names seven's storage, is refused the same way. This covers `Storage.*`, `IndexedDB.*`, `CacheStorage.*`, `DOMStorage.*`, `FileSystem.*`, `ServiceWorker.*`, `PWA.*`, `Browser.setPermission` and `Browser.grantPermissions`. Free-form values (`expression`, `arguments`, `value`, `headers`, `postData` and the like) aren't searched.
796
800
  - **Cookies.** `Network.getCookies`, `Network.getAllCookies`, `Storage.getCookies` and `Page.getCookies` answer without the cookies whose domain is protected (`sliccy.ai` and every subdomain, and each extra origin's host and its parents: cookies ignore ports), and without partitioned cookies whose `partitionKey.topLevelSite` is such a site. `Network.setCookie`, `setCookies`, `deleteCookies`, `Storage.setCookies` and `Page.deleteCookie` for such a domain, a URL on such a host, or a partition key under such a site, are refused with `slicc: refused: cookies on seven's origin`. So are `Network.clearBrowserCookies` and `Storage.clearCookies`, except in a browser context this stream created with `Target.createBrowserContext`.
@@ -985,7 +989,7 @@ The watcher per OS:
985
989
  - **macOS:** one FSEvents stream on the folder (`FSEventStreamCreate` with file events, through purego, so the binary stays free of cgo). It holds no descriptor on anything in the tree and never follows symlinks, so tree size doesn't matter. A dropped or coalesced batch (`MustScanSubDirs`) is an `all`, and a folder that is moved or removed (`RootChanged`) is lost until it comes back.
986
990
  - **Windows:** one `ReadDirectoryChangesW` on the folder with `bWatchSubtree`, which is what libuv does. A handle on every directory would stop Windows from renaming any of their parents.
987
991
 
988
- **Mount updates** (page cap `mounts`). When folders are added or removed while a link is up (`fs.Server.Add` and `Remove`, for the TUI), the node opens `OPEN { "kind": "mounts", "mounts": [ … ] }` on the control channel, with the full list in HELLO's shape. It replaces the previous list, and there are no deltas. The page answers OPENED `{}` and END, and the node sends END. Updates go one at a time per link, and one the page doesn't finish within 10 s is reset with `ETIMEDOUT`. A removed folder's handles close (`EBADF` if the name comes back), its operations are `ENOENT`, and a watch stream covering it ends with `RESET ENOENT: no such folder`. `--mount` alone never changes the set.
992
+ **Mount updates** (page cap `mounts`). When folders are added or removed while a link is up (`fs.Server.Add` and `Remove`, from the TUI's capabilities screen; `fs.Server.Live` says whether a linked page takes them), the node opens `OPEN { "kind": "mounts", "mounts": [ … ] }` on the control channel, with the full list in HELLO's shape. It replaces the previous list, and there are no deltas. The page answers OPENED `{}` and END, and the node sends END. Updates go one at a time per link, and one the page doesn't finish within 10 s is reset with `ETIMEDOUT`. A removed folder's handles close (`EBADF` if the name comes back), its operations are `ENOENT`, and a watch stream covering it ends with `RESET ENOENT: no such folder`. `--mount` alone never changes the set.
989
993
 
990
994
  **Errors.** The code is the POSIX name, `EIO` when there is none. The message is libuv's lower-case text for it (`no such file or directory`), or slicc's own for the cases it names (`path escapes the folder`, `read-only folder`, `file changed since it was opened`, `the folder root cannot be moved or removed`). It never contains a host path. Windows errors are mapped the way libuv maps them, for example `ERROR_ACCESS_DENIED` to `EPERM` and `ERROR_SHARING_VIOLATION` to `EBUSY`.
991
995
 
@@ -1085,7 +1089,7 @@ slicc @<file> follow [flags]
1085
1089
 
1086
1090
  `follow` links this machine to a seven on another machine through the tray hub (see [Tray hub signaling](#tray-hub-signaling-remote-setup)). It serves the link until interrupted (`SIGINT`, `SIGTERM`) and reconnects when the link drops. Status lines go to stderr, and `SLICC_DEBUG=1` adds diagnostics. The join URL is never printed.
1087
1091
 
1088
- **The join URL off the command line.** On the command line, `ps` shows the join URL to every user of this machine. In its place, `-` reads it from stdin and `@<file>` from a file: the first non-empty line, trimmed. From stdin slicc reads only up to that line's end, so the rest of stdin is left for `prompt -`. The file must be a regular file, not a symlink. On macOS and Linux it must also belong to you and be readable by no one else (`chmod 600`). Exit 2 follows when there's no join URL, when the file can't be read, breaks those rules or is over 64 KiB, or when what's there isn't a join URL. The message names the file but never shows what it holds, inside seven too. `prompt`, `watch` and `abort` take `-` and `@<file>` the same way, inside seven too. There is no environment variable for it, because `--runner`'s processes would inherit it.
1092
+ **The join URL off the command line.** On the command line, `ps` shows the join URL to every user of this machine. In its place, `-` reads it from stdin and `@<file>` from a file: the first non-empty line, trimmed. From stdin slicc reads only up to that line's end, so the rest of stdin is left for `prompt -`. The file must be a regular file, not a symlink. On macOS and Linux it must also belong to you and be readable by no one else (`chmod 600`). Exit 2 follows when there's no join URL, when the file can't be read, breaks those rules or is over 64 KiB, or when what's there isn't a join URL. The message names the file but never shows what it holds, inside seven too. `prompt`, `watch`, `wait` and `abort` take `-` and `@<file>` the same way, inside seven too. There is no environment variable for it, because `--runner`'s processes would inherit it.
1089
1093
 
1090
1094
  **Flags.**
1091
1095
  - `--exit`: offer this machine's network to seven, as `tcp` and `resolve` streams (cap `net`), `http` streams (cap `http`), `ws` streams (cap `ws`) and `udp` streams (cap `udp`) under the remote policy, so public addresses only by default. Without `--exit`, all five are refused with `EOPNOTSUPP`.
@@ -1109,16 +1113,18 @@ HELLO from `follow`: `{ "v": 1, "role": "node", "name", "host", "version", "mode
1109
1113
 
1110
1114
  **Stops** with exit 1 and says why on `INVALID_JOIN_CAPABILITY`, `TRAY_EXPIRED` or `TRAY_SUPERSEDED_MISSING_JOIN_URL` from the hub, and on `UNTRUSTED`, `UNSUPPORTED_RUNTIME` or `NO_FINGERPRINT` (an internal error: the answer had no SHA-256 fingerprint) from seven. The device's fingerprint is always SHA-256.
1111
1115
 
1112
- ## Remote control: `slicc prompt`, `watch` and `abort`
1116
+ ## Remote control: `slicc prompt`, `watch`, `wait` and `abort`
1113
1117
 
1114
1118
  ```
1115
- slicc <join-url> prompt [--steer | --reject] [--thinking] [--json] [--plain] [--abort-on-interrupt] <text>|@file|-
1119
+ slicc <join-url> prompt [--allsettled <quiet>] [--steer | --reject] [--thinking] [--json] [--plain] [--abort-on-interrupt] <text>|@file|-
1116
1120
  slicc <join-url> watch [--thinking] [--json] [--plain]
1121
+ slicc <join-url> wait [--allsettled <quiet>] [--timeout <limit>] [--json]
1117
1122
  slicc <join-url> abort
1118
1123
  slicc prompt --cdp <url> [--origin <origin>]... [flags] <text>|@file|-
1119
1124
  slicc watch --cdp <url> [--origin <origin>]... [flags]
1125
+ slicc wait --cdp <url> [--origin <origin>]... [flags]
1120
1126
  slicc abort --cdp <url> [--origin <origin>]...
1121
- slicc prompt|watch|abort --signal stdio [flags] …
1127
+ slicc prompt|watch|wait|abort --signal stdio [flags] …
1122
1128
  ```
1123
1129
 
1124
1130
  These commands talk to seven's agent over a link that offers nothing to seven (`caps: []`).
@@ -1148,6 +1154,28 @@ These commands talk to seven's agent over a link that offers nothing to seven (`
1148
1154
  - `130` on Ctrl-C or `SIGTERM`.
1149
1155
  - **Ctrl-C** leaves the run going in seven. With `--abort-on-interrupt`, slicc first opens `abort`.
1150
1156
 
1157
+ **`prompt --allsettled <quiet>`** (for benchmarks and scripts) goes on after the prompt's run until seven has nothing left to do. The cone can end a run while scoops it started are still working, and run again when their results come in. `<quiet>` is a duration such as `2m` or `0s`.
1158
+ - **Needs** a seven whose agent is @ai-ecoverse/slicc-agent 6.15.0 or later, the first that reports `busy`. `wait` needs it too.
1159
+ - **Settled** means seven says it isn't busy (`busy` false, see [`agent`](#agent-page-cap-agent-node--page) below), and nothing has come from seven for `<quiet>`. The quiet period starts once the stream is open, and every line from seven restarts it.
1160
+ - **A seven that doesn't report `busy`:** before sending anything, slicc opens a `watch` with `busy`. When seven's answer doesn't say whether it's busy, slicc says `this seven doesn't report when its agent is idle; update it` and exits 1. The prompt isn't sent.
1161
+ - **Output:** the prompt's run streams as usual. After its `end`, slicc opens `watch` with `busy`, and `since` the last event it showed, so the cone's later runs stream as they would in `watch`, with nothing shown twice.
1162
+ - **`--json`:** the prompt's events up to its `end` line, then the watch's events, as seven sent them. The last line is slicc's own `{"t":"settled"}`.
1163
+ - **Exit status:** the prompt's own run decides. An `end` with `error` or `aborted` exits 1 at once, as without `--allsettled`. Otherwise slicc exits 0 once seven has settled, whatever the later runs did.
1164
+ - A stream or link that ends before seven has settled exits 1 (`lost the link to seven; the run goes on there`).
1165
+ - Bad usage, a missing permission and Ctrl-C exit as for `prompt` above.
1166
+ - **No time limit of its own.** A lick that's held in seven's outbox keeps seven busy, and so does a scoop that never finishes. Bound the wait with the caller's own timeout, or with `wait --timeout`.
1167
+
1168
+ **`wait`** follows the agent without sending anything, and exits once seven has settled, as `prompt --allsettled` does. `--allsettled <quiet>` sets the quiet period (default `0s`: exit as soon as seven isn't busy).
1169
+ - **Output:** `slicc: seven is idle` on stderr, and nothing on stdout. With `--json`, the events as seven sent them, then `{"t":"settled"}`.
1170
+ - **`--timeout <limit>`:** give up after `<limit>` (default: no limit). slicc says `seven didn't settle within <limit>` and exits 124.
1171
+ - **Exit status:**
1172
+ - `0` once seven has settled;
1173
+ - `1` for a seven that doesn't report `busy`, and for a stream or link that ends first;
1174
+ - `2` for bad usage;
1175
+ - `3` without permission;
1176
+ - `124` on timeout;
1177
+ - `130` on Ctrl-C or `SIGTERM`.
1178
+
1151
1179
  **`watch`** follows the agent until Ctrl-C or `SIGTERM`, then exits 0.
1152
1180
  - **Output:** `user` events go to stdout as `> <text>`, on one line. Assistant text goes to stdout as for `prompt`, and a line ends at each run's end. Everything else goes to stderr, as for `prompt`, plus `run ended: <reason>` for a run that didn't end with `stop`. `--json` prints the events instead.
1153
1181
  - **Status lines** on stderr: `slicc: watching <name>`, `slicc: lost the link to <name>; reconnecting`, and for the tray `<reason>; trying again in <wait>`.
@@ -1186,10 +1214,11 @@ Summaries are one line of at most 200 characters. Escape sequences and control c
1186
1214
  - The page then delivers it. Its first line is `{"t":"delivered","delivered"}`, or it resets with `EBUSY` instead.
1187
1215
  - **Aborting:** a RESET from the node, or a closed link, doesn't abort the run. Only `abort` does.
1188
1216
 
1189
- **`watch`:** `OPEN { "kind": "agent", "op": "watch", "since"?: "<entry id>", "thinking"?: true }` on its own channel.
1190
- - **Answer:** OPENED `{ "head": "<entry id>", "gap"?: true }`, then NDJSON events until either side resets.
1217
+ **`watch`:** `OPEN { "kind": "agent", "op": "watch", "since"?: "<entry id>", "thinking"?: true, "busy"?: true }` on its own channel.
1218
+ - **Answer:** OPENED `{ "head": "<entry id>", "gap"?: true, "busy"?: true | false }`, then NDJSON events until either side resets.
1191
1219
  - **Without `since`:** the events start at the next one.
1192
1220
  - **With `since`:** the page first replays `since`'s own entry when it's `text` or `thinking` (its whole text so far, as one `delta`), then every entry after it. `gap: true` means the transcript no longer holds `since`, and the events start at the next one.
1221
+ - **With `busy`** (`prompt --allsettled` and `wait`): OPENED says whether the agent has anything left to do, and a `busy` line follows each time that changes. An OPENED without `busy` means seven doesn't report it.
1193
1222
 
1194
1223
  **`abort`:** `OPEN { "kind": "agent", "op": "abort" }`, answered OPENED `{ "aborted": true | false }` (`false` when nothing was running), then END.
1195
1224
 
@@ -1206,6 +1235,7 @@ Summaries are one line of at most 200 characters. Escape sequences and control c
1206
1235
  | `{"t":"notice","id","text"}` | retries, compactions, rewinds |
1207
1236
  | `{"t":"error","id","message","detail"?}` | an error |
1208
1237
  | `{"t":"delivered","delivered"}` | a long prompt was taken (`prompt` only) |
1238
+ | `{"t":"busy","busy","why"?}` | the agent became busy or idle (`watch` with `busy` only) |
1209
1239
  | `{"t":"end","reason","message"?}` | the last line of a `prompt` stream |
1210
1240
 
1211
1241
  - **Ids:** `id` is the transcript entry id, and `delta` lines for one `id` arrive in order.
@@ -1257,8 +1287,9 @@ PID MODE SEVEN DEVICE STATE OFFERS
1257
1287
  `slicc` also runs inside seven's kernel, so that one seven's agent or user can prompt, watch and abort another seven's agent, and see and manage this seven's own tray. That build is `slicc.wasm`, slicc-cli's client commands compiled with `GOOS=wasip1 GOARCH=wasm` and `CGO_ENABLED=0`, and it ships as the npm package `@ai-ecoverse/wasi-slicc`, at slicc-cli's own version. seven's page holds the WebRTC link; slicc only reaches the page's link facade.
1258
1288
 
1259
1289
  ```
1260
- slicc <join-url> prompt [--steer | --reject] [--thinking] [--json] [--plain] <text>|@file|-
1290
+ slicc <join-url> prompt [--allsettled <quiet>] [--steer | --reject] [--thinking] [--json] [--plain] <text>|@file|-
1261
1291
  slicc <join-url> watch [--thinking] [--json] [--plain]
1292
+ slicc <join-url> wait [--allsettled <quiet>] [--timeout <limit>] [--json]
1262
1293
  slicc <join-url> abort
1263
1294
  slicc tray [--json]
1264
1295
  slicc tray url
@@ -1269,7 +1300,7 @@ slicc --version
1269
1300
  slicc --help
1270
1301
  ```
1271
1302
 
1272
- - **Commands:** `prompt`, `watch` and `abort` with a join URL behave as in [Remote control](#remote-control-slicc-prompt-watch-and-abort): the same flags, output, styling, `--json` events, resuming and exit codes, from the same code. The differences:
1303
+ - **Commands:** `prompt`, `watch`, `wait` and `abort` with a join URL behave as in [Remote control](#remote-control-slicc-prompt-watch-wait-and-abort): the same flags, output, styling, `--json` events, resuming and exit codes, from the same code. The differences:
1273
1304
  - **Ctrl-C** ends slicc with 130 (the kernel's default for `SIGINT`), `watch` included, and a running prompt goes on in the other seven. `--abort-on-interrupt` isn't offered.
1274
1305
  - **Exit 3** covers the facade's own refusals too. When the facade gives a message (`unlock the vault to continue`, or the other seven's refusal), slicc prints it instead of the Linked devices hint.
1275
1306
  - **A terminal** is a standard stream that is a character device. Styling then follows `TERM`, `NO_COLOR` and `--plain` as usual.
@@ -1282,7 +1313,7 @@ slicc --help
1282
1313
  - **`slicc tray retry`** reconnects the tray's links, as the Reconnect button does.
1283
1314
  - **Not here:** choosing the exit (that's routing, not tray state), and SLICC six's `host join` and `host leave`: a seven follower runs no kernel or agent of its own, so following stays with `#follow=` and the UI's Leave.
1284
1315
  - **Exit status:** 0, 1 on failure (`ENOTCONN` when the tray isn't connected, a facade that doesn't answer within 1 minute, or 5 minutes for `rotate` and `unlink`, which seven may ask you to approve), 2 for bad usage, 3 when seven refuses (`EACCES`).
1285
- - **Everything else** exits 2 with `slicc: <command> isn't available inside seven`: `follow`, `attach`, `open`, `browsers`, the default flow, `update`, `install`, and `prompt`, `watch` or `abort` without a join URL. `--help` lists only what works there.
1316
+ - **Everything else** exits 2 with `slicc: <command> isn't available inside seven`: `follow`, `attach`, `open`, `browsers`, the default flow, `update`, `install`, and `prompt`, `watch`, `wait` or `abort` without a join URL. `--help` lists only what works there.
1286
1317
  - **What the build leaves out** (build constraints): pion and the link, CDP, SSH, mounts, the kernel listener, the policy, browser discovery, telemetry and updates.
1287
1318
 
1288
1319
  **The link facade.** slicc reads its address from `SLICC_LINK_URL` only, as `tcp://127.0.0.1:<port>` on the kernel's loopback (any `127.x` address, or `localhost`). Without it, or with another shape, slicc exits 1. Each `agent` stream (see [`agent`](#agent-page-cap-agent-node--page)) is one connection, carrying records of a `u8` type, a `u32` big-endian length and the payload, both ways:
@@ -1651,6 +1682,15 @@ On a [§1.3](#13-local-setup-without-cdp-loopback-signalling) loopback link, `un
1651
1682
  - **Waiting for that guard:** page commands on a touched target's session wait while the node sets its guard up there. The wait fails closed after 30 s without progress (any answer from the browser on the stream's socket, the node's own `Browser.getVersion` probe included, or any event for that target or its sessions) or 5 min in all, whichever comes first, and the refusal names the bound. At most 4096 commands or 64 MiB wait on a stream; more is `RESET ENOBUFS`.
1652
1683
  - **Sessions the node doesn't know:** a page command whose `sessionId` the node hasn't seen on this stream (in a `Target.attachedToTarget`, in the answer to the page's `Target.attachToTarget` or `Target.attachToBrowserTarget`, or its own) is held, not forwarded, until the session appears, and is then decided as usual. Only messages on that session id count as progress. The same bounds and caps apply, and the refusal is `slicc: refused: slicc doesn't know which target this session belongs to: …`, naming the bound. A session that detached is refused at once, without the suffix. Commands without a `sessionId` are unaffected.
1653
1684
  - **Auto-attached targets with no URL yet** (OOPIFs, popups) are held until the URL is known, then detached if protected. After 10 s with no URL they are treated as protected. The node's own setup connection ([§1.1](#11-local-setup-over-cdp)) isn't a `cdp` stream and is the one exception. [`cdp` (browser control)](#cdp-browser-control) has the details.
1685
+ - **Protection follows the target's own effective origin. Execution contexts on a protected origin are hidden, not cut,** unless the context is the target's own main-frame default context; that one still cuts the target as above. The typical case is an extension's content-script isolated world (`chrome-extension://<id>`) on an ordinary page. The page stays usable, and the hidden world is invisible and out of reach.
1686
+ - **Hidden:** the node learns every context of a touched target on its own guard session, whatever the page enabled. It drops these events from the stream: the world's `Runtime.executionContextCreated` and `executionContextDestroyed`, its `consoleAPICalled`, `exceptionThrown` and `bindingCalled`, and `Debugger.scriptParsed`/`scriptFailedToParse` for scripts in it. It also drops `Debugger.breakpointResolved` in those scripts. A `Debugger.paused` whose top frame is in the world is dropped, and the node resumes the target. The world's frames lower in the stack are removed from the call frames.
1687
+ - **Refused** with `slicc: refused: that execution context belongs to a browser extension`: any command that names the world through a `contextId` or `executionContextId`, `uniqueContextId`, an `executionContextName` the world has, a remote `objectId` or `*ObjectId` (call arguments included) or `callFrameId` whose injected-script part is the world, or the `scriptId` of a script in it. So `Runtime.evaluate`, `callFunctionOn`, `compileScript`, `runScript`, `globalLexicalScopeNames`, `queryObjects`, `getProperties`, `addBinding`, `Debugger.evaluateOnCallFrame`, `setVariableValue`, `setScriptSource` and `DOM.resolveNode` there are all refused. The default world, and isolated worlds the page creates itself, are unaffected. A `uniqueContextId` on a target's session that names no live context the node has seen announced on that target, on its guard session or the page's (say one from a closed tab or another live tab, or made up), is refused with `slicc: refused: that execution context is not exposed`, rather than passed to Chrome. On page-like sessions, a `Runtime.addBinding` with neither `executionContextId` nor `executionContextName` is sent with `executionContextName: ""`, so it reaches every frame's main world now and after reloads, but no isolated world.
1688
+ - **Lifetime:** a hidden world is forgotten only when Chrome reports it gone (`executionContextDestroyed`, or `executionContextsCleared` on a session that reported it), never to make room. A target with more than 4096 live hidden worlds is refused every command there that names an execution context, object, call frame, script or context name, until its contexts are cleared. Seen contexts follow the same lifetime and bound; once a target passes 4096, the `uniqueContextId`s of contexts announced after that are refused as not exposed until its contexts are cleared, even if some are destroyed in between.
1689
+ - **Heap profiling:** a page's heap snapshot covers its whole renderer, other worlds included. So on page, iframe, background page and webview sessions every `HeapProfiler` command except `enable`, `disable`, `collectGarbage` and `getHeapObjectId` is refused with `slicc: refused: HeapProfiler reads the page's whole renderer, browser extensions' worlds included`, whether or not an extension is there.
1690
+ - **Breakpoint conditions** run in whatever world the script belongs to, and a breakpoint by URL outlives reloads. So on those sessions a `Debugger.setBreakpointByUrl` with a `condition` needs a non-empty `url`, which can't name a protected script (see above). One by `urlRegex` or `scriptHash` or with an empty `url` is refused with `slicc: refused: a breakpoint condition that could run in a browser extension's world needs a url naming the page's own script`. Breakpoints without a condition are allowed; a pause they cause in a hidden world is resumed out of sight.
1691
+ - **Profiles, coverage, stacks and initiators** can name any script in the isolate. So the node scrubs every message it sends the page, events and command results alike, outside opaque values (`value`, `preview`, `customPreview`, `arguments`, `returnValue`). Any `callFrames` list loses the frames on a protected URL or in a hidden world's script. That covers console, exception, pause, async, node-creation and initiator stacks, whatever the method. A coverage or type-profile list (entries with `scriptId` and `functions` or `entries`) also loses its entries for scripts without a URL. A profile node's `callFrame` in such a script becomes `(program)` and loses its `positionTicks`. An `initiator` on a protected `url` loses that `url` and its position.
1692
+ - **Messages without a context:** on page-like sessions, the node drops `Console.messageAdded` whose `url` is empty or protected. It drops `Log.entryAdded` on a protected `url` or with a stack through such a frame. It drops `Profiler.consoleProfileStarted` and `consoleProfileFinished` altogether: they name no context, so the node can't tell whose `console.profile` they are.
1693
+ - **Requests and sockets a hidden world starts:** a `Network.requestWillBeSent`, `webSocketCreated` or `webTransportCreated` whose initiator stack runs through a frame on a protected URL or in a hidden script, or whose initiator `url` is protected, loses those frames and that URL. The node treats it like a request to a protected origin: cookie headers are stripped, and its bodies are refused.
1654
1694
 
1655
1695
  #### 5.8.1 `browsers` updates (page cap `browsers`), node → page
1656
1696
  - **When:** the browser list changes while a link is up (a browser quits, or the TUI adds one). The node then opens `OPEN { "kind": "browsers", "browsers": [ … ] }` on the control channel, carrying the **full** current list in HELLO's shape. It replaces the previous list; there are no deltas.
@@ -1721,14 +1761,14 @@ slicc-node's hostfs ([its README, *Host folders*](https://github.com/ai-ecoverse
1721
1761
  - Paths go through `\\?\`, so device names and 8.3 names are plain names inside the folder.
1722
1762
 
1723
1763
  #### 5.11 `agent` (page cap `agent`), node → page: remote control
1724
- `slicc prompt`, `slicc watch` and `slicc abort` talk to seven's agent, the cone, which slicc-agent hosts. The page serves each stream through `remoteControl(connection)` from `@ai-ecoverse/slicc-agent/page`, on top of its `AgentConnection`, so the CLI never speaks pi's protocol.
1764
+ `slicc prompt`, `slicc watch`, `slicc wait` and `slicc abort` talk to seven's agent, the cone, which slicc-agent hosts. The page serves each stream through `remoteControl(connection)` from `@ai-ecoverse/slicc-agent/page`, on top of its `AgentConnection`, so the CLI never speaks pi's protocol.
1725
1765
 
1726
1766
  - **Who may:** the page advertises `agent` only to links the user allowed to control it.
1727
1767
  - A **local** link ([§1.1](#11-local-setup-over-cdp)) gets it by default, because a CLI holding the browser's CDP can drive the page anyway.
1728
1768
  - A **remote** link gets it only after the user turns on *Allow remote control* for that device in Linked devices. It's off by default, and keyed on the fingerprint.
1729
1769
  - Seven lists every device that has linked once, even one that's offline or offered no caps, so the toggle can be found after a refused `prompt`.
1730
1770
  - Without the cap, the node doesn't open `agent` streams, and the page refuses them with `EACCES`.
1731
- - **Control-only links:** `prompt`, `watch` and `abort` links advertise `caps: []`. The page gives them no `<host>.slicc.internal`, no exit entry, and opens no `kernel` streams to them.
1771
+ - **Control-only links:** `prompt`, `watch`, `wait` and `abort` links advertise `caps: []`. The page gives them no `<host>.slicc.internal`, no exit entry, and opens no `kernel` streams to them.
1732
1772
  - **One device, several processes:** the node's HELLO ([§4](#4-hello)) and the [§1.1](#11-local-setup-over-cdp) hello both carry a random per-process `"node"` id.
1733
1773
  - The page replaces an older link of the same device (local: any local link; remote: the same fingerprint) only when `node` matches.
1734
1774
  - Over the tray, the page learns `node` at HELLO, so any replacement waits for HELLO.
@@ -1748,6 +1788,21 @@ slicc-node's hostfs ([its README, *Host folders*](https://github.com/ai-ecoverse
1748
1788
  - **Without `since`,** the stream starts at the next event.
1749
1789
  - **With `since`,** the page first replays `since`'s own entry when it's `text` or `thinking`, as one `delta` with its whole text so far. Then it replays every entry after it. Clients skip what they already printed.
1750
1790
  - **`gap: true`** means the transcript no longer holds `since` (a new session, or a compaction). The stream then starts at the next event.
1791
+ - **`busy`** (`"busy": true` in the OPEN) is for `slicc prompt --allsettled` and `slicc wait`. The cone can end a run while scoops it started are still working, and run again when their results come in as `scoop-notify` licks. A client that sees only the cone's events can't tell that apart from being done.
1792
+ - **OPENED** carries `"busy": true | false`, the state after any replay. A page that doesn't support it leaves `busy` out, and the node then reports that seven doesn't support it.
1793
+ - **Then** the page sends `{"t":"busy","busy":true|false}` each time the value changes, in order with the other events. It has no `id`, and it's never replayed.
1794
+ - **`busy` is true** while, for the cone the stream belongs to:
1795
+ - the cone has a run or a queued input;
1796
+ - a live scoop of that cone has a run, a queued input or a feed;
1797
+ - a lick for the cone is queued or still to be delivered (a `scoop-notify` result, or a scoop `wait`'s delivery). That includes a lick held in the agent's outbox until its conversation's model is known. If the model never comes, `busy` stays true, so the client bounds its wait.
1798
+ - **It turns false** only when none of that holds. A lick from outside (a cron, a file watch) counts once it's queued, not before.
1799
+ - **`why`:** a `busy: true` line may carry `"why"`, a list of what keeps the agent busy, to be logged. The node doesn't use it, and it's left out when `busy` is false. The values come from a fixed list:
1800
+ - `"cone"`: the cone has a run or a queued input;
1801
+ - `"scoop:<name>"`: that live scoop (its name, as seven shows it) has a run, a queued input or a feed;
1802
+ - `"pending-lick"`: a lick for the cone is queued or still to be delivered.
1803
+ - **Read-only status:** `busy` lines carry only the boolean and `why`. They never carry prompt text, tool arguments or output, and asking for `busy` changes nothing in the agent.
1804
+ - **Ordering:** a `run start` never comes before the `busy: true` that covers it, and the `busy: false` comes after the last `run end`.
1805
+ - **Computed** from the durable documents' subscriptions (the cone's and its scoops' `pi.live`, `ScoopWorkDoc.feeds` and the licks outbox), and a line goes out only when the value flips.
1751
1806
  - **Events:** one JSON object per line, mapped by slicc-agent from pi-durable's agent events (`watchEvents`) and handed to the page by `remoteControl(connection)` from `@ai-ecoverse/slicc-agent/page`.
1752
1807
  - `{"t":"user","id","text","delivered"}`;
1753
1808
  - `{"t":"text","id","delta"}`: assistant text as it streams;
@@ -1755,7 +1810,8 @@ slicc-node's hostfs ([its README, *Host folders*](https://github.com/ai-ecoverse
1755
1810
  - `{"t":"tool","id","call","name","summary"}` and `{"t":"tool-result","id","call","ok","summary"}`;
1756
1811
  - `{"t":"run","state":"start"|"end","reason"?}`. These have no `id`, so a resume doesn't replay a missed `run end`. Clients use them only for layout and notices, and the `end` line or the next `user` event is authoritative.
1757
1812
  - `{"t":"notice","id","text"}`: retries, compactions, rewinds;
1758
- - `{"t":"error","id","message","detail"?}`.
1813
+ - `{"t":"error","id","message","detail"?}`;
1814
+ - `{"t":"busy","busy":true|false,"why"?:[…]}`: only on `watch` streams opened with `busy` (above).
1759
1815
  - **Ids** are opaque strings.
1760
1816
  - `user`, `tool-result`, and a `notice` or `error` that is a transcript entry: the entry id, such as `"42"`.
1761
1817
  - An assistant message's `text`, `thinking` and `tool` blocks: `"a<ts>.<block>"`, where `<ts>` is the message's own timestamp (set when its generation starts, and kept in the committed entry) and `<block>` its content index. An assistant message isn't an entry until it's committed, so this id is stable from the first delta to the committed entry. A second message with the same timestamp gets `"a<ts>~1.<block>"`, a third `~2`, and so on.
@@ -1827,7 +1883,7 @@ jerk ([ai-ecoverse/slicc-jerk](https://github.com/ai-ecoverse/slicc-jerk)) gets
1827
1883
  |---|---|---|
1828
1884
  | `slicc`, `slicc attach` (local) | yes, when `jerk` ≥ the minimum version is on the host's `PATH` or in slicc's data dir. `--no-jerk` turns both off. | yes, by default |
1829
1885
  | `slicc <join> follow` (remote) | only with an explicit `--jerk` | only with an explicit `--jerk-seal`, which implies `--jerk`. A remote seven pulling this machine's tokens into its browser is a bigger grant than starting a sign-in. |
1830
- | `prompt`, `watch`, `abort` | no | no |
1886
+ | `prompt`, `watch`, `wait`, `abort` | no | no |
1831
1887
  | any node without host jerk | no. Host providers are missing in seven. | no |
1832
1888
 
1833
1889
  HELLO adds `"jerk": { "version": "1.0.0" }` next to `caps`.
@@ -1918,7 +1974,7 @@ From [slicc-bios#360](https://github.com/ai-ecoverse/slicc-bios/pull/360) and [s
1918
1974
  How a kernel program in seven reaches [§1.4](#14-a-seven-as-follower-leader-to-leader), and this seven's own tray. This isn't a link stream. It's seven's page serving its own kernel. The page holds the WebRTC; the program never sees the hub, the join state or the peer connection.
1919
1975
 
1920
1976
  - **The program:** `@ai-ecoverse/wasi-slicc`, slicc-cli's client command layer built for `GOOS=wasip1` (published by slicc-cli, offered by bios's catalog).
1921
- - **Commands:** `slicc <join-url> prompt|watch|abort`, with the CLI's own flags, output and `--json` events, and `slicc tray` (below).
1977
+ - **Commands:** `slicc <join-url> prompt|watch|wait|abort`, with the CLI's own flags, output and `--json` events, and `slicc tray` (below).
1922
1978
  - **Anything else** exits 2 with `slicc: <command> isn't available inside seven`. A prompt without a join URL is refused the same way.
1923
1979
  - **How it reaches the facade:** through its host module, at `SLICC_LINK_URL` (below).
1924
1980
  - **Where it's served:** a page-served kernel facade with the caller's stamp on each connection, mirroring the CDP facade on `127.0.0.1:9222`. The mechanism is [slicc-kernel#526](https://github.com/ai-ecoverse/slicc-kernel/issues/526), shipped in [slicc-kernel 1.90.0](https://github.com/ai-ecoverse/slicc-kernel/tree/v1.90.0#page-facades) ([#556](https://github.com/ai-ecoverse/slicc-kernel/pull/556)):
@@ -2029,7 +2085,7 @@ Go 1.26 and Node ≥ 24 (for the lint tools). `npm run build` writes `bin/slicc`
2029
2085
 
2030
2086
  **Test names: `--hosts <file>`** (test-only, left out of `--help`). `slicc attach`, `slicc open`, `slicc pair` and `slicc <join-url> follow` take a hosts file, so tests get deterministic names without public DNS. Each line is `<ip> <name> [<name>…]`, and `#` starts a comment. Names match case-insensitively, with or without a trailing dot, and a name on several lines gets every address, in file order. Wherever the node resolves a name (`resolve`, `tcp` by name, `http`, and the policy's [browser endpoints](#network-policy)), a name in the file gets the file's addresses of the requested family, possibly none, and never the system resolver's. Other names go to the system resolver. The policy applies afterwards as usual: `resolve` drops refused addresses, and a dial to a refused address is `EACCES`. `tcp` and `http` try the addresses in file order. A file that can't be read or parsed exits 2. A name mapped to a loopback address only helps the node's own dials, `http` and `tcp` by name: a seven kernel that gets a loopback address from `resolve` dials its own loopback, because loopback never goes out through an exit. For end-to-end exit tests, map test names to a non-loopback address of the machine.
2031
2087
 
2032
- Front ends and capabilities follow a node's links with `node.Node.Subscribe(func(node.Event)) (unsubscribe func())`. Events: `connecting` (an offer was answered), `linked` (the peer's HELLO arrived, in `Peer`), `link-down` (`Reason` when the node closed the link: seven's `bye` reason, `replaced by a new offer` or `slicc is exiting`; otherwise `Err` for a failure; a failed answer has no `Link`), and `stream-open` and `stream-close` for each page-opened stream, with its kind. Every event but a failed answer carries its `*link.Link`. A link's `link-down` comes before the next link's `connecting`, but its streams' `stream-close` can come after its `link-down`. Events are delivered synchronously on the node's and the link's goroutines, so a subscriber must return quickly and never block; one that needs to wait hands the event to its own goroutine.
2088
+ Front ends and capabilities follow a node's links with `node.Node.Subscribe(func(node.Event)) (unsubscribe func())`. Events: `connecting` (an offer was answered), `linked` (the peer's HELLO arrived, in `Peer`), `link-down` (`Reason` when the node closed the link: seven's `bye` reason, `replaced by a new offer`, or the reason of its own `bye`: `slicc is exiting`, or the `node.Bye` that cancelled `Serve`'s context as its cause, such as `slicc is relinking`; otherwise `Err` for a failure; a failed answer has no `Link`), and `stream-open` and `stream-close` for each page-opened stream, with its kind. Every event but a failed answer carries its `*link.Link`. A link's `link-down` comes before the next link's `connecting`, but its streams' `stream-close` can come after its `link-down`. Events are delivered synchronously on the node's and the link's goroutines, so a subscriber must return quickly and never block; one that needs to wait hands the event to its own goroutine.
2033
2089
 
2034
2090
  **Test isolation: `SLICC_DISCOVERY`** (test-only). When it is set, browser discovery (`slicc browsers`, the default flow, and `browser.Discover` in the same process) never probes ports 9222–9229, never reads the process list, and finds only `--cdp` URLs, plus:
2035
2091
  - `isolated`, or any value not listed here: nothing else;
@@ -2071,13 +2127,13 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2071
2127
  2. `scripts/package.sh <version>` builds `darwin`, `linux` and `windows` × `amd64`, `arm64` with `CGO_ENABLED=0`, `-trimpath`, `-buildvcs=false` and `-X main.version=<version>`, and `slicc-wasip1-wasm.wasm` (`GOOS=wasip1 GOARCH=wasm`) the same way, with the Go toolchain `go.mod` names (see [Reproducing a release](#reproducing-a-release)). It signs and notarizes the darwin binaries and writes `SHA256SUMS`. The windows and linux binaries are not signed. Without the Apple signing secrets (forks, pull requests) it warns and leaves the darwin binaries unsigned.
2072
2128
  3. `scripts/npm-stage.mjs` stages `@ai-ecoverse/slicc` and the six platform packages into `dist/npm/`. It also stages the same launcher as `sliccy` (with two bins, `sliccy` and `slicc`, and a short preview README) into `dist/npm-next/`, as version `7.0.0-next.<version>`, with exact `optionalDependencies` on this release's platform packages. `npx sliccy@next` runs the bin named after the package, `sliccy`, and both bins run the same launcher. `--bin <name>` (repeatable) overrides the bins of any staged launcher. `--wasi` also stages `@ai-ecoverse/wasi-slicc` into `dist/npm/wasi-slicc/` from `slicc-wasip1-wasm.wasm` and `npm/wasi-slicc/host/slicc-host.mjs`, with README.md's [Inside seven](#inside-seven) section as its README; `--wasi-only` stages only that package.
2073
2129
  4. `actions/attest-build-provenance` attests every binary, the `.wasm` included, through `SHA256SUMS`.
2074
- 5. semantic-release checks the version again (`dist/VERSION`, and `scripts/go-major.mjs`, see below), tags, creates the GitHub release with every binary and `SHA256SUMS`, then `scripts/npm-publish.mjs` publishes the platform packages and the launcher through npm trusted publishing (OIDC, with provenance), and then `sliccy` with `--tag next`. Versions already on npm are skipped. Before publishing `sliccy`, the script checks that every exact-pinned `optionalDependencies` version is on the registry, waits up to `SLICC_REGISTRY_WAIT_SECONDS` (default 900) for freshly published ones to appear, and fails otherwise, so a `sliccy` version never points at missing platform packages.
2130
+ 5. semantic-release checks the version again (`dist/VERSION`, and `scripts/go-major.mjs`, see below), tags, creates the GitHub release with every binary and `SHA256SUMS`, then `scripts/npm-publish.mjs` publishes the platform packages and the launcher through npm trusted publishing (OIDC, with provenance), and then `sliccy` with `--tag next`. Versions already on npm are skipped. Before publishing `sliccy`, the script checks that every exact-pinned `optionalDependencies` version is on the registry, waits up to `SLICC_REGISTRY_WAIT_SECONDS` (default 900) for freshly published ones to appear, and fails otherwise (exit code 3), so a `sliccy` version never points at missing platform packages.
2075
2131
  6. `scripts/go-warm.sh <version>`, once the tag is on `origin`, asks `proxy.golang.org` for the module at `v<version>` (`go list -m` with a scratch module cache) and asks `pkg.go.dev` to fetch it. The proxy fetch is retried briefly; the `pkg.go.dev` fetch is one attempt of at most 10 s, since the proxy fetch already gets the version indexed. Both only warn, and the step can't fail the release.
2076
2132
 
2077
2133
  **Certification in the pull request** (`.github/workflows/cert-review-pr.yml`, `tools/cert-review/`). Every same-repo pull request gets the commit status `cert-review` on its head, which the merge queue requires beside `ci`. Fork pull requests get none: only Lars merges one. Every tool runs from the pull request's base commit, main's copy, never from the code under test, which is only built and tested; the review uses `refs/pull/N/merge` and records its first parent and the head.
2078
- - **plan** (light runner) runs on opened, synchronize, reopened, and `merge-ok` or `cert-review-approved` being added (or the approval removed), and plans the whole run again, since such an event cancels a run in progress; other labels neither start nor cancel anything. A `pending` status left on the head by a run that ended without a result becomes `error` (re-run the failed jobs), so the check never waits forever. The shared self-guard (`cert-self-guard`, pinned) holds a change to the reviewer's own paths (`REVIEWER_GLOBS` in `tools/cert-review/lib.mjs`: all of `.github/**`, every workflow and action, so no pull request's own workflow can post `cert-review`, and `tools/cert-review/**`) until a coordinator puts `cert-review-approved` on that head. Such pull requests always go to the coordinator before queueing.
2134
+ - **plan** (light runner) runs on opened, synchronize, reopened, ready_for_review, converted_to_draft, and `cert-review-approved` being added or removed, and plans the whole run again, since such an event cancels a run in progress; other labels neither start nor cancel anything. **Draft status replaces the old `merge-ok` label:** a draft is neither built nor reviewed and its `cert-review` is red `draft: not reviewed, mark it ready for review`, so an older result on the same head can't satisfy the check; **mark ready = reviewed, and merges on green**. With the hold rule (held = draft) the order for a PR that needs the coordinator's security or link-spec read is: the coordinator reviews the draft, it is marked ready, the cert runs (with its Claude review), then the queue; a cert finding means a new head, back to draft and a re-read. Renovate's pull requests aren't drafts, so they're reviewed by themselves. A `pending` status left on the head by a run that ended without a result becomes `error` (re-run the failed jobs), so the check never waits forever. The shared self-guard (`cert-self-guard`, pinned) holds a change to the reviewer's own paths (`REVIEWER_GLOBS` in `tools/cert-review/lib.mjs`: all of `.github/**`, every workflow and action, so no pull request's own workflow can post `cert-review`, and `tools/cert-review/**`) until a coordinator puts `cert-review-approved` on that head. Such pull requests always go to the coordinator before queueing.
2079
2135
  - **build** (hosted `ubuntu-latest`, no secrets) builds the merge commit and its first parent the release's way, each in a full clone with its tags: `SLICC_SIGN=0 scripts/package.sh <v>`, `npm-stage.mjs` for every package, and `npm pack`, with one version `X.Y.(Z+1)-pr.N.<run>` on both sides. `outputs.mjs` hashes every release file and tarball.
2080
- - **classify** decides by output where the build can see it: a pull request is behaviour when a binary, the wasm, `SHA256SUMS` or an npm tarball differs from main's build. Paths whose effect the unsigned build never shows are behaviour whatever it outputs: `scripts/**` (signing and notarization, publishing, the release plan, `go-*`, `sliccy-next.mjs`), `install.sh`, `npm/**`, `package.json` and `package-lock.json` (they choose the semantic-release code the release runs), `.releaserc*`, and the reviewer's paths. Otherwise the status is `not behaviour: identical build outputs` (success), so a docs or test change that ships nothing new needs nothing more. Renovate puts `merge-ok` on its pull requests (`renovate.json`), so a dev-pin bump is reviewed by itself and merges on green. A behaviour change without `merge-ok` is `waits for merge-ok to be reviewed` (failure); with it, the review starts.
2136
+ - **classify** decides by output where the build can see it: a pull request is behaviour when a binary, the wasm, `SHA256SUMS` or an npm tarball differs from main's build. Paths whose effect the unsigned build never shows are behaviour whatever it outputs: `scripts/**` (signing and notarization, publishing, the release plan, `go-*`, `sliccy-next.mjs`), `install.sh`, `npm/**`, `package.json` and `package-lock.json` (they choose the semantic-release code the release runs), `.releaserc*`, and the reviewer's paths. Otherwise the status is `not behaviour: identical build outputs` (success), so a docs or test change that ships nothing new needs nothing more. Renovate's pull requests aren't drafts, so a dev-pin bump is reviewed by itself and merges on green. A behaviour change on a draft is `draft: not reviewed, mark it ready for review` (failure); once it's ready, the review starts.
2081
2137
  - **rebuild** (heavy runner) runs [the recipe](#reproducing-a-release) on a `git archive` of the merge commit, in another path on another machine; every release file must be byte-identical to build's.
2082
2138
  - **delta** names the Go tests the pull request adds or changes in `test/integration/*_test.go` (the `func Test…` a hunk falls in). A changed helper counts as changing every test in its file; a helper in a file with no tests is a test gap (`helper <name>`), never a guess.
2083
2139
  - **suite** (heavy runner): the candidate leg runs the whole integration suite on the merge commit on Linux (`go test -json`); the base leg is the NEGATIVE: main's tree with the pull request's whole `test/` diff applied, running only its tests, against main's own binary. A fix whose test fails there and passes on the candidate is `negative`; passing on both is `no-negative`; not compiling or skipping on main is `prev-not-run`. Gaps are known issues, never a FAIL by themselves.
@@ -2115,7 +2171,7 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2115
2171
 
2116
2172
  **Recovering `sliccy@next`.** When a release tagged and published its platform packages but `sliccy` didn't go out (a registry that lagged past the wait, say), `gh workflow run release.yml --ref main -f sliccy_next_only=true` publishes it without a new release. On `main` only, that run skips the release job and runs `scripts/sliccy-next.mjs`. The script takes the latest `vX.Y.Z` tag reachable from `HEAD`, stages `sliccy@7.0.0-next.<version>` with the launcher (`npm/slicc.js`) from that tag and exact `optionalDependencies` on that version's platform packages, and publishes it through `npm-publish.mjs` with `--tag next`. The same guards apply: the `SLICCY_NEXT_PUBLISH` gate, the tag and version checks, the registry check, and a version that is already published is skipped. Only the latest release can be recovered, so `next` never moves backwards.
2117
2173
 
2118
- Every pull request's CI checks the packaging in its `installer-assets` job: an unsigned build of all six targets, the npm packages and `sliccy` (with `--tag next`) packed with `--dry-run`, and `scripts/sliccy-next.mjs --dry-run` (the recovery path against the latest real release, registry check included). The `installer` jobs then run `install.sh` against those assets with curl on Linux and with wget on Alpine; the macOS curl run is left out of pull requests, the merge queue and pushes to `main`, so each needs only one macOS runner, and runs in the nightly's full matrix. A pull request that changes `release.yml`, `.releaserc.json`, or a script only the release runs (`scripts/package.sh`, `scripts/npm-*.mjs`, `scripts/nightly-gate.mjs`, `scripts/release-plan.mjs`, `scripts/sliccy-next.mjs`, `scripts/go-*`, `scripts/is-main.mjs`, `scripts/verdict.sh`) also runs the release workflow as a dry run: the same checks on the release's macOS runner, and each binary run with `--version` on its own OS and CPU.
2174
+ Every pull request's CI checks the packaging in its `installer-assets` job: an unsigned build of all six targets, the npm packages and `sliccy` (with `--tag next`) packed with `--dry-run`, and `scripts/sliccy-next.mjs --dry-run` (the recovery path against the latest real release, registry check included). The dry run doesn't wait for the latest release: when its platform packages aren't all served yet, it is still publishing (npm has taken 25 minutes), so it checks the release before it instead, with the usual wait, and fails when that one is missing too; only the real publish waits for the latest. The `installer` jobs then run `install.sh` against those assets with curl on Linux and with wget on Alpine; the macOS curl run is left out of pull requests, the merge queue and pushes to `main`, so each needs only one macOS runner, and runs in the nightly's full matrix. A pull request that changes `release.yml`, `.releaserc.json`, or a script only the release runs (`scripts/package.sh`, `scripts/npm-*.mjs`, `scripts/nightly-gate.mjs`, `scripts/release-plan.mjs`, `scripts/sliccy-next.mjs`, `scripts/go-*`, `scripts/is-main.mjs`, `scripts/verdict.sh`) also runs the release workflow as a dry run: the same checks on the release's macOS runner, and each binary run with `--version` on its own OS and CPU.
2119
2175
 
2120
2176
  The workflow reads these repository secrets. Each group is optional: without the first the darwin binaries stay unsigned, without the second they are signed but not notarized:
2121
2177
  - macOS signing: `APPLE_CERTIFICATE_BASE64`, `APPLE_DEVELOPER_CERTIFICATE_PASSWORD`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc",
3
- "version": "1.44.8",
3
+ "version": "1.46.0",
4
4
  "description": "SLICC's command line for seven: links a machine's network, compute and browsers to seven over WebRTC",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -18,12 +18,12 @@
18
18
  "node": ">=18"
19
19
  },
20
20
  "optionalDependencies": {
21
- "@ai-ecoverse/slicc-darwin-arm64": "1.44.8",
22
- "@ai-ecoverse/slicc-darwin-amd64": "1.44.8",
23
- "@ai-ecoverse/slicc-linux-arm64": "1.44.8",
24
- "@ai-ecoverse/slicc-linux-amd64": "1.44.8",
25
- "@ai-ecoverse/slicc-windows-arm64": "1.44.8",
26
- "@ai-ecoverse/slicc-windows-amd64": "1.44.8",
21
+ "@ai-ecoverse/slicc-darwin-arm64": "1.46.0",
22
+ "@ai-ecoverse/slicc-darwin-amd64": "1.46.0",
23
+ "@ai-ecoverse/slicc-linux-arm64": "1.46.0",
24
+ "@ai-ecoverse/slicc-linux-amd64": "1.46.0",
25
+ "@ai-ecoverse/slicc-windows-arm64": "1.46.0",
26
+ "@ai-ecoverse/slicc-windows-amd64": "1.46.0",
27
27
  "@ai-ecoverse/slicc-jerk": "^1.0.0"
28
28
  }
29
29
  }