@ai-ecoverse/slicc-spectrum 1.86.7 → 1.88.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 +22 -5
- package/dist/{chunk-X4C3AZJT.js → chunk-VRFAMBWK.js} +12 -2
- package/dist/chunk-VRFAMBWK.js.map +7 -0
- package/dist/slicc-dummy.js +321 -54
- package/dist/slicc-dummy.js.map +4 -4
- package/dist/slicc-kernel-model.js +1 -1
- package/dist/slicc-ui.js +26751 -25918
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/chat.d.ts +1 -0
- package/dist/types/app/freezer-import.d.ts +67 -0
- package/dist/types/app/freezer.d.ts +13 -0
- package/dist/types/app/messages.d.ts +12 -1
- package/dist/types/app/network.d.ts +23 -2
- package/dist/types/app/settings.d.ts +1 -0
- package/dist/types/dummy/agent.d.ts +7 -0
- package/dist/types/dummy/import.d.ts +21 -0
- package/dist/types/dummy/index.d.ts +4 -1
- package/dist/types/dummy/network.d.ts +9 -1
- package/dist/types/dummy/settings.d.ts +6 -1
- package/dist/types/dummy.d.ts +3 -1
- package/dist/types/model/types.d.ts +54 -2
- package/dist/types/ui.d.ts +3 -1
- package/package.json +2 -2
- package/dist/chunk-X4C3AZJT.js.map +0 -7
package/README.md
CHANGED
|
@@ -280,7 +280,7 @@ Under 480 px of column width the label takes a row of its own under the icons, w
|
|
|
280
280
|
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.
|
|
281
281
|
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.
|
|
282
282
|
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.
|
|
283
|
-
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`, by the two-minute rule), 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 (as decision cards, below); delegations; link previews; errors with a fix-it action, as a grey card (`.error-card[role=alert]`, `data-id` on system errors) inset by a wide margin on each side, a narrower one under 480 px: a red alert icon, the title in bold, the detail subdued and the action on the right, or under the detail below 360 px, announced as "Error: " and the title; 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 (error cards; compactions and notices, rewinds and model changes included, as full-width dividers with a line on each side, like the day: a compaction shows only its title, its `text` kept for screen readers, one that lost messages (`state: 'fallback'`, by default "Compaction failed, transcript truncated", or `discarded`, "Context discarded") an alert icon in the negative colour, and a notice its title and text on one line, `role="note"`; a divider that wraps keeps a short line on each side) and licks, SLICC's events into a cone, on every channel, on the right (`.lick-turn`): a byline with the title and the time by the two-minute rule, then a dashed box (`.lick[data-id][data-channel]`) with the text and the count (×3); emoji pills on its bottom edge (`.reactions .reaction`, `aria-hidden`, each named by its `title`) give the channel (⏰ Schedule, 🪝 Webhook, ✨ Sprinkle, …) and the outcome (👍 Allowed, 👎 Denied, also said in the box's hidden text).
|
|
283
|
+
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`, by the two-minute rule), 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 (as decision cards, below); delegations; link previews; errors with a fix-it action, as a grey card (`.error-card[role=alert]`, `data-id` on system errors) inset by a wide margin on each side, a narrower one under 480 px: a red alert icon, the title in bold, the detail subdued and the action on the right, or under the detail below 360 px, announced as "Error: " and the title; an error with `action: 'login'` and an `account` (an `Account.id`) signs that account in: **Log in** calls `settings.connect(account)` synchronously in the click when the account is OAuth or needs no secret or fields, so a sign-in pop-up opens within the user gesture, and otherwise, or when `accounts()` doesn't have it, opens Settings at its row; the card follows the account's status from `accounts` (`data-login` is `idle`, `signing-in` or `connected`): while it is `signing-in` or `connect` is pending, **Log in** is pending and reads “Signing in…”, for an OAuth account the detail reads “Continue in the sign-in window that opened.” and, after `SliccChat.closedAfter` (20 s, since a closed window can't be detected), “Closed the sign-in window? Cancel, then Log in again.”, with **Cancel** (`data-action="cancel-login"`) when the port has `cancel`; once `connected`, the card is no longer an alert: a neutral info icon in the detail colour replaces the red one, the "Error: " prefix goes, the title reads “Signed in to {provider} again.” (the account's `provider`), the detail reads “Signed in. Retry to send it again.” (a polite status) and the button becomes **Retry**, which sends the last prompt again only when clicked; a rejected `connect` shows its message as negative help text (`[data-error=login]`), keeps **Log in** and adds a quiet **Open settings** (`data-action="settings"`) to that account; without an `account`, **Log in** opens Settings at Accounts, and watch-only and guest seats keep their gating; 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 (error cards; compactions and notices, rewinds and model changes included, as full-width dividers with a line on each side, like the day: a compaction shows only its title, its `text` kept for screen readers, one that lost messages (`state: 'fallback'`, by default "Compaction failed, transcript truncated", or `discarded`, "Context discarded") an alert icon in the negative colour, and a notice its title and text on one line, `role="note"`; a divider that wraps keeps a short line on each side) and licks, SLICC's events into a cone, on every channel, on the right (`.lick-turn`): a byline with the title and the time by the two-minute rule, then a dashed box (`.lick[data-id][data-channel]`) with the text and the count (×3); emoji pills on its bottom edge (`.reactions .reaction`, `aria-hidden`, each named by its `title`) give the channel (⏰ Schedule, 🪝 Webhook, ✨ Sprinkle, …) and the outcome (👍 Allowed, 👎 Denied, also said in the box's hidden text).
|
|
284
284
|
A lick with a body is a `details.lick` whose head (`summary.lick-head`, with a quiet chevron that turns when open) toggles it with a click, <kbd>Enter</kbd> or <kbd>Space</kbd>; clicking the body doesn't close it.
|
|
285
285
|
Open licks and open tool runs hold for every view of the chat on the same model, so a layout switch keeps them.
|
|
286
286
|
A lick's `severity` (`warn` or `error`) adds an alert icon to its byline, a coloured edge to its box and a spoken prefix.
|
|
@@ -409,6 +409,7 @@ An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign
|
|
|
409
409
|
An account with `fields` (`{ name, label, required? }`) shows a plain labelled input for each, and their values go to `connect(id, secret?, fields)`.
|
|
410
410
|
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.
|
|
411
411
|
Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before.
|
|
412
|
+
A `show-surface` whose `section` is an account id scrolls to that row and focuses its button, and `accounts` scrolls to the Accounts heading (`h2[data-section=accounts]`).
|
|
412
413
|
With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
|
|
413
414
|
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.
|
|
414
415
|
|
|
@@ -483,6 +484,11 @@ A thawed scoop or agent run keeps its row, which says what it was thawed as.
|
|
|
483
484
|
**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.
|
|
484
485
|
Either way its conversation stays here, and a row without a `kind` counts as a cone.
|
|
485
486
|
A title longer than two lines ends in an ellipsis and carries its full text as a `title`; the dummy's titles are the whole first line of the first message, so only the card cuts them.
|
|
487
|
+
A row with `v6: true` came from SLICC 6 and carries a neutral **SLICC 6** badge (`data-badge="v6"`, `data-v6` on the row).
|
|
488
|
+
When the agent port has `importProfile` and `importCancel` and the page is neither watch-only nor a guest seat, the bar offers **Import from SLICC 6…** (`data-action="import-v6"`), which picks a `.zip`; dropping a zip on the Freezer (with a "Drop to import" dropzone while dragging) does the same.
|
|
489
|
+
Either opens `<slicc-freezer-import>`, a modal (`dialog[data-import]` set to `checking`, `preview`, `importing`, `done` or `failed`): `importProfile(file, { preview: true })` runs behind a progress circle and "Checking the profile…"; then **Import from SLICC 6?** shows the agent's `text`, a summary (cones, scoops, frozen, skills, crons, webhooks, size) and its notes (`[data-note]`): "You imported this export on {day}." when `already` is set, "Sign in again in Settings: {providers}." (named by their accounts), a **Warning:** with an icon for credential-like files, which are imported for review afterwards (their zip paths as code; more than three fold under a quiet chevron), and a muted "Not imported: {n} mounted folders.".
|
|
490
|
+
**Import** (`data-action="import-confirm"`, **Import again** with `again: true` after an earlier import) runs the import with a progress bar from the `importing` event, announced only when it starts and ends; **Done** (`data-action="import-done"`) after "Imported {n} cones." and the report path with **Copy** (`data-action="copy-report"`) closes it and focuses the first imported row.
|
|
491
|
+
A rejection shows the error card with its sentence (a check without a `preview` shows "Couldn’t read this export. Try another zip."), **Try again** (`data-action="import-retry"`, picks again) and **Close**; **Cancel** or <kbd>Escape</kbd> while checking or importing calls `importCancel`.
|
|
486
492
|
|
|
487
493
|
#### `<slicc-sprinkle>`
|
|
488
494
|
|
|
@@ -673,12 +679,15 @@ Every port is subscribable with `on(type, listener)`, which returns an unsubscri
|
|
|
673
679
|
|
|
674
680
|
Later backed by: `@ai-ecoverse/slicc-agent`.
|
|
675
681
|
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.
|
|
682
|
+
An error part (`{ type: 'error', message, detail?, action?, account? }`) and an error `SystemMessage` may carry `account`, the `Account.id` that the card's **Log in** signs in; slicc-agent sets it on the expired-session card.
|
|
676
683
|
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`.
|
|
677
684
|
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.
|
|
678
685
|
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.
|
|
679
686
|
`freeze(agentId)` is optional too: without it, **Delete cone** and `/freeze` don't show.
|
|
680
687
|
`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.
|
|
681
688
|
`thaw(id)` brings a cone back, or forks a scoop or agent run into a new cone, and `discard(id)` removes a row.
|
|
689
|
+
The optional `importProfile(file, { preview, again? })` takes a SLICC 6 profile export (a `Blob`) and resolves with a `ProfileImportResult` (`preview`, the imported `cones`' ids, the `report` path and a `text`), or rejects with a sentence to show; with `preview: true` it only checks, and its `ProfileImportPreview` counts the export (`origin`, `createdAt`, `cones`, `scoops`, `frozen`, `bytes`, `skills`, `crons`, `webhooks`), lists the zip paths of `flagged` credential-like files, which are imported as they are (at 0600) and listed in the report for review, `providers` to sign in to again and `mounts` it can't bring, and has `already`, when this export was imported before, or `null`.
|
|
690
|
+
`importCancel()` stops it, and `importing()` returns a `ProfileImportProgress` (`phase`: `idle`, `checking`, `writing`, `done` or `failed`, with `done`, `total` and `text`), with an `importing` event; the UI never sees kernel paths, so the adapter decides how the file reaches the agent.
|
|
682
691
|
Names don't have to be unique, and an optional `Agent.title` names a cone's conversation; without it, the first user message does.
|
|
683
692
|
Assistant messages stream: the same message is re-emitted as its parts grow and its tool calls go from `running` to `done`.
|
|
684
693
|
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.
|
|
@@ -789,11 +798,16 @@ An optional `tailnet` (`TailnetStatus`) shows the Tailscale section: `state` (`o
|
|
|
789
798
|
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.
|
|
790
799
|
A rejected call shows its message in the section.
|
|
791
800
|
Peers are never named beyond the exit-node list.
|
|
792
|
-
An optional `links` (`NetworkLinks`) shows **Linked devices**, slicc CLIs linked over WebRTC: `joinUrl` and `joinCommand` (each with Copy, `data-copy="join-url"` and `data-copy="join-command"`; both `null` shows "Preparing a join URL…"), an optional `followUrl` (the page URL with `#follow=` and the join URL, shown with a QR code).
|
|
801
|
+
An optional `links` (`NetworkLinks`) shows **Linked devices**, slicc CLIs linked over WebRTC: `joinUrl` and `joinCommand` (each with Copy, `data-copy="join-url"` and `data-copy="join-command"`; both `null` shows "Preparing a join URL…"; both left out, as with no tray, hide the join part and keep the devices and pairing), an optional `followUrl` (the page URL with `#follow=` and the join URL, shown with a QR code).
|
|
793
802
|
Join URLs let a device join, so the join URL, the command, the follow link and the tray's join link are masked (the last path segment and anything after it, or, without a path, everything after `#` or `?`; text without a URL is masked whole), with `data-secret="hidden"` until **Show** (`data-action="reveal-<copy>"`) sets it to `shown`; a new URL masks again, Copy copies the real value either way, and the QR code stays scannable, `devices` and `permission`.
|
|
794
|
-
A `LinkedDevice` has an `id`, a display `name` (a leading `slicc` is shown as `SLICC`), the kernel `host` and `address`, a `mode` (`local` for a CLI on this computer attached over CDP, or `remote`), `offers` (`net`, `http`, `ssh`; `ssh` adds a copyable `ssh <host>`), a one-line `policy`, `exit` (whether it can carry internet traffic), a `state` (`connecting`, `connected`, `reconnecting` or `offline`, for a device that linked before and isn't there now; every device that ever linked is listed, so one that was refused can be allowed), optional `control` (`{ allowed, fixed? }`; `fixed` for one on this computer, always allowed), optional `controlling` (`prompt` or `watch` while a CLI prompts or watches the agent) optional `browsers` (`{ runtime, name }`, listed in the row as `data-browsers`, with the runtime such as `pier-nuc/chrome` that the 9222 facade drives), optional `kind` (`cli`, or `seven` for another SLICC that follows this one, "Follower" in the row), optional `access` (`{ view?, control?, terminal?, fixed? }`: only the keys present get a switch, so a CLI sends `{ control }`; `fixed` lists them as always allowed from this computer; it replaces `control`), optional `asking` (a follower that waits to be allowed), optional `terminals` (how many terminals it has open here) and optional `folders` (`LinkedFolder`s the device shares: `name`, `source` such as `pier-nuc/proj`, `readonly`, and `mounted`, the mount point once mounted).
|
|
803
|
+
A `LinkedDevice` has an `id`, a display `name` (a leading `slicc` is shown as `SLICC`), the kernel `host` and `address`, a `mode` (`local` for a CLI on this computer attached over CDP, or `remote`), `offers` (`net`, `http`, `ssh`; `ssh` adds a copyable `ssh <host>`), a one-line `policy`, `exit` (whether it can carry internet traffic), a `state` (`connecting`, `connected`, `reconnecting` or `offline`, for a device that linked before and isn't there now; every device that ever linked is listed, so one that was refused can be allowed), optional `control` (`{ allowed, fixed? }`; `fixed` for one on this computer, always allowed), optional `controlling` (`prompt` or `watch` while a CLI prompts or watches the agent) optional `browsers` (`{ runtime, name }`, listed in the row as `data-browsers`, with the runtime such as `pier-nuc/chrome` that the 9222 facade drives), optional `kind` (`cli`, or `seven` for another SLICC that follows this one, "Follower" in the row), optional `access` (`{ view?, control?, terminal?, fixed? }`: only the keys present get a switch, so a CLI sends `{ control }`; `fixed` lists them as always allowed from this computer; it replaces `control`), optional `asking` (a follower that waits to be allowed), optional `terminals` (how many terminals it has open here), an optional `note` (one warning line in the row, `[data-note]`, with the alert icon, such as a forgotten device's "Pair it again") and optional `folders` (`LinkedFolder`s the device shares: `name`, `source` such as `pier-nuc/proj`, `readonly`, and `mounted`, the mount point once mounted).
|
|
795
804
|
Folders list in the row (`[data-folders] li[data-folder]`, with `data-mounted` `yes` or `no`, a **Read-only** badge and "Mounted at …"); `mountFolder(source)` adds **Mount** (`data-action="mount-folder"`, `data-id` the source; only on a connected device) and resolves with the mount point, such as `/mnt/pier-nuc/proj`, and `ejectFolder(source)` adds **Eject** (`data-action="eject-folder"`); a failure shows on the links error line.
|
|
796
805
|
Each device is an `li[data-device]` with `data-state`, `data-mode` and `data-kind`, and `data-asking` while it asks; once there are devices, the join details fold into **Link another device**.
|
|
806
|
+
An optional `pairing` (`LinkPairing`: `state` `idle`, `confirm`, `linking`, `linked` or `failed`, the node's short `code`, the device `name` once linked and a one-sentence `error` once failed) adds **Link this computer** (`section[data-pairing=<state>]`) for `slicc pair` on this computer, inside **Link another device**, which opens by itself while the state isn't `idle` or a device has a `note`; without `pairComputer` its Link stays disabled.
|
|
807
|
+
`idle` has the **Pairing link** field (`#pair-url`, not kept, cleared once accepted) and **Link** (`data-action="pair"`, disabled while empty; Enter submits) through `pairComputer(url)`, whose rejection shows under the field (`[data-error=pairing]`) and keeps the text.
|
|
808
|
+
`confirm` shows the code and **Link** (`data-action="confirm-pairing"`, `confirmPairing()`) and **Cancel** (`data-action="cancel-pairing"`); when the state becomes `confirm`, or the panel opens on it, the block scrolls into view and its heading takes focus.
|
|
809
|
+
`linking` shows a progress circle and **Cancel**, `linked` a positive status light and **Done** (`data-action="done-pairing"`), and `failed` an error box with **Try again** (`data-action="retry-pairing"`, which focuses the field) and **Dismiss** (`data-action="dismiss-pairing"`); Cancel, Done, Try again and Dismiss call `cancelPairing()`, and a polite status line (`[data-pairing-status]`) announces each state.
|
|
810
|
+
A paired device is a `local` `cli` row without `control` or `access`.
|
|
797
811
|
`permission` is `prompt` (an informative line asking to choose Allow in Chrome's Local Network Access prompt) or `denied` (a warning, with **Try again**, `data-action="retry-links"`, through `retryLinks()`).
|
|
798
812
|
An optional `exit` (`NetworkExit`: `null`, `{ kind: 'tailnet', node }` with a Tailscale node id or `'auto'`, or `{ kind: 'link', id }`) is the default exit.
|
|
799
813
|
The optional `setExit(exit)` replaces the Tailscale **Exit node** picker with one **Internet traffic** picker in Connection: Directly, then Tailscale (Automatic and its nodes) and Linked devices (those with `exit: true`), offline ones disabled unless chosen; a chosen device that isn't connected shows that traffic waits, since the exit fails closed.
|
|
@@ -918,10 +932,10 @@ Dummy Update now and Retry simulate download and link progress; Restart agent an
|
|
|
918
932
|
`model.network.setScenario()` switches it and Check again re-emits after a short wait.
|
|
919
933
|
`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.
|
|
920
934
|
While the dummy tailnet runs with an exit node, the route is `tailnet` and the health `ok`.
|
|
921
|
-
`createDummyModel({ links: 'none' | 'preparing' | 'linked' | 'reconnecting' | 'exit' | 'prompt' | 'denied' | 'remote' | 'followers' })` (or `?links=` on `/ui/`, off by default) adds invented linked devices and the exit members: `linked` has quay-studio (on this computer) and pier-nuc, which can be exits and have a Chrome each, and dock-builder, which can't; quay-studio shares `sketches` and pier-nuc `proj` and a read-only `archive`, which **Mount** attaches to the dummy files at `/mnt/<source>` (with `mountInfo` naming the source) and **Eject** or the Files strip detaches; with no other browser automation, the dummy reports `via: 'link'`; `exit` sends traffic through quay-studio; `reconnecting` holds traffic for pier-nuc (health `failing`, and `ok` while the exit device is connected, both without a `detail`, as bios reports them); `remote` has pier-nuc allowed and prompting, quay-studio watching, and an offline reef-laptop that only ever connected for control; `prompt` and `denied` wait for Local Network Access, and Try again links quay-studio.
|
|
935
|
+
`createDummyModel({ links: 'none' | 'preparing' | 'linked' | 'reconnecting' | 'exit' | 'prompt' | 'denied' | 'remote' | 'followers' | 'pairing' | 'pairing-notray' | 'forgotten' })` (or `?links=` on `/ui/`, off by default) adds invented linked devices and the exit members: `linked` has quay-studio (on this computer) and pier-nuc, which can be exits and have a Chrome each, and dock-builder, which can't; quay-studio shares `sketches` and pier-nuc `proj` and a read-only `archive`, which **Mount** attaches to the dummy files at `/mnt/<source>` (with `mountInfo` naming the source) and **Eject** or the Files strip detaches; with no other browser automation, the dummy reports `via: 'link'`; `exit` sends traffic through quay-studio; `reconnecting` holds traffic for pier-nuc (health `failing`, and `ok` while the exit device is connected, both without a `detail`, as bios reports them); `remote` has pier-nuc allowed and prompting, quay-studio watching, and an offline reef-laptop that only ever connected for control; `prompt` and `denied` wait for Local Network Access, and Try again links quay-studio; `pairing` has a tray, pier-nuc and dock-builder, and `pairing` at `idle`: `pairComputer` takes a URL starting `http://127.0.0.1:` to `confirm` with the code `ab12…ef` and refuses anything else, and `confirmPairing` goes through `linking` to `linked` as SLICC on quay-studio, adding it as a local CLI row without access, or to `failed` when the URL contains `fail`; `pairing-notray` is the same without `joinUrl` and `joinCommand`, and `forgotten` adds quay-studio offline with a `note`.
|
|
922
936
|
New join URL changes the URL, and `model.network.setLinks()` switches scenarios.
|
|
923
937
|
`createDummyModel({ leaders: 'connected' | 'failed' | 'unlinked' | 'disconnected' | 'two' })` (or `?leaders=` on `/ui/`, off by default) adds invented leaders and their members: cove-studio connected, marsh-desk failed with an error, cove-studio unlinked (gone on the next status), cove-studio disconnected, or both cove-studio and marsh-desk; Retry and Reconnect move a leader to connecting, then connected, Disconnect makes it disconnected and Forget drops it, each after a short wait.
|
|
924
|
-
`networkFixtures`, `tailnetFixtures`, `linksFixtures`, `leadersFixtures`, `joinUrl` and `DummyNetwork` are exported from `/dummy`.
|
|
938
|
+
`networkFixtures`, `tailnetFixtures`, `linksFixtures`, `leadersFixtures`, `joinUrl`, `pairingCode`, `pairingRefused`, `pairingStopped`, `pairedDevice`, `forgottenDevice` and `DummyNetwork` are exported from `/dummy`.
|
|
925
939
|
`createDummyModel({ guests: 'on' | 'none' | 'unavailable' | 'fail' | 'policy' })` (or `?guests=` on `/ui/`, off by default) adds an invented `guests` port: `on` has Seat 1 (Mara) on sliccy, connected with 2 waiting and 5 days left, and Seat 2 (Ole) on harbor, not connected and without expiry; `none` has no seats; `unavailable` has none and says guests need the tray hub, so minting rejects; `fail` makes `mint` reject with a sentence; and `policy` has the `on` seats and a `gates()` (`guestsPolicy`) that also allows tools `off`.
|
|
926
940
|
`mint` adds a seat after a short wait and resolves with an invented `https://www.example.com/guest/…` URL, `revoke` drops the seat, and `model.guests.setScenario()` switches scenarios.
|
|
927
941
|
`guestsFixtures`, `guestsPolicy`, `guestsUnavailable`, `guestsRefused` and `DummyGuests` are exported from `/dummy`.
|
|
@@ -952,6 +966,9 @@ The SLICC you follow runs a newer version.
|
|
|
952
966
|
Update this one to send prompts.", and shows a warning notice without a close button (`[data-notice="readonly"]`) in place of any other notices.
|
|
953
967
|
`model.agent.setReadOnly(text)` changes it, and `null` ends it.
|
|
954
968
|
|
|
969
|
+
`createDummyModel({ import: 'v6' | 'again' | 'fail' | 'cancel-check' })` (`?import=` on `/ui/`, off by default) adds `importProfile`, `importCancel` and `importing` to the agent: any zip previews an invented export from www.sliccy.ai (3 cones, 14 scoops, 2 frozen, Anthropic and OpenAI to sign in to again, 2 mounts and a flagged `files/cones/cone/.env`), and the import ticks to 14 in a few steps and adds 3 frozen cones with `v6: true` and the report `/root/v6-import-2026-10-18.md`; a second preview says when, and **Import again** adds fresh copies. `again` starts with an earlier import, `fail` rejects with "That zip isn’t a SLICC 6 profile export.", and `cancel-check` checks slowly. `DummyImport` and its fixtures (`importPreview`, `importCones`, `importReport`, …) are exported from `/dummy`.
|
|
970
|
+
|
|
971
|
+
The kitchen sink's “docs-pass failed” card is an expired-session error for the invented `ai-adobe` account (Adobe AI, harbor-ops@example.com, `expired`, OAuth): **Log in** goes `signing-in`, then `connected` after a short wait, and the dummy records each `connect` in `settings.connects` (`{ id, gesture }`, whether `navigator.userActivation.isActive` was true at the call); inbox-triage's “Tickets connector” card names `mcp:tickets`, a token account, so its **Log in** opens Settings at that row; `createDummyModel({ login: 'fail' })` (`?login=fail` on `/ui/`) makes OAuth sign-ins reject with “The sign-in window was closed before it finished.”
|
|
955
972
|
`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`).
|
|
956
973
|
|
|
957
974
|
`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.
|
|
@@ -12,6 +12,7 @@ function clock(delay) {
|
|
|
12
12
|
|
|
13
13
|
// src/dummy/settings.ts
|
|
14
14
|
var dummySettingsKey = "slicc-ui.settings";
|
|
15
|
+
var signInClosed = "The sign-in window was closed before it finished.";
|
|
15
16
|
var DummySettings = class extends Emitter {
|
|
16
17
|
#settings;
|
|
17
18
|
#accounts;
|
|
@@ -21,8 +22,11 @@ var DummySettings = class extends Emitter {
|
|
|
21
22
|
#keys;
|
|
22
23
|
#signIns = /* @__PURE__ */ new Map();
|
|
23
24
|
#gated;
|
|
24
|
-
|
|
25
|
+
#fails;
|
|
26
|
+
connects = [];
|
|
27
|
+
constructor(defaults, models, accounts, storage, clock2, keys = [dummySettingsKey], gated = false, login) {
|
|
25
28
|
super();
|
|
29
|
+
this.#fails = login === "fail";
|
|
26
30
|
this.#gated = gated;
|
|
27
31
|
this.#keys = keys;
|
|
28
32
|
this.#storage = storage;
|
|
@@ -64,8 +68,14 @@ var DummySettings = class extends Emitter {
|
|
|
64
68
|
this.emit("accounts", this.accounts());
|
|
65
69
|
}
|
|
66
70
|
async connect(id, secret) {
|
|
71
|
+
const activation = globalThis.navigator?.userActivation;
|
|
72
|
+
this.connects.push({ id, gesture: activation ? activation.isActive : null });
|
|
67
73
|
const account = this.#accounts.find((candidate) => candidate.id === id);
|
|
68
74
|
if (account && account.auth !== "api-key" && !await this.#signIn(account)) return;
|
|
75
|
+
if (account && account.auth !== "api-key" && this.#fails) {
|
|
76
|
+
this.#set(id, { status: account.status });
|
|
77
|
+
throw new Error(signInClosed);
|
|
78
|
+
}
|
|
69
79
|
if (account?.auth === "api-key" || !account) await this.#clock.sleep(10);
|
|
70
80
|
if (account?.auth === "api-key" && account.store !== "vault" && !secret)
|
|
71
81
|
throw new Error(
|
|
@@ -105,4 +115,4 @@ export {
|
|
|
105
115
|
dummySettingsKey,
|
|
106
116
|
DummySettings
|
|
107
117
|
};
|
|
108
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-VRFAMBWK.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/dummy/clock.ts", "../src/dummy/settings.ts"],
|
|
4
|
+
"sourcesContent": ["export interface Clock {\n delay: number;\n sleep(ticks?: number): Promise<void>;\n}\n\nexport function clock(delay: number): Clock {\n return {\n delay,\n sleep: (ticks = 1) => new Promise((resolve) => setTimeout(resolve, delay * ticks)),\n };\n}\n", "import { Emitter } from '../model/emitter.ts';\nimport type {\n Account,\n AccountStatus,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '../model/types.ts';\nimport type { Clock } from './clock.ts';\n\nexport const dummySettingsKey = 'slicc-ui.settings';\nexport const signInClosed = 'The sign-in window was closed before it finished.';\n\nexport class DummySettings extends Emitter<SettingsEvents> implements SettingsPort {\n #settings: Settings;\n #accounts: Account[];\n #models: readonly ModelOption[];\n #storage: Storage | null;\n #clock: Clock;\n #keys: readonly string[];\n #signIns = new Map<string, { before: AccountStatus; stop: () => void }>();\n #gated: boolean;\n #fails: boolean;\n readonly connects: Array<{ id: string; gesture: boolean | null }> = [];\n\n constructor(\n defaults: Settings,\n models: readonly ModelOption[],\n accounts: readonly Account[],\n storage: Storage | null,\n clock: Clock,\n keys: readonly string[] = [dummySettingsKey],\n gated = false,\n login?: 'ok' | 'fail'\n ) {\n super();\n this.#fails = login === 'fail';\n this.#gated = gated;\n this.#keys = keys;\n this.#storage = storage;\n this.#clock = clock;\n this.#models = models;\n this.#accounts = accounts.map((account) => ({ ...account }));\n this.#settings = { ...defaults, ...this.#load() };\n }\n\n #load(): Partial<Settings> {\n try {\n const saved = this.#keys\n .map((key) => this.#storage?.getItem(key))\n .find((value) => value != null);\n return JSON.parse(saved ?? '{}');\n } catch {\n return {};\n }\n }\n\n get(): Settings {\n return { ...this.#settings };\n }\n\n update(patch: Partial<Settings>): void {\n this.#settings = { ...this.#settings, ...patch };\n this.#storage?.setItem(this.#keys[0], JSON.stringify(this.#settings));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n if (!this.#gated) return this.#models;\n const live = new Set(\n this.#accounts\n .filter((account) => account.status === 'connected')\n .map((account) => account.provider)\n );\n return this.#models.filter((option) => live.has(option.provider));\n }\n\n accounts(): readonly Account[] {\n return this.#accounts.map((account) => ({ ...account }));\n }\n\n #set(id: string, patch: Partial<Account>): void {\n this.#accounts = this.#accounts.map((account) =>\n account.id === id ? { ...account, ...patch } : account\n );\n this.emit('accounts', this.accounts());\n }\n\n async connect(id: string, secret?: string): Promise<void> {\n const activation = (globalThis.navigator as Navigator | undefined)?.userActivation;\n this.connects.push({ id, gesture: activation ? activation.isActive : null });\n const account = this.#accounts.find((candidate) => candidate.id === id);\n if (account && account.auth !== 'api-key' && !(await this.#signIn(account))) return;\n if (account && account.auth !== 'api-key' && this.#fails) {\n this.#set(id, { status: account.status });\n throw new Error(signInClosed);\n }\n if (account?.auth === 'api-key' || !account) await this.#clock.sleep(10);\n if (account?.auth === 'api-key' && account.store !== 'vault' && !secret)\n throw new Error(\n `${account.provider} needs ${account.secret === 'token' ? 'a token' : 'an API key'}`\n );\n const identity =\n account?.group === 'mcp'\n ? account.identity\n : account?.auth === 'api-key'\n ? 'API key'\n : account?.identity || `${id}-user@example.com`;\n this.#set(id, { status: 'connected', identity });\n }\n\n #signIn(account: Account): Promise<boolean> {\n const before = this.#signIns.get(account.id)?.before ?? account.status;\n this.#signIns.get(account.id)?.stop();\n this.#set(account.id, { status: 'signing-in' });\n return new Promise((resolve) => {\n const stop = () => {\n this.#signIns.delete(account.id);\n this.#set(account.id, { status: before });\n resolve(false);\n };\n this.#signIns.set(account.id, { before, stop });\n void this.#clock.sleep(100).then(() => {\n if (this.#signIns.get(account.id)?.stop !== stop) return;\n this.#signIns.delete(account.id);\n resolve(true);\n });\n });\n }\n\n cancel(id: string): void {\n this.#signIns.get(id)?.stop();\n }\n\n disconnect(id: string): void {\n this.#set(id, { status: 'disconnected' });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["clock"]
|
|
7
|
+
}
|