@ai-ecoverse/slicc-spectrum 1.60.2 → 1.61.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
@@ -147,7 +147,9 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
147
147
  | `<slicc-diff-panel>` | The diff of one pending change (`path`), unified or split, with accept and revert. A change with `before` and `after` both `null` (a binary file, or text over 1 MB) shows “No diff for this file” and Open the file instead, and keeps Accept and Revert. |
148
148
  | `<slicc-terminals>` | Terminal sessions in tabs, each a `<slicc-terminal>` on the session's `TerminalBackend`. Sessions stay alive while hidden; `exit` closes the tab. |
149
149
  | `<slicc-browser>` | What the agents' browser control is doing. It opens on the most recently driven tab (`.focused`), or on the tab overview: cards (`li[data-id]`, `data-controlled` while an agent drives the tab, `data-pulse` when another tab starts being driven, without switching to it) with a thumbnail, the agent and a small **Stop <agent>** (`data-action="stop-agent"`), and an address bar to open a URL. The focused tab has **All tabs** (`data-action="all-tabs"`), a tab picker (a list of tabs at 720 px and wider), the URL with **Copy** (`data-action="copy-url"`) and **Open** (`data-action="open-tab"`, activates the tab), who's using it (`.driving[data-driving=agent|user|none]`; a controlled tab without an agent reads **In use** and has no Stop) with **Stop**, a view-only preview (`figure[data-view]`: `live` while watching with a frame under 3 s old, `shot` with "Updated N s ago", `none`) and the recent actions (`li[data-action-id][data-kind][data-status]`, failures with their error inline), folded into **Recent actions (n)** below 480 px with the last one showing. Labels, URLs and errors are text only. With no tabs and `browser.via` `null` in the network status, it says browser control isn't connected (`data-browser="none"`) with the extension link and the slicc-node command. The Browser rail button carries a dot (`data-driven`) while a tab is in use. |
150
- | `<slicc-settings>` | Theme, the model and thinking level for new cones, composer and diff preferences, and accounts to connect, reconnect or disconnect. An account whose status is `signing-in` (or a non-API-key account while its `connect` is pending) reads “Signing in…”; when the port has `cancel(id)`, its row offers Cancel (`data-action="cancel-sign-in"`), and the rejected `connect` that follows shows no error. Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`). An `api-key` account asks for its `secret`: “{provider} API key” by default, “{provider} token” for `secret: 'token'`; when it is `expired` it reads **API key rejected** or **Token rejected**. An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign in**, and it reads **Needs sign-in** when `disconnected` and **Sign-in expired** when `expired`. |
150
+ | `<slicc-settings>` | Theme, the model and thinking level for new cones, composer and diff preferences, and accounts to connect, reconnect or disconnect. An account whose status is `signing-in` (or a non-API-key account while its `connect` is pending) reads “Signing in…”; when the port has `cancel(id)`, its row offers Cancel (`data-action="cancel-sign-in"`), and the rejected `connect` that follows shows no error. Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`). An `api-key` account asks for its `secret`: “{provider} API key” by default, “{provider} token” for `secret: 'token'`; when it is `expired` it reads **API key rejected** or **Token rejected**. An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign in**, and it reads **Needs sign-in** when `disconnected` and **Sign-in expired** when `expired`. With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
151
+ | `<slicc-secrets>` | Settings › Secrets from the model's `vault`: never a value, a placeholder or a value field. A head with **Locked** or **Unlocked**, the lock mode and a vault button (**Unlock** or **Open the vault**); waiting requests under **Waiting for you** (`[data-request]`, the agent's `prompt` shown as plain text under "From the agent, not checked by SLICC:", capped at 200 characters, with **Answer in the vault**); unacknowledged `PolicyNotice`s as alerts (`[data-notice]`, each widening in a sentence, **Acknowledge**, `data-action="acknowledge"`); the secrets (`li[data-secret][data-state]`, with `[data-rules]`, `[data-exits]`, `[data-exec]` and how often programs were given it today ("at least" while older activity on the same day isn't loaded), `[data-source]`, `[data-used]`, `[data-expires]`, and **Replace value**, **Sign in again** or **Manage**) or `[data-empty]`; where the rules live (`[data-policy]`); **Import…** and **Export…**; and **Activity** (`ol.audit li[data-seq][data-type]`, **Only reveals to programs**, `data-action="reveals"`, and **Show older**, `data-action="older"`, paging with `audit(before)`). The section's `data-vault` is `locked`, `unlocked` or `loading`. A popup the browser blocked shows how to allow it (`[data-popup=blocked]`), a done one which secret was saved (`[data-popup=done]`). |
152
+ | `<slicc-vault-button>` | Places `vault.button()`'s element (the vault's own button frame), posts its `config` (`ButtonConfig` plus `theme`) once the button is `ready`, takes the reported `size` (with the focus-ring `inset` as negative margin) and `label` (as its title), moves focus to the control before or after it when the frame reports `focus` (`previous` or `next`), and fires `vault-popup` (bubbles, composed) with each `popup` event's `{ intent, state, result? }`. |
151
153
  | `<slicc-updates>` | Install / Update: component states, version changes, last checks, download and link counts, action buttons, failures with Retry, and collapsible install logs. Opens at boot until the agent is ready without failures. Background updates stay in the header's update status; new failures reopen the panel, and after a reload into a boot that isn't ready yet such a panel closes once the agent is ready. Every automatic close is saved to the layout. When the port offers packages, an Optional packages section lists them below as compact rows, each with its state, version, download size, requirements, commands, progress, errors and Install log; packages never count in the header or keep the panel open at boot. Packages the user added or pinned with pnpm show up in their own Your packages section below it, with a Pinned or Added light, their version against seven's, and Unpin or Remove. Also available through View and the rails. |
152
154
  | `<slicc-memory>` | What agents remember, per scope (everyone, a cone or a role), grouped by section: search, filter by tag, expand, edit, add and forget. Free text outside an entry shows “From MEMORY.md”. |
153
155
  | `<slicc-monitor>` | The live monitor: vitals with sparklines (active agents, spend, budget, fullest context), alerts, and the topology (cones and scoops, terminals, tabs, pending changes, sprinkles, tray followers). Each section (`li.section[data-id]`) shows its count and an attention badge, and opens its surface with **Open** where one exists (`agents`, `terminals` and `processes` → terminal, `tabs`, `changes`). A section with rows expands into them with its toggle (`.toggle`, `aria-expanded`; `data-open` on the section): collapsed by default, expanded while a row is `warn` or `error`, until the user toggles it. Each row (`li.row[data-status]`, `data-id` from the row's `id`) shows a status light (blue active, gray idle, orange warn, red error, with the status spoken), the name, the meta line and its badges, indented by `depth` (`data-depth`, up to 6). |
@@ -229,7 +231,8 @@ With an `agentId` or a `cone`, the caller reaches the shared surfaces, the chats
229
231
  | `network` (optional) | BIOS | `status()` returns the `NetworkStatus`: `route` (`proxy` for slicc-node, `extension` for the Chrome extension relay, `tailnet` while a Tailscale exit node carries the traffic, `page` for the page's own fetch, limited by CORS, or `null`), `health` (`ok`, `limited` or `failing`, decided by the port), a one-line `detail`, recent `failures` (`{ url, error, at }`, newest first), an optional `extensionUrl` and an optional `browser` (`{ via, detail? }`, `via` being `extension`, `proxy`, `link` when linked devices' browsers carry it (the line names the browser and its device, or counts them when there are several, counting connected devices only), or `null` when browser automation isn't available; a legacy `declined` flag is ignored, since there is no consent prompt any more). Emit `network` when it changes. The optional `check()` tests the route again. An optional `tailnet` (`TailnetStatus`) shows the Tailscale section: `state` (`off`, `loading`, `needs-login`, `starting`, `running` or `failed`, a status light), a plain-language `detail` (progress, or the failure reason in an error box with **Retry** through `check()`), a `loginUrl` (**Sign in to Tailscale**, a new tab), the `node` (`{ name, addresses }`; the name and the first 100.x address, each with a Copy button), `exitNode` (the name in use or `null`), `exitNodes` (`{ id, name, online }`), `autoExitNode`, `shieldsUp` (a muted line when `true`, a warning when `false`) and `peers` (a count). The optional `setTailnet(on)` adds the **Use Tailscale** switch (`data-action="tailnet"`); `submitAuthKey(key)` adds an auth-key field to `needs-login` (a password field without a `name`, cleared on submit, never kept in component state); `setExitNode(id | 'auto' | null)` adds the **Exit node** picker (None, Automatic, and each node, offline ones disabled) to `running`, with a line saying where traffic goes; `logoutTailnet()` adds **Sign out**, behind a confirm. A rejected call shows its message in the section. Peers are never named beyond the exit-node list. An optional `links` (`NetworkLinks`) shows **Linked devices**, slicc CLIs linked over WebRTC: `joinUrl` and `joinCommand` (each with Copy, `data-copy="join-url"` and `data-copy="join-command"`; both `null` shows "Preparing a join URL…"), an optional `followUrl` (the page URL with `#follow=` and the join URL, shown with a QR code). Join URLs let a device join, so the join URL, the command, the follow link and the tray's join link are masked (the last path segment and anything after it, or, without a path, everything after `#` or `?`; text without a URL is masked whole), with `data-secret="hidden"` until **Show** (`data-action="reveal-<copy>"`) sets it to `shown`; a new URL masks again, Copy copies the real value either way, and the QR code stays scannable, `devices` and `permission`. A `LinkedDevice` has an `id`, a display `name` (a leading `slicc` is shown as `SLICC`), the kernel `host` and `address`, a `mode` (`local` for a CLI on this computer attached over CDP, or `remote`), `offers` (`net`, `http`, `ssh`; `ssh` adds a copyable `ssh <host>`), a one-line `policy`, `exit` (whether it can carry internet traffic), a `state` (`connecting`, `connected`, `reconnecting` or `offline`, for a device that linked before and isn't there now; every device that ever linked is listed, so one that was refused can be allowed), optional `control` (`{ allowed, fixed? }`; `fixed` for one on this computer, always allowed), optional `controlling` (`prompt` or `watch` while a CLI prompts or watches the agent) optional `browsers` (`{ runtime, name }`, listed in the row as `data-browsers`, with the runtime such as `pier-nuc/chrome` that the 9222 facade drives), optional `kind` (`cli`, or `seven` for another SLICC that follows this one, "Follower" in the row), optional `access` (`{ view?, control?, terminal?, fixed? }`: only the keys present get a switch, so a CLI sends `{ control }`; `fixed` lists them as always allowed from this computer; it replaces `control`), optional `asking` (a follower that waits to be allowed), optional `terminals` (how many terminals it has open here) and optional `folders` (`LinkedFolder`s the device shares: `name`, `source` such as `pier-nuc/proj`, `readonly`, and `mounted`, the mount point once mounted). Folders list in the row (`[data-folders] li[data-folder]`, with `data-mounted` `yes` or `no`, a **Read-only** badge and "Mounted at …"); `mountFolder(source)` adds **Mount** (`data-action="mount-folder"`, `data-id` the source; only on a connected device) and resolves with the mount point, such as `/mnt/pier-nuc/proj`, and `ejectFolder(source)` adds **Eject** (`data-action="eject-folder"`); a failure shows on the links error line. Each device is an `li[data-device]` with `data-state`, `data-mode` and `data-kind`, and `data-asking` while it asks; once there are devices, the join details fold into **Link another device**. `permission` is `prompt` (an informative line asking to choose Allow in Chrome's Local Network Access prompt) or `denied` (a warning, with **Try again**, `data-action="retry-links"`, through `retryLinks()`). An optional `exit` (`NetworkExit`: `null`, `{ kind: 'tailnet', node }` with a Tailscale node id or `'auto'`, or `{ kind: 'link', id }`) is the default exit. The optional `setExit(exit)` replaces the Tailscale **Exit node** picker with one **Internet traffic** picker in Connection: Directly, then Tailscale (Automatic and its nodes) and Linked devices (those with `exit: true`), offline ones disabled unless chosen; a chosen device that isn't connected shows that traffic waits, since the exit fails closed. Without `setRemoteControl(id, allowed)`, `control` is a line saying whether it's allowed; with it, **Allow remote control** is a switch. Without `setAccess(id, 'view' \| 'control' \| 'terminal', allowed)`, `access` is a line saying what's allowed; with it, the switches call it, and **Allow** on a device that asks calls `setAccess(id, 'view', true)`. Turning View off should turn Control off too. `unlink(id)` adds **Unlink** (`data-action="unlink"`) to each device and `rotateJoinUrl()` adds **New join URL** (`data-action="rotate-join-url"`), both behind a confirm. Their failures show in the section. Without the port, the indicator and the Network panel don't show. |
230
232
  | `notices` (optional) | BIOS | `list()` returns `Notice`s for quiet, dismissible in-line notices at the top of every chat panel, above the transcript, in list order: `id`, `tone` (`info` reads as informative, `warning` as a notice), `title`, a plain-text `body` and `actions` (`{ id, label }`). Title, body and labels are text only. Notices use `role="status"` and never take focus. An action is a secondary button that stays pending until `act(id, action)` settles; a rejection shows its message under the body. The close button, labelled "Dismiss: <title>", hides the notice at once and calls `dismiss(id)` synchronously; it shows again only if the port drops the notice from `list()` and brings it back later. Emit `notices` whenever they change. Hooks: `[data-notice="<id>"]`, buttons with `data-action`, `[data-dismiss]` and `[data-error]`. |
231
233
  | `tabs` (optional) | BIOS | `state()` returns this tab's `TabsState`: `role` (`owner` runs SLICC; `follower` shows the owner tab's SLICC; `connecting` while the owner boots or hands over; `alone` without SharedWorker), `stalled` (the owner stopped answering) and `skew` (`newer` or `older` when the owner runs another SLICC version, else `null`). Emit `tabs` when it changes. An owner shows nothing. A follower gets a quiet header light, "Another tab" (`[data-tabs][data-role]`, with `data-stalled` and `data-skew`; a dot on phones), info while connecting, and a notice while stalled or skewed, with its explanation as the tooltip. While connecting, an empty chat says "Connecting to the tab running SLICC…" (`[data-connecting]`). Skew, or else a stall, shows a warning banner at the top of every chat panel (`[data-tabs-notice="skew"\|"stalled"]`, `data-skew`): "Another tab runs a newer SLICC. Reload this tab." with **Reload** (`data-action="reload"`, calls `reload()`), "Another tab runs an older SLICC. Reload the other SLICC tab.", or "Chrome paused the tab running SLICC. Switch to it to continue.". An `alone` tab shows only the brand and "SLICC is open in another tab." (`[data-tabs="alone"]`). |
232
- | `approvals` (optional) | BIOS | `list()` returns waiting `Approval`s; emit `approvals` when the list changes. 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`, `createdAt`, `expiresAt` (epoch ms) and `always` (offer Always allow, which covers the requester's user and this exact command). 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}", 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`). For half a second after a request shows, only Deny works, so a double-click can't approve it. A rejected `decide` shows its message in the dialog. Pass `decide` in every tab: the first decision wins. When a request ends without a decision in this tab, emit `ended` with `{ id, reason }` and drop it from `list()`: `answered` (another tab decided) shows "Answered in another tab", `expired` shows "Sudo request timed out", each with the command and **Close** (`data-action="close"`, `dialog[data-gone]`), and `withdrawn` (the requester went away) closes the dialog silently. Without `ended`, a request that leaves the list reads as expired past `expiresAt` and answered before it. Without `decide`, each request is a warning notice in the chat panels: "Waiting for sudo approval", who asks and the command, and "Answer it in the tab running SLICC. Denied automatically at {time}." |
234
+ | `approvals` (optional) | BIOS | `list()` returns waiting `Approval`s; emit `approvals` when the list changes. 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`, `createdAt`, `expiresAt` (epoch ms) and `always` (offer Always allow, which covers the requester's user and this exact command). 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`). 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` or `grep`), 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). Read-only commands count only by bare name or from `/bin` and `/usr/bin`. `/proc/<pid>/root` counts as `/` and `/proc/self/cwd` as `cwd`. 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. Other symlinks aren't followed. 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. For half a second after a request shows, only Deny works, so a double-click can't approve it. A rejected `decide` shows its message in the dialog. Pass `decide` in every tab: the first decision wins. When a request ends without a decision in this tab, emit `ended` with `{ id, reason }` and drop it from `list()`: `answered` (another tab decided) shows "Answered in another tab", `expired` shows "Sudo request timed out", each with the command and **Close** (`data-action="close"`, `dialog[data-gone]`), and `withdrawn` (the requester went away) closes the dialog silently. Without `ended`, a request that leaves the list reads as expired past `expiresAt` and answered before it. Without `decide`, each request is a warning notice in the chat panels: "Waiting for sudo approval", who asks and the command, and "Answer it in the tab running SLICC. Denied automatically at {time}." |
235
+ | `vault` (optional) | BIOS | The vault's view for SLICC, typed after `@ai-ecoverse/slicc-vault` (spectrum re-declares the types it uses): never values. `status()` returns the `VaultStatus` (`locked`, `lock`, `waiting` requests) or `null` while loading, and `list()` the `VaultList` (`secrets` as `SecretView`s, `notices`, and the first `audit` page with `auditMore`) or `null`. Emit `status` and `list` when they change (bios re-lists after the vault's `changed` events) and `notice` for a new `PolicyNotice`. `audit(before?, limit?)` resolves with an older `AuditPage` (`before` is a `seq`), and `acknowledge(id)` acknowledges a notice. `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. Value entry, unlock, sign-in, import and export happen in the vault's own popup, which only a click in that frame can open. |
233
236
  | `follow` (optional) | BIOS | Only on a follower page, a SLICC that shows another SLICC's agent over a link. `state()` returns the `FollowState`: `owner` (its display name), `link` (`connecting` for the first connection, `connected`, `reconnecting` after a connection dropped, `unlinked` when the owner unlinked this device, or `failed`), `access` (`{ view, control, terminal }`, what the owner allows this device), an optional `name` (this device's name on the owner, for the waiting page) and an optional `error` (shown when `failed`). Emit `follow` when it changes. Connected without `view` means waiting to be allowed. `leave()` forgets the link and the optional `retry()` tries again from `failed`. Pass only the surfaces a follower has to `<slicc-app>` (agents, chat, terminal and settings) and leave out `network`. |
234
237
 
235
238
  The dummy supports `createDummyModel({ updates: 'boot' | 'starting' | 'current' | 'available' | 'restart' | 'failure' })`, also selectable with `?updates=` on `/ui/`. Fixtures stay stable for inspection; `model.updates.setScenario('current')` completes boot. Dummy Update now and Retry simulate download and link progress; Restart agent and Reload only change fixture state, never restart a real process or reload the browser. `createDummyModel({ packages: 'mixed' | 'empty' | 'fail' | 'user' | 'off' })` (or `?packages=` on `/ui/`, off by default) adds invented optional packages in every state: Install and Retry finish after a short wait and install missing requirements too, `fail` makes them fail, `user` adds a pinned agent-kit and two additions (one with a warning) that Unpin and Remove take out of the list, and removing Python while uv is installed asks through the `confirm` option (the `/ui/` page passes spectrum's `confirm`). `updateFixtures`, `packageFixtures` and `DummyUpdates` are exported from `/dummy`. `createDummyModel({ mounts: 'mount' | 'cancel' | 'fail' | 'needs' })` (or `?mounts=` on `/ui/`) turns on the optional mount members, which are off by default: `mountFolder` mounts invented files at `/mnt/photos`, resolves with `null`, or rejects, as `model.files.mount.scenario` says; `needs` also lists `/mnt/photos` in `needsFolder()` until a folder is inserted there. `createDummyModel({ network: 'ok' | 'limited' | 'failing' | 'off' })` (or `?network=` on `/ui/`, default `limited`) sets the dummy network: slicc-node (with browser automation through it), the page fetch with CORS failures, or no route, all invented; `off` leaves out the port. `model.network.setScenario()` switches it and Check again re-emits after a short wait. `createDummyModel({ tailnet: 'off' | 'needs-login' | 'running' | 'failed' })` (or `?tailnet=` on `/ui/`, off by default) adds an invented tailnet: an auth key starting with `tskey-` signs in, `mast-pi` and `keel-router` are online exit nodes and `buoy-laptop` is offline, and Retry on `failed` connects. While the dummy tailnet runs with an exit node, the route is `tailnet` and the health `ok`. `createDummyModel({ links: 'none' | 'preparing' | 'linked' | 'reconnecting' | 'exit' | 'prompt' | 'denied' | 'remote' | 'followers' })` (or `?links=` on `/ui/`, off by default) adds invented linked devices and the exit members: `linked` has quay-studio (on this computer) and pier-nuc, which can be exits and have a Chrome each, and dock-builder, which can't; quay-studio shares `sketches` and pier-nuc `proj` and a read-only `archive`, which **Mount** attaches to the dummy files at `/mnt/<source>` (with `mountInfo` naming the source) and **Eject** or the Files strip detaches; with no other browser automation, the dummy reports `via: 'link'`; `exit` sends traffic through quay-studio; `reconnecting` holds traffic for pier-nuc (health `failing`, and `ok` while the exit device is connected, both without a `detail`, as bios reports them); `remote` has pier-nuc allowed and prompting, quay-studio watching, and an offline reef-laptop that only ever connected for control; `prompt` and `denied` wait for Local Network Access, and Try again links quay-studio. New join URL changes the URL, and `model.network.setLinks()` switches scenarios. `networkFixtures`, `tailnetFixtures`, `linksFixtures`, `joinUrl` and `DummyNetwork` are exported from `/dummy`. `createDummyModel({ changes: 'files' | 'git' | 'nogit' })` (or `?changes=` on `/ui/`, default `files`) adds a git `changes` port: `git` lists the pending changes under `/workspace/harbor` and `/workspace/skills` with their `repo` and no author, and `nogit` reports git as unavailable. `DummyChanges` is exported from `/dummy`. `createDummyModel({ notices: 'storage' | 'mixed' | 'fail' | 'off' })` (or `?notices=` on `/ui/`, off by default) adds an invented storage warning with Try again, which clears it after a short wait (`mixed` adds an informative notice without actions, and `fail` makes Try again fail). `noticeFixtures` and `DummyNotices` are exported from `/dummy`. `createDummyModel({ browser: 'idle' | 'driving' | 'off' })` (or `?browser=` on `/ui/`, default `idle`) sets the dummy browser: `idle` has a history of invented actions and no driven tab, `driving` has harbor and quiet-otter driving two tabs, with harbor's last click still running and frames every 500 ms while watched, and `off` has no tabs. Stopping an agent releases its tabs. `DummyBrowser`, `browserHistory` and `page` are exported from `/dummy`. `createDummyModel({ sprinkles: 'builtin' | 'cones' })` (or `?sprinkles=` on `/ui/`, default `builtin`) adds, with `cones`, invented sprinkles of other cones: a Board in harbor and one in tide-tables, and a Changelog in release-notes. `createDummyModel({ tabs: 'owner' | 'follower' | 'connecting' | 'stalled' | 'newer' | 'older' | 'alone' | 'off' })` (or `?tabs=` on `/ui/`, off by default) adds a tabs port in that state; `model.tabs.setScenario()` switches it, and Reload connects again and becomes a follower after a short wait. `tabsFixtures` and `DummyTabs` are exported from `/dummy`.
@@ -238,7 +241,9 @@ The dummy supports `createDummyModel({ updates: 'boot' | 'starting' | 'current'
238
241
 
239
242
  `createDummyModel({ monitor: 'agent' })` (`?monitor=agent` on `/ui/`) adds the agent's Tasks (a tree by `depth`), Processes and Recent problems sections to the monitor.
240
243
 
241
- `createDummyModel({ approvals: 'agent' | 'user' | 'many' | 'follower' | 'fail' | 'soon' | 'elsewhere' | 'withdrawn' })` (`?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. Decisions are recorded in `model.approvals.decisions`, and `model.approvals.end(id, reason)` ends a request.
244
+ `createDummyModel({ approvals: 'agent' | 'user' | 'many' | 'follower' | 'fail' | 'soon' | 'elsewhere' | 'withdrawn' | 'secrets' })` (`?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`. Decisions are recorded in `model.approvals.decisions`, and `model.approvals.end(id, reason)` ends a request.
245
+
246
+ `createDummyModel({ vault: 'empty' | 'secrets' | 'notice' | 'locked' | 'off' })` (`?vault=` on `/ui/`, off by default) adds an invented vault: no secrets yet, five secrets (an API key, a jerk lease from pier-nuc revealed to `pier publish`, a Tide Mail sign-in, one without a rule and a removed one) with an activity log that pages back once, the same with a widened rule notice (a new exit and more methods), or locked with a waiting request from docs-pass. Its buttons are plain Spectrum buttons that answer at once: Unlock unlocks, and Replace value and Answer in the vault save the secret. Acknowledged notices are recorded in `model.vault.acknowledged` and created buttons in `model.vault.buttons`. `vaultFixtures`, `DummyVault` and `DummyVaultButton` are exported from `/dummy`.
242
247
 
243
248
  On phones the update status and the tray connection show only their status light (the tray's budget is hidden) and the network indicator a cloud icon, with the text in their labels and tooltips; the agent picker keeps room for the cone's name, cut with an ellipsis when long, and the bottom rail scrolls when needed. The update status comes first so progress and failures remain reachable on narrow screens.
244
249
 
@@ -39,6 +39,19 @@ function approvalFixtures(scenario, now2 = Date.now()) {
39
39
  expiresAt: now2 + 5 * minutes,
40
40
  always: false
41
41
  };
42
+ if (scenario === "secrets")
43
+ return [
44
+ {
45
+ id: "sudo-rules",
46
+ kind: "sudo",
47
+ requester: { kind: "agent", name: "docs-pass", agentId: "scoop-docs" },
48
+ command: "cp fnox.toml /etc/fnox/config.toml",
49
+ cwd: "/home/cone/harbor",
50
+ createdAt: now2,
51
+ expiresAt: now2 + 5 * minutes,
52
+ always: true
53
+ }
54
+ ];
42
55
  if (scenario === "user") return [user];
43
56
  if (scenario === "many") return [agent, user, cache];
44
57
  if (scenario === "elsewhere" || scenario === "withdrawn") return [agent, user];
@@ -100,7 +113,7 @@ function titleFor(url) {
100
113
  var second = 1e3;
101
114
  function browserHistory(scenario, now2 = Date.now()) {
102
115
  if (scenario === "off") return [];
103
- const harbor = { tabId: "tab-preview", agentId: "cone-harbor" };
116
+ const harbor2 = { tabId: "tab-preview", agentId: "cone-harbor" };
104
117
  const otter = { tabId: "tab-pull", agentId: "scoop-otter" };
105
118
  return [
106
119
  {
@@ -112,13 +125,13 @@ function browserHistory(scenario, now2 = Date.now()) {
112
125
  { ...otter, kind: "snapshot", at: now2 - 296 * second },
113
126
  { ...otter, kind: "scroll", at: now2 - 290 * second },
114
127
  { ...otter, kind: "screenshot", at: now2 - 288 * second },
115
- { ...harbor, kind: "open", value: "http://localhost:8787/", at: now2 - 120 * second },
116
- { ...harbor, kind: "snapshot", at: now2 - 116 * second },
117
- { ...harbor, kind: "type", target: 'textbox "City"', length: 6, at: now2 - 110 * second },
118
- { ...harbor, kind: "click", target: 'button "Show forecast"', at: now2 - 106 * second },
119
- { ...harbor, kind: "snapshot", at: now2 - 100 * second },
128
+ { ...harbor2, kind: "open", value: "http://localhost:8787/", at: now2 - 120 * second },
129
+ { ...harbor2, kind: "snapshot", at: now2 - 116 * second },
130
+ { ...harbor2, kind: "type", target: 'textbox "City"', length: 6, at: now2 - 110 * second },
131
+ { ...harbor2, kind: "click", target: 'button "Show forecast"', at: now2 - 106 * second },
132
+ { ...harbor2, kind: "snapshot", at: now2 - 100 * second },
120
133
  {
121
- ...harbor,
134
+ ...harbor2,
122
135
  kind: "click",
123
136
  target: 'link "Tomorrow"',
124
137
  status: "failed",
@@ -132,8 +145,8 @@ function browserHistory(scenario, now2 = Date.now()) {
132
145
  value: "GET https://api.example.com/v2/tides",
133
146
  at: now2 - 80 * second
134
147
  },
135
- { ...harbor, kind: "eval", at: now2 - 70 * second },
136
- { ...harbor, kind: "press", value: "End", at: now2 - 64 * second }
148
+ { ...harbor2, kind: "eval", at: now2 - 70 * second },
149
+ { ...harbor2, kind: "press", value: "End", at: now2 - 64 * second }
137
150
  ];
138
151
  }
139
152
  var drive = [
@@ -5441,6 +5454,411 @@ Done in 3.8s`
5441
5454
  }
5442
5455
  };
5443
5456
 
5457
+ // src/dummy/vault.ts
5458
+ var minute2 = 6e4;
5459
+ var hour = 60 * minute2;
5460
+ var day2 = 24 * hour;
5461
+ var harbor = { domain: "api.harbor.example", header: "Authorization", methods: [], paths: [] };
5462
+ var pier = {
5463
+ domain: "packages.pier.example",
5464
+ header: "Authorization",
5465
+ methods: ["PUT"],
5466
+ paths: ["/-/package/*"]
5467
+ };
5468
+ function vaultSecrets(now2 = Date.now()) {
5469
+ return [
5470
+ {
5471
+ name: "HARBOR_API_KEY",
5472
+ kind: "secret",
5473
+ placeholder: "fnox_proxy_harbor01",
5474
+ state: "active",
5475
+ source: "popup",
5476
+ sourceDevice: null,
5477
+ domains: ["api.harbor.example"],
5478
+ rules: [harbor],
5479
+ exits: ["vault-direct", "local-proxy", "tailscale", "device:link-pier"],
5480
+ exec: false,
5481
+ createdAt: now2 - 12 * day2,
5482
+ usedAt: now2 - 4 * minute2
5483
+ },
5484
+ {
5485
+ name: "PIER_PUBLISH_TOKEN",
5486
+ kind: "secret",
5487
+ placeholder: "fnox_proxy_pier0002",
5488
+ state: "active",
5489
+ source: "jerk:cone@link-pier",
5490
+ sourceDevice: "link-pier",
5491
+ domains: ["packages.pier.example"],
5492
+ rules: [pier],
5493
+ exits: ["local-proxy"],
5494
+ exec: { argv: [["pier", "publish"]] },
5495
+ createdAt: now2 - 3 * day2,
5496
+ usedAt: now2 - 2 * hour,
5497
+ expiresAt: now2 + 3 * day2
5498
+ },
5499
+ {
5500
+ name: "TIDE_MAIL",
5501
+ kind: "grant",
5502
+ placeholder: "fnox_proxy_tide0003",
5503
+ state: "active",
5504
+ source: "oauth:tide-mail",
5505
+ sourceDevice: null,
5506
+ provider: "Tide Mail",
5507
+ account: "cone@tide.example",
5508
+ domains: ["mail.tide.example"],
5509
+ rules: [
5510
+ {
5511
+ domain: "mail.tide.example",
5512
+ header: "Authorization",
5513
+ methods: ["GET", "POST"],
5514
+ paths: ["/v1/messages/*"]
5515
+ },
5516
+ {
5517
+ domain: "files.tide.example",
5518
+ header: "Authorization",
5519
+ methods: ["GET"],
5520
+ paths: ["/v1/attachments/*"]
5521
+ }
5522
+ ],
5523
+ exits: ["vault-direct"],
5524
+ exec: false,
5525
+ createdAt: now2 - 20 * day2,
5526
+ expiresAt: now2 + 50 * minute2
5527
+ },
5528
+ {
5529
+ name: "REEF_DEPLOY_KEY",
5530
+ kind: "secret",
5531
+ placeholder: "fnox_proxy_reef0004",
5532
+ state: "inert",
5533
+ source: "process",
5534
+ sourceDevice: null,
5535
+ domains: [],
5536
+ rules: [],
5537
+ exits: ["vault-direct", "local-proxy", "cli-v"],
5538
+ exec: true,
5539
+ createdAt: now2 - 2 * hour
5540
+ },
5541
+ {
5542
+ name: "BUOY_OLD_TOKEN",
5543
+ kind: "secret",
5544
+ placeholder: "fnox_proxy_buoy0005",
5545
+ state: "removed",
5546
+ source: "import:six",
5547
+ sourceDevice: null,
5548
+ domains: ["buoy.example"],
5549
+ rules: [{ domain: "buoy.example", header: "X-Buoy-Key", methods: ["GET"], paths: [] }],
5550
+ exits: ["vault-direct"],
5551
+ exec: false,
5552
+ createdAt: now2 - 40 * day2,
5553
+ removedAt: now2 - day2
5554
+ }
5555
+ ];
5556
+ }
5557
+ function vaultAudit(now2 = Date.now()) {
5558
+ const who = { uid: 1e3, tag: "cone" };
5559
+ return [
5560
+ {
5561
+ ...who,
5562
+ type: "inject",
5563
+ seq: 12,
5564
+ id: "audit-12",
5565
+ at: now2 - 4 * minute2,
5566
+ name: "HARBOR_API_KEY",
5567
+ domain: "api.harbor.example",
5568
+ method: "GET",
5569
+ path: "/v2/ships",
5570
+ rule: harbor,
5571
+ exit: "local-proxy"
5572
+ },
5573
+ {
5574
+ ...who,
5575
+ type: "materialize",
5576
+ seq: 11,
5577
+ id: "audit-11",
5578
+ at: now2 - 2 * hour,
5579
+ name: "PIER_PUBLISH_TOKEN",
5580
+ grant: { argv: [["pier", "publish"]] },
5581
+ path: "/usr/bin/pier",
5582
+ argv: "pier publish --tag next",
5583
+ argvSha256: "b1946ac92492d2347c6235b4d2611184",
5584
+ cwd: "/home/cone/harbor",
5585
+ pid: 4211,
5586
+ ppid: 4100,
5587
+ ticket: "ticket-11",
5588
+ consumed: true
5589
+ },
5590
+ {
5591
+ type: "materialize",
5592
+ seq: 10,
5593
+ id: "audit-10",
5594
+ at: now2 - 3 * hour,
5595
+ name: "PIER_PUBLISH_TOKEN",
5596
+ uid: 1001,
5597
+ tag: "docs-pass",
5598
+ grant: { argv: [["pier", "publish"]] },
5599
+ path: "/usr/bin/pier",
5600
+ argv: "pier publish --dry-run",
5601
+ argvSha256: "591785b794601e212b260e25925636fd",
5602
+ cwd: "/home/cone/harbor/docs",
5603
+ pid: 3980,
5604
+ ppid: 3901,
5605
+ ticket: "ticket-10",
5606
+ consumed: true
5607
+ },
5608
+ {
5609
+ ...who,
5610
+ type: "forward",
5611
+ seq: 9,
5612
+ id: "audit-9",
5613
+ at: now2 - 6 * hour,
5614
+ name: "HARBOR_API_KEY",
5615
+ domain: "api.harbor.example",
5616
+ method: "POST",
5617
+ path: "/v2/ships/quay/berths",
5618
+ rule: harbor,
5619
+ exit: "device:link-pier"
5620
+ },
5621
+ {
5622
+ type: "policy",
5623
+ seq: 8,
5624
+ id: "audit-8",
5625
+ at: now2 - day2,
5626
+ file: "/etc/slicc/secrets-policy.toml",
5627
+ sha256: "4e07408562bedb8b60ce05c1decfe3ad",
5628
+ accepted: true,
5629
+ widened: [{ kind: "exit", secret: "HARBOR_API_KEY", exit: "tailscale" }]
5630
+ },
5631
+ {
5632
+ ...who,
5633
+ type: "remove",
5634
+ seq: 7,
5635
+ id: "audit-7",
5636
+ at: now2 - day2 - hour,
5637
+ name: "BUOY_OLD_TOKEN"
5638
+ },
5639
+ {
5640
+ ...who,
5641
+ type: "seal",
5642
+ seq: 6,
5643
+ id: "audit-6",
5644
+ at: now2 - 3 * day2,
5645
+ name: "PIER_PUBLISH_TOKEN",
5646
+ source: "jerk:cone@link-pier",
5647
+ rotated: false
5648
+ }
5649
+ ];
5650
+ }
5651
+ function vaultOlder(now2 = Date.now()) {
5652
+ return [
5653
+ {
5654
+ type: "materialize",
5655
+ seq: 5,
5656
+ id: "audit-5",
5657
+ at: now2 - 4 * day2,
5658
+ name: "PIER_PUBLISH_TOKEN",
5659
+ uid: 1e3,
5660
+ tag: null,
5661
+ grant: { argv: [["pier", "publish"]] },
5662
+ path: "/usr/bin/pier",
5663
+ argv: "pier publish",
5664
+ argvSha256: "2c26b46b68ffc68ff99b453c1d304134",
5665
+ cwd: "/home/cone/harbor",
5666
+ pid: 2210,
5667
+ ppid: 2200,
5668
+ ticket: "ticket-5",
5669
+ consumed: true
5670
+ },
5671
+ {
5672
+ type: "policy",
5673
+ seq: 4,
5674
+ id: "audit-4",
5675
+ at: now2 - 5 * day2,
5676
+ file: "/etc/fnox/config.toml",
5677
+ sha256: "8f14e45fceea167a5a36dedd4bea2543",
5678
+ accepted: false,
5679
+ widened: [],
5680
+ warning: "The file did not parse, so the vault kept the last good rules."
5681
+ },
5682
+ {
5683
+ type: "restore",
5684
+ seq: 3,
5685
+ id: "audit-3",
5686
+ at: now2 - 6 * day2,
5687
+ name: "TIDE_MAIL",
5688
+ uid: 0,
5689
+ tag: null
5690
+ }
5691
+ ];
5692
+ }
5693
+ function vaultNotice(now2 = Date.now()) {
5694
+ return {
5695
+ id: "notice-widened",
5696
+ at: now2 - 20 * minute2,
5697
+ file: "/etc/slicc/secrets-policy.toml",
5698
+ sha256: "c9f0f895fb98ab9159f51fd0297e236d",
5699
+ acknowledged: false,
5700
+ widened: [
5701
+ { kind: "exit", secret: "HARBOR_API_KEY", exit: "device:link-quay" },
5702
+ {
5703
+ kind: "methods",
5704
+ secret: "PIER_PUBLISH_TOKEN",
5705
+ domain: "packages.pier.example",
5706
+ methods: ["PUT", "DELETE"]
5707
+ }
5708
+ ]
5709
+ };
5710
+ }
5711
+ function vaultFixtures(scenario, now2 = Date.now()) {
5712
+ const empty = scenario === "empty";
5713
+ const notices = scenario === "notice" ? [vaultNotice(now2)] : [];
5714
+ const status = {
5715
+ version: "0.1.0",
5716
+ protocol: 1,
5717
+ origin: "https://vault.sliccy.ai",
5718
+ locked: scenario === "locked",
5719
+ lock: "passkey",
5720
+ waiting: scenario === "locked" ? [
5721
+ {
5722
+ id: "request-reef",
5723
+ kind: "value",
5724
+ name: "REEF_DEPLOY_KEY",
5725
+ prompt: "Needed to deploy the reef preview. It is the deploy key from the reef dashboard, under Settings > Keys.",
5726
+ uid: 1001,
5727
+ tag: "docs-pass",
5728
+ createdAt: now2 - minute2,
5729
+ expiresAt: now2 + 4 * minute2
5730
+ }
5731
+ ] : [],
5732
+ notices: notices.length
5733
+ };
5734
+ const list = {
5735
+ secrets: empty ? [] : vaultSecrets(now2),
5736
+ notices,
5737
+ audit: empty ? [] : vaultAudit(now2),
5738
+ auditMore: !empty
5739
+ };
5740
+ return { status, list };
5741
+ }
5742
+ var labels = {
5743
+ unlock: "Unlock the vault",
5744
+ enter: "Enter the value",
5745
+ request: "Answer in the vault",
5746
+ import: "Import",
5747
+ export: "Export",
5748
+ signin: "Sign in",
5749
+ manage: "Open the vault"
5750
+ };
5751
+ var DummyVaultButton = class {
5752
+ element;
5753
+ config = null;
5754
+ #listeners = /* @__PURE__ */ new Set();
5755
+ #vault;
5756
+ #button;
5757
+ constructor(vault, time) {
5758
+ this.#vault = vault;
5759
+ this.element = document.createElement("span");
5760
+ this.element.dataset.vaultButton = "";
5761
+ this.#button = document.createElement("swc-button");
5762
+ this.element.append(this.#button);
5763
+ this.#button.addEventListener("click", () => void this.#click());
5764
+ void time.sleep(0).then(() => this.#emit({ event: "ready" }));
5765
+ }
5766
+ post(message) {
5767
+ if (message.op === "focus") {
5768
+ this.#button.focus();
5769
+ return;
5770
+ }
5771
+ this.config = message.button;
5772
+ const { intent, label, size = "m", variant = "secondary", treatment = "fill" } = message.button;
5773
+ this.#button.textContent = label ?? (intent === "auto" ? this.#vault.autoLabel() : labels[intent.kind]);
5774
+ this.#button.setAttribute("size", size);
5775
+ this.#button.setAttribute("variant", variant);
5776
+ this.#button.setAttribute("fill-style", treatment);
5777
+ this.#button.toggleAttribute("disabled", !!message.button.disabled);
5778
+ }
5779
+ listen(listener) {
5780
+ this.#listeners.add(listener);
5781
+ return () => this.#listeners.delete(listener);
5782
+ }
5783
+ #emit(event) {
5784
+ const message = { type: "slicc-vault", v: 1, ...event };
5785
+ for (const listener of [...this.#listeners]) listener(message);
5786
+ }
5787
+ async #click() {
5788
+ const intent = this.config?.intent ?? "auto";
5789
+ const resolved = intent === "auto" ? this.#vault.autoIntent() : intent;
5790
+ this.#emit({ event: "popup", intent: resolved, state: "opened" });
5791
+ const result = await this.#vault.answer(resolved);
5792
+ this.#emit({ event: "popup", intent: resolved, state: "done", ...result ? { result } : {} });
5793
+ }
5794
+ };
5795
+ var DummyVault = class extends Emitter {
5796
+ #status;
5797
+ #list;
5798
+ #time;
5799
+ buttons = [];
5800
+ acknowledged = [];
5801
+ constructor(scenario, time) {
5802
+ super();
5803
+ const { status, list } = vaultFixtures(scenario);
5804
+ this.#status = status;
5805
+ this.#list = list;
5806
+ this.#time = time;
5807
+ }
5808
+ status() {
5809
+ return this.#status;
5810
+ }
5811
+ list() {
5812
+ return this.#list;
5813
+ }
5814
+ async audit(before, limit = 50) {
5815
+ await this.#time.sleep(5);
5816
+ const all = [...this.#list.audit, ...vaultOlder()];
5817
+ const page2 = all.filter((entry) => before === void 0 || entry.seq < before).slice(0, limit);
5818
+ const more = page2.length > 0 && all.some((entry) => entry.seq < page2[page2.length - 1].seq);
5819
+ return { audit: page2, more, ...more ? { next: page2[page2.length - 1].seq } : {} };
5820
+ }
5821
+ async acknowledge(notice) {
5822
+ await this.#time.sleep(5);
5823
+ this.acknowledged.push(notice);
5824
+ const notices = this.#list.notices.filter((item) => item.id !== notice);
5825
+ this.#list = { ...this.#list, notices };
5826
+ this.#status = { ...this.#status, notices: notices.length };
5827
+ this.emit("list", this.#list);
5828
+ this.emit("status", this.#status);
5829
+ }
5830
+ button() {
5831
+ const button = new DummyVaultButton(this, this.#time);
5832
+ this.buttons.push(button);
5833
+ return button;
5834
+ }
5835
+ autoLabel() {
5836
+ return this.#status.locked ? "Vault locked" : "Vault unlocked";
5837
+ }
5838
+ autoIntent() {
5839
+ return this.#status.locked ? { kind: "unlock" } : { kind: "manage" };
5840
+ }
5841
+ async answer(intent) {
5842
+ await this.#time.sleep(10);
5843
+ if (intent.kind === "unlock") {
5844
+ this.#status = { ...this.#status, locked: false };
5845
+ this.emit("status", this.#status);
5846
+ return null;
5847
+ }
5848
+ if (intent.kind !== "request" && intent.kind !== "enter") return null;
5849
+ const request = this.#status.waiting.find((item) => item.id === intent.request);
5850
+ const name2 = intent.kind === "enter" ? intent.name : request?.name ?? "NEW_SECRET";
5851
+ this.#status = {
5852
+ ...this.#status,
5853
+ locked: false,
5854
+ waiting: this.#status.waiting.filter((item) => item !== request)
5855
+ };
5856
+ this.emit("status", this.#status);
5857
+ const record = this.#list.secrets.find((secret) => secret.name === name2);
5858
+ return { name: name2, placeholder: record?.placeholder ?? "fnox_proxy_new00001", state: "active" };
5859
+ }
5860
+ };
5861
+
5444
5862
  // src/dummy/index.ts
5445
5863
  function createDummyModel({
5446
5864
  delay = 30,
@@ -5457,6 +5875,7 @@ function createDummyModel({
5457
5875
  tabs: tabs2 = "off",
5458
5876
  approvals = "off",
5459
5877
  follow = "off",
5878
+ vault = "off",
5460
5879
  browser: scenario = "idle",
5461
5880
  sprinkles: sprinkles2 = "builtin",
5462
5881
  monitor = "dummy"
@@ -5515,7 +5934,8 @@ function createDummyModel({
5515
5934
  ...notices === "off" ? {} : { notices: new DummyNotices(notices, time) },
5516
5935
  ...tabs2 === "off" ? {} : { tabs: new DummyTabs(tabs2, time) },
5517
5936
  ...approvals === "off" ? {} : { approvals: new DummyApprovals(approvals, time) },
5518
- ...follow === "off" ? {} : { follow: new DummyFollow(follow, time) }
5937
+ ...follow === "off" ? {} : { follow: new DummyFollow(follow, time) },
5938
+ ...vault === "off" ? {} : { vault: new DummyVault(vault, time) }
5519
5939
  };
5520
5940
  return {
5521
5941
  ...base,
@@ -5531,6 +5951,8 @@ export {
5531
5951
  DummyNotices,
5532
5952
  DummyTabs,
5533
5953
  DummyUpdates,
5954
+ DummyVault,
5955
+ DummyVaultButton,
5534
5956
  approvalFixtures,
5535
5957
  browserHistory,
5536
5958
  createDummyModel,
@@ -5543,6 +5965,11 @@ export {
5543
5965
  page,
5544
5966
  tabsFixtures,
5545
5967
  tailnetFixtures,
5546
- updateFixtures
5968
+ updateFixtures,
5969
+ vaultAudit,
5970
+ vaultFixtures,
5971
+ vaultNotice,
5972
+ vaultOlder,
5973
+ vaultSecrets
5547
5974
  };
5548
5975
  //# sourceMappingURL=slicc-dummy.js.map