@ai-ecoverse/slicc-spectrum 1.112.0 → 2.0.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.
package/README.md CHANGED
@@ -161,6 +161,8 @@ Things found while building this against wterm 0.5.4, recorded here, not pushed
161
161
  The element runs on `@wterm/ghostty` instead, which reflows on resize, keeps wrap flags in the scrollback and renders 24-bit and 256 colours exactly (xterm's 0/95/135/175/215/255 cube).
162
162
  - Ghostty resolves palette colours 0–15 to RGB inside the WASM, so wterm's `--term-color-N` variables (and `--slicc-terminal-color-N`) would stop applying.
163
163
  The element gives the core a palette of 16 marker colours (`#51cc00` … `#51cc0f`) and maps cells in those colours back to palette indexes, so the CSS variables theme them and follow a theme switch, in the scrollback too. Text drawn in exactly one of those 24-bit colours shows in the theme's palette colour instead.
164
+ From `@wterm/dom` 0.5.7, `WTerm` also reads those CSS variables on init (and on OSC 10/11) and calls `setThemeColors` on the core; the element keeps the marker palette on that path so the CSS mapping still works.
165
+ 0.5.7 also paints cell backgrounds onto a canvas, which would drop live CSS backgrounds from the spans; the element suspends that canvas so palette and 256-colour backgrounds stay on the spans.
164
166
  - Ghostty answers OSC 10 and OSC 11 queries itself, with the colours it was loaded with. The element drops those replies and answers from the theme it shows.
165
167
  - Ghostty's `scrollbackLimit` is in bytes and allocated in pages, so the number of lines it keeps depends on the width. Its default of 10,000 bytes keeps about 300 lines at 200 columns; the element asks for 4 MB.
166
168
  - `WTerm.destroy()` doesn't dispose a core passed in as `core`. The element disposes it.
@@ -324,7 +326,7 @@ The model's `notices` as in-line alerts with their actions and a close button (n
324
326
 
325
327
  The model's waiting `approvals` as a modal alert dialog, one at a time, oldest first; `<slicc-app>` mounts it.
326
328
  Renders nothing without `approvals.decide`.
327
- **Always allow** (**Always allow in this directory**, with the cwd in its hint, when the approval has `alwaysScope: 'cwd'` and a `cwd`) shows when `offersAlways(approval)` is true, and the element's `decide()` sends `always` only when it holds for the live approval with that id in `list()`, so a stale dialog can't send it after the request lost it. With a level 1, 2 or 3 verdict, `offersAlways` is the approval's `always`: the embedder decides. Until every embedder sends levels, an approval without one (any other `level` counts as none) keeps the old gates: a sudo request with a `deny` decision or one `secretRules()` flags offers no Always allow, and the flagged one shows the old warning (`[data-secret-rules]`, the path or token) or the once-only hint. These gates go with the `secretRules` export.
329
+ **Always allow** (**Always allow in this directory**, with the cwd in its hint, when the approval has `alwaysScope: 'cwd'` and a `cwd`) shows when `offersAlways(approval)` is true, and the element's `decide()` sends `always` only when it holds for the live approval with that id in `list()`, so a stale dialog can't send it after the request lost it. For a sudo request, `offersAlways` is true only when `always` is true and the verdict has a level 1, 2 or 3: a level-less sudo (an older agent) never offers Always allow, even when `always` is true. Guest requests still offer Always allow whenever `always` is true. Tray requests never do.
328
330
  The command (`[data-command]`, or a guest tool call's `[data-summary]`) wraps in full; past four lines the dialog shows the first four and **Show the full command** (as in the chat, `data-action="full-command"` with `data-shown`), and shown, it scrolls inside its own box of at most `min(16lh, 50dvh)`. Always allow, its hint and Allow once wait until the end of the command has been in view (a zero-size `[data-end]` after its last line), and so does the element's `decide()` for anything but `deny`; once seen, the end stays seen while the request waits, and hiding the command again hides them again. Until then a subdued line (`[data-allow-hint]`) says "Show the full command to allow it." or, shown, "Scroll to the end of the command to allow it." The dialog keeps one width, so only its height changes. A closed request shows its command whole.
329
331
  Control characters in the sudo command, target, requester name and place, and cwd are shown: C0 controls use Unicode Control Pictures (including ␍ for CR, ␊ for LF and ␉ for TAB), DEL uses ␡, and C1, format, default-ignorable characters and line/paragraph separators use uppercase `U+XXXX` notation (at least four digits). LF keeps its line break after ␊ in the command and shows only ␊ in paragraphs; ordinary spaces stay spaces. Each shown control has a chip with its character name or notation as its accessible label and title; Control Pictures also show a readable abbreviation or notation. Joiners and variation selectors use the same chips. The clamped and full views show the same symbols.
330
332
  Sudo and guest requests show the verdict under the command, in a live region (`.judge[aria-live=polite]`). A level verdict is a block (`.judged[data-verdict][data-level]`): level 1 "Routine." (`routine`, positive subtle background), level 2 "Changes permissions." (`permissions`, notice) or level 3 "Could harm the system." (`harm`, negative, with the alert-triangle icon), then the reason, then "It fits what you asked for." or "It isn’t clear it serves what you asked for." when `aligned` is set. While `judging` it reads "Checking…" (`checking`); with no verdict and a `verdictError`, "No verdict: {verdictError}" (`none`). A verdict with only a `decision` keeps the line "The automatic check would {decision} this: {reason}" or "The automatic check wasn’t sure: {reason}" (`p.verdict[data-verdict]`, the decision in the attribute). The reason and `verdictError` show control characters as chips, as in the command, and are cut at 300 characters. A sudo request without Always allow says "Always allow isn’t offered for permission changes." at level 2 and "Always allow appears only with a level 1 verdict." with no verdict (`[data-verdict-note]`). No level adds a confirm step: Deny and Allow once work at every level.
@@ -675,34 +677,6 @@ import { prompt } from './dist/slicc-ui.js';
675
677
  const name = await prompt({ title: 'New cone', label: 'Name', action: 'Create', submit: (name) => model.agent.createCone(name).then(() => {}) });
676
678
  ```
677
679
 
678
- ### Secret rules check
679
-
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, once }`:
682
-
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
- It sees through quotes, backslashes, `$'…'` and `$"…"`, `cd` and `pushd` where a command starts (also inside `bash -c`, `bash -lc` or `su -c`, and after `if`, `then`, `do`, `{`, `!`, `function NAME` and the like, but not after a `-c` of another program, such as `chown -c cd /etc`), relative paths and `..`, extglobs, `/proc/self/root` and `/proc/self/cwd`, option clusters like `-t/etc/fnox`, and comma lists.
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
- - `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.
690
-
691
- All three are `null` for a clean command, and at most one is set.
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.
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
- The dialog runs this check only for a sudo request without a level verdict (see `<slicc-approvals>`); with a level, the judge's verdict replaces it.
695
- 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.
696
-
697
- ```js
698
- import { secretRules } from './dist/slicc-ui.js';
699
-
700
- secretRules("bash -c 'rm /etc/fn\\ox/config.toml'");
701
- secretRules('rm -rf $TARGET', '/home/cone');
702
- ```
703
-
704
- 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`.
705
-
706
680
  ### Sprinkle bridge
707
681
 
708
682
  A sprinkle's frame gets `window.slicc` with `name` and `lick(event)`. When the model's `sprinkles` port has `call`, it also gets:
@@ -969,15 +943,9 @@ An `alone` tab shows only the brand and "SLICC is open in another tab."
969
943
 
970
944
  Later backed by: BIOS.
971
945
  `list()` returns waiting `Approval`s; emit `approvals` when the list changes.
972
- 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` (`{ level?: 1 | 2 | 3, aligned?: boolean, decision?: 'allow' | 'deny' | 'unsure', reason }`, the judge's view: `level` 1 routine, 2 changes permissions, 3 could harm the system; `aligned`, whether it serves what the owner asked for; `decision`, the older shape, which may be missing), optional `judging` (`true` while the judge runs) and `verdictError` (why there is no verdict), `createdAt`, `expiresAt` (epoch ms) and `always` (offer Always allow, which covers the requester's user and this exact command).
973
- 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}", the verdict, 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`).
974
- 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`, and coreutils' pure output programs, which take no output file and run nothing: `basename`, `dirname`, `printf`, `seq`, `tr`, `fold`, `paste`, `comm`, `join`, `expand`, `unexpand`, `fmt`, `numfmt`, `pr`, `expr`, `factor`, `sum`, `tsort`, `pathchk`, `base32`, `basenc`, `true`, `false`, `id`, `groups`, `whoami`, `logname`, `printenv`, `uname` and `nproc`, so `ls | xargs -n1 basename` is a read; `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, but for getopt-style programs (`sort`, `uniq`, `base64`, `file`, `yq`) a cluster ends at `=` or at an option that takes a value, so `sort -to /etc` and `yq -o=json` don't count as writes; `tree` takes each option's value from the next word and reads on in the cluster, so any `o` or `R` in a `tree` cluster counts (`tree -Ho /tmp/x /etc` writes); `xxd` is read with its own rules (one option per word, values attached or in the next word, options end at the first operand), and an option's value isn't a `uniq` or `xxd` operand (`uniq -f 1 /etc`, `xxd -l 16 /etc`); `xargs` counts as read-only when every command it may run is read-only and has no gated write flags (`find /etc … | xargs grep x`, but not `xargs sort`, whose input could add `-o`); `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).
975
- Read-only commands count only by bare name or from `/bin` and `/usr/bin`.
976
- 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.
977
- `/proc/<pid>/root` counts as `/` and `/proc/self/cwd` as `cwd`.
978
- 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.
979
- Other symlinks aren't followed.
980
- For a request without a level verdict, 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. With a level verdict none of this applies.
946
+ 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` (`{ level?: 1 | 2 | 3, aligned?: boolean, decision?: 'allow' | 'deny' | 'unsure', reason }`, the judge's view: `level` 1 routine, 2 changes permissions, 3 could harm the system; `aligned`, whether it serves what the owner asked for; `decision`, the older shape, which may be missing), optional `judging` (`true` while the judge runs) and `verdictError` (why there is no verdict), `createdAt`, `expiresAt` (epoch ms) and `always` (for a sudo request with a level verdict, offer Always allow, which covers the requester's user and this exact command; without a level it is ignored).
947
+ 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}", the verdict, 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`) when offered and **Allow once** (`once`).
948
+ A sudo request without a level verdict never offers Always allow, even when `always` is `true` (`decide(id, 'always')` is ignored for it); only Deny and Allow once show. With a level verdict, `always` alone decides whether Always allow is offered.
981
949
  For half a second after a request shows, only Deny works, so a double-click can't approve it.
982
950
  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.
983
951
  Pass `decide` in every tab: the first decision wins.
@@ -1116,7 +1084,7 @@ The dummy's `addServer` and `removeServer` record each call in `settings.serverE
1116
1084
  `createDummyModel({ budget: 'plain' | 'adobe' | 'xai' })` (`?budget=` on `/ui/`, `plain` by default) sets the tray budget's `provider`: none, so it reads "Budget 32%", or Adobe or SuperGrok, which the tray and the monitor's budget vital name.
1117
1085
  Like slicc-agent's snapshot, the dummy's vitals are the budget, Spend, Active agents, Processes and Process memory (both with a `series`) and Fullest context.
1118
1086
 
1119
- `createDummyModel({ approvals: 'agent' | 'user' | 'many' | 'follower' | 'fail' | 'soon' | 'elsewhere' | 'withdrawn' | 'secrets' | 'judge-denied' | 'level-1' | 'level-2' | 'level-3' | 'checking' | 'no-verdict' | 'legacy' | 'guest' | 'guest-follower' | 'guest-always' | 'long' | 'controls-cr' | 'controls-lf' | 'controls-place' | 'controls-cwd' | 'guest-args' | 'guest-empty' | 'guest-truncated' | 'tray' | 'tray-url' | 'tray-rotate' | 'tray-unlink' | 'tray-expired' })` (`?approvals=` on `/ui/`) adds waiting sudo requests: one from an agent, one from a terminal user, three in a row (the last as `www-data`), one without `decide`, one whose `decide` fails, one that expires in 24 seconds, or two whose first is answered in another tab or withdrawn after a few seconds, or one from an agent that copies a file over `/etc/fnox/config.toml`, or (`judge-denied`) a terminal user’s sudo request the automatic check would deny, or one sudo request per verdict: `level-1` (docs-pass reading `/etc/slicc/secrets-policy.toml` in `/`, with Always allow), `level-2` (a terminal user editing it), `level-3` (quiet-otter running `rm -rf /`), `checking` (`judging`), `no-verdict` (a `verdictError`) and `legacy` (an older agent's `unsure` decision without a level and `always: true` for a copy over `/etc/fnox/config.toml`, which keeps the old warning and offers no Always allow), or (`guest`) three guest requests: a message from Seat 2 (Mara) with attachments and an allow verdict, a tool call with a deny verdict, and a message whose `decide` rejects with "Someone else already answered this."
1087
+ `createDummyModel({ approvals: 'agent' | 'user' | 'many' | 'follower' | 'fail' | 'soon' | 'elsewhere' | 'withdrawn' | 'secrets' | 'judge-denied' | 'level-1' | 'level-2' | 'level-3' | 'checking' | 'no-verdict' | 'legacy' | 'guest' | 'guest-follower' | 'guest-always' | 'long' | 'controls-cr' | 'controls-lf' | 'controls-place' | 'controls-cwd' | 'guest-args' | 'guest-empty' | 'guest-truncated' | 'tray' | 'tray-url' | 'tray-rotate' | 'tray-unlink' | 'tray-expired' })` (`?approvals=` on `/ui/`) adds waiting sudo requests: one from an agent, one from a terminal user, three in a row (the last as `www-data`), one without `decide`, one whose `decide` fails, one that expires in 24 seconds, or two whose first is answered in another tab or withdrawn after a few seconds, or one from an agent that copies a file over `/etc/fnox/config.toml`, or (`judge-denied`) a terminal user’s sudo request the automatic check would deny, or one sudo request per verdict: `level-1` (docs-pass reading `/etc/slicc/secrets-policy.toml` in `/`, with Always allow), `level-2` (a terminal user editing it), `level-3` (quiet-otter running `rm -rf /`), `checking` (`judging`), `no-verdict` (a `verdictError`) and `legacy` (an older agent's `unsure` decision without a level and `always: true` for a copy over `/etc/fnox/config.toml`, which offers only Deny and Allow once), or (`guest`) three guest requests: a message from Seat 2 (Mara) with attachments and an allow verdict, a tool call with a deny verdict, and a message whose `decide` rejects with "Someone else already answered this."
1120
1088
  (`guest-follower`: the same without `decide`), or (`guest-always`) one guest tool call from Seat 2 (Mara) to `read_file` with `always: true`, or (`long`) one sudo request whose seven-line command ends in `curl … | sh`, or one guest `write_file` call with a blank `summary`: with seven lines of `args` ending in the same tail (`guest-args`), with no `args` (`guest-empty`), or with a prefix of them and `argsTruncated` (`guest-truncated`), or tray requests from sliccy: one to share the join URL (`tray-url`), one to make a new one (`tray-rotate`), one to unlink Kitchen tablet (`tray-unlink`), all three in a row (`tray`), or the first, which times out after a few seconds (`tray-expired`).
1121
1089
  `controls-cr` and `controls-lf` show control characters in a sudo command's quoted word; `controls-place` shows them in the requester name, place and target; `controls-cwd` shows them in the cwd.
1122
1090
  `createDummyModel({ asks: 'long' })` (`?asks=long`) adds a pending sudo request with that seven-line command to kitchen-sink (`k-long`).