@ai-ecoverse/slicc-spectrum 1.70.0 → 1.72.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 +7 -7
- package/dist/slicc-dummy.js +119 -50
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +1197 -1072
- package/dist/slicc-ui.js.map +3 -3
- package/dist/types/app/approvals.d.ts +1 -0
- package/dist/types/app/messages.d.ts +2 -4
- package/dist/types/app/tool-runs.d.ts +8 -0
- package/dist/types/dummy/agent.d.ts +1 -0
- package/dist/types/dummy/script.d.ts +1 -0
- package/dist/types/model/types.d.ts +3 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -137,9 +137,9 @@ 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 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 runs,
|
|
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 runs, one icon per run (`<slicc-tool-runs>`): three or more runs in a row form a cluster, a strip of icon buttons (`data-action="open-run"`, `data-target` the call's id, named by its `title` and status, `aria-expanded`) that each open one run's card below it, and on the right the model's label for the run, a button (`data-action="open-cluster"`, `aria-expanded`, named "Show all runs" while it has no text) that opens every card; the strip's tooltip and the group's accessible name sum it up, such as "3 commands, 1 read. Done"; prose breaks a run, hidden thinking and source links don't, and a standalone tool message or a done reply with only tool calls continues the run before it on the same day, its `data-id` moving onto its first card (`.tool[data-id]`); a single run is a strip of one, its icon and its `title` as the label, which opens with <kbd>Enter</kbd> or <kbd>Space</kbd> like the strip's buttons. A strip and its cards share one width, the column less a wide margin on each side, so they read as centred, and their edges line up. Icons fill from the start of a space kept for five, so a run that grows doesn't move its strip or its label; past five, the strip shows the newest five and scrolls back to the older ones, with <kbd>Tab</kbd> or a swipe, and returns to the newest when a run is added or the strip comes back into view. The label sits at the end and is cut with an ellipsis before it reaches that space. Under 480 px of column width the label takes a row of its own under the icons, which then show as many whole icons as fit. A cluster's label comes from the agent port's optional `toolLabel(agentId, callIds)`, asked once for each agent port, agent and set of runs; while a growing run waits for its new label, it keeps its last one, and without `toolLabel` the label is empty. A command's icon is the favicon of the site it names when an open browser tab already has a `data:` favicon for it (nothing is fetched; otherwise the globe), else the file browser's icon for the file it names, else the code icon; other tools show their kind's icon. A running run pulses (still, with a dashed ring, under `prefers-reduced-motion`), a done or cancelled one is grey and a failed one red, each with a corner glyph. The card (`.tool[data-tool][data-status][data-kind]`, a `details.run` under a pill and a `div[role=region]` named by its `title` and status under a strip) has no header of its own, no chevron, title, tool name or status: its input (a shell command after `$`), an edit's `diff` shown as a diff view, its output wrapped rather than cut off, an `image`, such as a screenshot, an optional `meta` such as a timeout, a standalone tool message's time ("Ran at 16:12", `.ran`), 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), 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 is one row: the status, a model picker that names each model's provider and leaves out classifiers and models with `tools: false` (with no model on offer, **Set up a model**, `data-action="set-up-model"`, opens Settings), the thinking picker, the context meter and **New conversation**, which calls `clear` and puts focus back in the composer; on a cone, when the port can freeze, its tooltip says the old conversation stays in the Freezer. It never wraps: when it's short of room, the model picker's label is cut with an ellipsis, and under 560px wide (`header[data-compact]`) New conversation is icon-only, with `accessible-label` and its tooltip. The pickers re-read `models()` on `settings`, `accounts` and `agents`. When a live agent's model, or `Settings.model`, is no longer offered and its provider (the id before `/`) isn't a connected account, it moves to the offered chat model with `default: true`, else the first one; frozen agents are left alone, and so is everything on a follower unless it has Control and is connected. The chat re-runs this on `follow` too, and `<slicc-app>` does it as well, when it binds, on those events and on `follow`, so a new cone that starts on a provider that isn't set up moves at once. 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`. 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. |
|
|
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 (**Always allow in this directory**, with the cwd in its hint, when the approval has `alwaysScope: 'cwd'` and a `cwd`)**. 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
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. Above the tree, **Search files** (the shared `sp-search[data-action=search]` from `search.ts`, as in Memory) filters the tree to the matching paths while it holds text, or hides it and says “No files match.” (`.note[data-empty]`) when none match; a letter or digit typed in the tree moves into it, <kbd>↓</kbd> goes back to the tree, and <kbd>Esc</kbd> in the tree, or opening a file from it, clears the search. File icons are monochrome, so colour in the tree only means git status. |
|
|
@@ -149,12 +149,12 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
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
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. When the model for new cones was moved off a provider that isn't set up, a quiet note under its picker (`p[data-switched][role=status]`) says “Switched to {model} because the previous model's provider isn't set up.” until a model is picked.
|
|
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
|
|
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 ("Unlocks automatically in this browser", "Unlocks with a passkey" or "Unlocks with a passphrase"); 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, every form (Add secret, Replace value, Import, Export) says "Unlock the vault first" (`p[data-locked]`) next to the vault's own small **Unlock** button (`slicc-vault-button[data-action=unlock]`) and disables its submit; once unlocked, the form stays open with what was typed and its submit enables. 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
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, grouped by section: search, filter by scope and tag, expand, edit, add and forget. The scope picker starts on **Everyone** (`*`), which lists every memory with its scope on it (`.scope[data-scope]`); `global` reads **Shared** whatever the port calls it, and the cones and roles follow. The count (“N of M memories”) counts the memories in the picked scope. With a search or tag filter that hides everything it says “No memories match.”, otherwise “No memories yet.”, “No shared memories yet.” or “No memories for {scope} yet.” Each memory is a row (`li.row[data-id]`) that opens in place: open rows (`data-open`) take the accent's subtle background and turn their chevron down, and show the body with **Edit** and **Forget** (`data-action="edit"`, `data-action="forget"`). Under the summary, the tag is a subtle badge (`swc-badge[data-tag]`: User indigo, Feedback purple, Project seafoam), then the scope and, for free text outside an entry, “From MEMORY.md” (`.from`). Below 560 px of panel width, the search takes its own line over the two pickers. The search field is the shared one from `search.ts` (`sp-search[data-action=search]`), as in Files. |
|
|
157
|
-
| `<slicc-monitor>` | The live monitor: vitals
|
|
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. 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
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"`). 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. |
|
|
@@ -237,7 +237,7 @@ With an `agentId` or a `cone`, the caller reaches the shared surfaces, the chats
|
|
|
237
237
|
|
|
238
238
|
| Port | Later backed by | |
|
|
239
239
|
| :--- | :--- | :--- |
|
|
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`. |
|
|
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`. The optional `toolLabel(agentId, callIds)` resolves with a short label for a cluster of tool runs (`callIds` in order), such as "Compare the docs with the live API", or `null`; the agent dedupes, caches and persists it. Spectrum asks once a cluster forms and again each time it grows, once per set of ids, and shows the label at the end of the strip. |
|
|
241
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. |
|
|
242
242
|
| `terminals` | slicc-kernel's terminal API | Terminal sessions; `backend(id)` returns a `TerminalBackend` for `<slicc-terminal>`. |
|
|
243
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)`. |
|
|
@@ -261,7 +261,7 @@ The dummy supports `createDummyModel({ updates: 'boot' | 'starting' | 'current'
|
|
|
261
261
|
|
|
262
262
|
`createDummyModel({ providers: 'none' })` (`?providers=none` on `/ui/`) starts with no model provider connected: `models()` offers only the models of connected accounts, and connecting OpenAI offers the invented GPT-6 and GPT-6 mini (its `default`).
|
|
263
263
|
|
|
264
|
-
`createDummyModel({ monitor: 'agent' })` (`?monitor=agent` on `/ui/`) adds the agent's Tasks (a tree by `depth`), Processes and Recent problems sections to the monitor.
|
|
264
|
+
`createDummyModel({ monitor: 'agent' })` (`?monitor=agent` on `/ui/`) adds the agent's Tasks (a tree by `depth`), Processes and Recent problems sections to the monitor, and its Running vital. Like slicc-agent's snapshot, the dummy's vitals are the budget, Spend, Active agents, Processes and Process memory (both with a `series`) and Fullest context.
|
|
265
265
|
|
|
266
266
|
`createDummyModel({ approvals: 'agent' | 'user' | 'many' | 'follower' | 'fail' | 'soon' | 'elsewhere' | 'withdrawn' | 'secrets' })` (`?approvals=` on `/ui/`) adds waiting sudo requests: one from an agent, one from a terminal user, three in a row (the last as `www-data`), one without `decide`, one whose `decide` fails, one that expires in 24 seconds, or two whose first is answered in another tab or withdrawn after a few seconds, or one from an agent that copies a file over `/etc/fnox/config.toml`. Decisions are recorded in `model.approvals.decisions`, and `model.approvals.end(id, reason)` ends a request.
|
|
267
267
|
|
|
@@ -293,7 +293,7 @@ Each section is its own port, so an embedder can mix real and dummy per section,
|
|
|
293
293
|
|
|
294
294
|
`<slicc-file-view>` has **Edit**: the file opens in a plain text area, and **Save** or `Mod+S` writes it through `files.write`; `Escape` cancels.
|
|
295
295
|
|
|
296
|
-
The `kitchen-sink` cone holds every kind of message, content and lick in one conversation, for design work and screenshots. Its tool turn has two clusters and a standalone run with URL, file and plain commands in every state, and two dummy browser tabs carry invented `data:` favicons. The dummy's fixtures are invented: a small forecast API called harbor, with cones, scoops, a conversation per agent, pending changes (one of them binary, without a diff), browser tabs and accounts. Its only sprinkles are SLICC's own: `welcome` (the onboarding wizard, as a dip in the sliccy cone) and `suggestions`, copied from `slicc` (`packages/vfs-root/shared/sprinkles/`, commit `aa29784`) with their comments stripped, as is the sprinkle theme in `src/app/sprinkle-theme.css`. `suggestions` reads its stream from the invented `/home/.gelatiere/suggestions.json` through the bridge; there is no `/home/.welcomed`, so `welcome` starts its wizard. Its replies are scripted by keyword (tests, fix or add, open or docs, files, anything else), and their tool calls act on the other ports: an edit writes a file and shows up as a change, a browse opens a tab.
|
|
296
|
+
The `kitchen-sink` cone holds every kind of message, content and lick in one conversation, for design work and screenshots. Its clusters carry invented labels, which the dummy's `toolLabel` answers after a short pause, keyed by the title of the cluster's first call; one is long enough to be cut. Its tool turn has two clusters and a standalone run with URL, file and plain commands in every state, and two dummy browser tabs carry invented `data:` favicons. The dummy's fixtures are invented: a small forecast API called harbor, with cones, scoops, a conversation per agent, pending changes (one of them binary, without a diff), browser tabs and accounts. Its only sprinkles are SLICC's own: `welcome` (the onboarding wizard, as a dip in the sliccy cone) and `suggestions`, copied from `slicc` (`packages/vfs-root/shared/sprinkles/`, commit `aa29784`) with their comments stripped, as is the sprinkle theme in `src/app/sprinkle-theme.css`. `suggestions` reads its stream from the invented `/home/.gelatiere/suggestions.json` through the bridge; there is no `/home/.welcomed`, so `welcome` starts its wizard. Its replies are scripted by keyword (debug or investigate, tests, fix or add, open or docs, files, anything else), and their tool calls act on the other ports: an edit writes a file and shows up as a change, a browse opens a tab. Debugging runs seven commands in a row, so its cluster grows past five while it streams.
|
|
297
297
|
|
|
298
298
|
### Distribution
|
|
299
299
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -566,6 +566,54 @@ function files() {
|
|
|
566
566
|
}
|
|
567
567
|
];
|
|
568
568
|
}
|
|
569
|
+
function probe(title, input, output, paths = []) {
|
|
570
|
+
return { type: "tool", name: "bash", title, input, output, paths, ticks: 12 };
|
|
571
|
+
}
|
|
572
|
+
function investigate() {
|
|
573
|
+
return [
|
|
574
|
+
{ type: "thinking", text: "Celsius requests return Fahrenheit. Follow one request through." },
|
|
575
|
+
probe(
|
|
576
|
+
"Read the error log",
|
|
577
|
+
"tail -n 3 /workspace/harbor/logs/worker.log",
|
|
578
|
+
"GET /forecast?city=Porto&unit=C 200 (cache hit)",
|
|
579
|
+
["/workspace/harbor/logs/worker.log"]
|
|
580
|
+
),
|
|
581
|
+
probe(
|
|
582
|
+
"Ask the API for Porto",
|
|
583
|
+
"curl -s https://api.example.com/v2/forecast?city=Porto&unit=C",
|
|
584
|
+
'{ "city": "Porto", "high": 70, "unit": "F" }'
|
|
585
|
+
),
|
|
586
|
+
probe(
|
|
587
|
+
"Find the cache key",
|
|
588
|
+
'rg -n "cacheKey" src',
|
|
589
|
+
"src/lib/cache.ts:9: const cacheKey = city;"
|
|
590
|
+
),
|
|
591
|
+
probe("Read the cache", "cat /workspace/harbor/src/lib/cache.ts", "const cacheKey = city;", [
|
|
592
|
+
"/workspace/harbor/src/lib/cache.ts"
|
|
593
|
+
]),
|
|
594
|
+
probe("Check the working tree", "git status --short", " M src/lib/cache.ts"),
|
|
595
|
+
probe("Clear the preview cache", "wrangler kv key delete Porto --env preview", "Deleted 1 key"),
|
|
596
|
+
probe(
|
|
597
|
+
"Ask the API again",
|
|
598
|
+
"curl -s https://api.example.com/v2/forecast?city=Porto&unit=C",
|
|
599
|
+
'{ "city": "Porto", "high": 21, "unit": "C" }'
|
|
600
|
+
),
|
|
601
|
+
{
|
|
602
|
+
type: "text",
|
|
603
|
+
text: "The cache key is only the city, so a Fahrenheit answer is served to Celsius requests. The key needs the unit too."
|
|
604
|
+
}
|
|
605
|
+
];
|
|
606
|
+
}
|
|
607
|
+
var labels = {
|
|
608
|
+
"Read the error log": "Find why Celsius requests get Fahrenheit",
|
|
609
|
+
"Search for the cache expiry": "Find and fix the stale cache entry",
|
|
610
|
+
"Count the routes": "Walk through every kind of tool",
|
|
611
|
+
"Ask the API for Lisbon": "Compare the docs with the live API",
|
|
612
|
+
"Switch the page to Celsius": "Move the forecast docs to Celsius, then build and test the site"
|
|
613
|
+
};
|
|
614
|
+
function label(title) {
|
|
615
|
+
return labels[title] ?? null;
|
|
616
|
+
}
|
|
569
617
|
function chat(prompt) {
|
|
570
618
|
return [
|
|
571
619
|
{ type: "thinking", text: "Short question. Answer directly, then offer the next step." },
|
|
@@ -624,6 +672,7 @@ function look(attachments) {
|
|
|
624
672
|
];
|
|
625
673
|
}
|
|
626
674
|
var suggestions = [
|
|
675
|
+
[/\b(debug|investigate)\b/, "Add the unit to the cache key"],
|
|
627
676
|
[/\btests?\b/, "Open a pull request with the fix"],
|
|
628
677
|
[/\b(fix|edit|add|change)\b/, "Run the tests"],
|
|
629
678
|
[/\b(open|browse|page|docs)\b/, "Summarize the page"],
|
|
@@ -635,6 +684,7 @@ function script(prompt, attachments = []) {
|
|
|
635
684
|
if (attachments.length > 0) return { steps: look(attachments), suggestion };
|
|
636
685
|
if (/\bforbidden\b/.test(text)) return { steps: filtered(), suggestion };
|
|
637
686
|
if (/\b(ask|choose|decide)\b/.test(text)) return { steps: ask(), suggestion };
|
|
687
|
+
if (/\b(debug|investigate)\b/.test(text)) return { steps: investigate(), suggestion };
|
|
638
688
|
if (/\btests?\b/.test(text)) return { steps: test(), suggestion };
|
|
639
689
|
if (/\b(fix|edit|add|change)\b/.test(text)) return { steps: edit(), suggestion };
|
|
640
690
|
if (/\b(open|browse|page|docs)\b/.test(text)) return { steps: browse(), suggestion };
|
|
@@ -729,6 +779,20 @@ var DummyAgent = class extends Emitter {
|
|
|
729
779
|
suggestion(agentId) {
|
|
730
780
|
return this.#suggestions.get(agentId) ?? null;
|
|
731
781
|
}
|
|
782
|
+
#call(agentId, id) {
|
|
783
|
+
for (const message of this.#messages.get(agentId) ?? []) {
|
|
784
|
+
if (message.role === "tool" && message.tool.id === id) return message.tool;
|
|
785
|
+
if (message.role !== "assistant") continue;
|
|
786
|
+
for (const part of message.parts)
|
|
787
|
+
if (part.type === "tool" && part.tool.id === id) return part.tool;
|
|
788
|
+
}
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
async toolLabel(agentId, callIds) {
|
|
792
|
+
await this.#clock.sleep(15);
|
|
793
|
+
const first = this.#call(agentId, callIds[0] ?? "");
|
|
794
|
+
return first ? label(first.title) : null;
|
|
795
|
+
}
|
|
732
796
|
#post(agentId, message) {
|
|
733
797
|
this.#messages.get(agentId)?.push(message);
|
|
734
798
|
this.emit("message", { agentId, message });
|
|
@@ -2545,8 +2609,8 @@ var DummyFiles = class extends Emitter {
|
|
|
2545
2609
|
// src/dummy/showcase.ts
|
|
2546
2610
|
var start = Date.UTC(2026, 9, 4, 16, 0);
|
|
2547
2611
|
var at = (minutes2) => start + minutes2 * 6e4;
|
|
2548
|
-
function picture(
|
|
2549
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${from}"/><stop offset="1" stop-color="${to}"/></linearGradient></defs><rect width="${width}" height="${height}" fill="url(#g)"/><text x="50%" y="50%" font-family="sans-serif" font-size="${Math.round(height / 9)}" fill="#ffffff" text-anchor="middle" dominant-baseline="middle">${
|
|
2612
|
+
function picture(label2, from, to, width = 640, height = 400) {
|
|
2613
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${from}"/><stop offset="1" stop-color="${to}"/></linearGradient></defs><rect width="${width}" height="${height}" fill="url(#g)"/><text x="50%" y="50%" font-family="sans-serif" font-size="${Math.round(height / 9)}" fill="#ffffff" text-anchor="middle" dominant-baseline="middle">${label2}</text></svg>`;
|
|
2550
2614
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
2551
2615
|
}
|
|
2552
2616
|
function tone(seconds = 0.6, frequency = 440, rate = 8e3) {
|
|
@@ -4152,13 +4216,6 @@ var DummyMemory = class extends Emitter {
|
|
|
4152
4216
|
};
|
|
4153
4217
|
|
|
4154
4218
|
// src/dummy/monitor.ts
|
|
4155
|
-
var statuses = {
|
|
4156
|
-
idle: "idle",
|
|
4157
|
-
thinking: "active",
|
|
4158
|
-
working: "active",
|
|
4159
|
-
waiting: "warn",
|
|
4160
|
-
error: "error"
|
|
4161
|
-
};
|
|
4162
4219
|
function wave(seed, length = 24) {
|
|
4163
4220
|
return Array.from(
|
|
4164
4221
|
{ length },
|
|
@@ -4268,33 +4325,35 @@ var DummyMonitor = class extends Emitter {
|
|
|
4268
4325
|
model.tray.on("status", update);
|
|
4269
4326
|
}
|
|
4270
4327
|
snapshot() {
|
|
4271
|
-
const { agent, terminals: terminals2, browser, files: files3,
|
|
4328
|
+
const { agent, terminals: terminals2, browser, files: files3, tray: tray2 } = this.#model;
|
|
4272
4329
|
const changes = this.#model.changes ?? files3;
|
|
4273
4330
|
const listed = agent.list();
|
|
4274
4331
|
const agents2 = listed.filter((candidate) => candidate.kind === "cone").flatMap((cone) => [cone, ...listed.filter((candidate) => candidate.parentId === cone.id)]);
|
|
4275
4332
|
const active = agents2.filter((candidate) => candidate.status !== "idle").length;
|
|
4276
4333
|
const status = tray2.status();
|
|
4277
4334
|
const fill = Math.max(...agents2.map((candidate) => candidate.contextFill), 0);
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
);
|
|
4335
|
+
const rows = (id) => this.#extra.find((section) => section.id === id)?.rows;
|
|
4336
|
+
const tasks = rows("tasks");
|
|
4337
|
+
const running = rows("processes") ?? [];
|
|
4338
|
+
const processes = terminals2.list().length + running.length + 1;
|
|
4339
|
+
const byAgents = running.filter(
|
|
4340
|
+
(row) => row.badges?.some((badge) => badge !== "terminal")
|
|
4341
|
+
).length;
|
|
4342
|
+
const tools = (tasks ?? []).filter(
|
|
4343
|
+
(row) => row.name.startsWith("tool ") && row.status === "active"
|
|
4344
|
+
).length;
|
|
4288
4345
|
return {
|
|
4289
4346
|
updatedAt: Date.now(),
|
|
4290
4347
|
vitals: [
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4348
|
+
...status.budget ? [
|
|
4349
|
+
{
|
|
4350
|
+
id: "budget",
|
|
4351
|
+
label: `Budget (${status.budget.window})`,
|
|
4352
|
+
value: `${status.budget.percent}%`,
|
|
4353
|
+
unit: status.budget.resets,
|
|
4354
|
+
ratio: status.budget.percent / 100
|
|
4355
|
+
}
|
|
4356
|
+
] : [],
|
|
4298
4357
|
{
|
|
4299
4358
|
id: "spend",
|
|
4300
4359
|
label: "Spend",
|
|
@@ -4302,15 +4361,35 @@ var DummyMonitor = class extends Emitter {
|
|
|
4302
4361
|
delta: `$${(status.rate ?? 0).toFixed(2)}/h`,
|
|
4303
4362
|
series: wave(this.#tick + 7)
|
|
4304
4363
|
},
|
|
4305
|
-
|
|
4364
|
+
{
|
|
4365
|
+
id: "agents",
|
|
4366
|
+
label: "Active agents",
|
|
4367
|
+
value: String(active),
|
|
4368
|
+
unit: `of ${agents2.length}`,
|
|
4369
|
+
series: wave(this.#tick + 1)
|
|
4370
|
+
},
|
|
4371
|
+
...tasks ? [
|
|
4306
4372
|
{
|
|
4307
|
-
id: "
|
|
4308
|
-
label:
|
|
4309
|
-
value:
|
|
4310
|
-
unit:
|
|
4311
|
-
ratio: status.budget.percent / 100
|
|
4373
|
+
id: "tasks",
|
|
4374
|
+
label: "Running",
|
|
4375
|
+
value: String(tools),
|
|
4376
|
+
unit: `${tools === 1 ? "tool" : "tools"} \xB7 ${tasks.length} ${tasks.length === 1 ? "task" : "tasks"}`
|
|
4312
4377
|
}
|
|
4313
4378
|
] : [],
|
|
4379
|
+
{
|
|
4380
|
+
id: "processes",
|
|
4381
|
+
label: "Processes",
|
|
4382
|
+
value: String(processes),
|
|
4383
|
+
unit: `${byAgents} by agents`,
|
|
4384
|
+
series: wave(this.#tick + 3)
|
|
4385
|
+
},
|
|
4386
|
+
{
|
|
4387
|
+
id: "memory",
|
|
4388
|
+
label: "Process memory",
|
|
4389
|
+
value: `${17 + 24 * (processes - 1)} MiB`,
|
|
4390
|
+
unit: `${24 * byAgents} MiB by agents`,
|
|
4391
|
+
series: wave(this.#tick + 5)
|
|
4392
|
+
},
|
|
4314
4393
|
{
|
|
4315
4394
|
id: "context",
|
|
4316
4395
|
label: "Fullest context",
|
|
@@ -4345,7 +4424,6 @@ var DummyMonitor = class extends Emitter {
|
|
|
4345
4424
|
]
|
|
4346
4425
|
],
|
|
4347
4426
|
sections: [
|
|
4348
|
-
{ id: "agents", label: "Cones and scoops", rows: agentRows },
|
|
4349
4427
|
...this.#extra,
|
|
4350
4428
|
{
|
|
4351
4429
|
id: "terminals",
|
|
@@ -4375,15 +4453,6 @@ var DummyMonitor = class extends Emitter {
|
|
|
4375
4453
|
status: "warn"
|
|
4376
4454
|
}))
|
|
4377
4455
|
},
|
|
4378
|
-
{
|
|
4379
|
-
id: "sprinkles",
|
|
4380
|
-
label: "Sprinkles",
|
|
4381
|
-
rows: sprinkles2.list().map((sprinkle) => ({
|
|
4382
|
-
name: sprinkle.title,
|
|
4383
|
-
meta: sprinkle.name,
|
|
4384
|
-
status: "idle"
|
|
4385
|
-
}))
|
|
4386
|
-
},
|
|
4387
4456
|
{
|
|
4388
4457
|
id: "followers",
|
|
4389
4458
|
label: "Tray followers",
|
|
@@ -5334,9 +5403,9 @@ function updateFixtures(scenario) {
|
|
|
5334
5403
|
["global", "Global pnpm packages", "2.3.0"],
|
|
5335
5404
|
["skills", "Skills", "1.6.0"]
|
|
5336
5405
|
];
|
|
5337
|
-
const items = components.map(([kind,
|
|
5406
|
+
const items = components.map(([kind, label2, version]) => ({
|
|
5338
5407
|
id: kind,
|
|
5339
|
-
label,
|
|
5408
|
+
label: label2,
|
|
5340
5409
|
kind,
|
|
5341
5410
|
state: "current",
|
|
5342
5411
|
progress: null,
|
|
@@ -5382,10 +5451,10 @@ function updateFixtures(scenario) {
|
|
|
5382
5451
|
}
|
|
5383
5452
|
return items;
|
|
5384
5453
|
}
|
|
5385
|
-
var offer = (id,
|
|
5454
|
+
var offer = (id, label2, description, commands, offered, size, requires) => ({
|
|
5386
5455
|
id,
|
|
5387
5456
|
package: `@harbor/${id}-wasm`,
|
|
5388
|
-
label,
|
|
5457
|
+
label: label2,
|
|
5389
5458
|
description,
|
|
5390
5459
|
commands,
|
|
5391
5460
|
...requires ? { requires } : {},
|
|
@@ -6050,7 +6119,7 @@ function vaultFixtures(scenario, now2 = Date.now()) {
|
|
|
6050
6119
|
};
|
|
6051
6120
|
return { status, list };
|
|
6052
6121
|
}
|
|
6053
|
-
var
|
|
6122
|
+
var labels2 = {
|
|
6054
6123
|
unlock: "Unlock the vault",
|
|
6055
6124
|
protect: "Protect with a passkey"
|
|
6056
6125
|
};
|
|
@@ -6095,8 +6164,8 @@ var DummyVaultButton = class {
|
|
|
6095
6164
|
return;
|
|
6096
6165
|
}
|
|
6097
6166
|
this.config = message.button;
|
|
6098
|
-
const { intent, label, size = "m", variant = "secondary", treatment = "fill" } = message.button;
|
|
6099
|
-
this.#button.textContent =
|
|
6167
|
+
const { intent, label: label2, size = "m", variant = "secondary", treatment = "fill" } = message.button;
|
|
6168
|
+
this.#button.textContent = label2 ?? (intent === "auto" ? this.#vault.autoLabel() : labels2[intent.kind]);
|
|
6100
6169
|
this.#button.setAttribute("size", size);
|
|
6101
6170
|
this.#button.setAttribute("variant", variant);
|
|
6102
6171
|
this.#button.setAttribute("fill-style", treatment);
|