@ai-ecoverse/slicc-spectrum 1.111.0 → 1.111.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -255,9 +255,9 @@ The chevron only expands and collapses; a click on the row, <kbd>Enter</kbd> or
255
255
  <kbd>→</kbd> expands a cone and then moves to its first scoop, <kbd>←</kbd> moves from a scoop to its cone and then collapses it, and neither changes the active agent.
256
256
  Several cones can be open at once, and they stay open for every view of the panel on the same model, so a layout switch keeps them; when the active agent has changed since the panel last showed it, its cone opens again as the panel mounts, so on a phone a cone you tapped still shows its scoops when you come back to Agents, and a cone you closed stays closed.
257
257
  When the active scoop's cone is collapsed, the cone row takes the Tab stop.
258
- A row's first line starts with one circle: its unread count when there are unread messages (`99+` from a hundred up, at `--swc-font-size-50`, white on the accent fill, or on the negative fill when the cone is in error), otherwise a dot coloured by status.
258
+ A row's first line starts with a dot coloured by status, always in the same place, and, when there are unread messages, the unread count beside it (`99+` from a hundred up, at `--swc-font-size-50`, white on the accent fill, or on the negative fill when the cone is in error).
259
259
  Then comes the name; the context fill, when it's high, sits in the top right corner.
260
- Every row has up to two lines, aligned to the top under the name, starting with its status in words (`.state`, such as "Working"), then its conversation title, so the status stays visible when the unread count takes the circle; the accessible name and the tooltip say the status and the exact unread count too.
260
+ Every row has up to two lines of conversation title, aligned to the top under the name, with no status word, so nothing moves when the status changes; the accessible name and the tooltip say the status and the exact unread count.
261
261
  A cone with scoops has a ribbon line below: one segment per scoop, coloured by status at 3:1 or more on every row background, then its scoops in words, naming only the busy ones, as in "2 scoops · 1 waiting" ("2 scoops" when all are idle).
262
262
  Its `aria-description` sums up every status, as in "2 scoops: 1 working, 1 idle".
263
263
  Rows without scoops or errors have no empty ribbon or error line.
@@ -298,7 +298,7 @@ While pending, cards leave the log for their own region between the header and t
298
298
  Clicking a waiting card (not its controls), or <kbd>Enter</kbd> on it, scrolls the log to where it was asked, at the bottom of the view above the composer.
299
299
  When a card leaves the region (answered, timed out or decided elsewhere) while it has focus, focus moves to the newest card still waiting, else the composer; a timed-out card goes back to its place in the log.
300
300
  Under `prefers-reduced-motion`, nothing animates and the scroll is instant.
301
- The header is one row: the status, a model picker that names each model's provider and leaves out classifiers and models with `tools: false` (with no model on offer, **Set up a model**, `data-action="set-up-model"`, opens Settings), the thinking picker, the context meter and **New conversation**, which calls `clear` and puts focus back in the composer; on a cone, when the port can freeze, its tooltip says the old conversation stays in the Freezer.
301
+ The header is one row: a dot coloured by status (`data-agent-status`, with the status in its `aria-label` and tooltip, and no word, so the pickers never move; in forced colours it paints `CanvasText`, like the Agents markers), a model picker that names each model's provider and leaves out classifiers and models with `tools: false` (with no model on offer, **Set up a model**, `data-action="set-up-model"`, opens Settings), the thinking picker, the context meter and **New conversation**, which calls `clear` and puts focus back in the composer; on a cone, when the port can freeze, its tooltip says the old conversation stays in the Freezer.
302
302
  It never wraps: when it's short of room, the model picker's label is cut with an ellipsis, and under 560px wide (`header[data-compact]`) New conversation is icon-only, with `accessible-label` and its tooltip.
303
303
  The pickers re-read `models()` on `settings`, `accounts` and `agents`.
304
304
  When a live agent's model, or `Settings.model`, is no longer offered and its provider (the id before `/`) isn't a connected account, it moves to the offered chat model with `default: true`, else the first one; frozen agents are left alone, and so is everything on a follower unless it has Control and is connected.
@@ -678,16 +678,20 @@ const name = await prompt({ title: 'New cone', label: 'Name', action: 'Create',
678
678
  ### Secret rules check
679
679
 
680
680
  `secretRules(command, cwd)`, exported from `dist/slicc-ui.js` (`@ai-ecoverse/slicc-spectrum/ui`), is the check behind the sudo dialog's secret-rules warning.
681
- It takes a sudo request's command (without `sudo` and its options) and working folder, and returns `{ certain, maybe }`:
681
+ It takes a sudo request's command (without `sudo` and its options) and working folder, and returns `{ certain, maybe, once }`:
682
682
 
683
683
  - `certain` is an absolute path when the command names a file or folder holding SLICC's secret rules (`/etc/fnox`, `/etc/fnox/config.toml`, `/etc/slicc/secrets-policy.toml`), a folder above them it changes as a whole (`/`, `/etc`, `/etc/slicc`), a glob or brace that matches one, or a file named like them (`fnox`, `secrets-policy`) anywhere, as `/tmp/fnox/config.toml`.
684
684
  It sees through quotes, backslashes, `$'…'` and `$"…"`, `cd` and `pushd` (also inside `bash -c`), relative paths and `..`, extglobs, `/proc/self/root` and `/proc/self/cwd`, option clusters like `-t/etc/fnox`, and comma lists.
685
685
  When every command in it is read-only and nothing in it expands, quoted text is data: a jq or yq program such as `'.a // .b | {x: .y}'` isn't split into paths, and `*`, `?`, `[` and `{` in quotes (and `$` in single quotes) are literal, as the shell treats them. Other commands, such as `sh -c '…'`, are still looked into.
686
686
  - `maybe` is the token when the command may reach one of them and the check can't tell where it leads: an expansion (`$X`, `$(…)`, backticks), a `~user/..`, `~+` or `~-` path, a `/proc/<pid>/cwd` or `/dev/fd` path, or a command over 4096 characters (its first 40 followed by `…`).
687
+ It's also set, only when nothing above applies (a separate check that never clears or changes those verdicts), when an explicit write names its target from data the check can't see: `yq -s`, `--split-exp` or `--split-exp-file` (`yq -s`), or `xargs` running a command that can write or run others, such as `xargs rm < list` (`xargs rm`). `xargs` running a read-only command (`xargs cat < list`, `xargs grep x -- < list`, `xargs --replace=X cat X < list`, and `xargs sort`, `xargs find`, `xargs uniq -c` or `xargs yq .` with no write flag in the command; with `--replace` or `--eof` split from its value, both readings must be read-only) is a plain read and stays clean.
688
+ - `once` is set, when the other two are `null`, to `xargs` and the program when `xargs` runs a program that's read-only only without its write flags (`sort`, `find`, `file`, `tree`, `base64`, `uniq`, `xxd`, `yq`) and shows none: `xargs sort < list` gives `once: 'xargs sort'`. The command is a plain read, but `xargs` adds arguments from its input, which could be a write flag (`printf -- '-o\n/etc/slicc/secrets-policy.toml\n' | xargs sort`) or, for `uniq` and `xxd`, an output file. So it can be allowed once but not always. `xargs cat`, `xargs grep` and other programs without write flags leave it `null`. No other read-only program takes arguments from data.
689
+ Leading assignments (`A=1`, `A+=1`, `a[1]=x`) and the wrappers `command`, `env`, `exec`, `ionice`, `nice`, `nohup`, `setsid`, `stdbuf`, `time` and `timeout`, with their options (abbreviated long ones too), don't hide `xargs` or `yq` (`command xargs rm < list`, `env -i A=1 nice -n 5 xargs rm < list`, `timeout --sig KILL 5 xargs rm < list`); `env -S`/`--split-string` is split into the command it runs (`env -S'xargs rm'`; one with a `\` escape or `$` gives `env -S`), a string run by a shell's `-c` (also in a cluster, `bash -lc '…'`, after `sh`, `bash`, `dash`, `zsh`, `ksh` or `fish`) or by `eval` is looked into, the word after `!`, `{`, `if`, `then`, `else`, `elif`, `do`, `while`, `until` and `coproc` is a command, and `command -v xargs` runs nothing.
687
690
 
688
- Both are `null` for a clean command, and at most one is set.
691
+ All three are `null` for a clean command, and at most one is set.
689
692
  The dialog shows "This changes secret rules" for `certain` and "This may change secret rules" for `maybe`, and offers no Always allow for either.
690
- An embedder that records "always" decisions itself, such as slicc-bios, must refuse "always" (answer "once" at most) whenever either is set, so a decision sent without the dialog can't add a rule that runs it unasked.
693
+ For `once` it shows no notice and offers no Always allow, with "Always allow isn’t offered: xargs adds arguments from its input, and they could include a write flag." (`[data-once-only]`, the program in the attribute) when the request has `always`.
694
+ An embedder that records "always" decisions itself, such as slicc-bios, must refuse "always" (answer "once" at most) whenever any of the three is set, so a decision sent without the dialog can't add a rule that runs it unasked.
691
695
 
692
696
  ```js
693
697
  import { secretRules } from './dist/slicc-ui.js';
@@ -696,7 +700,7 @@ secretRules("bash -c 'rm /etc/fn\\ox/config.toml'");
696
700
  secretRules('rm -rf $TARGET', '/home/cone');
697
701
  ```
698
702
 
699
- The first gives `{ certain: '/etc/fnox/config.toml', maybe: null }`, the second `{ certain: null, maybe: '$TARGET' }`. The result's type is `SecretRulesVerdict`.
703
+ The first gives `{ certain: '/etc/fnox/config.toml', maybe: null, once: null }`, the second `{ certain: null, maybe: '$TARGET', once: null }`. The result's type is `SecretRulesVerdict`.
700
704
 
701
705
  ### Sprinkle bridge
702
706
 
@@ -966,13 +970,13 @@ Later backed by: BIOS.
966
970
  `list()` returns waiting `Approval`s; emit `approvals` when the list changes.
967
971
  A `sudo` approval has `id`, `requester` (`kind` `agent` or `user`, `name`, optional `agentId` and `where`, such as a terminal's title), `command` (without `sudo`; bios joins the kernel's argv shell-quoted), optional `target` (the user to run as; shown as `sudo -u {target}` unless `root`), optional `cwd` and `verdict` (`{ decision: 'allow' | 'deny' | 'unsure', reason }`, the automatic check’s view), `createdAt`, `expiresAt` (epoch ms) and `always` (offer Always allow, which covers the requester's user and this exact command).
968
972
  With `decide(id, 'once' | 'always' | 'deny')`, which resolves once the decision is recorded and then drops the request from `list()`, the tab shows a warning alert dialog (`dialog[data-approval][data-kind][data-requester]`): "Allow sudo?", "{name} wants to run this with sudo[ in {where}]:" for an agent or "You ran this with sudo[ in {where}] (as {name}):" for a user, the command, "In {cwd}", a notice when the command changes secret rules, what Always allow does, "Denied automatically in m:ss." and how many more wait, with **Deny** (`data-action="deny"`, focused; Escape also denies), **Always allow** (`always`) and **Allow once** (`once`).
969
- A command that names `/etc/fnox` (or anything in it) or `/etc/slicc/secrets-policy.toml`, resolved against `cwd`, globs included (with quotes and backslashes removed, operands after `--` included; a path with `[`, `{` or `$` counts when the part before it could lead to them), or that names `/`, `/etc` or `/etc/slicc` in a command other than a read-only one (such as `ls`, `cat`, `stat`, `grep`, `jq`, `cut`, `od`, `sha256sum` or `md5sum`; `find` without `-exec`, `-execdir`, `-ok`, `-okdir`, `-delete` or `-fprint`/`-fls`; `sort` without `-o`, `-T` or `--compress-program`; `yq` without `-i`, `-s` or `--split-exp-file`; `base64` without `-o`; `tree` without `-o` or `-R`; `file` without `-C`; `uniq` and `xxd` with one operand at most, counting everything after `--`; a flag counts inside a short cluster, with a value attached (`-o/x`, `-i.bak`), as `--name=value` and abbreviated; `less` and other pagers aren't, since they run commands), or runs with `cwd` in `/etc/fnox`, or names a relative path with `fnox` or `secrets-policy` in it wherever `cd` may have led, gets "This changes secret rules." with the path (`[data-secret-rules]`, the path in the attribute).
973
+ A command that names `/etc/fnox` (or anything in it) or `/etc/slicc/secrets-policy.toml`, resolved against `cwd`, globs included (with quotes and backslashes removed, operands after `--` included; a path with `[`, `{` or `$` counts when the part before it could lead to them), or that names `/`, `/etc` or `/etc/slicc` in a command other than a read-only one (such as `ls`, `cat`, `stat`, `grep`, `jq`, `cut`, `od`, `sha256sum` or `md5sum`; `find` without `-exec`, `-execdir`, `-ok`, `-okdir`, `-delete` or `-fprint`/`-fls`; `sort` without `-o`, `-T` or `--compress-program`; `yq` without `-i`, `-s` or `--split-exp-file`; `base64` without `-o`; `tree` without `-o` or `-R`; `file` without `-C`; `uniq` and `xxd` with one operand at most, counting everything after `--`; a flag counts inside a short cluster, with a value attached (`-o/x`, `-i.bak`), as `--name=value` and abbreviated (the data-write check below reads clusters more exactly: there a cluster ends at `=` or at an option that takes a value, so `yq -o=json` isn't `yq -s`, and an option's value isn't a `uniq` or `xxd` operand); `less` and other pagers aren't, since they run commands), or runs with `cwd` in `/etc/fnox`, or names a relative path with `fnox` or `secrets-policy` in it wherever `cd` may have led, gets "This changes secret rules." with the path (`[data-secret-rules]`, the path in the attribute).
970
974
  Read-only commands count only by bare name or from `/bin` and `/usr/bin`.
971
975
  In a command made only of read-only commands and with no live expansion, the quoted words count whole: `jq '.a // .b' f` doesn't name `/`, and `jq '{a: .b}' f` in `/etc` isn't a brace that could lead to the rules.
972
976
  `/proc/<pid>/root` counts as `/` and `/proc/self/cwd` as `cwd`.
973
- Otherwise a command with a parameter expansion or command substitution (`$…`, backticks), a `~+` or `~-` path or a `~` path that climbs with `..`, a path through another process's `/proc/<pid>/cwd`, `fd` or `map_files` or through `/dev/fd`, or more than 4096 characters, gets "This may change secret rules." with the first such part in the attribute.
977
+ Otherwise a command with a parameter expansion or command substitution (`$…`, backticks), a `~+` or `~-` path or a `~` path that climbs with `..`, a path through another process's `/proc/<pid>/cwd`, `fd` or `map_files` or through `/dev/fd`, an explicit write whose target comes from data (`yq -s`, `--split-exp`, `--split-exp-file`, or `xargs` running a command that isn't read-only), or more than 4096 characters, gets "This may change secret rules." with the first such part in the attribute.
974
978
  Other symlinks aren't followed.
975
- Either way there's no Always allow, even when `always` is `true` (`decide(id, 'always')` isn't called for it), so each change to the rules is asked for.
979
+ Either way there's no Always allow, even when `always` is `true` (`decide(id, 'always')` isn't called for it), so each change to the rules is asked for. The same goes for `xargs` running a gated read-only program (`once`), which shows no notice but says why Always allow isn’t offered.
976
980
  For half a second after a request shows, only Deny works, so a double-click can't approve it.
977
981
  A rejected `decide` shows its message in the dialog (`[data-error]`, `dialog[data-rejected]`) in place of the countdown, with only **Close** (`data-action="close"`, focused; Escape also closes), which moves on to the next waiting request or closes the dialog.
978
982
  Pass `decide` in every tab: the first decision wins.
@@ -1001,7 +1005,7 @@ The vault's view for SLICC, typed after `@ai-ecoverse/slicc-vault` (spectrum re-
1001
1005
  Emit `status` and `list` when they change (bios re-lists after the vault's `changed` events), and `availability` when the optional `availability()` (`connecting`, `waiting`, `locked` or `ready`) changes.
1002
1006
  `audit(before?, limit?)` resolves with an older `AuditPage` (`before` is a `seq`).
1003
1007
  The optional page ops, each hiding its control when missing: `put(name, value, request?)` (a value request's `put` must use its `name`), `cancel(request)`, `remove(name, kind?)`, `restore(name, kind?)`, `import(kind, bytes, { password?, pick? })` (a `pick` stage, then `done`), `export(passphrase)` (the bundle's bytes), `lock()`, `dismissBackupReminder()` and `endpoints({ add?, remove? })`, which registers or takes back the URLs a page-sealed credential may be filled on (each an `EndpointSet`, `{ model?: [{ name, url }], mcp?: [{ server, url }] }`, a model URL bound to one record by name), checks the whole change first (`EINVAL` changes nothing) and rejects with a sentence. Settings registers them itself, never through the agent. `settings(name, kind, settings)` replaces a record's tenant settings as a whole without touching its key (the vault's `put` without a value); `put`'s options take the same `settings` on a first save.
1004
- They reject with a `VaultFailure`, an `Error` with an `ErrorCode` `code` (`isVaultFailure` from `/ui`).
1008
+ They reject with a `VaultFailure`, an `Error` with an `ErrorCode` `code` (`isVaultFailure` from `/ui`) and an optional `ErrorCause` `cause`, matching slicc-vault 1.22.0’s stable refusal causes; `isPolicyPaused` still identifies only `policy-paused`.
1005
1009
  Values go to the vault only through `put`, and never through another port.
1006
1010
  `button()` returns a new `VaultButton`, `{ element, post(message), listen(fn) }` returning an unsubscribe: bios wraps the vault's `button.html` frame and its postMessage protocol, and spectrum only sends `ButtonMessage`s and receives `ButtonEvent`s.
1007
1011
  The passkey lock (unlock and protect) happens in the vault's own popup, which only a click in that frame can open.