@ai-ecoverse/slicc-spectrum 1.100.0 → 1.101.1

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
@@ -356,6 +356,7 @@ They're also kept in `sessionStorage` (`slicc-spectrum.starting`, so per origin
356
356
  Only a reload of the same tab brings them back (the navigation's type is `reload`); any other load, such as a duplicated tab that got a copy of the storage, clears them, so nothing is sent twice.
357
357
  After a reload the rows come back in order with their text; attachments can't be restored, so a row that had any isn't sent on its own.
358
358
  A row with text and lost files is held (`[data-held]`): it shows an alert icon instead of the clock, a second, muted line "Not sent: tides.csv wasn’t kept after reload." (or "Not sent: 2 files weren’t kept after reload.", `[data-lost]`), **Put back** (`data-action="restore-starting"`), which puts its text into the field (after any draft, on a new line), focuses it and drops the row so the files can be attached again, and **Dismiss**.
359
+ After **Put back**, a muted line under the field with a paperclip icon (`[data-reattach]`) names what to attach: "Attach tides.csv again.", "Attach tides.csv and notes.txt again." and, with more than three, "Attach these 4 files again: …"; it's a quiet button (`data-action="reattach"`) that opens the file picker. It follows the listed files by name: attaching tides.csv leaves "Attach notes.txt again.", other files don't change it, and removing a listed file brings it back; it hides while all of them are attached, and goes once the field is cleared or the message is sent. The list is kept with each agent's draft, and a file is listed once.
359
360
  A row that had only attachments shows the alert icon, its file names and "Not kept after reload", and reads "Not sent. Attach the files again." (`[data-unsent]`) with **Dismiss**.
360
361
  Neither kind is sent on arrival or holds the other rows back, which still go in order; they stay until they're put back or dismissed.
361
362
  Text typed but not sent while it starts is kept for the page the same way, and stays in the field once the agent arrives.
@@ -418,6 +419,7 @@ The Browser rail button carries a dot (`data-driven`) while a tab is in use.
418
419
  #### `<slicc-settings>`
419
420
 
420
421
  Theme, the model and thinking level for new cones, composer and diff preferences (push to talk only where the browser has speech recognition), and accounts to connect, reconnect or disconnect.
422
+ When the `updates` port has `setDeveloper`, an Updates row has the **Developer mode** switch (`sp-switch[data-setting=developer]`, `checked` from `developer()` on every `items`), described as "Check npm for newer releases that aren't certified yet, and offer them as betas in Install / Update."; while `setDeveloper(on)` is pending it is disabled with `aria-busy` and a progress circle, and a rejection shows its message under it (`[data-error]`).
421
423
  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.
422
424
  Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`).
423
425
  An `api-key` account asks for its `secret`: “{provider} API key” by default, “{provider} token” for `secret: 'token'`; when it is `expired` it reads **API key rejected** or **Token rejected**.
@@ -472,13 +474,18 @@ It closes, clearing the field, when the request leaves `waiting` or while the va
472
474
  #### `<slicc-updates>`
473
475
 
474
476
  Install / Update: component states, version changes, last checks, download and link counts, action buttons, failures with Retry, and collapsible install logs behind a quiet chevron, which stay open across a layout switch.
475
- A row with a pending version change (`from → to`, or a package's `version → offered`) and release notes shows a What’s new disclosure after the row's actions (in package rows, after the command chips), next to the Install log, `details[data-releases]` with `summary[data-action="whats-new"]`, which stays open across a layout switch like the log. It lists every release after the installed version up to the new one, newest first, each as `.release[data-version]` with its version linking to the release page (`a[data-action="release-notes"]`, named "Kernel 1.13.0 release notes") and its notes rendered as markdown in strict mode (`markdown(text, { strict: true })`: raw HTML tags are dropped and their text kept as plain text, only `https:` links stay links, and an image becomes a link with its alt text, so nothing loads; the notes' headings sit one level under the release's, through `level`). Notes over 8 KB (8,192 characters) per release are cut at the last line break before that, with "…" and a Read the full notes link (`[data-cut]`, named "Read the full notes for Kernel 1.13.0"); a release whose `url` isn't `https:` shows its version as plain text. The `<!-- cert-verify -->` block is dropped, and a block that starts with "certified" shows as a subtle Certified badge (`[data-certified]`); a heading line naming the version is dropped too, since the link already shows it. Without notes in the range there is no disclosure.
477
+ A row with a pending version change (`from → to`, or a package's `version → offered`) and release notes shows a What’s new disclosure after the row's actions (in package rows, after the command chips), next to the Install log, `details[data-releases]` with `summary[data-action="whats-new"]`, which stays open across a layout switch like the log. It lists every release after the installed version up to the new one, newest first, each as `.release[data-version]` with its version linking to the release page (`a[data-action="release-notes"]`, named "Kernel 1.13.0 release notes"). Only the newest (`.release[data-newest]`) shows its notes; each older release is a collapsed header, `details.release` with `summary[data-action="release"]` holding the same chevron, its version link and badge across the full row and named "Kernel 1.12.2 notes, certified", that opens to its notes indented under its title and stays open across a layout switch like the disclosure (an older release without notes is a plain header). The notes are rendered as markdown in strict mode (`markdown(text, { strict: true })`: raw HTML tags are dropped and their text kept as plain text, only `https:` links stay links, and an image becomes a link with its alt text, so nothing loads; the notes' headings sit one level under the release's, through `level`). Notes over 8 KB (8,192 characters) per release are cut at the last line break before that, with "…" and a Read the full notes link (`[data-cut]`, named "Read the full notes for Kernel 1.13.0"); a release whose `url` isn't `https:` shows its version as plain text. The `<!-- cert-verify -->` block is dropped, and a block that starts with "certified" shows as a subtle Certified badge (`[data-certified]`); a heading line naming the version is dropped too, since the link already shows it. Without notes in the range there is no disclosure.
476
478
  An up-to-date row (no pending change) links its installed release with Release notes next to the version; an installed optional package without one links its npm page instead (`a[data-action="npm-page"]`, named "npm page for Biome 2.2.4"). Pins and additions in Your packages show no link. Every link opens in a new tab.
477
479
  An install shows one bar for update rows and package rows alike: it fills 0–50% while downloading and 50–100% while linking (`value` against `max-value="100"`). While the row runs it never goes back, not when pnpm's total grows and not when progress drops out between passes. While a download reports `bytes` with a total above 0, the first half fills by bytes and the value label reads "10.0 MB of 40.0 MB". Otherwise, and always while linking, the label falls back to the phase's "n of m" count. The bar starts at 0 again once the row settles.
478
480
  Opens at boot until the agent is ready without failures.
479
481
  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.
480
482
  Every automatic close is saved to the layout.
481
483
  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.
484
+ With a port that has `setDeveloper`, a row's `npm` check shows under its version: "npm: no newer release", "Checking npm…", its `reason` when the check failed (the state light stays), "{installed} (certified) · {latest} on npm, not offered: {reason}" (a certified update keeps its target, "6.6.0 (certified) → 6.7.0 · …", and so does a beta row, "6.13.5 beta · certified 6.6.0 → 6.7.0"), or, when `offered`, "{installed} (certified) · {latest} on npm, not certified" with "12 packages change in /opt/agent." (the folder in the label's parentheses; "1 package changes …"), followed by "@slicc/kernel-bridge and @slicc/vault stay at their certified versions." for `gated` ("… stays at its certified version." for one) and **Install beta**; `article[data-npm]` is the state.
485
+ A row with `beta` has a notice **Beta** badge by its name (`[data-beta]`), a notice "Beta installed" light while it is `current` or `installed` ("Up to date" means the certified version only; Ready to apply, Failed and the running states keep their words), reads "{version} beta · certified {certified}" and offers **Back to certified** next to its other actions; Install beta shows only while `npm` is `offered`.
486
+ While `npm` is `offered`, What’s new covers the releases from the installed version up to npm's `latest`, so an offered beta lists its own notes.
487
+ Install beta and Back to certified are secondary buttons and call `act`; without `setDeveloper` (a mirrored port) neither shows.
488
+ For now bios sets these on the agent row only; the other rows follow with bios.
482
489
  Packages the user added with pnpm in `/opt/user`, or pinned, show up in their own Your packages section below it, under "Packages you added with pnpm in /opt/user. Seven’s updates keep them until you remove them." (plus "Pinned versions stay until you unpin them." while a row offers `unpin`), with a neutral Pinned light (a hold, not a success) or a positive Added light, their version against seven's, and Unpin or Remove.
483
490
  Also available through View and the rails.
484
491
 
@@ -808,8 +815,10 @@ Emit `items` whenever rows or readiness change.
808
815
  Use `queued` for a row that waits for another component before it starts; it reads "Waiting" and doesn't count as updating.
809
816
  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.
810
817
  A `failed` row with an installed `from` version reads "Update failed".
811
- `act(id, action)` resolves when dispatched or rejects with a displayed error; actions are `retry`, `update-now`, `restart-agent` and `reload`.
818
+ `act(id, action)` resolves when dispatched or rejects with a displayed error; actions are `retry`, `update-now`, `restart-agent`, `reload`, `install-beta` and `back-to-certified` (the embedder asks for confirmation itself).
812
819
  A row's and a package's optional `releases` (`UpdateRelease[]`) lists the GitHub releases the embedder knows for it, each with its `version`, the release page `url` and optional `notes` (the release body, semantic-release markdown as GitHub returns it). Send every release after the installed version up to the new one, so the panel can show the whole range, plus the installed one for its link; spectrum orders them by version and never fetches them itself. Leave it out, or leave out `notes`, when there's nothing to show.
820
+ Optional `developer()` (false by default) and `setDeveloper(on)`, on the owner's page only, turn developer mode on and off; `setDeveloper` resolves once settled or rejects with a displayed error, and the port emits `items` when either changes.
821
+ A row's optional `beta` (`{ version, certified }`) is set while a beta is installed, and its optional `npm` (`{ state, latest?, reason?, changes?: { count, gated } }`, `state` one of `checking`, `current`, `offered`, `not-offered` or `failed`) is `null` while developer mode is off.
813
822
  Missing ports preserve existing adapters and report disconnected information in the panel.
814
823
  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 "Uses Python" while it's installed, or "Installs Python too" on a row that isn't installed yet; an installed row leaves out a requirement that isn't installed, since its warning says so), optional `notes` (short plain-text lines shown muted under the row, such as "1 untested dependency: …"; leave it out when there's nothing to say), optional `warnings` (lines with an alert icon, `data-warnings`, such as "Needs Python, which isn't installed.
815
824
  Install Python again to use it."; the dummy adds that one when a required package is removed), `state` (`available`, `queued`, `installing`, `installed`, `outdated`, `updating`, `removing` or `failed`), the installed `version`, the `offered` version and its `size` in bytes, `progress` (`InstallProgress`, or `null` for an indeterminate bar), `error`, `actions` (`install`, `update`, `remove`, `retry`, `unpin`) and an optional `log`.
@@ -870,6 +879,7 @@ An action is a secondary button that stays pending until `act(id, action)` settl
870
879
  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.
871
880
  Emit `notices` whenever they change.
872
881
  A notice with `dismissible: false` has no close button and stays until the port drops it.
882
+ An optional `session: true` marks a notice the embedder keeps for the session; the panel shows it like any other, and other fields pass through.
873
883
  Hooks: `[data-notice="<id>"]`, buttons with `data-action`, `[data-dismiss]` and `[data-error]`.
874
884
 
875
885
  #### `tabs` (optional)
@@ -966,7 +976,8 @@ Fixtures stay stable for inspection; `model.updates.setScenario('current')` comp
966
976
  The dummy's kernel offers three releases from 1.12.0 to 1.13.0, two of them certified, the agent one release to 0.25.0, and the syntax grammars only their installed release; its uv package offers two releases, Python links its release and the other installed packages their npm pages.
967
977
  Dummy Update now and Retry simulate download and link progress, and a dummy package install reports half its `size` in bytes on the way; Restart agent and Reload only change fixture state, never restart a real process or reload the browser.
968
978
  `createDummyModel({ packages: 'mixed' | 'empty' | 'fail' | 'user' | 'elsewhere' | '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, `user` adds a pinned agent-kit and two additions (one with a warning) that Unpin and Remove take out of the list, `elsewhere` has git installed for cone with pnpm and TypeScript there at an older version, whose Update keeps it in place, and Biome both from the panel and for cone, whose Remove leaves cone's copy, and removing Python while uv is installed asks through the `confirm` option (the `/ui/` page passes spectrum's `confirm`).
969
- `updateFixtures`, `packageFixtures` and `DummyUpdates` are exported from `/dummy`.
979
+ `createDummyModel({ beta: 'offered' | 'installed' | 'blocked' | 'failed' | 'off' })` (or `?beta=` on `/ui/`, off by default) turns developer mode on with an invented agent at 6.6.0 (certified) and 6.13.5 on npm, 12 packages and nothing gated: `offered` offers Install beta, `installed` runs the beta, `blocked` isn't offered because it needs a newer kernel, and `failed` is a failed beta install with Retry and Install beta; `setDeveloper` toggles it after a short wait (checking npm again when turned on), and Install beta and Back to certified simulate a short install.
980
+ `updateFixtures`, `betaFixtures`, `packageFixtures` and `DummyUpdates` are exported from `/dummy`.
970
981
  `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.
971
982
  `createDummyModel({ network: 'ok' | 'limited' | 'failing' | 'off' })` (or `?network=` on `/ui/`, default `limited`) sets the dummy network: slicc on this computer as a linked device (`route: 'link'`, with browser automation through it), the page fetch with CORS failures, or no route, all invented; `off` leaves out the port.
972
983
  `model.network.setScenario()` switches it and Check again re-emits after a short wait.
@@ -1036,6 +1047,7 @@ Its buttons are plain Spectrum buttons that answer at once: Unlock unlocks, and
1036
1047
 
1037
1048
  On phones the update status and the tray connection show only their status light (the tray's budget is hidden) 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.
1038
1049
  The update status comes first so progress and failures remain reachable on narrow screens.
1050
+ While a row has `beta`, the update status shows a notice **Beta** badge (`[data-updates] [data-beta]`) after its light, or alone when nothing else applies, at every size; its tooltip and label add "{label} runs beta {version}, not certified", with the label's folder left out ("Agent runs beta 6.13.5, not certified").
1039
1051
 
1040
1052
  ### Kernel model
1041
1053
 
@@ -1741,8 +1741,8 @@ var DummyAgent = class extends Emitter {
1741
1741
  this.emit("message", { agentId, message: reply });
1742
1742
  const steers = run.steers.splice(0);
1743
1743
  for (const steer of steers) this.#post(agentId, this.#user(steer, "steer"));
1744
- const latest = steers.at(-1);
1745
- ({ steps, suggestion } = script(latest.text.trim(), latest.attachments ?? []));
1744
+ const latest2 = steers.at(-1);
1745
+ ({ steps, suggestion } = script(latest2.text.trim(), latest2.attachments ?? []));
1746
1746
  run.pace = this.#pace(agentId, steps);
1747
1747
  reply = this.#reply(agent.model);
1748
1748
  this.#post(agentId, reply);
@@ -5061,7 +5061,89 @@ function updateFixtures(scenario) {
5061
5061
  }
5062
5062
  return items;
5063
5063
  }
5064
- var offer = (id, label2, description, commands, offered, size, requires) => ({
5064
+ var pin = "6.6.0";
5065
+ var latest = "6.13.5";
5066
+ var betaReleases = [
5067
+ tag(
5068
+ "harbor-agent",
5069
+ "6.13.5",
5070
+ notes(
5071
+ "harbor-agent",
5072
+ "6.13.5",
5073
+ "### Bug Fixes\n\n* **tools:** a tide table with a blank row no longer stops the import"
5074
+ )
5075
+ ),
5076
+ tag(
5077
+ "harbor-agent",
5078
+ "6.10.0",
5079
+ notes(
5080
+ "harbor-agent",
5081
+ "6.10.0",
5082
+ "### Features\n\n* **chat:** answers can quote a buoy reading with its time and place"
5083
+ )
5084
+ ),
5085
+ tag(
5086
+ "harbor-agent",
5087
+ "6.6.0",
5088
+ notes("harbor-agent", "6.6.0", "### Bug Fixes\n\n* **chat:** a pasted chart keeps its axes") + certified("harbor-agent", "6.6.0")
5089
+ )
5090
+ ];
5091
+ var offered = { state: "offered", latest, changes: { count: 12, gated: [] } };
5092
+ var blocked = {
5093
+ state: "not-offered",
5094
+ latest,
5095
+ reason: "it needs kernel 1.14.0 or later"
5096
+ };
5097
+ var betaFailed = "Couldn\u2019t install the beta: pnpm stopped with an error, shown in the install log. Retry.";
5098
+ function betaActions(item) {
5099
+ return [
5100
+ ...item.beta ? ["back-to-certified"] : [],
5101
+ ...item.npm?.state === "offered" ? ["install-beta"] : []
5102
+ ];
5103
+ }
5104
+ function withNpm(items, npm) {
5105
+ return items.map((item) => {
5106
+ if (item.kind !== "agent") return item;
5107
+ const next = { ...item, npm };
5108
+ const kept = item.actions.filter(
5109
+ (action) => action !== "install-beta" && action !== "back-to-certified"
5110
+ );
5111
+ return { ...next, actions: [...kept, ...betaActions(next)] };
5112
+ });
5113
+ }
5114
+ function betaFixtures(items, scenario) {
5115
+ return items.map((item) => {
5116
+ if (item.kind !== "agent") return item;
5117
+ const base = {
5118
+ ...item,
5119
+ from: pin,
5120
+ to: pin,
5121
+ beta: null,
5122
+ npm: offered,
5123
+ releases: betaReleases
5124
+ };
5125
+ const next = scenario === "installed" ? {
5126
+ ...base,
5127
+ from: latest,
5128
+ to: latest,
5129
+ beta: { version: latest, certified: pin },
5130
+ npm: { state: "current", latest }
5131
+ } : scenario === "blocked" ? { ...base, npm: blocked } : scenario === "failed" ? {
5132
+ ...base,
5133
+ state: "failed",
5134
+ to: latest,
5135
+ error: betaFailed,
5136
+ log: `pnpm add @slicc/demo-agent@${latest}
5137
+ ERR_PNPM_FETCH_TIMEOUT Request timed out after 30000ms`
5138
+ } : base;
5139
+ const extra = betaActions(next);
5140
+ return {
5141
+ ...next,
5142
+ actions: [...scenario === "failed" ? ["retry"] : item.actions, ...extra]
5143
+ };
5144
+ });
5145
+ }
5146
+ var offer = (id, label2, description, commands, offered2, size, requires) => ({
5065
5147
  id,
5066
5148
  package: `@harbor/${id}-wasm`,
5067
5149
  label: label2,
@@ -5070,14 +5152,14 @@ var offer = (id, label2, description, commands, offered, size, requires) => ({
5070
5152
  ...requires ? { requires } : {},
5071
5153
  state: "available",
5072
5154
  version: null,
5073
- offered,
5155
+ offered: offered2,
5074
5156
  size,
5075
5157
  progress: null,
5076
5158
  error: null,
5077
5159
  actions: ["install"]
5078
5160
  });
5079
5161
  var fetchFailed = "The download stopped. Check the connection and try again.";
5080
- var yours = (project, name2, description, version, pin, change = {}) => ({
5162
+ var yours = (project, name2, description, version, pin2, change = {}) => ({
5081
5163
  id: `user:${project}:${name2}`,
5082
5164
  package: name2,
5083
5165
  label: name2.replace(/^@[^/]+\//, ""),
@@ -5088,9 +5170,9 @@ var yours = (project, name2, description, version, pin, change = {}) => ({
5088
5170
  offered: null,
5089
5171
  progress: null,
5090
5172
  error: null,
5091
- actions: [pin.base === null ? "remove" : "unpin"],
5173
+ actions: [pin2.base === null ? "remove" : "unpin"],
5092
5174
  origin: "user",
5093
- pin: { ...pin, project: `/opt/${project}` },
5175
+ pin: { ...pin2, project: `/opt/${project}` },
5094
5176
  ...change
5095
5177
  });
5096
5178
  var userPackages = () => [
@@ -5239,9 +5321,16 @@ var DummyUpdates = class extends Emitter {
5239
5321
  #packages = [];
5240
5322
  #fail = false;
5241
5323
  #confirm;
5324
+ #beta;
5325
+ #developer = true;
5242
5326
  constructor(scenario, clock2, options = {}) {
5243
5327
  super();
5244
- this.#items = updateFixtures(scenario);
5328
+ this.#beta = options.beta;
5329
+ this.#items = this.#fixtures(scenario);
5330
+ if (options.beta) {
5331
+ this.developer = () => this.#developer;
5332
+ this.setDeveloper = (on) => this.#setDeveloper(on);
5333
+ }
5245
5334
  this.#ready = scenario !== "boot" && scenario !== "starting";
5246
5335
  this.#clock = clock2;
5247
5336
  this.#confirm = options.confirm;
@@ -5371,8 +5460,32 @@ Done in 3.8s`
5371
5460
  ready() {
5372
5461
  return this.#ready;
5373
5462
  }
5463
+ #fixtures(scenario) {
5464
+ const items = updateFixtures(scenario);
5465
+ if (!this.#beta) return items;
5466
+ const fixed = betaFixtures(items, this.#beta);
5467
+ return this.#developer ? fixed : withNpm(fixed, null);
5468
+ }
5469
+ async #setDeveloper(on) {
5470
+ await this.#clock.sleep(4);
5471
+ this.#developer = on;
5472
+ this.#npm(on ? { state: "checking" } : null);
5473
+ if (!on) return;
5474
+ await this.#clock.sleep(4);
5475
+ const agent = this.#items.find((item) => item.kind === "agent");
5476
+ this.#npm(this.#check(agent.from));
5477
+ }
5478
+ #check(version) {
5479
+ if (!this.#developer) return null;
5480
+ if (version === latest) return { state: "current", latest };
5481
+ return this.#beta === "blocked" ? blocked : offered;
5482
+ }
5483
+ #npm(npm) {
5484
+ this.#items = withNpm(this.#items, npm);
5485
+ this.emit("items", this.#items);
5486
+ }
5374
5487
  setScenario(scenario) {
5375
- this.#items = updateFixtures(scenario);
5488
+ this.#items = this.#fixtures(scenario);
5376
5489
  this.#ready = scenario !== "boot" && scenario !== "starting";
5377
5490
  this.emit("items", this.#items);
5378
5491
  }
@@ -5383,7 +5496,9 @@ Done in 3.8s`
5383
5496
  async act(id, action) {
5384
5497
  const item = this.#items.find((candidate) => candidate.id === id);
5385
5498
  if (!item?.actions.includes(action)) throw new Error(`Update action unavailable: ${id}`);
5386
- if (action === "update-now" || action === "retry") {
5499
+ const back = action === "back-to-certified";
5500
+ const beta = back || action === "install-beta" || action === "retry" && item.to === latest;
5501
+ if (action === "update-now" || action === "retry" || beta) {
5387
5502
  this.#patch(id, {
5388
5503
  state: "downloading",
5389
5504
  progress: { phase: "download", done: 0, total: 95 },
@@ -5399,13 +5514,30 @@ Done in 3.8s`
5399
5514
  });
5400
5515
  await this.#clock.sleep(2);
5401
5516
  }
5517
+ if (beta) {
5518
+ const version = back ? pin : latest;
5519
+ const next = {
5520
+ ...this.#items.find((candidate) => candidate.id === id),
5521
+ state: "ready",
5522
+ from: version,
5523
+ to: version,
5524
+ progress: null,
5525
+ error: null,
5526
+ checkedAt,
5527
+ beta: back ? null : { version, certified: pin },
5528
+ npm: this.#check(version)
5529
+ };
5530
+ this.#patch(id, { ...next, actions: ["restart-agent", ...betaActions(next)] });
5531
+ return;
5532
+ }
5402
5533
  const apply = action === "update-now" && ["agent", "bios", "kernel", "ui"].includes(item.kind);
5403
5534
  this.#patch(id, {
5404
5535
  state: apply ? "ready" : "installed",
5405
5536
  from: apply ? item.from : item.to,
5406
5537
  progress: null,
5407
5538
  checkedAt,
5408
- actions: apply ? [item.kind === "agent" ? "restart-agent" : "reload"] : []
5539
+ actions: apply ? [item.kind === "agent" ? "restart-agent" : "reload"] : [],
5540
+ ...apply ? {} : { actions: betaActions(item) }
5409
5541
  });
5410
5542
  }
5411
5543
  };
@@ -5512,7 +5644,7 @@ function refused(settings) {
5512
5644
  if (bad)
5513
5645
  throw vaultFailure("EINVAL", `The vault refused ${bad[0]}. Check that value and try again.`);
5514
5646
  }
5515
- function accountRecords(now2 = Date.now(), pin) {
5647
+ function accountRecords(now2 = Date.now(), pin2) {
5516
5648
  return [
5517
5649
  {
5518
5650
  name: "MCP_DOCS_TOKEN",
@@ -5522,7 +5654,7 @@ function accountRecords(now2 = Date.now(), pin) {
5522
5654
  source: "settings",
5523
5655
  sourceDevice: null,
5524
5656
  server: "docs",
5525
- ...pin ? { url: pin, pinned: true } : {},
5657
+ ...pin2 ? { url: pin2, pinned: true } : {},
5526
5658
  domains: ["docs.example"],
5527
5659
  rules: [{ domain: "docs.example", header: "Authorization", methods: [], paths: [] }],
5528
5660
  exec: false,
@@ -6103,11 +6235,11 @@ var DummyVault = class extends Emitter {
6103
6235
  await this.#time.sleep(5);
6104
6236
  this.#unlocked();
6105
6237
  for (const name2 of names) {
6106
- const pin = this.#list.audit.find(
6238
+ const pin2 = this.#list.audit.find(
6107
6239
  (entry) => entry.type === "endpoints" && entry.pinned?.name === name2
6108
6240
  );
6109
- if (pin?.type === "endpoints" && pin.pinned)
6110
- this.#log(name2, { type: "endpoints", unpinned: pin.pinned });
6241
+ if (pin2?.type === "endpoints" && pin2.pinned)
6242
+ this.#log(name2, { type: "endpoints", unpinned: pin2.pinned });
6111
6243
  }
6112
6244
  this.emit("list", this.#list);
6113
6245
  }
@@ -6144,20 +6276,20 @@ var DummyVault = class extends Emitter {
6144
6276
  password: !!options.password,
6145
6277
  ...pick ? { pick: [...pick] } : {}
6146
6278
  });
6147
- const offered = kind === "keepass" ? [...candidates, duplicate] : candidates;
6148
- if (!pick) return { stage: "pick", candidates: offered.map((item) => ({ ...item })) };
6279
+ const offered2 = kind === "keepass" ? [...candidates, duplicate] : candidates;
6280
+ if (!pick) return { stage: "pick", candidates: offered2.map((item) => ({ ...item })) };
6149
6281
  const chosen = new Map(
6150
6282
  pick.map(
6151
6283
  (item) => typeof item === "string" ? [item, "secret"] : [item.name, item.kind]
6152
6284
  )
6153
6285
  );
6154
- const taken = offered.filter((item) => chosen.has(item.name) && !item.refused);
6286
+ const taken = offered2.filter((item) => chosen.has(item.name) && !item.refused);
6155
6287
  return {
6156
6288
  stage: "done",
6157
6289
  imported: taken.map((item) => this.#seal(item.name, `import:${kind}`, chosen.get(item.name))),
6158
- skipped: offered.filter((item) => !taken.includes(item)).map((item) => item.name),
6290
+ skipped: offered2.filter((item) => !taken.includes(item)).map((item) => item.name),
6159
6291
  reasons: Object.fromEntries(
6160
- offered.flatMap((item) => item.refused ? [[item.name, item.refused]] : [])
6292
+ offered2.flatMap((item) => item.refused ? [[item.name, item.refused]] : [])
6161
6293
  )
6162
6294
  };
6163
6295
  }
@@ -6248,6 +6380,7 @@ function createDummyModel({
6248
6380
  updates = "current",
6249
6381
  packages = "off",
6250
6382
  confirm,
6383
+ beta = "off",
6251
6384
  mounts = "off",
6252
6385
  network = "limited",
6253
6386
  tailnet,
@@ -6328,6 +6461,7 @@ function createDummyModel({
6328
6461
  tray: new DummyTray(tray, time),
6329
6462
  updates: new DummyUpdates(updates, time, {
6330
6463
  ...packages === "off" ? {} : { packages },
6464
+ ...beta === "off" ? {} : { beta },
6331
6465
  confirm
6332
6466
  }),
6333
6467
  ...network === "off" ? {} : { network: new DummyNetwork(network, time, tailnet, links, files3, leaders) },
@@ -6365,6 +6499,7 @@ export {
6365
6499
  DummyVaultButton,
6366
6500
  actionLimits,
6367
6501
  approvalFixtures,
6502
+ betaFixtures,
6368
6503
  browserHistory,
6369
6504
  createDummyModel,
6370
6505
  createGuestModel,