@ai-ecoverse/slicc-spectrum 1.68.2 → 1.69.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 +25 -7
- package/dist/slicc-dummy.js +52 -4
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +828 -648
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/agents.d.ts +4 -0
- package/dist/types/app/approvals.d.ts +5 -1
- package/dist/types/app/browser.d.ts +2 -0
- package/dist/types/dummy/approvals.d.ts +2 -1
- package/dist/types/dummy/browser.d.ts +5 -0
- package/dist/types/dummy.d.ts +1 -1
- package/dist/types/ui.d.ts +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -137,20 +137,20 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
137
137
|
| `<slicc-app>` | The shell: header with the agent picker, status items, the update status, the network indicator, the tray, the View menu and the theme switch, rails, and a `<slicc-dock>`. Saves the dock layout per screen class to `storage` (default `localStorage`) under `layoutKey`. While connected it sets `overscroll-behavior` to `none !important` on the page's `html` and `body`, so only scroll areas inside panels rubber-band. It sets the page's `color-scheme` to its resolved theme, and when the page has a `theme-color` meta per `prefers-color-scheme`, gives every one of them the content of the meta for that theme, so the page and the installed app's title bar follow the app's setting instead of the OS. When the last app on the page is removed, the page's own inline values and `theme-color` contents come back. With a `follow` port, the header shows a `<slicc-follow>` chip, and while the follower connects, waits to be allowed, is unlinked or fails, a `<slicc-follow page>` (`.alone[data-follow]` with the page kind) takes the place of the panels; without Terminal access, the terminal surface and New terminal aren't offered, and open terminals close when it's taken away. |
|
|
138
138
|
| `<slicc-dock>` | Generic. [dockview-core](https://dockview.dev/) 8 with a theme mapped to Spectrum tokens, in its own shadow root. Panels come from `factories` (`name → (params) => HTMLElement`). `open`, `close`, `has`, `content`, `focusPanel`, `cycleGroup`, `cycleTab`, `toJSON`, `restore`, `clear`, and the full `api`. Tabs have a context menu (close, close others, float, maximize). Events: `layout-change`, `active-panel-change`, `panel-close`. |
|
|
139
139
|
| `<slicc-agents>` | Cones with their scoops, status, context fill and unread count. A cone's row has a second line with its conversation title, as does its item in the header's cone picker: `Agent.title`, or else the first line of the first user message, or "No messages yet". When cones share a name, their accessible names and the Delete cone, Freezer and confirmation labels add the title, as in "Delete cone sliccy (Hello from Bedrock)". Selecting one makes it the active agent. |
|
|
140
|
-
| `<slicc-chat>` | One thread's conversation (its `agent`, or the active agent if unset), by day: user messages (with attachments,
|
|
140
|
+
| `<slicc-chat>` | One thread's conversation (its `agent`, or the active agent if unset), by day: user messages (with attachments and a quiet timestamp, `time.when`; the reader's own carry no name or delivery tag, while `data-mode` and `data-delivered` still say how they went; a message from another cone, a follower or a guest says who sent it, and an agent's request carries its name and an Agent badge), assistant messages (with the time, and the model only on the first reply and whenever it changes from the previous one, `.meta .model` with the usage as its tooltip; the cone's name isn't repeated; Markdown with tables, task lists, quotes, code and images; thinking; tool calls, grouped into a collapsible cluster from three in a row, each with its input (a shell command after `$`), an optional `meta` such as a timeout, an edit's `diff` shown as a diff view, and an `image`, such as a screenshot, shown as a thumbnail under the closed card and in full when open, its output wrapped rather than cut off, and its `notes` (`{ severity, text }`, such as "Exited with code 5" or "Full output in /tmp/…") as lines under the output, `li[data-note][data-severity]`: `error` with an alert icon in the negative colour, `warn` with an alert icon and `info` with an info icon, both subdued; media galleries with images, video and audio; action cards for tools, pull requests and status; plans and checklists; inline diffs; question prompts; delegations; link previews; errors with a fix-it action; a content-filter stop offers **Drop the last turn**, which rewinds that turn and puts its prompt back into the composer; when the turns right before it failed too, the button reads **Drop N failed turns** and rewinds to before the first of them, putting the latest prompt back), standalone tool results, system messages (compaction markers, error cards, notices) and licks, SLICC's events into a cone, on every channel; a lick's `severity` (`warn` or `error`) turns its channel badge into a notice or negative one with an alert icon. Permission requests are allowed or denied in place. The header shows the status, a model picker that names each model's provider and leaves out classifiers and models with `tools: false`, the thinking picker, the context meter and **New conversation**, which calls `clear` and puts focus back in the composer; on a cone, when the port can freeze, its tooltip says the old conversation stays in the Freezer. Between the header and the transcript, a `<slicc-notices>` shows the model's notices. On a follower without Control, a note (`p[data-follow="watch"]`) takes the composer's place, the header shows the model's name instead of the pickers and New conversation, and there's no suggested follow-up. |
|
|
141
141
|
| `<slicc-notices>` | The model's `notices` as in-line alerts with their actions and a close button, after a banner from the `tabs` port when another tab is paused or runs a different SLICC, and a warning for each waiting approval when the `approvals` port has no `decide` (`[data-approval-notice]`); renders nothing without any. |
|
|
142
|
-
| `<slicc-approvals>` | The model's waiting `approvals` as a modal alert dialog, one at a time, oldest first; `<slicc-app>` mounts it. Renders nothing without `approvals.decide`. |
|
|
142
|
+
| `<slicc-approvals>` | The model's waiting `approvals` as a modal alert dialog, one at a time, oldest first; `<slicc-app>` mounts it. Renders nothing without `approvals.decide`. A request that `secretRules()` flags shows a warning (`[data-secret-rules]`, the path or token) and no **Always allow**. Focus starts on Deny and goes back, after Deny, Always allow, Allow once, <kbd>Esc</kbd> or Close, to what had it when the dialog opened; inside a terminal, to that `<slicc-terminal>` through its own `focus()`, even when it remounted meanwhile. |
|
|
143
143
|
| `<slicc-follow>` | The model's `follow` state. By default a header chip (`#chip[data-follow]` with the link state: **Connecting to**, **Following**, **Reconnecting to**, **Unlinked from** or **Can’t reach** the owner; `compact` keeps only a follow icon and the light) whose popover says what this device can do (`[data-can]`), that the conversation may be out of date while reconnecting, and with Control that sending waits (`[data-reconnecting]`), and has **Leave** (`data-action="leave"`, behind a confirm unless unlinked). With `page`, the full page for `connecting`, `waiting` (connected without View: "Waiting for {owner} to allow this device"), `unlinked` and `failed` (`[data-follow-page]`), with **Try again** (`data-action="retry"`) when failed and the port has `retry`, and **Leave**. A rejected call shows its message (`[data-error]`). Renders nothing without the port. |
|
|
144
|
-
| `<slicc-composer>` | The message box. <kbd>Enter</kbd> sends, or steers a running reply: the message joins after the current tool round, without interrupting; <kbd>Ctrl</kbd>/<kbd>⌘</kbd>+<kbd>Enter</kbd> queues a follow-up that runs after the reply and can be withdrawn; <kbd>Shift</kbd>+<kbd>Enter</kbd> adds a line; <kbd>Tab</kbd> takes the suggested follow-up; <kbd>↑</kbd>/<kbd>↓</kbd> at the start or end walk this agent's history; <kbd>Esc</kbd> stops. `/` completes commands (`/clear`, `/compact`, `/model`, `/thinking`, `/scoop`, `/stop`, `/freeze` when the port can freeze, `/theme`) and their arguments, `@` mentions agents and files. Attach from the computer, from SLICC's files, a browser screenshot or a shared secret (with a `vault.put`, the popup takes the value too, checks it like Settings › Secrets, puts it in the vault and attaches the name, showing errors inline and clearing the field; an empty value shares a name the vault already has, and a new value for one asks before it replaces it; while the vault is away (`availability()` `connecting` or `waiting`) or locked, it takes no value and says so; without one, it attaches the name and says to add the value in Settings › Secrets), or paste and drop files (25 MB each). Queued messages wait above it, to send now or drop. Model and thinking pickers, and dictation where the browser has speech recognition. Drafts are kept per agent. On a follower while the link reconnects, the field is disabled and says so; the draft stays. While the agent holds tool results for the vault (`agent.hold()`), a status line above it says "Waiting for the vault" or "Unlock the vault to continue", with how many results are held, and offers the vault's **Unlock** button when locked and the model has a `vault`; if the browser blocks the vault window, the line says to allow pop-ups. |
|
|
144
|
+
| `<slicc-composer>` | The message box. <kbd>Enter</kbd> sends, or steers a running reply: the message joins after the current tool round, without interrupting; <kbd>Ctrl</kbd>/<kbd>⌘</kbd>+<kbd>Enter</kbd> queues a follow-up that runs after the reply and can be withdrawn; <kbd>Shift</kbd>+<kbd>Enter</kbd> adds a line; <kbd>Tab</kbd> takes the suggested follow-up; <kbd>↑</kbd>/<kbd>↓</kbd> at the start or end walk this agent's history; <kbd>Esc</kbd> stops. `/` completes commands (`/clear`, `/compact`, `/model`, `/thinking`, `/scoop`, `/stop`, `/freeze` when the port can freeze, `/theme`) and their arguments, `@` mentions agents and files. Attach from the computer, from SLICC's files, a browser screenshot or a shared secret (with a `vault.put`, the popup takes the value too, checks it like Settings › Secrets, puts it in the vault and attaches the name, showing errors inline and clearing the field; an empty value shares a name the vault already has, and a new value for one asks before it replaces it; while the vault is away (`availability()` `connecting` or `waiting`) or locked, it takes no value and says so; without one, it attaches the name and says to add the value in Settings › Secrets), or paste and drop files (25 MB each). Queued messages wait above it, to send now or drop; a steer the agent still holds (`mode: 'steer'`, `li.queued[data-mode=steer]`) reads "Sends at the next step" and can only be dropped. Model and thinking pickers, and dictation where the browser has speech recognition. Drafts are kept per agent. On a follower while the link reconnects, the field is disabled and says so; the draft stays. While the agent holds tool results for the vault (`agent.hold()`), a status line above it says "Waiting for the vault" or "Unlock the vault to continue", with how many results are held, and offers the vault's **Unlock** button when locked and the model has a `vault`; if the browser blocks the vault window, the line says to allow pop-ups. |
|
|
145
145
|
| `<slicc-files>` | The file system as a `<slicc-file-tree>`, with pending changes as git status. Opening a file opens it in a tab. With `files.mountFolder`, **Mount a folder…** (`data-action="mount-folder"`) mounts a local folder, then selects and expands it in the tree; with `files.mounts`, a **Mounted** strip lists the mount points (with `files.mountInfo`, each mount with a `source` reads `/mnt/proj · ref-node/proj`, `data-source` on its `.path`), each with **Eject** (`data-action="eject"`, `data-path` the mount point) when the port has `eject`. With `files.needsFolder`, a **Needs a folder** section lists the mount points waiting for a folder, each with **Insert folder…** (`data-action="insert-folder"`, `data-id` the mount point), which calls `mountFolder(path)`. A failed mount, insert or eject shows its message under the bar. |
|
|
146
146
|
| `<slicc-file-view>` | One file (`path`), highlighted, with its pending change and a link to the diff. |
|
|
147
147
|
| `<slicc-changes>` | Pending changes with their author. Opens the diff, accepts or reverts one change or all of them. Changes with a `repo` show their folder relative to it, and no author when `agentId` is `null`; they group under a header per repository (its path and count), even when there is only one, in the order the port returns them. In a diff panel narrower than 480 px, Accept and Revert (`data-action="accept"`, `data-action="revert"`) show only their icons. Revert and Revert all ask with a destructive confirm that says the uncommitted (or, without a repo, pending) changes can't be undone; a failed revert shows its message under the bar. When the port's `unavailable()` returns a string, the panel shows it instead of the list, with each `backtick` span as inline code in the text's color and, below, each command once with a **Copy** button (`data-command`, `data-action="copy-command"`). |
|
|
148
148
|
| `<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. |
|
|
149
149
|
| `<slicc-terminals>` | Terminal sessions in tabs, each a `<slicc-terminal>` on the session's `TerminalBackend`. Sessions stay alive while hidden; `exit` closes the tab. |
|
|
150
|
-
| `<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
|
|
150
|
+
| `<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 a small **Stop <agent>** (`data-action="stop-agent"`) in the same row, which keeps its height with or without an agent, 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), the tab's own filtered first, then the latest 50, with **Show more** (`data-action="more-actions"`) adding 50 earlier ones each time up to all the port has ("Showing the latest N of M"); the list scrolls and takes focus. Actions with a URL (`open`, `goto`, `request`) expand (`data-action="action-url"`, `aria-expanded`) to show the full URL, or the request line, under the shortened one. Below 480 px they fold into **Recent actions (n)**, a chevron disclosure counting all of them, 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 commands to install and run slicc (`curl -fsSL https://get.sliccy.ai/slicc | sh`, then `slicc`, or `npx sliccy@next` with Node). The Browser rail button carries a dot (`data-driven`) while a tab is in use. |
|
|
151
151
|
| `<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`. An account with `fields` (`{ name, label, required? }`) shows a plain labelled input for each, and their values go to `connect(id, secret?, fields)`. An account with `store: 'vault'` keeps its key in the vault when the `vault` port has `put`: the masked key field (C5's value rules) goes to `vault.put(name, key, undefined, { kind: 'model', provider: account.id })` (or `{ kind: 'mcp', server }` for `mcp:<server>` accounts, named `MCP_<SERVER>_TOKEN`; model records use pi-ai's environment-variable name), then `connect(id)` runs without the secret; a connected one says “Key kept in the vault”, “Token kept in the vault” or “Kept in the vault”, and Disconnect removes the record first. Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before. With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
|
|
152
152
|
| `<slicc-secrets>` | Settings › Secrets from the model's `vault`. It never shows a value or a placeholder. Value fields are `type=password` with no reveal toggle, are read once on submit and cleared at once (on success, refusal, error, cancel, close and disconnect), and are checked before the vault is called: under 8 UTF-8 bytes, CR, LF or NUL, or containing `fnox_proxy_` are refused with the vault's own wording. Values go only to `vault.put` and are never kept on the element or put in an event. The head shows **Locked** or **Unlocked** and the lock mode; locked, the vault's **Unlock** button (`slicc-vault-button[data-action=unlock]`); unlocked, **Lock** (`data-action="lock"`, with `lock`) and the vault's **Protect with a passkey** (lock `auto`) or **Change protection** button (`data-action="protect"`). A status line (`[data-availability]`) says "Waiting for the vault" while `availability()` is `connecting` or `waiting` (or `status()` is `null` without it), and "Unlock the vault to continue" while `scrub.needed` and not `scrub.ready`. Waiting requests show under **Waiting for you** (`[data-request]`, the agent's `prompt` as plain text under "From the agent, not checked by SLICC:", capped at 200 characters, and "Expires in N min"), with **Enter value** (`data-action="answer"`, which fires `value-request` with `{ id, vault }` for the `<slicc-value-request>` on the same vault) for `value` requests and **Cancel request** (`data-action="cancel-request"`); `oauth`, `jerk-seal`, `jerk` and `unseal` requests say what they ask for and offer Cancel request only. With `backupReminder: 'due'`, an alert (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`). **Add secret** (`data-action="add-secret"`, `[data-form=add]`, a name and a value; a name that exists asks before it replaces the value), **Import…** (`data-action="import"`, `[data-form=import]`: Bundle, KeePass or six, a file, an optional password, then a pick of candidates, all checked but the `refused` ones (disabled, with their reason), marked "Replaces a secret" when they exist, with their suggested rules as untrusted text and a note that the file's rules aren't applied, and **Import N**, which sends `{ name, kind }` for a candidate of another `kind`; then "Imported N" and "Skipped NAME: reason" for each skipped name, from `reasons`) and **Export…** (`data-action="export"`, `[data-form=export]`: a passphrase twice, at least 8 bytes, behind a confirmation, downloaded as `slicc-secrets-<yyyy-mm-dd>.slicc-vault`, with "Last exported" or "Never exported" from `exportedAt`). Saved shows "Saved NAME in the vault" (`[data-said]`). The secrets, sorted by name (`li[data-secret][data-state]`, with `[data-rules]`, `[data-exits]` only when restricted, `[data-exec]` (any command, the listed commands, every bash) and how often programs were given it today, `[data-source]`, `[data-used]`, `[data-expires]`; a model record or an MCP token no rule names reads “Used for” (`[data-used-for]`, model requests to its provider or requests to its MCP server, by the agent) instead of where it's sent, with its account's state as its light, and shows its Provider (`[data-provider]`, the account's name) or Server (`[data-server]`)) offer **Replace value** (`data-action="replace"`, for `settings`, `process` and `import:*` secrets that aren't sign-ins) and **Remove** (`data-action="remove"`, behind a confirmation, passing the secret's `kind`), and removed ones, under **Removed**, **Restore** (`data-action="restore"`; ENOENT reads "Removed too long ago to restore"). Locked, value forms say "Unlock the vault first" and disable Save. Vault errors show their message, EACCES as "Unlock the vault first" and ENOTSUP as "This arrives with a later vault version". Each action shows only when the port has its method. Then **Rules SLICC refused** (`[data-refused]`), where the rules live (`[data-policy]`) and **Activity** (`ol.audit li[data-seq][data-type]`; an entry with `uid` and `tag` both `null` reads "by you", a `refresh` entry "Refreshed automatically" (with the sign-in it used), an `mcp` entry "Sent to the MCP server SERVER at DOMAIN" with its request and exit, and a policy entry lists its changes, its warning and, when accepted, how to change the file back; **Only reveals to programs**, `data-action="reveals"`, and **Show older**, `data-action="older"`). The section's `data-vault` is `locked`, `unlocked` or `loading`. A popup the browser blocked shows how to allow it (`[data-popup=blocked]`). Widened-rule notices aren't shown. |
|
|
153
|
-
| `<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 }`. spectrum sends only the `unlock` and `protect` intents. |
|
|
153
|
+
| `<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`, sets the frame's `color-scheme` to `theme` (or leaves it to the page) so the frame stays transparent, 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 }`. spectrum sends only the `unlock` and `protect` intents. |
|
|
154
154
|
| `<slicc-value-request>` | The vault's waiting `value` requests (`fnox set` on a terminal) as a modal dialog, one at a time, oldest first, with "N more waiting"; `<slicc-app>` mounts it. `dialog[data-request]`: "Enter a value for NAME" (a Name field when the request has no name), who asks, as on the Settings card ("docs-pass asks for NAME", or "A program running as uid N asks for NAME"), the prompt under "From the agent, not checked by SLICC:", a masked value field with the same rules as Settings › Secrets, "Expires in N min", and **Later** (`data-action="later"`, or Escape: closes, the request stays in Settings › Secrets, whose **Enter value** reopens it), **Cancel request** (`data-action="cancel-request"`, with `cancel`) and **Save** (`data-action="save"`, `put(name, value, request.id)`, with `put`). Locked, it shows the vault's Unlock button and disables Save. It closes, clearing the field, when the request leaves `waiting` or while the vault is away. |
|
|
155
155
|
| `<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 neutral Pinned light (a hold, not a success) or a positive Added light, their version against seven's, and Unpin or Remove. Also available through View and the rails. |
|
|
156
156
|
| `<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”. |
|
|
@@ -187,6 +187,24 @@ import { prompt } from './dist/slicc-ui.js';
|
|
|
187
187
|
const name = await prompt({ title: 'New cone', label: 'Name', action: 'Create', submit: (name) => model.agent.createCone(name).then(() => {}) });
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
+
### Secret rules check
|
|
191
|
+
|
|
192
|
+
`secretRules(command, cwd)`, exported from `dist/slicc-ui.js` (`@ai-ecoverse/slicc-spectrum/ui`), is the check behind the sudo dialog's secret-rules warning. It takes a sudo request's command (without `sudo` and its options) and working folder, and returns `{ certain, maybe }`:
|
|
193
|
+
|
|
194
|
+
- `certain` is an absolute path when the command names a file or folder holding SLICC's secret rules (`/etc/fnox`, `/etc/fnox/config.toml`, `/etc/slicc/secrets-policy.toml`), a folder above them it changes as a whole (`/`, `/etc`, `/etc/slicc`), a glob or brace that matches one, or a file named like them (`fnox`, `secrets-policy`) anywhere, as `/tmp/fnox/config.toml`. It sees through quotes, backslashes, `$'…'` and `$"…"`, `cd` and `pushd` (also inside `bash -c`), relative paths and `..`, extglobs, `/proc/self/root` and `/proc/self/cwd`, option clusters like `-t/etc/fnox`, and comma lists.
|
|
195
|
+
- `maybe` is the token when the command may reach one of them and the check can't tell where it leads: an expansion (`$X`, `$(…)`, backticks), a `~user/..`, `~+` or `~-` path, a `/proc/<pid>/cwd` or `/dev/fd` path, or a command over 4096 characters (its first 40 followed by `…`).
|
|
196
|
+
|
|
197
|
+
Both are `null` for a clean command, and at most one is set. The dialog shows "This changes secret rules" for `certain` and "This may change secret rules" for `maybe`, and offers no Always allow for either. An embedder that records "always" decisions itself, such as slicc-bios, must refuse "always" (answer "once" at most) whenever either is set, so a decision sent without the dialog can't add a rule that runs it unasked.
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { secretRules } from './dist/slicc-ui.js';
|
|
201
|
+
|
|
202
|
+
secretRules("bash -c 'rm /etc/fn\\ox/config.toml'");
|
|
203
|
+
secretRules('rm -rf $TARGET', '/home/cone');
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The first gives `{ certain: '/etc/fnox/config.toml', maybe: null }`, the second `{ certain: null, maybe: '$TARGET' }`. The result's type is `SecretRulesVerdict`.
|
|
207
|
+
|
|
190
208
|
### Sprinkle bridge
|
|
191
209
|
|
|
192
210
|
A sprinkle's frame gets `window.slicc` with `name` and `lick(event)`. When the model's `sprinkles` port has `call`, it also gets:
|
|
@@ -222,7 +240,7 @@ With an `agentId` or a `cone`, the caller reaches the shared surfaces, the chats
|
|
|
222
240
|
| `agent` | `@ai-ecoverse/slicc-agent` | Cones and scoops, the active agent, conversations, `send` (with attachments, as a send, a steer or a queued follow-up) and `stop`, the queue, suggestions, answers to questions, permission decisions, compaction, clearing (`clear(agentId)` starts a new conversation: on a cone, a port with `freeze` first puts the old one in the Freezer as its own row, which thaws into a cone of its own; a scoop's is just reset), the model per agent and new scoops. The optional `rewind(agentId, messageId)` drops the user turn before that message and everything after it, and resolves with the turn's prompt (text and attachments) for the composer, or `null`. The optional `hold()` returns an `AgentHold` (`{ reason: waiting | locked, held }`) while tool results wait for the vault (slicc-agent's `connection.vault`: `waiting` while the vault port doesn't answer, `locked` while it needs a passkey), or `null`, with a `hold` event when it changes. The optional `drop(agentId)` removes a scoop: it stops and its working folder is deleted, while its conversation and reports are kept, and `createCone(name)` resolves with a new cone; the dummy rejects a cone name that is taken. `freeze(agentId)` is optional too: without it, **Delete cone** and `/freeze` don't show. `frozen()` lists the Freezer's rows; a `FrozenCone` may carry `kind` (`cone`, `scoop` or `agent`), `live` for a conversation that's still running, and `thawedAs`, the id of the cone a scoop or agent run was thawed into. `thaw(id)` brings a cone back, or forks a scoop or agent run into a new cone, and `discard(id)` removes a row. Names don't have to be unique, and an optional `Agent.title` names a cone's conversation; without it, the first user message does. Assistant messages stream: the same message is re-emitted as its parts grow and its tool calls go from `running` to `done`. |
|
|
223
241
|
| `files` | slicc-kernel's OPFS | `list`, `read`, `write`, `remove`, and pending changes with their before and after text (`accept`, `revert`). The optional `mountFolder()` asks for a local folder and resolves with its mount point (for example `/mnt/photos`), or `null` when the user cancels; the panel calls it synchronously in the click, so the port can call `showDirectoryPicker()`. The optional `mounts()` lists the mount points, and the optional `mountInfo()` returns `MountInfo`s (`target`, optional `source` such as a linked device's `ref-node/proj`, and `type`) for them, used instead of `mounts()` when present; `eject(path)` unmounts one (the folder stays on disk). `mountFolder(path)` inserts the picked folder at a mount point that waits for one; the optional `needsFolder()` lists those mount points. The port emits `mounts` and then `files` after every mount and eject, including those made from the shell, and `mounts` whenever `needsFolder()` changes. |
|
|
224
242
|
| `terminals` | slicc-kernel's terminal API | Terminal sessions; `backend(id)` returns a `TerminalBackend` for `<slicc-terminal>`. |
|
|
225
|
-
| `browser` | `@ai-ecoverse/slicc-cdp` | Tabs, the active tab, `open`, `navigate`, `close`, `screenshot`. A `BrowserTab` may say it's `controlled` (an agent, or the user's terminal when `agentId` is `null`, is driving it now; `agentId` is the agent of its last action); `favicon` is ignored. The optional `actions(tabId?)` returns recent `BrowserAction`s (`{ id, tabId, agentId, kind, target?, value?, length?, status: running \| done \| failed, error?, at }`, `kind` one of `open`, `goto`, `back`, `reload`, `close`, `select`, `snapshot`, `screenshot`, `eval`, `click`, `fill`, `type`, `press`, `scroll`, `request`; a `request` has `value` `'GET https://host/path'` and may have no tab), with an `action` event per new or settled action. The optional `watch(tabId, size?)` streams `frame` events (`{ tabId, src, at }`) for a controlled tab and returns a stop function; the panel watches only the focused tab while it's controlled and visible, with the frame's CSS size. **Stop** calls `agent.stop(agentId)`. |
|
|
243
|
+
| `browser` | `@ai-ecoverse/slicc-cdp` | Tabs, the active tab, `open`, `navigate`, `close`, `screenshot`. A `BrowserTab` may say it's `controlled` (an agent, or the user's terminal when `agentId` is `null`, is driving it now; `agentId` is the agent of its last action); `favicon` is ignored. The optional `actions(tabId?)` returns recent `BrowserAction`s, oldest first, all of them (bios keeps 500 per tab for 24 h, 5,000 in all; the panel shows 50 at a time) (`{ id, tabId, agentId, kind, target?, value?, length?, status: running \| done \| failed, error?, at }`, `kind` one of `open`, `goto`, `back`, `reload`, `close`, `select`, `snapshot`, `screenshot`, `eval`, `click`, `fill`, `type`, `press`, `scroll`, `request`; a `request` has `value` `'GET https://host/path'` and may have no tab), with an `action` event per new or settled action. The optional `watch(tabId, size?)` streams `frame` events (`{ tabId, src, at }`) for a controlled tab and returns a stop function; the panel watches only the focused tab while it's controlled and visible, with the frame's CSS size. **Stop** calls `agent.stop(agentId)`. |
|
|
226
244
|
| `settings` | local storage | Theme, model, thinking level, composer and diff preferences, the models on offer, and accounts. |
|
|
227
245
|
| `changes` (optional) | `@ai-ecoverse/slicc-agent` | Pending changes from git instead of the file system: `changes()`, `accept(path)` (stage), `revert(path)` (discard), and a `changes` event, as on `files`. When present, the UI reads changes only from this port, including the rail's count. A `Change` may carry `repo`, its repository root; put the active agent's repository first. The optional `unavailable()` returns why there can be no changes (no git, no repository), which the panel shows with commands in backticks made copyable, or `null`; emit `changes` when it flips. The optional `commands()` adds more copyable commands under that text, in order, without repeating its own. |
|
|
228
246
|
| `memory` | `@ai-ecoverse/slicc-agent` | Memories with scope, section, tag and body; `save` and `remove`. `source: 'notes'` marks free text the panel shows as an entry titled after its section; saving it makes it an entry. The optional `scopes()` lists the scope picker's choices as `{ id, label, group }`, ungrouped first, then under “Cones” and “Roles”; without it the picker offers “Everyone” (`global`) and each cone. The panel re-reads `scopes()` on `memories` and `agents`, so emit `memories` when the list changes. A scope that disappears falls back to `global`. |
|
|
@@ -237,7 +255,7 @@ With an `agentId` or a `cone`, the caller reaches the shared surfaces, the chats
|
|
|
237
255
|
| `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. |
|
|
238
256
|
| `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`. |
|
|
239
257
|
|
|
240
|
-
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. `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`.
|
|
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. `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 (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`.
|
|
241
259
|
|
|
242
260
|
`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.
|
|
243
261
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -57,6 +57,7 @@ function approvalFixtures(scenario, now2 = Date.now()) {
|
|
|
57
57
|
always: true
|
|
58
58
|
}
|
|
59
59
|
];
|
|
60
|
+
if (scenario === "none") return [];
|
|
60
61
|
if (scenario === "user") return [user];
|
|
61
62
|
if (scenario === "many") return [agent, user, cache];
|
|
62
63
|
if (scenario === "elsewhere" || scenario === "withdrawn") return [agent, user];
|
|
@@ -77,6 +78,10 @@ var DummyApprovals = class extends Emitter {
|
|
|
77
78
|
if (scenario === "elsewhere" || scenario === "withdrawn")
|
|
78
79
|
void time.sleep(100).then(() => this.end("sudo-jq", scenario === "elsewhere" ? "answered" : "withdrawn"));
|
|
79
80
|
}
|
|
81
|
+
raise(approval) {
|
|
82
|
+
this.#approvals = [...this.#approvals, approval];
|
|
83
|
+
this.emit("approvals", this.list());
|
|
84
|
+
}
|
|
80
85
|
end(id, reason) {
|
|
81
86
|
this.emit("ended", { id, reason });
|
|
82
87
|
this.#approvals = this.#approvals.filter((approval) => approval.id !== id);
|
|
@@ -116,6 +121,32 @@ function titleFor(url) {
|
|
|
116
121
|
return pathname === "/" ? host : `${host}${pathname}`;
|
|
117
122
|
}
|
|
118
123
|
var second = 1e3;
|
|
124
|
+
var actionLimits = { perTab: 500, total: 5e3, age: 24 * 60 * 60 * second };
|
|
125
|
+
var cities = ["Lisbon", "Porto", "Faro", "Braga", "Coimbra", "Aveiro"];
|
|
126
|
+
function session(now2) {
|
|
127
|
+
const harbor2 = { tabId: "tab-preview", agentId: "cone-harbor" };
|
|
128
|
+
return Array.from({ length: 170 }, (_, i) => {
|
|
129
|
+
const city = cities[i % cities.length];
|
|
130
|
+
const at3 = now2 - 286 * second + i * 960;
|
|
131
|
+
switch (i % 5) {
|
|
132
|
+
case 0:
|
|
133
|
+
return {
|
|
134
|
+
...harbor2,
|
|
135
|
+
kind: "goto",
|
|
136
|
+
value: `http://localhost:8787/forecast?city=${city}&days=${i % 7 + 1}&units=metric&compare=tides,wind,swell&session=run-${i}`,
|
|
137
|
+
at: at3
|
|
138
|
+
};
|
|
139
|
+
case 1:
|
|
140
|
+
return { ...harbor2, kind: "snapshot", at: at3 };
|
|
141
|
+
case 2:
|
|
142
|
+
return { ...harbor2, kind: "click", target: `button "Day ${i % 7 + 1}"`, at: at3 };
|
|
143
|
+
case 3:
|
|
144
|
+
return { ...harbor2, kind: "scroll", at: at3 };
|
|
145
|
+
default:
|
|
146
|
+
return { ...harbor2, kind: "screenshot", at: at3 };
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
119
150
|
function browserHistory(scenario, now2 = Date.now()) {
|
|
120
151
|
if (scenario === "off") return [];
|
|
121
152
|
const harbor2 = { tabId: "tab-preview", agentId: "cone-harbor" };
|
|
@@ -130,6 +161,7 @@ function browserHistory(scenario, now2 = Date.now()) {
|
|
|
130
161
|
{ ...otter, kind: "snapshot", at: now2 - 296 * second },
|
|
131
162
|
{ ...otter, kind: "scroll", at: now2 - 290 * second },
|
|
132
163
|
{ ...otter, kind: "screenshot", at: now2 - 288 * second },
|
|
164
|
+
...session(now2),
|
|
133
165
|
{ ...harbor2, kind: "open", value: "http://localhost:8787/", at: now2 - 120 * second },
|
|
134
166
|
{ ...harbor2, kind: "snapshot", at: now2 - 116 * second },
|
|
135
167
|
{ ...harbor2, kind: "type", target: 'textbox "City"', length: 6, at: now2 - 110 * second },
|
|
@@ -186,7 +218,16 @@ var DummyBrowser = class extends Emitter {
|
|
|
186
218
|
return this.#active;
|
|
187
219
|
}
|
|
188
220
|
actions(tabId) {
|
|
189
|
-
|
|
221
|
+
const since = Date.now() - actionLimits.age;
|
|
222
|
+
const counts = /* @__PURE__ */ new Map();
|
|
223
|
+
const kept = [];
|
|
224
|
+
for (const action of [...this.#actions].reverse()) {
|
|
225
|
+
if (kept.length === actionLimits.total) break;
|
|
226
|
+
const count = (counts.get(action.tabId) ?? 0) + 1;
|
|
227
|
+
counts.set(action.tabId, count);
|
|
228
|
+
if (count <= actionLimits.perTab && action.at >= since) kept.push(action);
|
|
229
|
+
}
|
|
230
|
+
return kept.reverse().filter((action) => tabId === void 0 || action.tabId === tabId).map((action) => ({ ...action }));
|
|
190
231
|
}
|
|
191
232
|
#push(draft) {
|
|
192
233
|
const action = {
|
|
@@ -2857,7 +2898,7 @@ function opening() {
|
|
|
2857
2898
|
role: "assistant",
|
|
2858
2899
|
status: "done",
|
|
2859
2900
|
createdAt: at(1),
|
|
2860
|
-
model: "claude-
|
|
2901
|
+
model: "claude-sonnet-5-5",
|
|
2861
2902
|
usage: { input: 18204, output: 912, cost: 0.31 },
|
|
2862
2903
|
parts: [
|
|
2863
2904
|
{
|
|
@@ -2873,6 +2914,7 @@ function opening() {
|
|
|
2873
2914
|
role: "assistant",
|
|
2874
2915
|
status: "done",
|
|
2875
2916
|
createdAt: at(5),
|
|
2917
|
+
model: "claude-sonnet-5-5",
|
|
2876
2918
|
parts: [
|
|
2877
2919
|
{
|
|
2878
2920
|
type: "text",
|
|
@@ -2913,6 +2955,7 @@ function opening() {
|
|
|
2913
2955
|
role: "assistant",
|
|
2914
2956
|
status: "done",
|
|
2915
2957
|
createdAt: at(9),
|
|
2958
|
+
model: "claude-sonnet-5-5",
|
|
2916
2959
|
parts: [
|
|
2917
2960
|
{ type: "text", text: "Every tool from today\u2019s run, in order:" },
|
|
2918
2961
|
...toolShapes.map((call) => ({ type: "tool", tool: call }))
|
|
@@ -2939,6 +2982,7 @@ function work() {
|
|
|
2939
2982
|
role: "assistant",
|
|
2940
2983
|
status: "done",
|
|
2941
2984
|
createdAt: at(14),
|
|
2985
|
+
model: "claude-sonnet-5-5",
|
|
2942
2986
|
parts: [
|
|
2943
2987
|
{ type: "text", text: "Here is where things stand." },
|
|
2944
2988
|
{
|
|
@@ -3031,6 +3075,7 @@ function work() {
|
|
|
3031
3075
|
role: "assistant",
|
|
3032
3076
|
status: "done",
|
|
3033
3077
|
createdAt: at(18),
|
|
3078
|
+
model: "claude-sonnet-5-5",
|
|
3034
3079
|
parts: [
|
|
3035
3080
|
{ type: "text", text: "Three decisions from earlier:" },
|
|
3036
3081
|
{
|
|
@@ -3114,6 +3159,7 @@ function events() {
|
|
|
3114
3159
|
role: "assistant",
|
|
3115
3160
|
status: "stopped",
|
|
3116
3161
|
createdAt: at(1470),
|
|
3162
|
+
model: "claude-haiku-4-5",
|
|
3117
3163
|
parts: [{ type: "text", text: "Stopping the staging deploy and leaving" }]
|
|
3118
3164
|
},
|
|
3119
3165
|
{
|
|
@@ -3128,6 +3174,7 @@ function events() {
|
|
|
3128
3174
|
role: "assistant",
|
|
3129
3175
|
status: "error",
|
|
3130
3176
|
createdAt: at(1475),
|
|
3177
|
+
model: "claude-haiku-4-5",
|
|
3131
3178
|
parts: [
|
|
3132
3179
|
{
|
|
3133
3180
|
type: "thinking",
|
|
@@ -3589,7 +3636,7 @@ var agents = [
|
|
|
3589
3636
|
parentId: null,
|
|
3590
3637
|
status: "idle",
|
|
3591
3638
|
model: "claude-sonnet-5-5",
|
|
3592
|
-
contextFill: 0.
|
|
3639
|
+
contextFill: 0.7,
|
|
3593
3640
|
unread: 1
|
|
3594
3641
|
},
|
|
3595
3642
|
{
|
|
@@ -3599,7 +3646,7 @@ var agents = [
|
|
|
3599
3646
|
parentId: null,
|
|
3600
3647
|
status: "waiting",
|
|
3601
3648
|
model: "claude-sonnet-5-5",
|
|
3602
|
-
contextFill: 0.
|
|
3649
|
+
contextFill: 0.95,
|
|
3603
3650
|
unread: 2
|
|
3604
3651
|
},
|
|
3605
3652
|
{
|
|
@@ -6309,6 +6356,7 @@ export {
|
|
|
6309
6356
|
DummyUpdates,
|
|
6310
6357
|
DummyVault,
|
|
6311
6358
|
DummyVaultButton,
|
|
6359
|
+
actionLimits,
|
|
6312
6360
|
approvalFixtures,
|
|
6313
6361
|
browserHistory,
|
|
6314
6362
|
createDummyModel,
|