@ai-ecoverse/slicc-spectrum 1.41.0 → 1.43.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 -4
- package/dist/slicc-dummy.js +233 -4
- package/dist/slicc-dummy.js.map +4 -4
- package/dist/slicc-ui.js +7557 -7128
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/notices.d.ts +33 -0
- package/dist/types/app/updates.d.ts +14 -2
- package/dist/types/dummy/index.d.ts +7 -2
- package/dist/types/dummy/notices.d.ts +14 -0
- package/dist/types/dummy/updates.d.ts +17 -5
- package/dist/types/dummy.d.ts +4 -2
- package/dist/types/model/types.d.ts +47 -2
- package/dist/types/ui.d.ts +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,8 @@ 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. 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. |
|
|
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. |
|
|
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. Between the header and the transcript, a `<slicc-notices>` shows the model's notices. |
|
|
138
|
+
| `<slicc-notices>` | The model's `notices` as in-line alerts with their actions and a close button; renders nothing without the port or notices. |
|
|
138
139
|
| `<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
140
|
| `<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
141
|
| `<slicc-file-view>` | One file (`path`), highlighted, with its pending change and a link to the diff. |
|
|
@@ -143,7 +144,7 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
143
144
|
| `<slicc-terminals>` | Terminal sessions in tabs, each a `<slicc-terminal>` on the session's `TerminalBackend`. Sessions stay alive while hidden; `exit` closes the tab. |
|
|
144
145
|
| `<slicc-browser>` | Browser tabs with the agent driving each, an address bar, reload, and the active tab's screenshot. |
|
|
145
146
|
| `<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. |
|
|
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
|
+
| `<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. Also available through View and the rails. |
|
|
147
148
|
| `<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
149
|
| `<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
150
|
| `<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. |
|
|
@@ -205,10 +206,11 @@ Paths are the agent's: `/shared/…` and `/home/…`, nothing else. A refusal or
|
|
|
205
206
|
| `monitor` | all of them | A snapshot of vitals, alerts and sections, re-emitted as the system changes, and `resync`. |
|
|
206
207
|
| `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
208
|
| `tray` | the tray protocol | Connection, role, float kind, followers, spend and budget; `reconnect` and `disconnect`. |
|
|
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
|
+
| `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. Optional `packages()` returns `PackageItem` rows for optional tools, each with a short catalog `id`, its npm `package` (shown in the name's tooltip and at the top of its Install log), `label`, a one-sentence `description`, the `commands` it provides, `requires` (other package ids, read as "Needs Python", "Installs Python too", or "Needs Python (not installed)" on an installed row), `state` (`available`, `queued`, `installing`, `installed`, `outdated`, `updating`, `removing` or `failed`), the installed `version`, the `offered` version and its `size` in bytes, `progress` counted in packages (or `null` for an indeterminate bar), `error`, `actions` (`install`, `update`, `remove`, `retry`) and an optional `log`. Emit `packages` whenever they change. `actPackage(id, action)` keeps the row's button pending until it settles; it shows any confirm itself, resolves without a change when that confirm is cancelled, and rejects with a displayed error on a real failure. Hooks: `section[data-section="packages"]`, `li[data-id][data-state]`, buttons with `data-action` and `[data-error]`. Real installer wiring belongs to ai-ecoverse/slicc-bios#70. |
|
|
209
210
|
| `network` (optional) | BIOS | `status()` returns the `NetworkStatus`: `route` (`proxy` for slicc-node, `extension` for the Chrome extension relay, `tailnet` while a Tailscale exit node carries the traffic, `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. |
|
|
211
|
+
| `notices` (optional) | BIOS | `list()` returns `Notice`s for quiet, dismissible in-line notices at the top of every chat panel, above the transcript, in list order: `id`, `tone` (`info` reads as informative, `warning` as a notice), `title`, a plain-text `body` and `actions` (`{ id, label }`). Title, body and labels are text only. Notices use `role="status"` and never take focus. An action is a secondary button that stays pending until `act(id, action)` settles; a rejection shows its message under the body. The close button, labelled "Dismiss: <title>", hides the notice at once and calls `dismiss(id)` synchronously; it shows again only if the port drops the notice from `list()` and brings it back later. Emit `notices` whenever they change. Hooks: `[data-notice="<id>"]`, buttons with `data-action`, `[data-dismiss]` and `[data-error]`. |
|
|
210
212
|
|
|
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. While the dummy tailnet runs with an exit node, the route is `tailnet` and the health `ok`. `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`.
|
|
213
|
+
The dummy supports `createDummyModel({ updates: 'boot' | 'starting' | 'current' | 'available' | 'restart' | 'failure' })`, also selectable with `?updates=` on `/ui/`. Fixtures stay stable for inspection; `model.updates.setScenario('current')` completes boot. Dummy Update now and Retry simulate download and link progress; Restart agent and Reload only change fixture state, never restart a real process or reload the browser. `createDummyModel({ packages: 'mixed' | 'empty' | 'fail' | 'off' })` (or `?packages=` on `/ui/`, off by default) adds invented optional packages in every state: Install and Retry finish after a short wait and install missing requirements too, `fail` makes them fail, and removing Python while uv is installed asks through the `confirm` option (the `/ui/` page passes spectrum's `confirm`). `updateFixtures`, `packageFixtures` and `DummyUpdates` are exported from `/dummy`. `createDummyModel({ mounts: 'mount' | 'cancel' | 'fail' | 'needs' })` (or `?mounts=` on `/ui/`) turns on the optional mount members, which are off by default: `mountFolder` mounts invented files at `/mnt/photos`, resolves with `null`, or rejects, as `model.files.mount.scenario` says; `needs` also lists `/mnt/photos` in `needsFolder()` until a folder is inserted there. `createDummyModel({ network: 'ok' | 'limited' | 'failing' | 'off' })` (or `?network=` on `/ui/`, default `limited`) sets the dummy network: slicc-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. While the dummy tailnet runs with an exit node, the route is `tailnet` and the health `ok`. `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`. `createDummyModel({ notices: 'storage' | 'mixed' | 'fail' | 'off' })` (or `?notices=` on `/ui/`, off by default) adds an invented storage warning with Try again, which clears it after a short wait (`mixed` adds an informative notice without actions, and `fail` makes Try again fail). `noticeFixtures` and `DummyNotices` are exported from `/dummy`.
|
|
212
214
|
|
|
213
215
|
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
216
|
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -3951,6 +3951,52 @@ var DummyNetwork = class extends Emitter {
|
|
|
3951
3951
|
}
|
|
3952
3952
|
};
|
|
3953
3953
|
|
|
3954
|
+
// src/dummy/notices.ts
|
|
3955
|
+
var noticeFixtures = {
|
|
3956
|
+
storage: {
|
|
3957
|
+
id: "storage",
|
|
3958
|
+
tone: "warning",
|
|
3959
|
+
title: "Your files may be cleared",
|
|
3960
|
+
body: "This browser may clear seven\u2019s files and chats when the disk runs low on space. Bookmarking this page makes that less likely.",
|
|
3961
|
+
actions: [{ id: "retry", label: "Try again" }]
|
|
3962
|
+
},
|
|
3963
|
+
welcome: {
|
|
3964
|
+
id: "welcome",
|
|
3965
|
+
tone: "info",
|
|
3966
|
+
title: "Everything stays in this browser",
|
|
3967
|
+
body: "Seven keeps your files and chats on this computer. Nothing is uploaded unless you send it.",
|
|
3968
|
+
actions: []
|
|
3969
|
+
}
|
|
3970
|
+
};
|
|
3971
|
+
var DummyNotices = class extends Emitter {
|
|
3972
|
+
#scenario;
|
|
3973
|
+
#time;
|
|
3974
|
+
#notices;
|
|
3975
|
+
constructor(scenario, time) {
|
|
3976
|
+
super();
|
|
3977
|
+
this.#scenario = scenario;
|
|
3978
|
+
this.#time = time;
|
|
3979
|
+
this.#notices = scenario === "mixed" ? [noticeFixtures.storage, noticeFixtures.welcome] : [noticeFixtures.storage];
|
|
3980
|
+
}
|
|
3981
|
+
list() {
|
|
3982
|
+
return this.#notices;
|
|
3983
|
+
}
|
|
3984
|
+
async act(id, action) {
|
|
3985
|
+
await this.#time.sleep(10);
|
|
3986
|
+
if (this.#scenario === "fail") {
|
|
3987
|
+
throw new Error("The browser declined again. Bookmark this page, then try again.");
|
|
3988
|
+
}
|
|
3989
|
+
if (id === "storage" && action === "retry") this.#remove(id);
|
|
3990
|
+
}
|
|
3991
|
+
dismiss(id) {
|
|
3992
|
+
this.#remove(id);
|
|
3993
|
+
}
|
|
3994
|
+
#remove(id) {
|
|
3995
|
+
this.#notices = this.#notices.filter((notice) => notice.id !== id);
|
|
3996
|
+
this.emit("notices", this.#notices);
|
|
3997
|
+
}
|
|
3998
|
+
};
|
|
3999
|
+
|
|
3954
4000
|
// src/dummy/sprinkles.ts
|
|
3955
4001
|
function homePath(path) {
|
|
3956
4002
|
const text = String(path);
|
|
@@ -4336,15 +4382,188 @@ function updateFixtures(scenario) {
|
|
|
4336
4382
|
}
|
|
4337
4383
|
return items;
|
|
4338
4384
|
}
|
|
4385
|
+
var offer = (id, label, description, commands, offered, size, requires) => ({
|
|
4386
|
+
id,
|
|
4387
|
+
package: `@harbor/${id}-wasm`,
|
|
4388
|
+
label,
|
|
4389
|
+
description,
|
|
4390
|
+
commands,
|
|
4391
|
+
...requires ? { requires } : {},
|
|
4392
|
+
state: "available",
|
|
4393
|
+
version: null,
|
|
4394
|
+
offered,
|
|
4395
|
+
size,
|
|
4396
|
+
progress: null,
|
|
4397
|
+
error: null,
|
|
4398
|
+
actions: ["install"]
|
|
4399
|
+
});
|
|
4400
|
+
var fetchFailed = "The download stopped. Check the connection and try again.";
|
|
4401
|
+
function packageFixtures(scenario) {
|
|
4402
|
+
if (scenario === "empty") return [];
|
|
4403
|
+
const items = [
|
|
4404
|
+
offer("git", "git", "Version control for your projects.", ["git"], "2.47.1", 43e5),
|
|
4405
|
+
offer(
|
|
4406
|
+
"python",
|
|
4407
|
+
"Python",
|
|
4408
|
+
"Python with pip, for scripts and data work.",
|
|
4409
|
+
["python3", "pip"],
|
|
4410
|
+
"3.13.1",
|
|
4411
|
+
284e5
|
|
4412
|
+
),
|
|
4413
|
+
offer(
|
|
4414
|
+
"uv",
|
|
4415
|
+
"uv",
|
|
4416
|
+
"Fast Python package and project manager.",
|
|
4417
|
+
["uv", "uvx"],
|
|
4418
|
+
"0.9.2",
|
|
4419
|
+
61e5,
|
|
4420
|
+
["python"]
|
|
4421
|
+
),
|
|
4422
|
+
offer("ruff", "Ruff", "Python linter and formatter.", ["ruff"], "0.14.0", 98e5, [
|
|
4423
|
+
"python"
|
|
4424
|
+
]),
|
|
4425
|
+
offer(
|
|
4426
|
+
"esbuild",
|
|
4427
|
+
"esbuild",
|
|
4428
|
+
"Bundles and minifies JavaScript and TypeScript.",
|
|
4429
|
+
["esbuild"],
|
|
4430
|
+
"0.25.10",
|
|
4431
|
+
101e5
|
|
4432
|
+
),
|
|
4433
|
+
offer(
|
|
4434
|
+
"tsc",
|
|
4435
|
+
"TypeScript",
|
|
4436
|
+
"Type-checks and compiles TypeScript.",
|
|
4437
|
+
["tsc"],
|
|
4438
|
+
"5.9.3",
|
|
4439
|
+
236e5
|
|
4440
|
+
),
|
|
4441
|
+
offer(
|
|
4442
|
+
"biome",
|
|
4443
|
+
"Biome",
|
|
4444
|
+
"Formats and lints JavaScript, TypeScript and JSON.",
|
|
4445
|
+
["biome"],
|
|
4446
|
+
"2.2.4",
|
|
4447
|
+
192e5
|
|
4448
|
+
),
|
|
4449
|
+
offer(
|
|
4450
|
+
"pdf",
|
|
4451
|
+
"PDF tools",
|
|
4452
|
+
"Extract text, pages and details from PDF files.",
|
|
4453
|
+
["pdftotext", "pdfinfo", "pdfseparate"],
|
|
4454
|
+
"24.8.0",
|
|
4455
|
+
79e5
|
|
4456
|
+
)
|
|
4457
|
+
];
|
|
4458
|
+
const at3 = (id) => items.findIndex((item) => item.id === id);
|
|
4459
|
+
const patch = (id, change) => {
|
|
4460
|
+
items[at3(id)] = { ...items[at3(id)], ...change };
|
|
4461
|
+
};
|
|
4462
|
+
patch("python", { state: "installed", version: "3.13.1", actions: ["remove"] });
|
|
4463
|
+
patch("uv", { state: "outdated", version: "0.8.1", actions: ["update", "remove"] });
|
|
4464
|
+
patch("esbuild", {
|
|
4465
|
+
state: "installing",
|
|
4466
|
+
actions: [],
|
|
4467
|
+
log: "Packages: +1\nProgress: resolved 1, downloaded 0, added 0"
|
|
4468
|
+
});
|
|
4469
|
+
patch("biome", { state: "installed", version: "2.2.4", actions: ["remove"] });
|
|
4470
|
+
patch("pdf", {
|
|
4471
|
+
state: "failed",
|
|
4472
|
+
error: fetchFailed,
|
|
4473
|
+
actions: ["retry"],
|
|
4474
|
+
log: "Packages: +1\nERR_PNPM_FETCH_TIMEOUT Request timed out after 30000ms"
|
|
4475
|
+
});
|
|
4476
|
+
return items;
|
|
4477
|
+
}
|
|
4339
4478
|
var DummyUpdates = class extends Emitter {
|
|
4340
4479
|
#items;
|
|
4341
4480
|
#ready;
|
|
4342
4481
|
#clock;
|
|
4343
|
-
|
|
4482
|
+
#packages = [];
|
|
4483
|
+
#fail = false;
|
|
4484
|
+
#confirm;
|
|
4485
|
+
constructor(scenario, clock2, options = {}) {
|
|
4344
4486
|
super();
|
|
4345
4487
|
this.#items = updateFixtures(scenario);
|
|
4346
4488
|
this.#ready = scenario !== "boot" && scenario !== "starting";
|
|
4347
4489
|
this.#clock = clock2;
|
|
4490
|
+
this.#confirm = options.confirm;
|
|
4491
|
+
if (options.packages) {
|
|
4492
|
+
this.#packages = packageFixtures(options.packages);
|
|
4493
|
+
this.#fail = options.packages === "fail";
|
|
4494
|
+
this.packages = () => this.#packages;
|
|
4495
|
+
this.actPackage = (id, action) => this.#actPackage(id, action);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
#patchPackage(id, patch) {
|
|
4499
|
+
this.#packages = this.#packages.map((item) => item.id === id ? { ...item, ...patch } : item);
|
|
4500
|
+
this.emit("packages", this.#packages);
|
|
4501
|
+
}
|
|
4502
|
+
async #actPackage(id, action) {
|
|
4503
|
+
const item = this.#packages.find((candidate) => candidate.id === id);
|
|
4504
|
+
if (!item?.actions.includes(action)) throw new Error(`Package action unavailable: ${id}`);
|
|
4505
|
+
if (action === "remove") {
|
|
4506
|
+
const users = this.#packages.filter((other) => other.version !== null && other.requires?.includes(id)).map((other) => other.label);
|
|
4507
|
+
if (users.length && this.#confirm) {
|
|
4508
|
+
const names = new Intl.ListFormat("en", { type: "conjunction" }).format(users);
|
|
4509
|
+
const verb = users.length === 1 ? "needs it and stops" : "need it and stop";
|
|
4510
|
+
const ok = await this.#confirm({
|
|
4511
|
+
title: `Remove ${item.label}?`,
|
|
4512
|
+
body: `${names} ${verb} working until ${item.label} is back.`,
|
|
4513
|
+
action: "Remove",
|
|
4514
|
+
variant: "confirmation"
|
|
4515
|
+
});
|
|
4516
|
+
if (!ok) return;
|
|
4517
|
+
}
|
|
4518
|
+
this.#patchPackage(id, { state: "removing", error: null, actions: [] });
|
|
4519
|
+
await this.#clock.sleep(2);
|
|
4520
|
+
this.#patchPackage(id, {
|
|
4521
|
+
state: "available",
|
|
4522
|
+
version: null,
|
|
4523
|
+
actions: ["install"],
|
|
4524
|
+
log: void 0
|
|
4525
|
+
});
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4528
|
+
const log = "Packages: +1";
|
|
4529
|
+
this.#patchPackage(id, {
|
|
4530
|
+
state: action === "update" ? "updating" : "installing",
|
|
4531
|
+
error: null,
|
|
4532
|
+
actions: [],
|
|
4533
|
+
log: `${log}
|
|
4534
|
+
Progress: resolved 1, downloaded 0, added 0`
|
|
4535
|
+
});
|
|
4536
|
+
await this.#clock.sleep(4);
|
|
4537
|
+
if (this.#fail) {
|
|
4538
|
+
this.#patchPackage(id, {
|
|
4539
|
+
state: "failed",
|
|
4540
|
+
error: fetchFailed,
|
|
4541
|
+
actions: ["retry"],
|
|
4542
|
+
log: `${log}
|
|
4543
|
+
ERR_PNPM_FETCH_TIMEOUT Request timed out after 30000ms`
|
|
4544
|
+
});
|
|
4545
|
+
throw new Error(fetchFailed);
|
|
4546
|
+
}
|
|
4547
|
+
for (const need of item.requires ?? []) {
|
|
4548
|
+
const required = this.#packages.find((candidate) => candidate.id === need);
|
|
4549
|
+
if (required && required.version === null) {
|
|
4550
|
+
this.#patchPackage(need, {
|
|
4551
|
+
state: "installed",
|
|
4552
|
+
version: required.offered,
|
|
4553
|
+
error: null,
|
|
4554
|
+
actions: ["remove"]
|
|
4555
|
+
});
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
this.#patchPackage(id, {
|
|
4559
|
+
state: "installed",
|
|
4560
|
+
version: item.offered,
|
|
4561
|
+
progress: null,
|
|
4562
|
+
actions: ["remove"],
|
|
4563
|
+
log: `${log}
|
|
4564
|
+
Progress: resolved 1, downloaded 1, added 1
|
|
4565
|
+
Done in 3.8s`
|
|
4566
|
+
});
|
|
4348
4567
|
}
|
|
4349
4568
|
list() {
|
|
4350
4569
|
return this.#items;
|
|
@@ -4396,10 +4615,13 @@ function createDummyModel({
|
|
|
4396
4615
|
delay = 30,
|
|
4397
4616
|
storage = null,
|
|
4398
4617
|
updates = "current",
|
|
4618
|
+
packages = "off",
|
|
4619
|
+
confirm,
|
|
4399
4620
|
mounts = "off",
|
|
4400
4621
|
network = "limited",
|
|
4401
4622
|
tailnet,
|
|
4402
|
-
changes = "files"
|
|
4623
|
+
changes = "files",
|
|
4624
|
+
notices = "off"
|
|
4403
4625
|
} = {}) {
|
|
4404
4626
|
const time = clock(delay);
|
|
4405
4627
|
const mount = mounts === "off" ? null : { scenario: mounts, point: mountPoint, files: mounted };
|
|
@@ -4433,18 +4655,25 @@ function createDummyModel({
|
|
|
4433
4655
|
]),
|
|
4434
4656
|
sprinkles: new DummySprinkles(sprinkles, agent, files3),
|
|
4435
4657
|
tray: new DummyTray(tray, time),
|
|
4436
|
-
updates: new DummyUpdates(updates, time
|
|
4658
|
+
updates: new DummyUpdates(updates, time, {
|
|
4659
|
+
...packages === "off" ? {} : { packages },
|
|
4660
|
+
confirm
|
|
4661
|
+
}),
|
|
4437
4662
|
...network === "off" ? {} : { network: new DummyNetwork(network, time, tailnet) },
|
|
4438
|
-
...changes === "files" ? {} : { changes: new DummyChanges(files3, changes === "git") }
|
|
4663
|
+
...changes === "files" ? {} : { changes: new DummyChanges(files3, changes === "git") },
|
|
4664
|
+
...notices === "off" ? {} : { notices: new DummyNotices(notices, time) }
|
|
4439
4665
|
};
|
|
4440
4666
|
return { ...base, monitor: new DummyMonitor(base) };
|
|
4441
4667
|
}
|
|
4442
4668
|
export {
|
|
4443
4669
|
DummyChanges,
|
|
4444
4670
|
DummyNetwork,
|
|
4671
|
+
DummyNotices,
|
|
4445
4672
|
DummyUpdates,
|
|
4446
4673
|
createDummyModel,
|
|
4447
4674
|
networkFixtures,
|
|
4675
|
+
noticeFixtures,
|
|
4676
|
+
packageFixtures,
|
|
4448
4677
|
tailnetFixtures,
|
|
4449
4678
|
updateFixtures
|
|
4450
4679
|
};
|