@ai-ecoverse/slicc-spectrum 1.37.1 → 1.39.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 +6 -6
- package/dist/slicc-dummy.js +144 -15
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-ui.js +1548 -1261
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/agents.d.ts +1 -0
- package/dist/types/app/app.d.ts +1 -0
- package/dist/types/app/freezer.d.ts +5 -1
- package/dist/types/app/network.d.ts +16 -1
- package/dist/types/app/spectrum.d.ts +1 -0
- package/dist/types/dummy/index.d.ts +3 -2
- package/dist/types/dummy/network.d.ts +8 -2
- package/dist/types/dummy.d.ts +2 -2
- package/dist/types/model/types.d.ts +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
134
134
|
| `<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. When the last app on the page is removed, the page's own inline values come back. |
|
|
135
135
|
| `<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`. |
|
|
136
136
|
| `<slicc-agents>` | Cones with their scoops, status, context fill and unread count. Selecting one makes it the active agent. |
|
|
137
|
-
| `<slicc-chat>` | One thread's conversation (its `agent`, or the active agent if unset), by day: user messages (with attachments, tagged as steered or follow-up by `delivered`, or delegated from another cone), assistant messages (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; 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
|
|
137
|
+
| `<slicc-chat>` | One thread's conversation (its `agent`, or the active agent if unset), by day: user messages (with attachments, tagged as steered or follow-up by `delivered`, or delegated from another cone), assistant messages (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; 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. |
|
|
138
138
|
| `<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, 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. |
|
|
139
139
|
| `<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, 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. |
|
|
140
140
|
| `<slicc-file-view>` | One file (`path`), highlighted, with its pending change and a link to the diff. |
|
|
@@ -146,9 +146,9 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
146
146
|
| `<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. Also available through View and the rails. |
|
|
147
147
|
| `<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”. |
|
|
148
148
|
| `<slicc-monitor>` | The live monitor: vitals with sparklines (active agents, spend, budget, fullest context), alerts, and the topology (cones and scoops, terminals, tabs, pending changes, sprinkles, tray followers). |
|
|
149
|
-
| `<slicc-freezer>` |
|
|
149
|
+
| `<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. |
|
|
150
150
|
| `<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. |
|
|
151
|
-
| `<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-node, not available with how to get it, or `declined`, muted: off for this session, reload to be asked again) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, **Get the whole web** with the Chrome extension link (`data-action="install-extension"`, when `extensionUrl` is set) and `npx @ai-ecoverse/slicc-node` with a copy button (`data-action="copy-command"`), folded away when the route already reaches the whole web, and recent failures with host, error and time. 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; on a phone, a cloud icon for the state. Offered only when the model has a `network` port. |
|
|
151
|
+
| `<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-node, not available with how to get it, or `declined`, muted: off for this session, reload to be asked again) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, a **Tailscale** section (`data-tailnet` with the state) when the status has `tailnet`, **Get the whole web** with the Chrome extension link (`data-action="install-extension"`, when `extensionUrl` is set) and `npx @ai-ecoverse/slicc-node` with a copy button (`data-action="copy-command"`), folded away when the route already reaches the whole web, and recent failures with host, error and time. 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. Offered only when the model has a `network` port. |
|
|
152
152
|
| `<slicc-tray>` | The tray indicator in the header: connection, the float's name, followers and budget; its panel adds role, runtime, spend, the follower list, the join link, and disconnect or reconnect. |
|
|
153
153
|
| `<slicc-confirm>` | The confirmation dialog `confirm()` opens. Not for direct use. |
|
|
154
154
|
| `<slicc-prompt>` | The name dialog `prompt()` opens. Not for direct use. |
|
|
@@ -195,7 +195,7 @@ Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or
|
|
|
195
195
|
|
|
196
196
|
| Port | Later backed by | |
|
|
197
197
|
| :--- | :--- | :--- |
|
|
198
|
-
| `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
|
|
198
|
+
| `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 `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. Assistant messages stream: the same message is re-emitted as its parts grow and its tool calls go from `running` to `done`. |
|
|
199
199
|
| `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 `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. |
|
|
200
200
|
| `terminals` | slicc-kernel's terminal API | Terminal sessions; `backend(id)` returns a `TerminalBackend` for `<slicc-terminal>`. |
|
|
201
201
|
| `browser` | `@ai-ecoverse/slicc-cdp` | Tabs, the active tab, `open`, `navigate`, `close`, `screenshot`. |
|
|
@@ -206,9 +206,9 @@ Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or
|
|
|
206
206
|
| `sprinkles` | `@ai-ecoverse/slicc-agent` | Sprinkles (`inline` ones show as dips in the chat, not in the rail) and `send`, which turns a sprinkle's lick into a lick on its cone. The optional `call(id, method, args)` serves the [sprinkle bridge](#sprinkle-bridge) for sprinkle `id`: `readFile` and `exists` (`[path]`) resolve with a string and a boolean, `getState` (`[]`) with the saved value or `null`, and `setState` (`[value]`) once it is saved. The adapter owns path policy and keeps state per sprinkle; `<slicc-sprinkle>` awaits `getState` before it builds a frame, mirrors what the frame saves, and forwards only these four methods from its own frame. The dummy keeps state in memory and maps `/shared/x` to `/home/x` in its files, refusing anything outside `/home`. |
|
|
207
207
|
| `tray` | the tray protocol | Connection, role, float kind, followers, spend and budget; `reconnect` and `disconnect`. |
|
|
208
208
|
| `updates` (optional) | BIOS installers | `list()` returns `UpdateItem` rows for BIOS, kernel, agent, grammars, global pnpm packages, skills and UI. `ready()` explicitly reports agent readiness, independently of agent activity. Emit `items` whenever rows or readiness change. Use `queued` for a row that waits for another component before it starts; it reads "Waiting" and doesn't count as updating. Use `starting` for an installed row that is only starting up: it reads "Starting" without a checked line, and the header shows a neutral "Starting" only when nothing is failed, updating or ready. A `failed` row with an installed `from` version reads "Update failed". `act(id, action)` resolves when dispatched or rejects with a displayed error; actions are `retry`, `update-now`, `restart-agent` and `reload`. Missing ports preserve existing adapters and report disconnected information in the panel. Real installer wiring belongs to ai-ecoverse/slicc-bios#70. |
|
|
209
|
-
| `network` (optional) | BIOS | `status()` returns the `NetworkStatus`: `route` (`proxy` for slicc-node, `extension` for the Chrome extension relay, `page` for the page's own fetch, limited by CORS, or `null`), `health` (`ok`, `limited` or `failing`, decided by the port), a one-line `detail`, recent `failures` (`{ url, error, at }`, newest first), an optional `extensionUrl` and an optional `browser` (`{ via, detail?, declined? }`, `via` being `extension`, `proxy` or `null` when browser automation isn't available, and `declined: true` once the user declined browser control for the session). Emit `network` when it changes. The optional `check()` tests the route again. Without the port, the indicator and the Network panel don't show. |
|
|
209
|
+
| `network` (optional) | BIOS | `status()` returns the `NetworkStatus`: `route` (`proxy` for slicc-node, `extension` for the Chrome extension relay, `page` for the page's own fetch, limited by CORS, or `null`), `health` (`ok`, `limited` or `failing`, decided by the port), a one-line `detail`, recent `failures` (`{ url, error, at }`, newest first), an optional `extensionUrl` and an optional `browser` (`{ via, detail?, declined? }`, `via` being `extension`, `proxy` or `null` when browser automation isn't available, and `declined: true` once the user declined browser control for the session). Emit `network` when it changes. The optional `check()` tests the route again. An optional `tailnet` (`TailnetStatus`) shows the Tailscale section: `state` (`off`, `loading`, `needs-login`, `starting`, `running` or `failed`, a status light), a plain-language `detail` (progress, or the failure reason in an error box with **Retry** through `check()`), a `loginUrl` (**Sign in to Tailscale**, a new tab), the `node` (`{ name, addresses }`; the name and the first 100.x address, each with a Copy button), `exitNode` (the name in use or `null`), `exitNodes` (`{ id, name, online }`), `autoExitNode`, `shieldsUp` (a muted line when `true`, a warning when `false`) and `peers` (a count). The optional `setTailnet(on)` adds the **Use Tailscale** switch (`data-action="tailnet"`); `submitAuthKey(key)` adds an auth-key field to `needs-login` (a password field without a `name`, cleared on submit, never kept in component state); `setExitNode(id | 'auto' | null)` adds the **Exit node** picker (None, Automatic, and each node, offline ones disabled) to `running`, with a line saying where traffic goes; `logoutTailnet()` adds **Sign out**, behind a confirm. A rejected call shows its message in the section. Peers are never named beyond the exit-node list. Without the port, the indicator and the Network panel don't show. |
|
|
210
210
|
|
|
211
|
-
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. `updateFixtures` and `DummyUpdates` are exported from `/dummy`. `createDummyModel({ mounts: 'mount' | 'cancel' | 'fail' | 'needs' })` (or `?mounts=` on `/ui/`) turns on the optional mount members, which are off by default: `mountFolder` mounts invented files at `/mnt/photos`, resolves with `null`, or rejects, as `model.files.mount.scenario` says; `needs` also lists `/mnt/photos` in `needsFolder()` until a folder is inserted there. `createDummyModel({ network: 'ok' | 'limited' | 'failing' | 'off' })` (or `?network=` on `/ui/`, default `limited`) sets the dummy network: slicc-node (with browser automation through it), the page fetch with CORS failures, or no route, all invented; `off` leaves out the port. `model.network.setScenario()` switches it and Check again re-emits after a short wait. `networkFixtures` 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`.
|
|
211
|
+
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. `updateFixtures` and `DummyUpdates` are exported from `/dummy`. `createDummyModel({ mounts: 'mount' | 'cancel' | 'fail' | 'needs' })` (or `?mounts=` on `/ui/`) turns on the optional mount members, which are off by default: `mountFolder` mounts invented files at `/mnt/photos`, resolves with `null`, or rejects, as `model.files.mount.scenario` says; `needs` also lists `/mnt/photos` in `needsFolder()` until a folder is inserted there. `createDummyModel({ network: 'ok' | 'limited' | 'failing' | 'off' })` (or `?network=` on `/ui/`, default `limited`) sets the dummy network: slicc-node (with browser automation through it), the page fetch with CORS failures, or no route, all invented; `off` leaves out the port. `model.network.setScenario()` switches it and Check again re-emits after a short wait. `createDummyModel({ tailnet: 'off' | 'needs-login' | 'running' | 'failed' })` (or `?tailnet=` on `/ui/`, off by default) adds an invented tailnet: an auth key starting with `tskey-` signs in, `mast-pi` and `keel-router` are online exit nodes and `buoy-laptop` is offline, and Retry on `failed` connects. `networkFixtures`, `tailnetFixtures` 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`.
|
|
212
212
|
|
|
213
213
|
On phones the update status and the tray show only their status light and the network indicator a cloud icon, with the text in their labels and tooltips; the agent picker keeps room for the cone's name, cut with an ellipsis when long, and the bottom rail scrolls when needed. The update status comes first so progress and failures remain reachable on narrow screens.
|
|
214
214
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -265,6 +265,7 @@ var DummyAgent = class extends Emitter {
|
|
|
265
265
|
#next = 1;
|
|
266
266
|
#cones = 1;
|
|
267
267
|
#chats = 1;
|
|
268
|
+
#thaws = 1;
|
|
268
269
|
constructor(agents2, conversations2, effects, clock2, queues2 = {}, frozen2 = []) {
|
|
269
270
|
super();
|
|
270
271
|
this.#clock = clock2;
|
|
@@ -579,6 +580,7 @@ var DummyAgent = class extends Emitter {
|
|
|
579
580
|
const first = messages.find((message) => message.role === "user");
|
|
580
581
|
this.#frozen.unshift({
|
|
581
582
|
id,
|
|
583
|
+
kind: "cone",
|
|
582
584
|
name: agent.name,
|
|
583
585
|
title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
|
|
584
586
|
model: agent.model,
|
|
@@ -685,7 +687,21 @@ var DummyAgent = class extends Emitter {
|
|
|
685
687
|
});
|
|
686
688
|
}
|
|
687
689
|
frozen() {
|
|
688
|
-
|
|
690
|
+
const live = this.#agents.filter((agent) => !agent.frozen).map((agent) => {
|
|
691
|
+
const messages = this.#messages.get(agent.id);
|
|
692
|
+
const first = messages.find((message) => message.role === "user");
|
|
693
|
+
return {
|
|
694
|
+
id: agent.id,
|
|
695
|
+
kind: agent.kind,
|
|
696
|
+
live: true,
|
|
697
|
+
name: agent.name,
|
|
698
|
+
title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
|
|
699
|
+
model: agent.model,
|
|
700
|
+
messages: messages.length,
|
|
701
|
+
frozenAt: messages.at(-1)?.createdAt ?? 0
|
|
702
|
+
};
|
|
703
|
+
});
|
|
704
|
+
return [...live, ...this.#frozen.map((cone) => ({ ...cone }))];
|
|
689
705
|
}
|
|
690
706
|
freeze(agentId) {
|
|
691
707
|
const agent = this.#agent(agentId);
|
|
@@ -698,6 +714,7 @@ var DummyAgent = class extends Emitter {
|
|
|
698
714
|
const first = messages.find((message) => message.role === "user");
|
|
699
715
|
this.#frozen.unshift({
|
|
700
716
|
id: agentId,
|
|
717
|
+
kind: "cone",
|
|
701
718
|
name: agent.name,
|
|
702
719
|
title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
|
|
703
720
|
model: agent.model,
|
|
@@ -728,16 +745,25 @@ var DummyAgent = class extends Emitter {
|
|
|
728
745
|
this.#changed();
|
|
729
746
|
return { ...agent };
|
|
730
747
|
}
|
|
731
|
-
this.#
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
748
|
+
const row = this.#frozen.find((cone) => cone.id === id);
|
|
749
|
+
if (row.kind === "scoop" || row.kind === "agent") {
|
|
750
|
+
const [{ agent, messages }] = ice;
|
|
751
|
+
const fork = {
|
|
752
|
+
...agent,
|
|
753
|
+
id: `${id}-thaw-${this.#thaws++}`,
|
|
754
|
+
kind: "cone",
|
|
755
|
+
parentId: null
|
|
756
|
+
};
|
|
757
|
+
this.#agents.push(fork);
|
|
758
|
+
this.#messages.set(fork.id, structuredClone(messages));
|
|
759
|
+
this.#queues.set(fork.id, []);
|
|
760
|
+
row.thawedAs = fork.id;
|
|
761
|
+
this.emit("frozen", this.frozen());
|
|
762
|
+
this.select(fork.id);
|
|
763
|
+
return { ...fork };
|
|
740
764
|
}
|
|
765
|
+
this.#ice.delete(id);
|
|
766
|
+
this.#frozen = this.#frozen.filter((cone) => cone.id !== id);
|
|
741
767
|
for (const { agent, messages } of ice) {
|
|
742
768
|
this.#agents.push(agent);
|
|
743
769
|
this.#messages.set(agent.id, messages);
|
|
@@ -1818,6 +1844,7 @@ var frozen = [
|
|
|
1818
1844
|
{
|
|
1819
1845
|
cone: {
|
|
1820
1846
|
id: "cone-onboarding",
|
|
1847
|
+
kind: "cone",
|
|
1821
1848
|
name: "onboarding",
|
|
1822
1849
|
title: "Write the onboarding guide for new contributors",
|
|
1823
1850
|
model: "claude-sonnet-5-5",
|
|
@@ -1832,6 +1859,7 @@ var frozen = [
|
|
|
1832
1859
|
{
|
|
1833
1860
|
cone: {
|
|
1834
1861
|
id: "cone-kv-spike",
|
|
1862
|
+
kind: "cone",
|
|
1835
1863
|
name: "kv-spike",
|
|
1836
1864
|
title: "Spike: can the cache move to KV without a cold start?",
|
|
1837
1865
|
model: "claude-opus-5-5",
|
|
@@ -1846,6 +1874,7 @@ var frozen = [
|
|
|
1846
1874
|
{
|
|
1847
1875
|
cone: {
|
|
1848
1876
|
id: "cone-q3-report",
|
|
1877
|
+
kind: "cone",
|
|
1849
1878
|
name: "q3-report",
|
|
1850
1879
|
title: "Summarize Q3 uptime and incidents",
|
|
1851
1880
|
model: "claude-haiku-4-5",
|
|
@@ -1856,6 +1885,36 @@ var frozen = [
|
|
|
1856
1885
|
"Summarize Q3 uptime and incidents",
|
|
1857
1886
|
"Uptime 99.96%. Two incidents: an expired certificate and an upstream outage."
|
|
1858
1887
|
)
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
cone: {
|
|
1891
|
+
id: "scoop-tide-check",
|
|
1892
|
+
kind: "scoop",
|
|
1893
|
+
name: "tide-check",
|
|
1894
|
+
title: "Check the tide tables against the harbor feed",
|
|
1895
|
+
model: "claude-haiku-4-5",
|
|
1896
|
+
messages: 9,
|
|
1897
|
+
frozenAt: now - 1 * day
|
|
1898
|
+
},
|
|
1899
|
+
messages: frozenConversation(
|
|
1900
|
+
"Check the tide tables against the harbor feed",
|
|
1901
|
+
"They match, except the 3 October low tide, which the feed lists 4 minutes later."
|
|
1902
|
+
)
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
cone: {
|
|
1906
|
+
id: "run-nightly-digest",
|
|
1907
|
+
kind: "agent",
|
|
1908
|
+
name: "nightly-digest",
|
|
1909
|
+
title: "Write the nightly digest of merged pull requests",
|
|
1910
|
+
model: "claude-sonnet-5-5",
|
|
1911
|
+
messages: 6,
|
|
1912
|
+
frozenAt: now - 3 * day
|
|
1913
|
+
},
|
|
1914
|
+
messages: frozenConversation(
|
|
1915
|
+
"Write the nightly digest of merged pull requests",
|
|
1916
|
+
"Four pull requests merged: two fixes, a docs change and a dependency bump."
|
|
1917
|
+
)
|
|
1859
1918
|
}
|
|
1860
1919
|
];
|
|
1861
1920
|
var memories = [
|
|
@@ -3785,29 +3844,97 @@ function networkFixtures(scenario, now2 = Date.now()) {
|
|
|
3785
3844
|
browser: { via: null }
|
|
3786
3845
|
};
|
|
3787
3846
|
}
|
|
3847
|
+
var exitNodes = [
|
|
3848
|
+
{ id: "nKeel1CNTRL", name: "keel-router", online: true },
|
|
3849
|
+
{ id: "nMast2CNTRL", name: "mast-pi", online: true },
|
|
3850
|
+
{ id: "nBuoy3CNTRL", name: "buoy-laptop", online: false }
|
|
3851
|
+
];
|
|
3852
|
+
function tailnetFixtures(scenario) {
|
|
3853
|
+
const base = { exitNode: null, exitNodes: [], autoExitNode: false, shieldsUp: true };
|
|
3854
|
+
if (scenario === "needs-login") {
|
|
3855
|
+
return { ...base, state: "needs-login", loginUrl: "https://login.tailnet.example/a/7f3c2e" };
|
|
3856
|
+
}
|
|
3857
|
+
if (scenario === "running") {
|
|
3858
|
+
return {
|
|
3859
|
+
...base,
|
|
3860
|
+
state: "running",
|
|
3861
|
+
node: { name: "slicc-harbor", addresses: ["fd7a:115c:a1e0::7", "100.64.12.7"] },
|
|
3862
|
+
exitNodes,
|
|
3863
|
+
peers: 4
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
if (scenario === "failed") {
|
|
3867
|
+
return {
|
|
3868
|
+
...base,
|
|
3869
|
+
state: "failed",
|
|
3870
|
+
detail: "The coordination server didn\u2019t answer. Check the connection and try again."
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
return { ...base, state: "off" };
|
|
3874
|
+
}
|
|
3788
3875
|
var DummyNetwork = class extends Emitter {
|
|
3789
3876
|
scenario;
|
|
3790
3877
|
#status;
|
|
3791
3878
|
#clock;
|
|
3792
|
-
|
|
3879
|
+
#tailnet;
|
|
3880
|
+
constructor(scenario, clock2, tailnet) {
|
|
3793
3881
|
super();
|
|
3794
3882
|
this.scenario = scenario;
|
|
3795
|
-
this.#status = networkFixtures(scenario);
|
|
3796
3883
|
this.#clock = clock2;
|
|
3884
|
+
this.#tailnet = tailnet ? tailnetFixtures(tailnet) : void 0;
|
|
3885
|
+
this.#status = this.#with(networkFixtures(scenario));
|
|
3886
|
+
}
|
|
3887
|
+
#with(status) {
|
|
3888
|
+
return this.#tailnet ? { ...status, tailnet: this.#tailnet } : status;
|
|
3889
|
+
}
|
|
3890
|
+
#set(tailnet) {
|
|
3891
|
+
this.#tailnet = tailnet;
|
|
3892
|
+
this.#status = this.#with(this.#status);
|
|
3893
|
+
this.emit("network", this.#status);
|
|
3797
3894
|
}
|
|
3798
3895
|
status() {
|
|
3799
3896
|
return this.#status;
|
|
3800
3897
|
}
|
|
3801
3898
|
setScenario(scenario) {
|
|
3802
3899
|
this.scenario = scenario;
|
|
3803
|
-
this.#status = networkFixtures(scenario);
|
|
3900
|
+
this.#status = this.#with(networkFixtures(scenario));
|
|
3804
3901
|
this.emit("network", this.#status);
|
|
3805
3902
|
}
|
|
3806
3903
|
async check() {
|
|
3807
3904
|
await this.#clock.sleep(4);
|
|
3808
|
-
this.#
|
|
3905
|
+
if (this.#tailnet?.state === "failed") this.#tailnet = tailnetFixtures("running");
|
|
3906
|
+
this.#status = this.#with(networkFixtures(this.scenario));
|
|
3809
3907
|
this.emit("network", this.#status);
|
|
3810
3908
|
}
|
|
3909
|
+
async setTailnet(on) {
|
|
3910
|
+
await this.#clock.sleep(4);
|
|
3911
|
+
this.#set(tailnetFixtures(on ? "needs-login" : "off"));
|
|
3912
|
+
}
|
|
3913
|
+
async submitAuthKey(key) {
|
|
3914
|
+
if (!key.startsWith("tskey-")) {
|
|
3915
|
+
throw new Error("That isn\u2019t a Tailscale auth key. Auth keys start with tskey-.");
|
|
3916
|
+
}
|
|
3917
|
+
this.#set({ ...tailnetFixtures("off"), state: "starting", detail: "Joining your tailnet\u2026" });
|
|
3918
|
+
await this.#clock.sleep(8);
|
|
3919
|
+
this.#set(tailnetFixtures("running"));
|
|
3920
|
+
}
|
|
3921
|
+
async setExitNode(id) {
|
|
3922
|
+
await this.#clock.sleep(4);
|
|
3923
|
+
const tailnet = this.#tailnet ?? tailnetFixtures("running");
|
|
3924
|
+
const nodes = tailnet.exitNodes ?? [];
|
|
3925
|
+
if (id === "auto") {
|
|
3926
|
+
const node2 = nodes.find((item) => item.online);
|
|
3927
|
+
this.#set({ ...tailnet, autoExitNode: true, exitNode: node2?.name ?? null });
|
|
3928
|
+
return;
|
|
3929
|
+
}
|
|
3930
|
+
const node = nodes.find((item) => item.id === id);
|
|
3931
|
+
if (id && !node?.online) throw new Error("That exit node is offline.");
|
|
3932
|
+
this.#set({ ...tailnet, autoExitNode: false, exitNode: node?.name ?? null });
|
|
3933
|
+
}
|
|
3934
|
+
async logoutTailnet() {
|
|
3935
|
+
await this.#clock.sleep(4);
|
|
3936
|
+
this.#set(tailnetFixtures("needs-login"));
|
|
3937
|
+
}
|
|
3811
3938
|
};
|
|
3812
3939
|
|
|
3813
3940
|
// src/dummy/sprinkles.ts
|
|
@@ -4257,6 +4384,7 @@ function createDummyModel({
|
|
|
4257
4384
|
updates = "current",
|
|
4258
4385
|
mounts = "off",
|
|
4259
4386
|
network = "limited",
|
|
4387
|
+
tailnet,
|
|
4260
4388
|
changes = "files"
|
|
4261
4389
|
} = {}) {
|
|
4262
4390
|
const time = clock(delay);
|
|
@@ -4292,7 +4420,7 @@ function createDummyModel({
|
|
|
4292
4420
|
sprinkles: new DummySprinkles(sprinkles, agent, files3),
|
|
4293
4421
|
tray: new DummyTray(tray, time),
|
|
4294
4422
|
updates: new DummyUpdates(updates, time),
|
|
4295
|
-
...network === "off" ? {} : { network: new DummyNetwork(network, time) },
|
|
4423
|
+
...network === "off" ? {} : { network: new DummyNetwork(network, time, tailnet) },
|
|
4296
4424
|
...changes === "files" ? {} : { changes: new DummyChanges(files3, changes === "git") }
|
|
4297
4425
|
};
|
|
4298
4426
|
return { ...base, monitor: new DummyMonitor(base) };
|
|
@@ -4303,6 +4431,7 @@ export {
|
|
|
4303
4431
|
DummyUpdates,
|
|
4304
4432
|
createDummyModel,
|
|
4305
4433
|
networkFixtures,
|
|
4434
|
+
tailnetFixtures,
|
|
4306
4435
|
updateFixtures
|
|
4307
4436
|
};
|
|
4308
4437
|
//# sourceMappingURL=slicc-dummy.js.map
|