@ai-ecoverse/slicc-spectrum 1.78.0 → 1.79.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 +4 -2
- package/dist/slicc-dummy.js +131 -40
- package/dist/slicc-dummy.js.map +4 -4
- package/dist/slicc-ui.js +414 -8
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/confirm.d.ts +1 -0
- package/dist/types/app/guests.d.ts +98 -0
- package/dist/types/app/network.d.ts +10 -1
- package/dist/types/dummy/guests.d.ts +20 -0
- package/dist/types/dummy/index.d.ts +4 -1
- package/dist/types/dummy.d.ts +2 -0
- package/dist/types/model/types.d.ts +34 -0
- package/dist/types/ui.d.ts +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -157,10 +157,11 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
157
157
|
| `<slicc-monitor>` | The live monitor: vitals (`.vital[data-id]`), each a meter when it has a `ratio` (budget, fullest context) and otherwise a value, its unit and delta on one line and a sparkline from its `series` (spend, active agents, processes, process memory). Each card keeps its place whatever it shows: the value line never wraps (a unit or delta that doesn't fit is left out whole rather than cut, and stays in the line's `title` and its text; a vital's optional `hint` replaces that tooltip, such as the exact reset time behind "resets in 4 days"), a card without a `series` keeps the sparkline's room, a meter keeps its description's line, and a card left alone on the last row spans it, so short and long values, 0% and 100%, and missing data move nothing below them. Then alerts (an alert's optional `command` shows after its detail, led by its `commandLabel` or "Run", as unwrapped code with **Copy**, `[data-command]` and `data-action="copy-command"`), and the sections (the agent's Tasks, Processes and Recent problems, then terminals, tabs, pending changes and tray followers; cones and sprinkles have their own panels; any other section an agent sends, such as an older agent's `agents` or `sprinkles`, renders the same way). Each section (`li.section[data-id]`) shows its count and an attention badge, and opens its surface with **Open** where one exists; the counts line up in one column whether or not a section has **Open**, and a section without rows keeps its name in line with the others (`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). |
|
|
158
158
|
| `<slicc-freezer>` | Every conversation in the session, from `frozen()`: cones, scoops and agent runs, each with a kind badge, live ones first with a Live light, then the newest first. A live row offers **Open**, which selects that agent; a stopped one offers **Thaw** and **Remove**, which deletes its working folders after a confirmation. A thawed scoop or agent run keeps its row, which says what it was thawed as. **Delete cone**, in the agents list (or <kbd>Delete</kbd> on a cone's row) and the header's cone picker, stops a cone after a confirmation; `/freeze` does it at once. Either way its conversation stays here, and a row without a `kind` counts as a cone. |
|
|
159
159
|
| `<slicc-sprinkle>` | A SLICC sprinkle (`.shtml`), as a panel or, with `inline`, as a dip in the chat that grows to fit, even when it loads out of view (the frame measures itself on load, after icons and fonts arrive, and on every resize). A dip has a tab-like handle: drag it into the dock (or onto a rail, or use its open-as-panel button) and it lives on as a sprinkle panel, with a note in the chat where it was; which dips are out is saved in `slicc-ui.dips`. It runs in a sandboxed frame (`allow-scripts` only) with SLICC's sprinkle theme (the `--s2-*` tokens and `.sprinkle-*` classes), the app's fonts, and Lucide icons (`<i data-lucide>`, `LucideIcons.render()`). It talks back through `slicc.lick({ action, data })`, which reaches the owning cone as a `sprinkle` lick, and, when the port has `call`, reads files and keeps state (see [Sprinkle bridge](#sprinkle-bridge)). Panel sprinkles sit at the top of the right rail with their Lucide icon. |
|
|
160
|
-
| `<slicc-network>` | Network: the route and its health in plain words with the bios `detail`, a line on browser automation (`data-browser`: through the SLICC extension, through slicc on this computer (`proxy`, or a linked device on this computer), through a linked device's browser, that slicc on this computer has no browser to control when it carries the traffic and `via` is `null`, or not available with how to get it) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, an **Internet traffic** picker (`data-action="exit"`, in `[data-exit-picker]` with the exit's kind) when the port has `setExit`, the route line saying where traffic goes, and under the picker a warning that traffic waits while the chosen device isn't connected (`data-exit="device"`, `data-held`, with **Send directly**, `data-action="exit-direct"`, which calls `setExit(null)`) or, when `route` isn't `tailnet`, the Tailscale exit node in use (`data-exit="node"`), a **Tailscale** section (`data-tailnet` with the state) when the status has `tailnet`, a **Linked devices** section (`data-links`: `linked`, `connecting`, `reconnecting`, `offline`, `none` or `permission`; each device row has a **Prompting** or **Watching** badge, `data-controlling`, while it controls the agent, and **Allow remote control**, `sp-switch[data-action="remote-control"]`, behind a confirm when turned on, or `data-control="fixed"` for a device on this computer; with `access`, an **Allow** group of **View**, **Control** and **Terminal** switches, `data-action="access-view"`, `access-control` and `access-terminal`, each confirmed when turned on, Terminal's saying it gives a root shell, and Control disabled while View is off; a device that asks to follow shows **Asks to follow** and **Allow**, `data-action="allow-follow"`; a **Terminal open** badge, `data-terminals`, counts its terminals; with `followUrl`, the join details add **Open on another device** with a QR code and the link, `data-join="follow"` and `data-copy="follow-url"`) when the status has `links`, **Get the whole web** with the Chrome extension link (`data-action="install-extension"`, when `extensionUrl` is set) and the commands to run slicc, each in a `.command[data-command]` block with a copy button: `curl -fsSL https://get.sliccy.ai/slicc | sh` (its button is `data-action="copy-command"`), `slicc`, and, with Node, `npx sliccy@next`; folded away when the route already reaches the whole web (`proxy`, `extension`, `link` or `tailnet`, whose line names `tailnet.exitNode`) or a linked device is the exit (`exit.kind` `link`: the route line says "Through slicc on this computer" for a `local` device, or names a remote device and its `policy`, or says it's waiting for it, and `data-route` is `link`, whatever `route` says), and recent failures with host, error and time. When the model has a `tray` port, a **Sync** section (`data-sync` with the connection) follows the connection: the tray's state, the float's name, role and runtime, the join link, masked (`…/join/••••••`) until **Show** (`data-action="reveal-tray-join"`, which turns into **Hide**), with **Copy** (`data-action="copy-tray-join"`), the followers (`li[data-follower]`), and **Disconnect** (`data-action="tray-disconnect"`, confirmed) or **Reconnect** (`data-action="tray-reconnect"`). When the status has `leaders`, a **Leaders** section (`section[data-leaders]` with the count) follows Sync, since it's the other direction of the same tray link: Sync is this SLICC's own tray and who follows it, Leaders the SLICCs it follows. Each leader is an `li[data-leader]` (the id) with `data-state`: its name, its hub as a quiet second line and a status light with the state in words (Connecting, Reconnecting, Connected, Disconnected, Failed, Unlinked); `failed` adds its `error` as plain text (`[data-leader-error]`, "Couldn’t reach its hub." without one) and **Retry** (`data-action="retry-leader"`), and `unlinked` says "{name} stopped sharing with this SLICC." (`[data-unlinked]`) without actions. **Disconnect** (`data-action="disconnect-leader"`) runs at once, without a confirm, since it's reversible: the row stays as Disconnected (a neutral light), with **Reconnect** (`data-action="reconnect-leader"`) and **Forget** and no Disconnect or Retry. **Forget** (`data-action="forget-leader"`) asks "Forget {name}?" ("To follow it again, you’ll need a new invite from it.") with a negative confirm, and the row goes. Each shows only when the port has its method; while one runs, the row's buttons are disabled, and a failure shows in the row (`data-error="leader"`). The header's **Network** indicator (`data-network`, `data-health`) opens it: a status light, green for `ok`, yellow for `limited` and red for `failing`, with the state in its text and label and `detail` as its tooltip, which adds "Tailscale is connected." while the tailnet runs; on a phone, a cloud icon for the state. While a linked device asks to follow, controls the agent, watches it or has a terminal open, an indicator (`data-remote`, `ask`, `prompt`, `terminal` or `watch`) sits before it, **{name} asks to follow**, **Controlled by**, **Terminal open from** or **Watched by** the device's name (or the number of devices; in that order of precedence), says who does what in its tooltip and opens the panel too. Offered only when the model has a `network` port. |
|
|
160
|
+
| `<slicc-network>` | Network: the route and its health in plain words with the bios `detail`, a line on browser automation (`data-browser`: through the SLICC extension, through slicc on this computer (`proxy`, or a linked device on this computer), through a linked device's browser, that slicc on this computer has no browser to control when it carries the traffic and `via` is `null`, or not available with how to get it) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, an **Internet traffic** picker (`data-action="exit"`, in `[data-exit-picker]` with the exit's kind) when the port has `setExit`, the route line saying where traffic goes, and under the picker a warning that traffic waits while the chosen device isn't connected (`data-exit="device"`, `data-held`, with **Send directly**, `data-action="exit-direct"`, which calls `setExit(null)`) or, when `route` isn't `tailnet`, the Tailscale exit node in use (`data-exit="node"`), a **Tailscale** section (`data-tailnet` with the state) when the status has `tailnet`, a **Linked devices** section (`data-links`: `linked`, `connecting`, `reconnecting`, `offline`, `none` or `permission`; each device row has a **Prompting** or **Watching** badge, `data-controlling`, while it controls the agent, and **Allow remote control**, `sp-switch[data-action="remote-control"]`, behind a confirm when turned on, or `data-control="fixed"` for a device on this computer; with `access`, an **Allow** group of **View**, **Control** and **Terminal** switches, `data-action="access-view"`, `access-control` and `access-terminal`, each confirmed when turned on, Terminal's saying it gives a root shell, and Control disabled while View is off; a device that asks to follow shows **Asks to follow** and **Allow**, `data-action="allow-follow"`; a **Terminal open** badge, `data-terminals`, counts its terminals; with `followUrl`, the join details add **Open on another device** with a QR code and the link, `data-join="follow"` and `data-copy="follow-url"`) when the status has `links`, **Get the whole web** with the Chrome extension link (`data-action="install-extension"`, when `extensionUrl` is set) and the commands to run slicc, each in a `.command[data-command]` block with a copy button: `curl -fsSL https://get.sliccy.ai/slicc | sh` (its button is `data-action="copy-command"`), `slicc`, and, with Node, `npx sliccy@next`; folded away when the route already reaches the whole web (`proxy`, `extension`, `link` or `tailnet`, whose line names `tailnet.exitNode`) or a linked device is the exit (`exit.kind` `link`: the route line says "Through slicc on this computer" for a `local` device, or names a remote device and its `policy`, or says it's waiting for it, and `data-route` is `link`, whatever `route` says), and recent failures with host, error and time. When the model has a `tray` port, a **Sync** section (`data-sync` with the connection) follows the connection: the tray's state, the float's name, role and runtime, the join link, masked (`…/join/••••••`) until **Show** (`data-action="reveal-tray-join"`, which turns into **Hide**), with **Copy** (`data-action="copy-tray-join"`), the followers (`li[data-follower]`), and **Disconnect** (`data-action="tray-disconnect"`, confirmed) or **Reconnect** (`data-action="tray-reconnect"`). When the status has `leaders`, a **Leaders** section (`section[data-leaders]` with the count) follows Sync, since it's the other direction of the same tray link: Sync is this SLICC's own tray and who follows it, Leaders the SLICCs it follows. Each leader is an `li[data-leader]` (the id) with `data-state`: its name, its hub as a quiet second line and a status light with the state in words (Connecting, Reconnecting, Connected, Disconnected, Failed, Unlinked); `failed` adds its `error` as plain text (`[data-leader-error]`, "Couldn’t reach its hub." without one) and **Retry** (`data-action="retry-leader"`), and `unlinked` says "{name} stopped sharing with this SLICC." (`[data-unlinked]`) without actions. **Disconnect** (`data-action="disconnect-leader"`) runs at once, without a confirm, since it's reversible: the row stays as Disconnected (a neutral light), with **Reconnect** (`data-action="reconnect-leader"`) and **Forget** and no Disconnect or Retry. **Forget** (`data-action="forget-leader"`) asks "Forget {name}?" ("To follow it again, you’ll need a new invite from it.") with a negative confirm, and the row goes. Each shows only when the port has its method; while one runs, the row's buttons are disabled, and a failure shows in the row (`data-error="leader"`). When the model has a `guests` port, a **Guests** section (`section[data-guests]` with the count) follows Leaders: **Invite a guest** (`data-action="invite-guest"`, accent, disabled while no cone is live; frozen cones are left out of the invite too), or, while `unavailable()` (or the last `unavailable` event, when the port has no method) is a sentence, that sentence instead, subdued (`[data-guests-unavailable]`). Each seat is an `li[data-seat]` (the id) with `data-connected` (`true` or `false`): its label, the cone's name as a quiet second line (`[data-seat-cone]`), "Expires in {n} hours" under a day, "Expires in {n} days" after, or "No expiry" (`[data-expiry]`), a status light with **Connected** or **Not connected**, a count badge (`data-pending`) reading "{n} waiting", whose accessible name is "{n} waiting for review" when `pending` is above 0, and **Revoke** (`data-action="revoke-guest"`), which asks "Revoke {label}?" ("Their link stops working at once. To let them back in, invite them again.") with a negative confirm; while it runs, the rows' buttons are disabled, and a failure shows in the row (`data-error="guest"`). With no seats it says "No guests yet." (`[data-guests-empty]`). Invite opens `<slicc-guest-invite>`, a modal: **Cone** (`#guest-cone`, the active cone, or a selected scoop's cone, else the first), **Name** (`#guest-name`, required, trimmed, at most 40 characters, "Shown to you and in the cone, never set by the guest."), **Link expires** (`#guest-expiry`: 1 hour, 1 day, 7 days by default, or 30 days), **Their messages** (`#guest-messages`: You approve each one, The automatic check decides, {cone} decides, {scoop} decides for each of the cone's scoops, or No review, which are `user`, `agent`, `cone`, `scoop:<name>` and `off`) and **Their tool calls** (`#guest-tools`: `user`, `agent` and `scoop:<name>` only), both `user` by default, each with the chosen option's meaning under it. **Create link** (`data-action="create-guest-link"`) calls `mint(cone, { label, expiresAt, gates })`; while it runs the form is busy and Escape does nothing, and a rejection shows its message inline (`data-error="guest"`). On success the dialog shows the URL once (`[data-guest-invite="link"]`): a read-only field (`data-copy="guest-url"`) with **Copy** (`data-action="copy-guest-url"`), "This link is shown only once. Copy it now." and **Done** (`data-action="done-guest"`). The URL is never stored or shown again: closing the dialog drops it. The header's **Network** indicator (`data-network`, `data-health`) opens it: a status light, green for `ok`, yellow for `limited` and red for `failing`, with the state in its text and label and `detail` as its tooltip, which adds "Tailscale is connected." while the tailnet runs; on a phone, a cloud icon for the state. While a linked device asks to follow, controls the agent, watches it or has a terminal open, an indicator (`data-remote`, `ask`, `prompt`, `terminal` or `watch`) sits before it, **{name} asks to follow**, **Controlled by**, **Terminal open from** or **Watched by** the device's name (or the number of devices; in that order of precedence), says who does what in its tooltip and opens the panel too. Offered only when the model has a `network` port. |
|
|
161
161
|
| `<slicc-tray>` | The tray items in the header, each opening a panel when that panel is in `panels`: the connection (`#chip`, a status light with the float's name and followers, `data-surface="network"`) opens Network at its **Sync** section, and the budget (`#usage`, `data-surface="monitor"`) opens the Monitor. Without the panel an item is a plain status with its label. |
|
|
162
162
|
| `<slicc-confirm>` | The confirmation dialog `confirm()` opens. Not for direct use. |
|
|
163
163
|
| `<slicc-prompt>` | The name dialog `prompt()` opens. Not for direct use. |
|
|
164
|
+
| `<slicc-guest-invite>` | The invite dialog of the Network panel's Guests section. Not for direct use. |
|
|
164
165
|
| `<slicc-file-tree>` | Generic. [`@pierre/trees`](https://www.npmjs.com/package/@pierre/trees) through its `web-components` entry: `paths` (folders end in `/`), `gitStatus`, `expanded`, `query` (shows only the paths that match it; empty shows all), `reveal(path, focus)`. It has no search field of its own. Fires `file-open` on click and on <kbd>Enter</kbd>, and `tree-search` (`detail.key`) when a letter or digit is typed in it, for the panel's search field. |
|
|
165
166
|
| `<slicc-code-view>` | Generic. A highlighted file through [`@pierre/diffs`](https://www.npmjs.com/package/@pierre/diffs): `path`, `contents`, `color`. |
|
|
166
167
|
| `<slicc-diff-view>` | Generic. A diff through `@pierre/diffs`: `path`, `oldText`, `newText` (`null` for an added or deleted file), `color`, `diff-style` (`unified` or `split`). |
|
|
@@ -253,9 +254,10 @@ With an `agentId` or a `cone`, the caller reaches the shared surfaces, the chats
|
|
|
253
254
|
| `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"]`). |
|
|
254
255
|
| `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 (`[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. 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}." A guest request has `kind` `guest-message` (`text`, optional `attachments` of `{ name, mimeType, size }`) or `guest-tool` (`tool`, `summary`), plus `requester` (`kind` `guest`), `agentId`, optional `agentName`, `createdAt`, `expiresAt`, `always: false` and an optional `verdict` (`{ decision: 'allow' \| 'deny', reason }`, the automatic check's view). Its dialog (`[data-approval-kind]` on the body) reads "Allow a guest’s message?" with "{name} wants to send this to {agentName ?? 'this cone'}:", the text as plain text in a scrolling box (`[data-guest-text]`, never Markdown or HTML) and the attachments as name · type · size (`[data-attachments]`), or "Allow a guest’s tool call?" with "{agentName ?? 'This cone'} wants to run {tool} for {name}:" and the summary (`[data-summary]`); then "The automatic check would allow this: {reason}" or "…deny this: {reason}" (`[data-verdict]`, the decision in the attribute). It offers only **Deny** and **Allow once**, whatever `always` says. `decide` gets no `reason` from the UI; a rejected `decide` shows its message inline (`[data-error]`) with only **Close**, as for sudo. Ended guest requests read "Answered elsewhere", "Guest message timed out" or "Guest tool call timed out". Without `decide`, a guest request's notice reads "Waiting for approval of a guest’s message" with "{name}'s message to {agentName} waits for approval on the device in control.", or "Waiting for approval of a guest’s tool call" with "{agentName} wants to run {tool} for {name}, which waits for approval on the device in control.", then "Denied automatically at {time}." |
|
|
255
256
|
| `vault` (optional) | BIOS | The vault's view for SLICC, typed after `@ai-ecoverse/slicc-vault` (spectrum re-declares the types it uses, and `compat/vault.ts` checks that the package's types fit them). `status()` returns the `VaultStatus` (`locked`, `lock`, `scrub`, `waiting` requests, and optional `exportedAt` and `backupReminder`) or `null` while loading, and `list()` the `VaultList` (`secrets` as `SecretView`s, `refused` rules, `exits`, 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 `availability` when the optional `availability()` (`connecting`, `waiting`, `locked` or `ready`) changes. `audit(before?, limit?)` resolves with an older `AuditPage` (`before` is a `seq`). 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()` and `dismissBackupReminder()`. They reject with a `VaultFailure`, an `Error` with an `ErrorCode` `code` (`isVaultFailure` from `/ui`). Values go to the vault only through `put`, and never through another port. `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. The passkey lock (unlock and protect) happens in the vault's own popup, which only a click in that frame can open. `notices`, `acknowledge` and the `notice` event are still typed, and ignored. |
|
|
257
|
+
| `guests` (optional) | the tray hub | Guest seats on a cone. `list()` returns the `GuestSeat`s (`id`, `label`, `agentId`, the cone, `createdAt`, `expiresAt` or `null`, `gates`, `connected` and `pending`, how many of their messages or tool calls wait for review); a revoked or expired seat leaves it at once. Emit `seats` when it changes. `mint(agentId, { label, expiresAt, gates })` resolves with the `seat` and its `url`, which the panel shows once and never keeps, and `revoke(id)` ends a seat; both reject with an `Error` whose message is a sentence to show. The hub caps `expiresAt` at 30 days. `gates` (`{ messages, tools }`) are fixed at mint, so changing them means revoking and minting again; each is a `GuestGate`: `user` (a person with Control approves each one), `cone` (the shared cone itself decides, messages only), `agent` (the automatic check decides by the owner's intent and asks a person when unsure), `scoop:<name>` (that scoop decides) or `off` (no review: messages go straight in as follow-ups, messages only). The optional `unavailable()` returns a sentence when seats can't be minted here (no tray hub, or a follower without Control), or `null`; emit `unavailable` with it when it changes. |
|
|
256
258
|
| `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`. |
|
|
257
259
|
|
|
258
|
-
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 on this computer as a linked device (`route: 'link'`, 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. `createDummyModel({ leaders: 'connected' | 'failed' | 'unlinked' | 'disconnected' | 'two' })` (or `?leaders=` on `/ui/`, off by default) adds invented leaders and their members: cove-studio connected, marsh-desk failed with an error, cove-studio unlinked (gone on the next status), cove-studio disconnected, or both cove-studio and marsh-desk; Retry and Reconnect move a leader to connecting, then connected, Disconnect makes it disconnected and Forget drops it, each after a short wait. `networkFixtures`, `tailnetFixtures`, `linksFixtures`, `leadersFixtures`, `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 (about 180 on harbor's tab, enough for Show more) 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. Like bios, `actions()` keeps the latest 500 per tab from the last 24 hours, 5,000 in total (`actionLimits`). `DummyBrowser`, `actionLimits`, `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`.
|
|
260
|
+
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 on this computer as a linked device (`route: 'link'`, 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. `createDummyModel({ leaders: 'connected' | 'failed' | 'unlinked' | 'disconnected' | 'two' })` (or `?leaders=` on `/ui/`, off by default) adds invented leaders and their members: cove-studio connected, marsh-desk failed with an error, cove-studio unlinked (gone on the next status), cove-studio disconnected, or both cove-studio and marsh-desk; Retry and Reconnect move a leader to connecting, then connected, Disconnect makes it disconnected and Forget drops it, each after a short wait. `networkFixtures`, `tailnetFixtures`, `linksFixtures`, `leadersFixtures`, `joinUrl` and `DummyNetwork` are exported from `/dummy`. `createDummyModel({ guests: 'on' | 'none' | 'unavailable' | 'fail' })` (or `?guests=` on `/ui/`, off by default) adds an invented `guests` port: `on` has Seat 1 (Mara) on sliccy, connected with 2 waiting and 5 days left, and Seat 2 (Ole) on harbor, not connected and without expiry; `none` has no seats; `unavailable` has none and says guests need the tray hub, so minting rejects; and `fail` makes `mint` reject with a sentence. `mint` adds a seat after a short wait and resolves with an invented `https://www.example.com/guest/…` URL, `revoke` drops the seat, and `model.guests.setScenario()` switches scenarios. `guestsFixtures`, `guestsUnavailable`, `guestsRefused` and `DummyGuests` 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 (about 180 on harbor's tab, enough for Show more) 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. Like bios, `actions()` keeps the latest 500 per tab from the last 24 hours, 5,000 in total (`actionLimits`). `DummyBrowser`, `actionLimits`, `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`.
|
|
259
261
|
|
|
260
262
|
`createDummyModel({ follow: 'connecting' | 'waiting' | 'watch' | 'control' | 'full' | 'reconnecting' | 'unlinked' | 'failed' })` (`?follow=` on `/ui/`, which then leaves out the network port and offers only agents, chat, terminal and settings) makes the page a follower of SLICC on quay-studio: connecting, waiting to be allowed, View only, View and Control, everything, reconnecting, unlinked, or failed, where Try again connects with everything allowed. Leave counts in `model.follow.left` (the `/ui/` page then drops `?follow=`), and `model.follow.setScenario()` switches it. `followFixtures` and `DummyFollow` are exported from `/dummy`. `?links=followers` lists quay-studio (a CLI on this computer, Control always allowed), pier-nuc (a remote CLI, prompting), bay-laptop (a follower with everything allowed, watching, a terminal open), reef-phone (View only, watching) and tide-tablet (asking to follow); every links scenario has a follow link.
|
|
261
263
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -484,6 +484,88 @@ var DummyFollow = class extends Emitter {
|
|
|
484
484
|
}
|
|
485
485
|
};
|
|
486
486
|
|
|
487
|
+
// src/dummy/guests.ts
|
|
488
|
+
var hour = 36e5;
|
|
489
|
+
var day = 24 * hour;
|
|
490
|
+
var cap = 30 * day;
|
|
491
|
+
var guestsUnavailable = "Guests need the tray hub. Connect this SLICC to a tray to invite one.";
|
|
492
|
+
var guestsRefused = "The hub couldn\u2019t make a link right now. Try again in a minute.";
|
|
493
|
+
function guestsFixtures(scenario, now2 = Date.now()) {
|
|
494
|
+
if (scenario === "none" || scenario === "unavailable") return [];
|
|
495
|
+
return [
|
|
496
|
+
{
|
|
497
|
+
id: "seat-mara",
|
|
498
|
+
label: "Seat 1 (Mara)",
|
|
499
|
+
agentId: "cone-sliccy",
|
|
500
|
+
createdAt: now2 - 2 * hour,
|
|
501
|
+
expiresAt: now2 + 5 * day + 3 * hour,
|
|
502
|
+
gates: { messages: "user", tools: "user" },
|
|
503
|
+
connected: true,
|
|
504
|
+
pending: 2
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
id: "seat-ole",
|
|
508
|
+
label: "Seat 2 (Ole)",
|
|
509
|
+
agentId: "cone-harbor",
|
|
510
|
+
createdAt: now2 - 3 * day,
|
|
511
|
+
expiresAt: null,
|
|
512
|
+
gates: { messages: "scoop:quiet-otter", tools: "agent" },
|
|
513
|
+
connected: false,
|
|
514
|
+
pending: 0
|
|
515
|
+
}
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
var DummyGuests = class extends Emitter {
|
|
519
|
+
scenario;
|
|
520
|
+
#seats;
|
|
521
|
+
#clock;
|
|
522
|
+
#count = 0;
|
|
523
|
+
constructor(scenario, clock2) {
|
|
524
|
+
super();
|
|
525
|
+
this.scenario = scenario;
|
|
526
|
+
this.#clock = clock2;
|
|
527
|
+
this.#seats = guestsFixtures(scenario);
|
|
528
|
+
}
|
|
529
|
+
list() {
|
|
530
|
+
return this.#seats;
|
|
531
|
+
}
|
|
532
|
+
unavailable() {
|
|
533
|
+
return this.scenario === "unavailable" ? guestsUnavailable : null;
|
|
534
|
+
}
|
|
535
|
+
setScenario(scenario) {
|
|
536
|
+
this.scenario = scenario;
|
|
537
|
+
this.#seats = guestsFixtures(scenario);
|
|
538
|
+
this.emit("unavailable", this.unavailable());
|
|
539
|
+
this.emit("seats", this.#seats);
|
|
540
|
+
}
|
|
541
|
+
async mint(agentId, draft) {
|
|
542
|
+
await this.#clock.sleep(4);
|
|
543
|
+
const refused = this.unavailable() ?? (this.scenario === "fail" ? guestsRefused : null);
|
|
544
|
+
if (refused) throw new Error(refused);
|
|
545
|
+
this.#count += 1;
|
|
546
|
+
const now2 = Date.now();
|
|
547
|
+
const seat = {
|
|
548
|
+
id: `seat-${now2.toString(36)}-${this.#count}`,
|
|
549
|
+
label: draft.label,
|
|
550
|
+
agentId,
|
|
551
|
+
createdAt: now2,
|
|
552
|
+
expiresAt: draft.expiresAt === null ? null : Math.min(draft.expiresAt, now2 + cap),
|
|
553
|
+
gates: draft.gates,
|
|
554
|
+
connected: false,
|
|
555
|
+
pending: 0
|
|
556
|
+
};
|
|
557
|
+
this.#seats = [...this.#seats, seat];
|
|
558
|
+
this.emit("seats", this.#seats);
|
|
559
|
+
return { seat, url: `https://www.example.com/guest/${seat.id}#k=Tq7Lm2Vx9Rb4` };
|
|
560
|
+
}
|
|
561
|
+
async revoke(id) {
|
|
562
|
+
await this.#clock.sleep(4);
|
|
563
|
+
if (!this.#seats.some((seat) => seat.id === id)) throw new Error("That guest is already gone.");
|
|
564
|
+
this.#seats = this.#seats.filter((seat) => seat.id !== id);
|
|
565
|
+
this.emit("seats", this.#seats);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
|
|
487
569
|
// src/dummy/script.ts
|
|
488
570
|
var units = "/workspace/harbor/src/lib/units.ts";
|
|
489
571
|
var kelvin = `
|
|
@@ -2378,16 +2460,16 @@ var status = `<!DOCTYPE html>
|
|
|
2378
2460
|
`;
|
|
2379
2461
|
|
|
2380
2462
|
// src/dummy/extras.ts
|
|
2381
|
-
var
|
|
2463
|
+
var day2 = 864e5;
|
|
2382
2464
|
var now = Date.UTC(2026, 9, 5, 9, 0);
|
|
2383
2465
|
function frozenConversation(topic, reply) {
|
|
2384
2466
|
return [
|
|
2385
|
-
{ id: `f-${topic}-1`, role: "user", text: topic, createdAt: now - 3 *
|
|
2467
|
+
{ id: `f-${topic}-1`, role: "user", text: topic, createdAt: now - 3 * day2 },
|
|
2386
2468
|
{
|
|
2387
2469
|
id: `f-${topic}-2`,
|
|
2388
2470
|
role: "assistant",
|
|
2389
2471
|
status: "done",
|
|
2390
|
-
createdAt: now - 3 *
|
|
2472
|
+
createdAt: now - 3 * day2 + 6e4,
|
|
2391
2473
|
parts: [{ type: "text", text: reply }]
|
|
2392
2474
|
}
|
|
2393
2475
|
];
|
|
@@ -2401,7 +2483,7 @@ var frozen = [
|
|
|
2401
2483
|
title: "Write the onboarding guide for new contributors",
|
|
2402
2484
|
model: "claude-sonnet-5-5",
|
|
2403
2485
|
messages: 42,
|
|
2404
|
-
frozenAt: now - 2 *
|
|
2486
|
+
frozenAt: now - 2 * day2
|
|
2405
2487
|
},
|
|
2406
2488
|
messages: frozenConversation(
|
|
2407
2489
|
"Write the onboarding guide for new contributors",
|
|
@@ -2416,7 +2498,7 @@ var frozen = [
|
|
|
2416
2498
|
title: "Spike: can the cache move to KV without a cold start?",
|
|
2417
2499
|
model: "claude-opus-5-5",
|
|
2418
2500
|
messages: 118,
|
|
2419
|
-
frozenAt: now - 6 *
|
|
2501
|
+
frozenAt: now - 6 * day2
|
|
2420
2502
|
},
|
|
2421
2503
|
messages: frozenConversation(
|
|
2422
2504
|
"Spike: can the cache move to KV without a cold start?",
|
|
@@ -2431,7 +2513,7 @@ var frozen = [
|
|
|
2431
2513
|
title: "Summarize Q3 uptime and incidents",
|
|
2432
2514
|
model: "claude-haiku-4-5",
|
|
2433
2515
|
messages: 17,
|
|
2434
|
-
frozenAt: now - 21 *
|
|
2516
|
+
frozenAt: now - 21 * day2
|
|
2435
2517
|
},
|
|
2436
2518
|
messages: frozenConversation(
|
|
2437
2519
|
"Summarize Q3 uptime and incidents",
|
|
@@ -2446,7 +2528,7 @@ var frozen = [
|
|
|
2446
2528
|
title: "Check the tide tables against the harbor feed",
|
|
2447
2529
|
model: "claude-haiku-4-5",
|
|
2448
2530
|
messages: 9,
|
|
2449
|
-
frozenAt: now - 1 *
|
|
2531
|
+
frozenAt: now - 1 * day2
|
|
2450
2532
|
},
|
|
2451
2533
|
messages: frozenConversation(
|
|
2452
2534
|
"Check the tide tables against the harbor feed",
|
|
@@ -2461,7 +2543,7 @@ var frozen = [
|
|
|
2461
2543
|
title: "Write the nightly digest of merged pull requests",
|
|
2462
2544
|
model: "claude-sonnet-5-5",
|
|
2463
2545
|
messages: 6,
|
|
2464
|
-
frozenAt: now - 3 *
|
|
2546
|
+
frozenAt: now - 3 * day2
|
|
2465
2547
|
},
|
|
2466
2548
|
messages: frozenConversation(
|
|
2467
2549
|
"Write the nightly digest of merged pull requests",
|
|
@@ -2477,7 +2559,7 @@ var memories = [
|
|
|
2477
2559
|
title: "Lead with the result",
|
|
2478
2560
|
body: "Keep replies short. Lead with the result, then the evidence.",
|
|
2479
2561
|
tag: "feedback",
|
|
2480
|
-
updatedAt: now - 5 *
|
|
2562
|
+
updatedAt: now - 5 * day2
|
|
2481
2563
|
},
|
|
2482
2564
|
{
|
|
2483
2565
|
id: "mem-2",
|
|
@@ -2486,7 +2568,7 @@ var memories = [
|
|
|
2486
2568
|
title: "Tests before \u201Cfixed\u201D",
|
|
2487
2569
|
body: "Run the tests before saying something is fixed, and quote the summary line.",
|
|
2488
2570
|
tag: "feedback",
|
|
2489
|
-
updatedAt: now - 9 *
|
|
2571
|
+
updatedAt: now - 9 * day2
|
|
2490
2572
|
},
|
|
2491
2573
|
{
|
|
2492
2574
|
id: "mem-3",
|
|
@@ -2495,7 +2577,7 @@ var memories = [
|
|
|
2495
2577
|
title: "Works in UTC",
|
|
2496
2578
|
body: "Schedules, timestamps and cron expressions are in UTC unless stated otherwise.",
|
|
2497
2579
|
tag: "user",
|
|
2498
|
-
updatedAt: now - 30 *
|
|
2580
|
+
updatedAt: now - 30 * day2
|
|
2499
2581
|
},
|
|
2500
2582
|
{
|
|
2501
2583
|
id: "mem-4",
|
|
@@ -2504,7 +2586,7 @@ var memories = [
|
|
|
2504
2586
|
title: "harbor",
|
|
2505
2587
|
body: "Forecast API, deployed as a worker. Cache bugs show up around midnight UTC. Staging deploys are frozen on Fridays.",
|
|
2506
2588
|
tag: "project",
|
|
2507
|
-
updatedAt: now -
|
|
2589
|
+
updatedAt: now - day2
|
|
2508
2590
|
},
|
|
2509
2591
|
{
|
|
2510
2592
|
id: "mem-5",
|
|
@@ -2513,7 +2595,7 @@ var memories = [
|
|
|
2513
2595
|
title: "release-notes",
|
|
2514
2596
|
body: "Drafted every Friday from merged pull requests, grouped as features, fixes and chores.",
|
|
2515
2597
|
tag: "project",
|
|
2516
|
-
updatedAt: now - 4 *
|
|
2598
|
+
updatedAt: now - 4 * day2
|
|
2517
2599
|
},
|
|
2518
2600
|
{
|
|
2519
2601
|
id: "mem-6",
|
|
@@ -2522,7 +2604,7 @@ var memories = [
|
|
|
2522
2604
|
title: "Cache keys",
|
|
2523
2605
|
body: "Cache keys are `city:UTC-date`. Never key on local time.",
|
|
2524
2606
|
tag: "project",
|
|
2525
|
-
updatedAt: now - 2 *
|
|
2607
|
+
updatedAt: now - 2 * day2
|
|
2526
2608
|
},
|
|
2527
2609
|
{
|
|
2528
2610
|
id: "mem-7",
|
|
@@ -2531,7 +2613,7 @@ var memories = [
|
|
|
2531
2613
|
title: "Retries",
|
|
2532
2614
|
body: "Retry upstream calls three times with exponential backoff starting at 250 ms.",
|
|
2533
2615
|
tag: null,
|
|
2534
|
-
updatedAt: now - 2 *
|
|
2616
|
+
updatedAt: now - 2 * day2
|
|
2535
2617
|
},
|
|
2536
2618
|
{
|
|
2537
2619
|
id: "mem-8",
|
|
@@ -2540,7 +2622,7 @@ var memories = [
|
|
|
2540
2622
|
title: "Past tense, no ticket numbers",
|
|
2541
2623
|
body: "One sentence per change, in the past tense, without ticket numbers.",
|
|
2542
2624
|
tag: "feedback",
|
|
2543
|
-
updatedAt: now - 7 *
|
|
2625
|
+
updatedAt: now - 7 * day2
|
|
2544
2626
|
},
|
|
2545
2627
|
{
|
|
2546
2628
|
id: "mem-9",
|
|
@@ -2549,7 +2631,7 @@ var memories = [
|
|
|
2549
2631
|
title: "Read the tests first",
|
|
2550
2632
|
body: "Start a review from the tests, then read the change they cover.",
|
|
2551
2633
|
tag: "feedback",
|
|
2552
|
-
updatedAt: now - 3 *
|
|
2634
|
+
updatedAt: now - 3 * day2,
|
|
2553
2635
|
source: "entry"
|
|
2554
2636
|
},
|
|
2555
2637
|
{
|
|
@@ -2559,7 +2641,7 @@ var memories = [
|
|
|
2559
2641
|
title: "Checks",
|
|
2560
2642
|
body: "Flag any change to cache keys. Ask for a test when a retry count changes.",
|
|
2561
2643
|
tag: null,
|
|
2562
|
-
updatedAt: now - 3 *
|
|
2644
|
+
updatedAt: now - 3 * day2,
|
|
2563
2645
|
source: "notes"
|
|
2564
2646
|
}
|
|
2565
2647
|
];
|
|
@@ -5943,8 +6025,8 @@ Done in 3.8s`
|
|
|
5943
6025
|
|
|
5944
6026
|
// src/dummy/vault.ts
|
|
5945
6027
|
var minute2 = 6e4;
|
|
5946
|
-
var
|
|
5947
|
-
var
|
|
6028
|
+
var hour2 = 60 * minute2;
|
|
6029
|
+
var day3 = 24 * hour2;
|
|
5948
6030
|
var harbor = { domain: "api.harbor.example", header: "Authorization", methods: [], paths: [] };
|
|
5949
6031
|
var pier = {
|
|
5950
6032
|
domain: "packages.pier.example",
|
|
@@ -5965,7 +6047,7 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
5965
6047
|
rules: [harbor],
|
|
5966
6048
|
exits: ["proxy", "tailscale", "device:link-pier"],
|
|
5967
6049
|
exec: false,
|
|
5968
|
-
createdAt: now2 - 12 *
|
|
6050
|
+
createdAt: now2 - 12 * day3,
|
|
5969
6051
|
usedAt: now2 - 4 * minute2
|
|
5970
6052
|
},
|
|
5971
6053
|
{
|
|
@@ -5979,9 +6061,9 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
5979
6061
|
rules: [pier],
|
|
5980
6062
|
exec: "argv",
|
|
5981
6063
|
execArgv: [["pier", "publish"]],
|
|
5982
|
-
createdAt: now2 - 3 *
|
|
5983
|
-
usedAt: now2 - 2 *
|
|
5984
|
-
expiresAt: now2 + 3 *
|
|
6064
|
+
createdAt: now2 - 3 * day3,
|
|
6065
|
+
usedAt: now2 - 2 * hour2,
|
|
6066
|
+
expiresAt: now2 + 3 * day3
|
|
5985
6067
|
},
|
|
5986
6068
|
{
|
|
5987
6069
|
name: "TIDE_MAIL",
|
|
@@ -6008,7 +6090,7 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
6008
6090
|
}
|
|
6009
6091
|
],
|
|
6010
6092
|
exec: false,
|
|
6011
|
-
createdAt: now2 - 20 *
|
|
6093
|
+
createdAt: now2 - 20 * day3,
|
|
6012
6094
|
expiresAt: now2 + 50 * minute2
|
|
6013
6095
|
},
|
|
6014
6096
|
{
|
|
@@ -6021,7 +6103,7 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
6021
6103
|
domains: [],
|
|
6022
6104
|
rules: [],
|
|
6023
6105
|
exec: "any",
|
|
6024
|
-
createdAt: now2 - 2 *
|
|
6106
|
+
createdAt: now2 - 2 * hour2
|
|
6025
6107
|
},
|
|
6026
6108
|
{
|
|
6027
6109
|
name: "BUOY_OLD_TOKEN",
|
|
@@ -6033,8 +6115,8 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
6033
6115
|
domains: ["buoy.example"],
|
|
6034
6116
|
rules: [{ domain: "buoy.example", header: "X-Buoy-Key", methods: ["GET"], paths: [] }],
|
|
6035
6117
|
exec: "shell",
|
|
6036
|
-
createdAt: now2 - 40 *
|
|
6037
|
-
removedAt: now2 -
|
|
6118
|
+
createdAt: now2 - 40 * day3,
|
|
6119
|
+
removedAt: now2 - day3
|
|
6038
6120
|
}
|
|
6039
6121
|
];
|
|
6040
6122
|
}
|
|
@@ -6051,7 +6133,7 @@ function accountRecords(now2 = Date.now()) {
|
|
|
6051
6133
|
domains: ["docs.example"],
|
|
6052
6134
|
rules: [{ domain: "docs.example", header: "Authorization", methods: [], paths: [] }],
|
|
6053
6135
|
exec: false,
|
|
6054
|
-
createdAt: now2 - 6 *
|
|
6136
|
+
createdAt: now2 - 6 * day3,
|
|
6055
6137
|
usedAt: now2 - 20 * minute2
|
|
6056
6138
|
}
|
|
6057
6139
|
];
|
|
@@ -6077,7 +6159,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6077
6159
|
type: "materialize",
|
|
6078
6160
|
seq: 11,
|
|
6079
6161
|
id: "audit-11",
|
|
6080
|
-
at: now2 - 2 *
|
|
6162
|
+
at: now2 - 2 * hour2,
|
|
6081
6163
|
name: "PIER_PUBLISH_TOKEN",
|
|
6082
6164
|
exec: "argv",
|
|
6083
6165
|
path: "/usr/bin/pier",
|
|
@@ -6093,7 +6175,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6093
6175
|
type: "materialize",
|
|
6094
6176
|
seq: 10,
|
|
6095
6177
|
id: "audit-10",
|
|
6096
|
-
at: now2 - 3 *
|
|
6178
|
+
at: now2 - 3 * hour2,
|
|
6097
6179
|
name: "PIER_PUBLISH_TOKEN",
|
|
6098
6180
|
uid: 1001,
|
|
6099
6181
|
tag: "docs-pass",
|
|
@@ -6112,7 +6194,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6112
6194
|
type: "forward",
|
|
6113
6195
|
seq: 9,
|
|
6114
6196
|
id: "audit-9",
|
|
6115
|
-
at: now2 - 6 *
|
|
6197
|
+
at: now2 - 6 * hour2,
|
|
6116
6198
|
name: "HARBOR_API_KEY",
|
|
6117
6199
|
domain: "api.harbor.example",
|
|
6118
6200
|
method: "POST",
|
|
@@ -6124,7 +6206,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6124
6206
|
type: "policy",
|
|
6125
6207
|
seq: 8,
|
|
6126
6208
|
id: "audit-8",
|
|
6127
|
-
at: now2 -
|
|
6209
|
+
at: now2 - day3,
|
|
6128
6210
|
file: "/etc/slicc/secrets-policy.toml",
|
|
6129
6211
|
sha256: "4e07408562bedb8b60ce05c1decfe3ad",
|
|
6130
6212
|
accepted: true,
|
|
@@ -6142,7 +6224,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6142
6224
|
type: "remove",
|
|
6143
6225
|
seq: 7,
|
|
6144
6226
|
id: "audit-7",
|
|
6145
|
-
at: now2 -
|
|
6227
|
+
at: now2 - day3 - hour2,
|
|
6146
6228
|
name: "BUOY_OLD_TOKEN"
|
|
6147
6229
|
},
|
|
6148
6230
|
{
|
|
@@ -6150,7 +6232,7 @@ function vaultAudit(now2 = Date.now()) {
|
|
|
6150
6232
|
type: "seal",
|
|
6151
6233
|
seq: 6,
|
|
6152
6234
|
id: "audit-6",
|
|
6153
|
-
at: now2 - 3 *
|
|
6235
|
+
at: now2 - 3 * day3,
|
|
6154
6236
|
name: "PIER_PUBLISH_TOKEN",
|
|
6155
6237
|
source: "jerk:cone@link-pier",
|
|
6156
6238
|
rotated: false
|
|
@@ -6163,7 +6245,7 @@ function vaultOlder(now2 = Date.now()) {
|
|
|
6163
6245
|
type: "materialize",
|
|
6164
6246
|
seq: 5,
|
|
6165
6247
|
id: "audit-5",
|
|
6166
|
-
at: now2 - 4 *
|
|
6248
|
+
at: now2 - 4 * day3,
|
|
6167
6249
|
name: "PIER_PUBLISH_TOKEN",
|
|
6168
6250
|
uid: 1e3,
|
|
6169
6251
|
tag: null,
|
|
@@ -6181,7 +6263,7 @@ function vaultOlder(now2 = Date.now()) {
|
|
|
6181
6263
|
type: "policy",
|
|
6182
6264
|
seq: 4,
|
|
6183
6265
|
id: "audit-4",
|
|
6184
|
-
at: now2 - 5 *
|
|
6266
|
+
at: now2 - 5 * day3,
|
|
6185
6267
|
file: "/etc/fnox/config.toml",
|
|
6186
6268
|
sha256: "8f14e45fceea167a5a36dedd4bea2543",
|
|
6187
6269
|
accepted: false,
|
|
@@ -6192,7 +6274,7 @@ function vaultOlder(now2 = Date.now()) {
|
|
|
6192
6274
|
type: "restore",
|
|
6193
6275
|
seq: 3,
|
|
6194
6276
|
id: "audit-3",
|
|
6195
|
-
at: now2 - 6 *
|
|
6277
|
+
at: now2 - 6 * day3,
|
|
6196
6278
|
name: "TIDE_MAIL",
|
|
6197
6279
|
uid: 0,
|
|
6198
6280
|
tag: null
|
|
@@ -6201,7 +6283,7 @@ function vaultOlder(now2 = Date.now()) {
|
|
|
6201
6283
|
type: "mcp",
|
|
6202
6284
|
seq: 2,
|
|
6203
6285
|
id: "audit-2",
|
|
6204
|
-
at: now2 - 7 *
|
|
6286
|
+
at: now2 - 7 * day3,
|
|
6205
6287
|
name: "DOCS_MCP_TOKEN",
|
|
6206
6288
|
uid: 1e3,
|
|
6207
6289
|
tag: "cone",
|
|
@@ -6643,6 +6725,9 @@ function vaultAccounts(accounts2) {
|
|
|
6643
6725
|
(account) => account.auth === "api-key" || account.id === "github" ? { ...account, store: "vault" } : account
|
|
6644
6726
|
);
|
|
6645
6727
|
}
|
|
6728
|
+
function guestsPort(guests, time) {
|
|
6729
|
+
return guests === "off" ? {} : { guests: new DummyGuests(guests, time) };
|
|
6730
|
+
}
|
|
6646
6731
|
function createDummyModel({
|
|
6647
6732
|
delay = 30,
|
|
6648
6733
|
storage = null,
|
|
@@ -6660,6 +6745,7 @@ function createDummyModel({
|
|
|
6660
6745
|
approvals = "off",
|
|
6661
6746
|
follow = "off",
|
|
6662
6747
|
vault = "off",
|
|
6748
|
+
guests = "off",
|
|
6663
6749
|
accounts: accounts2 = "settings",
|
|
6664
6750
|
providers = "connected",
|
|
6665
6751
|
hold = "off",
|
|
@@ -6736,7 +6822,8 @@ function createDummyModel({
|
|
|
6736
6822
|
...follow === "off" ? {} : { follow: new DummyFollow(follow, time) },
|
|
6737
6823
|
...vault === "off" ? {} : {
|
|
6738
6824
|
vault: new DummyVault(vault, time, accounts2 === "vault" ? accountRecords() : [])
|
|
6739
|
-
}
|
|
6825
|
+
},
|
|
6826
|
+
...guestsPort(guests, time)
|
|
6740
6827
|
};
|
|
6741
6828
|
base.vault?.on("status", (status2) => {
|
|
6742
6829
|
if (!status2.locked && agent.hold()?.reason === "locked") agent.setHold(null);
|
|
@@ -6751,6 +6838,7 @@ export {
|
|
|
6751
6838
|
DummyBrowser,
|
|
6752
6839
|
DummyChanges,
|
|
6753
6840
|
DummyFollow,
|
|
6841
|
+
DummyGuests,
|
|
6754
6842
|
DummyNetwork,
|
|
6755
6843
|
DummyNotices,
|
|
6756
6844
|
DummyTabs,
|
|
@@ -6762,6 +6850,9 @@ export {
|
|
|
6762
6850
|
browserHistory,
|
|
6763
6851
|
createDummyModel,
|
|
6764
6852
|
followFixtures,
|
|
6853
|
+
guestsFixtures,
|
|
6854
|
+
guestsRefused,
|
|
6855
|
+
guestsUnavailable,
|
|
6765
6856
|
joinUrl,
|
|
6766
6857
|
leadersFixtures,
|
|
6767
6858
|
linksFixtures,
|