@ai-ecoverse/slicc-spectrum 1.95.0 → 1.96.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 CHANGED
@@ -284,6 +284,8 @@ The card (`.tool[data-tool][data-status][data-kind]`, a `details.run` under a pi
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.
287
+ A lick's optional `summary` (one plain line) replaces its `text` in the box and wraps instead of being cut, and its optional `report` (a file path) adds **Open the report** (`data-action="open-report"`, which fires `open-file` with the path) to the expanded lick, so a lick with only a `report` expands too.
288
+ A `scoop-notify` lick never shows the agent's bracketed status line: a `text` that is `[scoop … completed]`, `failed` or `stopped` (or `[@… completed]`) reads "Scoop {name} finished", "Scoop {name} failed" or "Scoop {name} stopped", the name being its `title` without the role in brackets, a failed or stopped one takes the `error` tone and a ❌ pill, named and announced as Scoop failed or Scoop stopped, instead of ✅, and its body, without that line, renders as markdown (`.report .text`), its headings stepped down to the chat's `####` size so the card doesn't outshout the chat.
287
289
  Pending permission requests (`sudo-request` licks with `state: 'pending'`) and open questions are decision cards in the centre column (`.decision-turn[data-pending]`, `data-id` the lick's or the question's), inset like a tool cluster (a wide margin on each side, a narrower one under 480 px), with their buttons on the right: **Deny**, then **Allow** (`data-action` `deny` and `allow`), or the question's options.
288
290
  A request with `expiresAt` counts down as m:ss; the count is hidden from screen readers, which hear its end time, then "Less than a minute left" and "Timed out", so the log's live region doesn't speak every second; a timed-out request (`data-expired`) loses its buttons.
289
291
  While pending, cards stick to the top of the log and pile, each a step lower than the one before; a card behind the front one (`data-pinned`, `--depth`) is a little smaller and softer for each card in front of it, and the log's `scroll-padding` keeps the pile from covering whatever has focus.
@@ -409,6 +411,7 @@ An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign
409
411
  An account with `fields` (`{ name, label, required?, placeholder?, help?, customBase?, options?, value? }`) shows a plain labelled input for each, with its `placeholder`, its current `value` and its `help` as a line under it, and their values go to `connect(id, secret?, fields)`; the key field above them gets a visible `sp-field-label` too ("Amazon Bedrock API key"), and both span the form's width. A field with `options` (`{ value, label }`) is an `sp-picker[data-account-field]` instead, labelled by an `sp-field-label` (clicking it focuses the picker), `aria-required` when required, with its `help` under it and in its `description` slot, and when refused `invalid`, with “Select a {label}.” in its `description` slot and under it without a second icon, so a provider can offer only the values it accepts, such as Bedrock's regions; it starts on the field's current `value`, otherwise, or when `value` is none of the options, on the first option, and it never holds back Connect in the provider picker, since it always has a choice. An empty `options` list is a disabled picker reading “None available”, never a free-text input, with the help text “Nothing to choose from yet. You can connect without it.” under it in the same field group (`[data-empty]`), and doesn't hold up Save even when `required`. The plain inputs' border is `--swc-neutral-visual-color`, which keeps 3:1 against the page. The dummy's Amazon Bedrock account has a region picker; connecting keeps the chosen values as the fields' `value` and shows the chosen options' labels in the row (“API key · Europe (Frankfurt)”).
410
412
  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
413
  Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before.
414
+ An `mcp:<server>` account with an `https:` `url` (the server's URL), when the `vault` port has `endpoints`, asks before its token is saved: “Connect to {url}?”, “SLICC sends this token only to {url}.”, **Connect** with Cancel focused; then the token goes to `vault.put`, the URL to `vault.endpoints({ add: { mcp: [{ server, url }] } })` and the account to `connect`, and a refusal shows on the token field and stops. The page keeps its own registrations (server and URL, not secret) in `localStorage` under `slicc-spectrum.mcp-endpoints`, written after a successful add or change and cleared after Disconnect takes the URL back. When the account's `url` differs from that registration, or, with none, from a first-use pin (the record's `url` while `SecretView.pinned` is `true`; a bridge's loopback `url` without `pinned` never counts), the row adds **Reconnect** (`data-action=reconnect-mcp`) and the note “The server moved to {url}. Reconnect to send the token there.” (`[data-notice=moved]`); after the same confirm, one `endpoints` call removes the old registration (none for a pin, which a registration overrides) and adds the new, then `connect` runs. Disconnect takes the URL back after removing the token. Settings registers MCP URLs itself, never through the agent.
412
415
  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]`).
413
416
  With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
414
417
  With `providers()`, Accounts lists only the added providers (`.accounts[data-group=providers]`), connected first, then expired, signing in and not connected; each row adds **Remove** (`data-action="remove-account"`, a quiet delete icon named “Remove {provider}”) when the port has `remove`, which asks “Remove {provider}?” (“Its key is deleted from the vault. Models from it stop working.”, “Its sign-in is deleted from the vault…” for OAuth, only “Models from it stop working.” for a local one) with a destructive **Remove** and Cancel focused, then runs `vault.remove(vaultName, 'model')` for a vaulted key and `settings.remove(id)`, stopping with the vault's error inline when it refuses; Disconnect removes the same record and keeps the row.
@@ -732,7 +735,7 @@ Later backed by: local storage.
732
735
  Theme, model, thinking level, composer and diff preferences, the models on offer, and accounts.
733
736
  A model can say it is its provider's `default`.
734
737
  Emit `settings` or `accounts` when the models on offer change.
735
- The optional `providers()` returns the `ProviderOption`s that can be added (`{ id, name, description?, auth: 'api-key' | 'oauth' | 'fields' | 'local', fields?, keyUrl?, needs?, blocked?, vaultName? }`, sorted by name, added ones included), with a `providers` event when they change; `id` is pi-ai's provider id and becomes the `Account.id`. With it, `accounts()` lists only added accounts, `connect(id, secret?, fields?)` adds one (`group: 'providers'`; a `fields` provider becomes an `api-key` account with `store: 'vault'` and only its non-secret `fields`), and the optional `remove(id)` drops one. `Account` adds `keyUrl`, `description` and `vaultName`, the vault record for its key (else pi-ai's environment-variable name). `needs: 'cors-free-transport'` on either is static (the provider doesn't answer a browser); `blocked: 'cors-free-transport'` is live, set while the current transport can't reach it, and cleared by a `providers` and an `accounts` event when an extension or a linked slicc appears.
738
+ The optional `providers()` returns the `ProviderOption`s that can be added (`{ id, name, description?, auth: 'api-key' | 'oauth' | 'fields' | 'local', fields?, keyUrl?, needs?, blocked?, vaultName? }`, sorted by name, added ones included), with a `providers` event when they change; `id` is pi-ai's provider id and becomes the `Account.id`. With it, `accounts()` lists only added accounts, `connect(id, secret?, fields?)` adds one (`group: 'providers'`; a `fields` provider becomes an `api-key` account with `store: 'vault'` and only its non-secret `fields`), and the optional `remove(id)` drops one. `Account` adds `keyUrl`, `description`, `vaultName`, the vault record for its key (else pi-ai's environment-variable name), and `url`, an `mcp:<server>` account's server URL. `needs: 'cors-free-transport'` on either is static (the provider doesn't answer a browser); `blocked: 'cors-free-transport'` is live, set while the current transport can't reach it, and cleared by a `providers` and an `accounts` event when an extension or a linked slicc appears.
736
739
 
737
740
  #### `changes` (optional)
738
741
 
@@ -983,6 +986,7 @@ The kitchen sink's “docs-pass failed” card is an expired-session error for t
983
986
  `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`).
984
987
  `createDummyModel({ providers: 'all' })` (`?providers=all` on `/ui/`) adds `providers()` and `remove` with 14 invented providers (`providerFixtures` from `/dummy`): API keys for Anthropic, Cerebras, Groq, Mistral, OpenAI and OpenRouter, OAuth for Adobe AI, GitHub Copilot and OpenAI Codex, fields for Amazon Bedrock (region), Azure OpenAI (endpoint, a `customBase`, and deployment) and Cloudflare (account id, CORS-free, its own `vaultName`), and Ollama and LM Studio on this device; today's rows start as added, with Amazon Bedrock and a connected Ollama (`providerAccounts(accounts)`, `providerAccount(option)`). `connect` adds a provider and drops it again when it fails or is cancelled, a key or field starting with `bad` is refused (“{name} didn’t accept that key.”), an endpoint without `https://` is refused, and `remove` drops it.
985
988
  `createDummyModel({ providers: 'all', transport: 'none' })` (`?providers=all&transport=none`) has no CORS-free transport: every provider with `needs` is also `blocked`, and Cloudflare starts connected, so it reads Unreachable. `model.settings.setTransport('extension')` (or `'link'`) clears `blocked` live, with a `providers` and an `accounts` event, and `setTransport('none')` sets it again.
989
+ The dummy's `docs` and `tickets` MCP accounts have a `url`, the dummy vault records each `endpoints` change and drops a record's pin once the page registers its server, and `createDummyModel({ mcp: 'moved' })` (`?mcp=moved` on `/ui/`) pins the vault's `MCP_DOCS_TOKEN` to docs' URL and moves `docs` to `movedDocs` (from `/dummy`).
986
990
 
987
991
  `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.
988
992
  Like slicc-agent's snapshot, the dummy's vitals are the budget, Spend, Active agents, Processes and Process memory (both with a `series`) and Fullest context.
@@ -991,7 +995,7 @@ Like slicc-agent's snapshot, the dummy's vitals are the budget, Spend, Active ag
991
995
  (`guest-follower`: the same without `decide`), or (`guest-always`) one guest tool call from Seat 2 (Mara) to `read_file` with `always: true`.
992
996
  Decisions are recorded in `model.approvals.decisions`, and `model.approvals.end(id, reason)` ends a request.
993
997
 
994
- `createDummyModel({ vault: 'empty' | 'secrets' | 'policy' | 'locked' | 'request' | 'backup' | 'waiting' | 'purged' | 'endpoints-fail' | 'off' })` (`?vault=` on `/ui/`, off by default; `notice` is kept as another name for `policy`) adds an invented vault: no secrets yet; five secrets (an API key, a jerk lease from pier-nuc revealed to `pier publish`, a Tide Mail sign-in, one without a rule and a removed one) with an activity log that pages back once; the same with a refused rule and a policy change in the log; locked before the first unlock, with a waiting request from docs-pass; unlocked with a `fnox set` request for `TIDE_STATION_TOKEN`; the secrets with a due backup reminder; or a vault that hasn't answered yet (`model.vault.arrive()` brings it); `endpoints-fail` is the secrets with an `endpoints` that refuses. The dummy vault records each `endpoints` change in `model.vault.endpointChanges`.
998
+ `createDummyModel({ vault: 'empty' | 'secrets' | 'policy' | 'locked' | 'request' | 'backup' | 'waiting' | 'purged' | 'endpoints-fail' | 'off' })` (`?vault=` on `/ui/`, off by default; `notice` is kept as another name for `policy`) adds an invented vault: no secrets yet; five secrets (an API key, a jerk lease from pier-nuc revealed to `pier publish`, a Tide Mail sign-in, one without a rule and a removed one) with an activity log that pages back once; the same with a refused rule and a policy change in the log; locked before the first unlock, with a waiting request from docs-pass; unlocked with a `fnox set` request for `TIDE_STATION_TOKEN`; the secrets with a due backup reminder; or a vault that hasn't answered yet (`model.vault.arrive()` brings it); `endpoints-fail` is the secrets with an `endpoints` that refuses (an MCP URL with “The vault refused {url} for this token. Check the server's address, then try again.”, a model URL with “The vault refused that endpoint. Check the address and try again.”). The dummy vault records each `endpoints` change in `model.vault.endpointChanges`.
995
999
  `purged` is `secrets` plus two `endpoints` audit entries: one with `purged` and `adopted`, and one `pinned` (`DOCS_MCP_TOKEN` on docs.example) that Unpin turns into an `unpinned` entry; `vaultPurged` and `vaultPinned` are exported.
996
1000
  It implements every op with the vault's checks, and keeps only names: `model.vault.puts` records `{ name, request? }`, `cancelled` the cancelled requests, `imports` each import's kind, size and pick, and `exported` the exports.
997
1001
  Import offers five invented candidates (one replaces a secret, one is a model key, one is refused with a reason that the done stage repeats; KeePass adds a `_2` duplicate), six answers ENOTSUP, page ops are recorded with `uid: null, tag: null`, and value ops while locked answer EACCES.
@@ -1039,6 +1043,7 @@ Each reply to it is paced to take about 20 seconds, so its streaming states can
1039
1043
  A prompt that says search or grep runs a search.
1040
1044
  Its tool turn has two clusters and a standalone run with URL, file and plain commands in every state, and two dummy browser tabs carry invented `data:` favicons.
1041
1045
  The dummy's fixtures are invented: a small forecast API called harbor, with cones, scoops, a conversation per agent, pending changes (one of them binary, without a diff), browser tabs and accounts.
1046
+ The harbor cone ends with three `scoop-notify` licks: amber-heron's, finished with a `summary`, a markdown body and a `report` at `/shared/scoop-notifications/amber-heron.md`; grey-tern's, failed, with no summary; and salt-gull's, only the legacy bracketed line.
1042
1047
  Its only sprinkles are SLICC's own: `welcome` (the onboarding wizard, as a dip in the sliccy cone) and `suggestions`, copied from `slicc` (`packages/vfs-root/shared/sprinkles/`, commit `aa29784`) with their comments stripped, as is the sprinkle theme in `src/app/sprinkle-theme.css`.
1043
1048
  `suggestions` reads its stream from the invented `/home/.gelatiere/suggestions.json` through the bridge; there is no `/home/.welcomed`, so `welcome` starts its wizard.
1044
1049
  Its replies are scripted by keyword (debug or investigate, tests, fix or add, open or docs, files, anything else), and their tool calls act on the other ports: an edit writes a file and shows up as a change, a browse opens a tab.
@@ -970,6 +970,7 @@ var suggestions = `${JSON.stringify(
970
970
  2
971
971
  )}
972
972
  `;
973
+ var upstreamReport = "# Upstream limits\n\n## Summary\n\nThe forecast upstream allows **60 requests a minute** per key and answers `429` with a `Retry-After` header past that. A one-minute cache keeps harbor well under it.\n\n## Findings\n\n- Bursts after midnight UTC reach about 40 requests a minute.\n- `Retry-After` is in seconds, never a date.\n- Timeouts come from the upstream, not the network: 3 in the last 1,000 calls.\n";
973
974
  var files = {
974
975
  "/workspace/harbor/README.md": readme,
975
976
  "/workspace/harbor/package.json": pkg,
@@ -986,6 +987,7 @@ var files = {
986
987
  "/workspace/skills/release-notes/scripts/collect.jsh": collect,
987
988
  "/shared/MEMORY.md": memory,
988
989
  "/shared/notes/2026-10-04-standup.md": standup,
990
+ "/shared/scoop-notifications/amber-heron.md": upstreamReport,
989
991
  "/home/user/.bashrc": bashrc,
990
992
  "/home/.gelatiere/suggestions.json": suggestions,
991
993
  "/tmp/forecast-sample.json": sample
@@ -1101,6 +1103,7 @@ var accounts = [
1101
1103
  id: "mcp:docs",
1102
1104
  provider: "docs",
1103
1105
  identity: "docs.example",
1106
+ url: "https://docs.example/mcp",
1104
1107
  status: "connected",
1105
1108
  auth: "api-key",
1106
1109
  group: "mcp",
@@ -1110,6 +1113,7 @@ var accounts = [
1110
1113
  id: "mcp:tickets",
1111
1114
  provider: "tickets",
1112
1115
  identity: "tickets.example",
1116
+ url: "https://tickets.example/mcp",
1113
1117
  status: "expired",
1114
1118
  auth: "api-key",
1115
1119
  group: "mcp",
@@ -1352,6 +1356,35 @@ var conversations = {
1352
1356
  title: "quiet-otter",
1353
1357
  text: "Started: add retries with backoff around the upstream fetch",
1354
1358
  createdAt: at2(9)
1359
+ },
1360
+ {
1361
+ id: "m-h5",
1362
+ role: "lick",
1363
+ channel: "scoop-notify",
1364
+ title: "amber-heron (scout)",
1365
+ text: "[scoop amber-heron (scout) completed]",
1366
+ summary: "The upstream allows 60 requests a minute, so a one-minute cache keeps harbor under it.",
1367
+ report: "/shared/scoop-notifications/amber-heron.md",
1368
+ body: `[scoop amber-heron (scout) completed]
1369
+ ${upstreamReport}`,
1370
+ createdAt: at2(11)
1371
+ },
1372
+ {
1373
+ id: "m-h6",
1374
+ role: "lick",
1375
+ channel: "scoop-notify",
1376
+ title: "grey-tern (worker)",
1377
+ text: "[scoop grey-tern (worker) failed]",
1378
+ body: "[scoop grey-tern (worker) failed]\nThe deploy preview timed out after **120 s**. Nothing was pushed.",
1379
+ createdAt: at2(13)
1380
+ },
1381
+ {
1382
+ id: "m-h7",
1383
+ role: "lick",
1384
+ channel: "scoop-notify",
1385
+ title: "salt-gull (scout)",
1386
+ text: "[scoop salt-gull (scout) completed]",
1387
+ createdAt: at2(15)
1355
1388
  }
1356
1389
  ],
1357
1390
  "scoop-otter": [
@@ -1448,7 +1481,7 @@ var conversations = {
1448
1481
  },
1449
1482
  {
1450
1483
  type: "text",
1451
- text: "## harbor 1.9.0\n\n**Features**\n- Added a `/health` endpoint.\n\n**Fixes**\n- Requests without a city now get a 400 instead of a 500.\n\n**Chores**\n- Updated wrangler to 4.40.0."
1484
+ text: "## harbor 1.9.0\n\n#### Features\n- Added a `/health` endpoint.\n\n#### Fixes\n- Requests without a city now get a 400 instead of a 500.\n\n#### Chores\n- Updated wrangler to 4.40.0."
1452
1485
  }
1453
1486
  ]
1454
1487
  },
@@ -1908,4 +1941,4 @@ export {
1908
1941
  dummySettingsKey,
1909
1942
  DummySettings
1910
1943
  };
1911
- //# sourceMappingURL=chunk-QGY4KBEB.js.map
1944
+ //# sourceMappingURL=chunk-CVMI4FSY.js.map