@ai-ecoverse/slicc-spectrum 2.4.1 → 2.6.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 +14 -11
- package/dist/slicc-dummy.js +93 -10
- package/dist/slicc-dummy.js.map +2 -2
- package/dist/slicc-ui.js +488 -30
- package/dist/slicc-ui.js.map +3 -3
- package/dist/types/app/composer.d.ts +13 -0
- package/dist/types/app/secrets.d.ts +10 -0
- package/dist/types/app/settings.d.ts +13 -1
- package/dist/types/app/vault.d.ts +7 -1
- package/dist/types/dummy/index.d.ts +2 -1
- package/dist/types/dummy/vault.d.ts +4 -1
- package/dist/types/model/types.d.ts +23 -3
- package/dist/types/ui.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -342,6 +342,7 @@ Secret requests (`secret`, `dialog[data-kind=secret]`) read "Enter a value for {
|
|
|
342
342
|
The body is the vault's own frame (`[data-value-frame]`, a `<slicc-vault-value>` on `vault.valueFrame(id)`). The value is typed and Save or Allow clicked there, so spectrum never sees the value and can't fake the consent. The frame is `inert` until the end of the command has been in view, and "Scroll to the end of the command to answer it." (`[data-allow-hint]`) shows while it isn't. Without `vault.valueFrame`, "The vault isn’t available here, so this request can only be denied." (`[data-id="no-frame"]`) stands in its place.
|
|
343
343
|
The title shows control characters in the name as chips, and so does the notice. The only button is **Deny** (`deny`, focused). The element's `decide()` takes only `deny` for a secret, and only while that request is the one showing: not once it was denied, answered in its frame, closed, or ended (timed out, answered elsewhere, withdrawn). A late `escape` from the frame does nothing either.
|
|
344
344
|
When a listed request is replaced (the same `id` with another `createdAt`, command, mode, name, `reveal`, prompt or requester), or an `id` comes back after it left `list()`, the dialog counts it as a new request: a new frame, the end of its command unseen again, and the half-second guard again. Escape on the dialog denies, and so does the frame's `escape` event, posted when Escape is pressed inside the frame. The frame's `done` for this request closes the dialog and moves on to the next request, with no "answered" dialog. The frame's `gone` shows "Secret request timed out" (`expired`) or "Answered elsewhere", or closes silently (`withdrawn`), except for a request this tab denied or answered. The frame's `error` shows its message under the frame (`[data-frame-error]`). A secret request that leaves `list()` without `ended` before `expiresAt` closes silently, because the frame's `done` and the list change can arrive in either order. Focus then goes back as for sudo, to the `<slicc-terminal>` that had it.
|
|
345
|
+
`valueEntry` (from `/ui`, `true`) says that every value entry point in this UI (the composer's Share a secret, Settings › Secrets Add and Replace, Settings › Accounts key fields) uses `vault.enter` and `valueFrame` whenever the port has `enter`, so an embedder can stop taking values through `put` only from a UI that has it; `<slicc-value-request>` still answers older `value` requests with `put`.
|
|
345
346
|
`approvalKinds` (from `/ui`) is a frozen array of the kinds this build shows (`sudo`, `guest-message`, `guest-tool`, `tray`, `secret`, `click`, `capture`), so an embedder can hold back the kinds an older UI would leave waiting unseen.
|
|
346
347
|
|
|
347
348
|
#### `<slicc-follow>`
|
|
@@ -357,7 +358,7 @@ Renders nothing without the port.
|
|
|
357
358
|
The message box uses Spectrum Web Components 2.0.0-beta.6. Its explicit accessible names stay "Message" for the textarea, "Send" ("Steer" while busy) for send, "Add" for upload and "Stop" for stop, using `accessible-label`, `send-accessible-label`, `upload-accessible-label` and `stop-accessible-label`. Attachment navigation stays "Show previous attachments" / "Show more attachments", and the strip stays "Uploaded assets strip", through their `*-accessible-label` attributes.
|
|
358
359
|
<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.
|
|
359
360
|
`/` completes commands (`/clear`, `/compact`, `/model`, `/thinking`, `/scoop`, `/stop`, `/freeze` when the port can freeze, `/theme`) and their arguments, `@` mentions agents and files.
|
|
360
|
-
Attach from the computer, from SLICC's files, a browser screenshot or a shared secret (with a `vault.put`, the popup takes the value too, checks it like Settings › Secrets, puts it in the vault and attaches the name, showing errors inline and clearing the field; an empty value shares a name the vault already has, and a new value for one asks before it replaces it; while the vault is away (`availability()` `connecting` or `waiting`) or locked, it takes no value and says so; without one, it attaches the name and says to add the value in Settings › Secrets; the popup closes on Cancel (`data-action="cancel-secret"`, disabled while a share is pending), on a press outside the form (ignored while a share is pending) and on Escape, each clearing the typed value), or paste and drop files (25 MB each; a dropped folder, told apart by its drag entry, becomes a “Folders can’t be attached” chip, and a file that can't be read a “Could not read the file” chip, instead of an attachment).
|
|
361
|
+
Attach from the computer, from SLICC's files, a browser screenshot or a shared secret (with `vault.enter` and `valueFrame`, the popup (`form.popup.secret`) takes only the name (`input[name=name]`): **Share** (`data-action="share-secret"`) attaches a name the vault already has at once, and for a new name opens `vault.enter({ name })` and shows the vault's own field (`slicc-vault-value`, `form.popup.secret[data-entry]`) in place of a value field, attaching the name on the frame's `done`; for a name the vault has, **Replace value** (`data-action="replace-secret"`) asks before it replaces the value and then opens `vault.enter({ name, replace: true })`; **Back** (`data-action="back-secret"`) cancels the request and goes back to the name, and **Cancel** (`data-action="cancel-secret"`), a press outside the form, Escape and the frame's `escape` cancel it and close the popup (Cancel is disabled and a press outside is ignored while a share is pending), and `enter`'s refusals, the frame's `error` and a `gone` frame ("The vault’s field timed out. Try again.") show inline; a locked vault unlocks in that field; while the vault is away it attaches the name only and opens no request, and a field already open stays through it; with `enter` it never shows a value input. Without `enter`, with a `vault.put`, the popup takes the value too, checks it like Settings › Secrets, puts it in the vault and attaches the name, showing errors inline and clearing the field; an empty value shares a name the vault already has, and a new value for one asks before it replaces it; while the vault is away (`availability()` `connecting` or `waiting`) or locked, it takes no value and says so; without one, it attaches the name and says to add the value in Settings › Secrets; the popup closes on Cancel (`data-action="cancel-secret"`, disabled while a share is pending), on a press outside the form (ignored while a share is pending) and on Escape, each clearing the typed value), or paste and drop files (25 MB each; a dropped folder, told apart by its drag entry, becomes a “Folders can’t be attached” chip, and a file that can't be read a “Could not read the file” chip, instead of an attachment).
|
|
361
362
|
<kbd>Esc</kbd> in the `+` menu closes it, from any item, and puts focus back on `+`; it goes no further, so it doesn't also stop a reply.
|
|
362
363
|
Queued messages wait above it in a dashed box (`--swc-neutral-visual-color`, 3:1 or more against the composer in both themes), to send now or drop; a steer the agent still holds (`mode: 'steer'`, `li.queued[data-mode=steer]`) reads "Sends at the next step" and can only be dropped.
|
|
363
364
|
Model and thinking pickers.
|
|
@@ -459,7 +460,7 @@ With `settings.removeServer` every MCP row adds **Remove** (`data-action="remove
|
|
|
459
460
|
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**.
|
|
460
461
|
An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign in**, and it reads **Needs sign-in** when `disconnected` and **Sign-in expired** when `expired`.
|
|
461
462
|
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)”). A field with `vaultSetting` fills that vault setting: the first save puts the key with `settings` holding every such field that has a value (a `customBase` field only when its value is a bare name, such as an Azure resource; an `https:` one goes through `endpoints` instead), and once the key's record is live and the vault port has `settings` (and `put`), the key may be left empty, so **Connect** waits only for the required fields and an edit sends the full object through `vault.settings(name, 'model', settings)` before the endpoint change and `connect`. A refusal shows on the field it names (its `vaultSetting`, name or label), else on the key or the form, and stops before `connect`; when a keyless edit fails after its `settings` call, the record's previous settings are put back. Records saved without settings keep working until they're saved again.
|
|
462
|
-
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.
|
|
463
|
+
With `vault.enter` and `valueFrame`, a `store: 'vault'` account's key form (`form.key[data-store=vault]`) has no key field: **Next** (`data-action="enter-key"`, or Enter in a field) checks its fields (and confirms an MCP server's URL), then opens `vault.enter({ name, kind: 'model', provider, settings })` (or `{ name, kind: 'mcp', server }`), with `replace: true` when the record exists, so the vault checks the settings there, and shows the vault's own field (`[data-entry] slicc-vault-value`) with **Back** (`data-action="back-key"`); the frame's `done` registers the endpoints and connects as below, and a refusal at `enter` shows on the field it names. An account without fields opens the field at once, and Back then leaves the form. A kept key whose settings can change alone also offers **Save settings** (`data-action="save"`), which sends only `settings`. While the vault is away, the form says "Waiting for the vault" and Next stays disabled; with `enter` it never shows a key input, and an open field stays. Leaving the form, a change of vault and removing the element cancel the open request. Without `enter`, 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.
|
|
463
464
|
Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before.
|
|
464
465
|
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.
|
|
465
466
|
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]`).
|
|
@@ -482,10 +483,10 @@ The head shows **Locked** or **Unlocked**, then, when `status.policy` marks a fi
|
|
|
482
483
|
A status line (`[data-availability]`) says "Waiting for the vault" while `availability()` is `connecting` or `waiting` (or `status()` is `null` without it), and "Unlock the vault to continue" while `scrub.needed` and not `scrub.ready`.
|
|
483
484
|
Waiting requests show under **Waiting for you** (`[data-request]`, the agent's `prompt` as plain text under "From the agent, not checked by SLICC:", capped at 200 characters, and "Expires in N min"), with **Enter value** (`data-action="answer"`, which fires `value-request` with `{ id, vault }` for the `<slicc-value-request>` on the same vault) for `value` requests and **Cancel request** (`data-action="cancel-request"`); `oauth`, `jerk-seal`, `jerk` and `unseal` requests say what they ask for and offer Cancel request only.
|
|
484
485
|
With `backupReminder: 'due'`, an alert (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`).
|
|
485
|
-
**Add secret** (`data-action="add-secret"`, `[data-form=add]`, a name and a value; a name that exists asks before it replaces the value), **Import…** (`data-action="import"`, `[data-form=import]`: Bundle, KeePass or six, a file, an optional password, then a pick of candidates, all checked but the `refused` ones (disabled, with their reason), marked "Replaces a secret" when they exist, with their suggested rules as untrusted text and a note that the file's rules aren't applied, and **Import N**, which sends `{ name, kind }` for a candidate of another `kind`; then "Imported N" and "Skipped NAME: reason" for each skipped name, from `reasons`) and **Export…** (`data-action="export"`, `[data-form=export]`: a passphrase twice, at least 8 bytes, behind a confirmation, downloaded as `slicc-secrets-<yyyy-mm-dd>.slicc-vault`, with "Last exported" or "Never exported" from `exportedAt`).
|
|
486
|
+
**Add secret** (`data-action="add-secret"`, `[data-form=add]`, a name and a value; a name that exists asks before it replaces the value; with `vault.enter` and `valueFrame`, a name and **Next** (`data-action="enter-value"`), which opens `vault.enter` (with `replace: true` after that question) and shows the vault's own field (`[data-form][data-entry] slicc-vault-value`); **Replace value** opens `vault.enter({ name, kind, provider | server, replace: true })` and that field at once; the frame's `done` closes the form and says it was saved (with the name the form asked for when `done` carries none), and Cancel, Escape, the frame's `escape`, another form and a change of vault cancel the request), **Import…** (`data-action="import"`, `[data-form=import]`: Bundle, KeePass or six, a file, an optional password, then a pick of candidates, all checked but the `refused` ones (disabled, with their reason), marked "Replaces a secret" when they exist, with their suggested rules as untrusted text and a note that the file's rules aren't applied, and **Import N**, which sends `{ name, kind }` for a candidate of another `kind`; then "Imported N" and "Skipped NAME: reason" for each skipped name, from `reasons`) and **Export…** (`data-action="export"`, `[data-form=export]`: a passphrase twice, at least 8 bytes, behind a confirmation, downloaded as `slicc-secrets-<yyyy-mm-dd>.slicc-vault`, with "Last exported" or "Never exported" from `exportedAt`).
|
|
486
487
|
Saved shows "Saved NAME in the vault" (`[data-said]`).
|
|
487
488
|
The secrets, sorted by name (`li[data-secret][data-state]`, with `[data-rules]`, `[data-exits]` only when restricted, `[data-exec]` (any command, the listed commands, every bash) and how often programs were given it today, `[data-source]`, `[data-used]`, `[data-expires]`; a model record or an MCP token no rule names reads “Used for” (`[data-used-for]`, model requests to its provider or requests to its MCP server, by the agent) instead of where it's sent, with its account's state as its light, and shows its Provider (`[data-provider]`, the account's name) or Server (`[data-server]`)) offer **Replace value** (`data-action="replace"`, for `settings`, `process` and `import:*` secrets that aren't sign-ins) and **Remove** (`data-action="remove"`, behind a confirmation, passing the secret's `kind`), and removed ones, under **Removed**, **Restore** (`data-action="restore"`; ENOENT reads "Removed too long ago to restore").
|
|
488
|
-
Locked, every form (Add secret
|
|
489
|
+
Locked, every form (Add secret and Replace value without `vault.enter`, Import, Export; with it, the vault's field unlocks itself) says "Unlock the vault first" (`p[data-locked]`) next to the vault's own small **Unlock** button (`slicc-vault-button[data-action=unlock]`) and disables its submit; once unlocked, the form stays open with what was typed and its submit enables.
|
|
489
490
|
Vault errors show their message, EACCES as "Unlock the vault first", an EACCES with `cause: 'policy-paused'` as "Secrets are paused until the missing policy file is back", and ENOTSUP as "This arrives with a later vault version".
|
|
490
491
|
Each action shows only when the port has its method.
|
|
491
492
|
Then **Rules SLICC refused** (`[data-refused]`), where the rules live (`[data-policy]`) and **Activity** (`ol.audit li[data-seq][data-type]`; an entry with `uid` and `tag` both `null` reads "by you", a `refresh` entry "Refreshed automatically" (with the sign-in it used), an `endpoints` entry (vault-originated, no "by …") one part per field it carries: (1.91.0's `EndpointsEntry` had a required `purged` with `url`; no vault wrote it before 1.13.0, so the shape was corrected rather than kept, with `purged` optional and `host` replacing `url`) `purged` "Removed custom model URLs an agent set, automatically: {provider name} at {host}, …. Only you can set a model URL now, in Settings › Accounts" (the provider's name from `settings.accounts()`, else its id), `adopted` "Kept the MCP servers your tokens use, automatically: {server} at {host}, …", `pinned` "{name} pinned to {host} automatically, on first use" with **Unpin** (`data-action=unpin`, calling the vault port's optional `unpin([name])`; disabled while the vault is locked; a failure shows in the row as `[data-error=unpin]`, in the same words as other vault failures; it's hidden once a newer `unpinned` entry for that name is in the log), and `unpinned` "{name} unpinned from {host}; the next use pins it again", an `mcp` entry "Sent to the MCP server SERVER at DOMAIN" with its request and exit, a `reveal` entry (vault 1.23.0, a secret ask whose plaintext the user allowed) "Revealed to {argv} in {cwd}, allowed in a dialog", (the argv, cwd and "by …" of `materialize` and `reveal` entries, and of a secret's "Revealed to programs…, last by … with {argv}" line, show control characters as chips, as the approval dialog does), which counts as a reveal for **Only reveals to programs** and for the secret's "Revealed to programs…" line, a policy change of `[reveal] agent` "Programs an agent runs may no longer ask for a secret’s plaintext" (or "…may ask for a secret’s plaintext again, in a dialog each time"), a `session` entry (a value the vault handed the kernel for a verified TLS session, vault 1.15.0) "Sent over {HTTP/1.1, SMTP or Postgres} to {domain}:{port}, {trusted by public roots, or trusted by its pinned anchor}, through {exit}" (no trust part when an older entry has no `verifiedBy` or a newer vault sends one spectrum doesn't know; a protocol it doesn't know reads as its raw name), and a policy entry lists its changes, its warning and, when accepted, how to change the file back, except that an entry with `absent` reads "{file} was removed. Secrets are paused until it’s back." and the accepted entry that ends its pause reads "{file} is back. Secrets are running again." ("Secrets stay paused until the other file is back." while the other file is still missing), neither with the way back; **Only reveals to programs**, `data-action="reveals"`, and **Show older**, `data-action="older"`).
|
|
@@ -500,7 +501,7 @@ Places `vault.button()`'s element (the vault's own button frame), posts its `con
|
|
|
500
501
|
|
|
501
502
|
#### `<slicc-vault-value>`
|
|
502
503
|
|
|
503
|
-
Places `vault.valueFrame(request)`'s element (the vault's `value.html` frame) and, once the frame is `ready`, posts `{ type: 'slicc-vault', v: 1, op: 'value', request, config }`, where `config` is a `ValueConfig` (`theme`, `dir`, `lang`, `scale`). It posts again when `config` changes, and mounts a new frame when `vault` or `request` changes. It sets the frame's `color-scheme` to `theme`. From `size` it takes the height, at most 480 px (and at most `45dvh`, beyond which the frame scrolls), and makes the frame its own width, with the `inset` (at most 4 px) as a negative margin above and below only, so the dialog never scrolls sideways. A value that isn't a finite number counts as 0. The element clips the frame to its own box (with room above and below for the largest inset it allows, 4 px, for the focus ring), so a frame can't paint over the command above it or the dialog's buttons. On the frame's `focus` event it moves focus to the control after or before the frame (`next` or `previous`), within its `<dialog>` and wrapping around. `focus()` posts `{ op: 'focus' }`, which focuses the frame's first control
|
|
504
|
+
Places `vault.valueFrame(request)`'s element (the vault's `value.html` frame) and, once the frame is `ready`, posts `{ type: 'slicc-vault', v: 1, op: 'value', request, config }`, where `config` is a `ValueConfig` (`theme`, `dir`, `lang`, `scale`). It posts again when `config` changes, and mounts a new frame when `vault` or `request` changes. It sets the frame's `color-scheme` to `theme`. From `size` it takes the height, at most 480 px (and at most `45dvh`, beyond which the frame scrolls), and makes the frame its own width, with the `inset` (at most 4 px) as a negative margin above and below only, so the dialog never scrolls sideways. A value that isn't a finite number counts as 0. The element clips the frame to its own box (with room above and below for the largest inset it allows, 4 px, for the focus ring), so a frame can't paint over the command above it or the dialog's buttons. On the frame's `focus` event it moves focus to the control after or before the frame (`next` or `previous`), within its `<dialog>` and wrapping around. `focus()` posts `{ op: 'focus' }`, which focuses the frame's first control, as soon as the frame is `ready`. It fires `vault-value` (bubbles, composed) with each `done`, `gone`, `error` and `escape` event. The detail holds only `event`, `request`, `state`, `code`, `message`, `name`, `placeholder` and `record` (each when it's a string), so anything else the frame sends goes no further.
|
|
504
505
|
|
|
505
506
|
#### `<slicc-value-request>`
|
|
506
507
|
|
|
@@ -585,7 +586,7 @@ On a guest's page (the model's `seat` set), every sprinkle, inline or panel, is
|
|
|
585
586
|
|
|
586
587
|
#### `<slicc-network>`
|
|
587
588
|
|
|
588
|
-
Network: the route and its health in plain words with the bios `detail`, a line on browser automation (`data-browser`: through the SLICC extension, through slicc on this computer (`proxy`, or a linked device on this computer), through a linked device's browser, that slicc on this computer has no browser to control when it carries the traffic and `via` is `null`, or not available with how to get it) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, an **Internet traffic** picker (`data-action="exit"`, in `[data-exit-picker]` with the exit's kind) when the port has `setExit`, the route line saying where traffic goes, and under the picker a warning that traffic waits while the chosen device isn't connected (`data-exit="device"`, `data-held`, with **Send directly**, `data-action="exit-direct"`, which calls `setExit(null)`) or, when `route` isn't `tailnet`, the Tailscale exit node in use (`data-exit="node"`), a **Tailscale** section (`data-tailnet` with the state) when the status has `tailnet`, a **Linked devices** section (`data-links`: `linked`, `connecting`, `reconnecting`, `offline`, `none` or `permission`; each device row has a **Prompting** or **Watching** badge, `data-controlling`, while it controls the agent, and **Allow remote control**, `sp-switch[data-action="remote-control"]`, behind a confirm when turned on, or `data-control="fixed"` for a device on this computer; with `access`, an **Allow** group of **View**, **Control** and **
|
|
589
|
+
Network: the route and its health in plain words with the bios `detail`, a line on browser automation (`data-browser`: through the SLICC extension, through slicc on this computer (`proxy`, or a linked device on this computer), through a linked device's browser, that slicc on this computer has no browser to control when it carries the traffic and `via` is `null`, or not available with how to get it) when the status has `browser`, **Check again** (`data-action="check"`) when the port has `check`, an **Internet traffic** picker (`data-action="exit"`, in `[data-exit-picker]` with the exit's kind) when the port has `setExit`, the route line saying where traffic goes, and under the picker a warning that traffic waits while the chosen device isn't connected (`data-exit="device"`, `data-held`, with **Send directly**, `data-action="exit-direct"`, which calls `setExit(null)`) or, when `route` isn't `tailnet`, the Tailscale exit node in use (`data-exit="node"`), a **Tailscale** section (`data-tailnet` with the state) when the status has `tailnet`, a **Linked devices** section (`data-links`: `linked`, `connecting`, `reconnecting`, `offline`, `none` or `permission`; each device row has a **Prompting** or **Watching** badge, `data-controlling`, while it controls the agent, and **Allow remote control**, `sp-switch[data-action="remote-control"]`, behind a confirm when turned on, or `data-control="fixed"` for a device on this computer; with `access`, an **Allow** group of **View**, **Control**, **Terminal** and **Browser** switches, `data-action="access-view"`, `access-control`, `access-terminal` and `access-browser`, each confirmed when turned on, Terminal's saying it gives a root shell and Browser's that it can drive this browser's tabs as you while this SLICC's own pages and the vault stay out of its reach, and Control disabled while View is off; a device that asks to follow shows **Asks to follow** and **Allow**, `data-action="allow-follow"`; a **Terminal open** badge, `data-terminals`, counts its terminals; with `followUrl`, the join details add **Open on another device** with a QR code and the link, `data-join="follow"` and `data-copy="follow-url"`) when the status has `links`, **Get the whole web** with the Chrome extension link (`data-action="install-extension"`, when `extensionUrl` is set) and the commands to run slicc, each in a `.command[data-command]` block with a copy button: `curl -fsSL https://get.sliccy.ai/slicc | sh` (its button is `data-action="copy-command"`), `slicc`, and, with Node, `npx sliccy@next`; folded away when the route already reaches the whole web (`proxy`, `extension`, `link` or `tailnet`, whose line names `tailnet.exitNode`) or a linked device is the exit (`exit.kind` `link`: the route line says "Through slicc on this computer" for a `local` device, or names a remote device and its `policy`, or says it's waiting for it, and `data-route` is `link`, whatever `route` says), and recent failures with host, error and time.
|
|
589
590
|
When the model has a `tray` port, a **Sync** section (`data-sync` with the connection) follows the connection: the tray's state, the float's name, role and runtime, the join link, masked (`…/join/••••••`) until **Show** (`data-action="reveal-tray-join"`, which turns into **Hide**), with **Copy** (`data-action="copy-tray-join"`), the followers (`li[data-follower]`), and **Disconnect** (`data-action="tray-disconnect"`, confirmed) or **Reconnect** (`data-action="tray-reconnect"`).
|
|
590
591
|
When the status has `leaders`, a **Leaders** section (`section[data-leaders]` with the count) follows Sync, since it's the other direction of the same tray link: Sync is this SLICC's own tray and who follows it, Leaders the SLICCs it follows.
|
|
591
592
|
Each leader is an `li[data-leader]` (the id) with `data-state`: its name, its hub as a quiet second line and a status light with the state in words (Connecting, Reconnecting, Connected, Disconnected, Failed, Unlinked); `failed` adds its `error` as plain text (`[data-leader-error]`, "Couldn’t reach its hub." without one) and **Retry** (`data-action="retry-leader"`), and `unlinked` says "{name} stopped sharing with this SLICC."
|
|
@@ -907,7 +908,7 @@ A rejected call shows its message in the section.
|
|
|
907
908
|
Peers are never named beyond the exit-node list.
|
|
908
909
|
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).
|
|
909
910
|
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`.
|
|
910
|
-
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).
|
|
911
|
+
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?, browser?, fixed? }`: only the keys present get a switch, so a CLI sends `{ control, browser }`; `browser` lets the device drive this browser over CDP; `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).
|
|
911
912
|
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.
|
|
912
913
|
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**.
|
|
913
914
|
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 an `offline` device has a `note` (one that has to be linked again; a note on a device that is still linked, such as an update, leaves it closed); without `pairComputer` its Link stays disabled.
|
|
@@ -919,7 +920,7 @@ A paired device is a `local` `cli` row without `control` or `access`.
|
|
|
919
920
|
An optional `exit` (`NetworkExit`: `null`, `{ kind: 'tailnet', node }` with a Tailscale node id or `'auto'`, or `{ kind: 'link', id }`) is the default exit.
|
|
920
921
|
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.
|
|
921
922
|
Without `setRemoteControl(id, allowed)`, `control` is a line saying whether it's allowed; with it, **Allow remote control** is a switch.
|
|
922
|
-
Without `setAccess(id, 'view' | 'control' | 'terminal', allowed)`, `access` is a line saying what's allowed; with it, the switches call it, and **Allow** on a device that asks calls `setAccess(id, 'view', true)`.
|
|
923
|
+
Without `setAccess(id, 'view' | 'control' | 'terminal' | 'browser', allowed)`, `access` is a line saying what's allowed; with it, the switches call it, and **Allow** on a device that asks calls `setAccess(id, 'view', true)`.
|
|
923
924
|
Turning View off should turn Control off too.
|
|
924
925
|
`unlink(id)` adds **Unlink** (`data-action="unlink"`) to each device and `rotateJoinUrl()` adds **New join URL** (`data-action="rotate-join-url"`), both behind a confirm.
|
|
925
926
|
Their failures show in the section.
|
|
@@ -1014,11 +1015,13 @@ The vault's view for SLICC, typed after `@ai-ecoverse/slicc-vault` (spectrum re-
|
|
|
1014
1015
|
Emit `status` and `list` when they change (bios re-lists after the vault's `changed` events), and `availability` when the optional `availability()` (`connecting`, `waiting`, `locked` or `ready`) changes.
|
|
1015
1016
|
`audit(before?, limit?)` resolves with an older `AuditPage` (`before` is a `seq`).
|
|
1016
1017
|
The optional page ops, each hiding its control when missing: `put(name, value, request?)` (a value request's `put` must use its `name`), `cancel(request)`, `remove(name, kind?)`, `restore(name, kind?)`, `import(kind, bytes, { password?, pick? })` (a `pick` stage, then `done`), `export(passphrase)` (the bundle's bytes), `lock()`, `dismissBackupReminder()` and `endpoints({ add?, remove? })`, which registers or takes back the URLs a page-sealed credential may be filled on (each an `EndpointSet`, `{ model?: [{ name, url }], mcp?: [{ server, url }] }`, a model URL bound to one record by name), checks the whole change first (`EINVAL` changes nothing) and rejects with a sentence. Settings registers them itself, never through the agent. `settings(name, kind, settings)` replaces a record's tenant settings as a whole without touching its key (the vault's `put` without a value); `put`'s options take the same `settings` on a first save.
|
|
1017
|
-
They reject with a `VaultFailure`, an `Error` with an `ErrorCode` `code` (`isVaultFailure` from `/ui`) and an optional `ErrorCause` `cause`, matching slicc-vault 1.22.0’s stable refusal causes (checked against the pinned 1.
|
|
1018
|
+
They reject with a `VaultFailure`, an `Error` with an `ErrorCode` `code` (`isVaultFailure` from `/ui`) and an optional `ErrorCause` `cause`, matching slicc-vault 1.22.0’s stable refusal causes (checked against the pinned 1.24.0, whose ask and entry types `compat/vault.ts` also checks: `Requester`, `ValueConfig`, `ValueEvent`, `EnterResult` and, the other way, `ValueMessage` and `EnterOptions`); `isPolicyPaused` still identifies only `policy-paused`.
|
|
1018
1019
|
Values go to the vault only through `put`, and never through another port.
|
|
1019
1020
|
`button()` returns a new `VaultButton`, `{ element, post(message), listen(fn) }` returning an unsubscribe: bios wraps the vault's `button.html` frame and its postMessage protocol, and spectrum only sends `ButtonMessage`s and receives `ButtonEvent`s.
|
|
1020
1021
|
The passkey lock (unlock and protect) happens in the vault's own popup, which only a click in that frame can open.
|
|
1021
|
-
|
|
1022
|
+
Each surface acts on a `vault-value` event only when it comes from the field of the request still open there (and, when it names one, for that request), so a late or stale `done`, `gone`, `error` or `escape` changes nothing.
|
|
1023
|
+
The optional `enter(options)` opens a page-started value request (`EnterOptions`: `name`, optional `kind` (`secret`, `model` or `mcp`), `provider`, `server`, `settings` and `replace`) and resolves with `{ request }`; the vault checks the name and binding there, refuses an existing live record without `replace` (`EEXIST`), takes the value only in `valueFrame(request)`, and ends it with `cancel(request)` or after 5 minutes. Spectrum then never holds a value and calls `put` with one only without `enter`.
|
|
1024
|
+
The optional `valueFrame(request)` returns a new `VaultValueFrame` for an `ask` or `enter` request, `{ element, post(message), listen(fn) }` like `button()`. bios wraps the vault's `value.html` frame. spectrum sends only `ValueMessage`s (`op: 'value'` with the request and a `ValueConfig`, and `op: 'focus'`) and receives only `ValueEvent`s (`ready`, `size`, `focus`, `done { request, state: 'sealed', name?, placeholder?, record? }` or `done { request, state: 'allowed' }`, `gone { request, state }`, `error { code, message }`, `escape`). The value is typed in that frame, and a reveal is allowed only by a click there, so the page never holds the value or the consent.
|
|
1022
1025
|
An `ask` request in `status.waiting` also carries `mode`, `reveal` and `requester` (`{ kind, name?, where?, exe, argv, cwd, tty, stdin }`). Settings › Secrets lists it with Cancel request only, and `RequestState` includes `withdrawn`.
|
|
1023
1026
|
`notices`, `acknowledge` and the `notice` event are still typed, and ignored.
|
|
1024
1027
|
|
|
@@ -1136,7 +1139,7 @@ Like slicc-agent's snapshot, the dummy's vitals are the budget, Spend, Active ag
|
|
|
1136
1139
|
Kitchen-sink's "Here is where things stand." ends with a `media` call that waited for a click in seven's tab (`k-media`).
|
|
1137
1140
|
Decisions are recorded in `model.approvals.decisions`, and `model.approvals.end(id, reason)` ends a request.
|
|
1138
1141
|
|
|
1139
|
-
`createDummyModel({ vault: 'empty' | 'secrets' | 'policy' | 'locked' | 'paused' | 'request' | 'backup' | 'waiting' | 'purged' | 'session' | '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; the secrets with `/etc/slicc/secrets-policy.toml` missing and an earlier pause of `/etc/fnox/config.toml` in the log; 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); Cloudflare's Account ID and AI Gateway ID and Azure's endpoint carry `vaultSetting`s; the dummy vault keeps `settings` on its records, logs each `settings` call in `model.vault.settingsCalls`, and refuses a setting whose value starts with `bad` (“The vault refused {KEY}. Check that value and try again.”). `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`.
|
|
1142
|
+
`createDummyModel({ vault: 'empty' | 'secrets' | 'policy' | 'locked' | 'paused' | 'request' | 'backup' | 'waiting' | 'purged' | 'session' | 'endpoints-fail' | 'off' })` (`?vault=` on `/ui/`, off by default; `notice` is kept as another name for `policy`; `entry: 'frame'` (`?entry=frame`) gives it `enter`, so page entry goes through its stand-in value frame, and the default `entry: 'input'` leaves `enter` out, as an older bios does) 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; the secrets with `/etc/slicc/secrets-policy.toml` missing and an earlier pause of `/etc/fnox/config.toml` in the log; 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); Cloudflare's Account ID and AI Gateway ID and Azure's endpoint carry `vaultSetting`s; the dummy vault keeps `settings` on its records, logs each `settings` call in `model.vault.settingsCalls`, and refuses a setting whose value starts with `bad` (“The vault refused {KEY}. Check that value and try again.”). `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`.
|
|
1140
1143
|
`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. `session` is `secrets` plus three `session` audit entries (`vaultSessions`, exported): SMTP trusted by public roots, Postgres trusted by a pinned anchor through pier-nuc, and HTTP/1.1 with no `verifiedBy`.
|
|
1141
1144
|
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.
|
|
1142
1145
|
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.
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -4708,7 +4708,7 @@ var followers = [
|
|
|
4708
4708
|
exit: true,
|
|
4709
4709
|
state: "connected",
|
|
4710
4710
|
kind: "cli",
|
|
4711
|
-
access: { control: true, fixed: true }
|
|
4711
|
+
access: { control: true, browser: true, fixed: true }
|
|
4712
4712
|
},
|
|
4713
4713
|
{
|
|
4714
4714
|
id: "link-pier",
|
|
@@ -4720,9 +4720,21 @@ var followers = [
|
|
|
4720
4720
|
exit: true,
|
|
4721
4721
|
state: "connected",
|
|
4722
4722
|
kind: "cli",
|
|
4723
|
-
access: { control: true },
|
|
4723
|
+
access: { control: true, browser: true },
|
|
4724
4724
|
controlling: "prompt"
|
|
4725
4725
|
},
|
|
4726
|
+
{
|
|
4727
|
+
id: "link-dock",
|
|
4728
|
+
name: "slicc on dock-builder",
|
|
4729
|
+
host: "dock-builder.slicc.internal",
|
|
4730
|
+
address: "198.18.57.13",
|
|
4731
|
+
mode: "remote",
|
|
4732
|
+
offers: ["http"],
|
|
4733
|
+
exit: false,
|
|
4734
|
+
state: "connected",
|
|
4735
|
+
kind: "cli",
|
|
4736
|
+
access: { control: false, browser: false }
|
|
4737
|
+
},
|
|
4726
4738
|
{
|
|
4727
4739
|
id: "link-bay",
|
|
4728
4740
|
name: "SLICC on bay-laptop",
|
|
@@ -7099,9 +7111,20 @@ var DummyValueFrame = class {
|
|
|
7099
7111
|
problem.textContent = "";
|
|
7100
7112
|
this.#finished = true;
|
|
7101
7113
|
try {
|
|
7102
|
-
if (reveal)
|
|
7103
|
-
|
|
7104
|
-
|
|
7114
|
+
if (reveal) {
|
|
7115
|
+
await this.#vault.allow(this.request);
|
|
7116
|
+
this.#emit({ event: "done", request: this.request, state: "allowed" });
|
|
7117
|
+
} else {
|
|
7118
|
+
const { name: name2, placeholder, state } = await this.#vault.seal(this.request, value);
|
|
7119
|
+
this.#emit({
|
|
7120
|
+
event: "done",
|
|
7121
|
+
request: this.request,
|
|
7122
|
+
state: "sealed",
|
|
7123
|
+
name: name2,
|
|
7124
|
+
placeholder,
|
|
7125
|
+
record: state
|
|
7126
|
+
});
|
|
7127
|
+
}
|
|
7105
7128
|
} catch (error) {
|
|
7106
7129
|
this.#finished = false;
|
|
7107
7130
|
const { code, message } = error;
|
|
@@ -7120,6 +7143,8 @@ var DummyVault = class extends Emitter {
|
|
|
7120
7143
|
#serial = 0;
|
|
7121
7144
|
buttons = [];
|
|
7122
7145
|
frames = [];
|
|
7146
|
+
entered = [];
|
|
7147
|
+
#entries = /* @__PURE__ */ new Map();
|
|
7123
7148
|
answered = /* @__PURE__ */ new Set();
|
|
7124
7149
|
revealed = [];
|
|
7125
7150
|
puts = [];
|
|
@@ -7174,6 +7199,10 @@ var DummyVault = class extends Emitter {
|
|
|
7174
7199
|
if (this.#available === "waiting") return "waiting";
|
|
7175
7200
|
return this.#status.locked ? "locked" : "ready";
|
|
7176
7201
|
}
|
|
7202
|
+
wait() {
|
|
7203
|
+
this.#available = "waiting";
|
|
7204
|
+
this.emit("availability", this.availability());
|
|
7205
|
+
}
|
|
7177
7206
|
arrive() {
|
|
7178
7207
|
this.#available = "ready";
|
|
7179
7208
|
this.emit("availability", this.availability());
|
|
@@ -7309,7 +7338,7 @@ var DummyVault = class extends Emitter {
|
|
|
7309
7338
|
async cancel(request) {
|
|
7310
7339
|
await this.#time.sleep(5);
|
|
7311
7340
|
this.#failed();
|
|
7312
|
-
this.#take(request);
|
|
7341
|
+
if (!this.#ended(request)) this.#take(request);
|
|
7313
7342
|
this.cancelled.push(request);
|
|
7314
7343
|
return "cancelled";
|
|
7315
7344
|
}
|
|
@@ -7409,7 +7438,44 @@ var DummyVault = class extends Emitter {
|
|
|
7409
7438
|
this.emit("status", this.#status);
|
|
7410
7439
|
}
|
|
7411
7440
|
ask(request) {
|
|
7412
|
-
return this.#status.waiting.find(({ id, kind }) => id === request && kind === "ask");
|
|
7441
|
+
return this.#entries.get(request)?.pending ?? this.#status.waiting.find(({ id, kind }) => id === request && kind === "ask");
|
|
7442
|
+
}
|
|
7443
|
+
enter = (options) => this.#open(options);
|
|
7444
|
+
async #open(options) {
|
|
7445
|
+
await this.#time.sleep(5);
|
|
7446
|
+
this.#failed();
|
|
7447
|
+
const { name: name2, kind = "secret", replace } = options;
|
|
7448
|
+
const bad = nameProblem(name2);
|
|
7449
|
+
if (bad) throw vaultFailure("EINVAL", bad);
|
|
7450
|
+
const live = this.#list.secrets.some(
|
|
7451
|
+
(secret) => secret.name === name2 && secret.kind === kind && secret.state !== "removed"
|
|
7452
|
+
);
|
|
7453
|
+
if (live && !replace)
|
|
7454
|
+
throw vaultFailure("EEXIST", `${name2} already holds a value; replace it instead`);
|
|
7455
|
+
if (options.settings) refused(options.settings);
|
|
7456
|
+
this.#serial += 1;
|
|
7457
|
+
const request = `enter-${this.#serial}`;
|
|
7458
|
+
const now2 = Date.now();
|
|
7459
|
+
this.entered.push({ ...options });
|
|
7460
|
+
this.#entries.set(request, {
|
|
7461
|
+
options,
|
|
7462
|
+
pending: {
|
|
7463
|
+
id: request,
|
|
7464
|
+
kind: "ask",
|
|
7465
|
+
name: name2,
|
|
7466
|
+
mode: "enter",
|
|
7467
|
+
uid: 0,
|
|
7468
|
+
tag: null,
|
|
7469
|
+
createdAt: now2,
|
|
7470
|
+
expiresAt: now2 + 5 * minute3
|
|
7471
|
+
}
|
|
7472
|
+
});
|
|
7473
|
+
return { request };
|
|
7474
|
+
}
|
|
7475
|
+
#ended(request) {
|
|
7476
|
+
if (!this.#entries.delete(request)) return false;
|
|
7477
|
+
this.emit("status", this.#status);
|
|
7478
|
+
return true;
|
|
7413
7479
|
}
|
|
7414
7480
|
#asked(request) {
|
|
7415
7481
|
this.#unlocked();
|
|
@@ -7423,8 +7489,18 @@ var DummyVault = class extends Emitter {
|
|
|
7423
7489
|
const problem = valueProblem(value);
|
|
7424
7490
|
if (problem) throw vaultFailure("EINVAL", problem);
|
|
7425
7491
|
this.answered.add(request);
|
|
7426
|
-
this.#
|
|
7427
|
-
|
|
7492
|
+
const entry = this.#entries.get(request);
|
|
7493
|
+
if (!entry) {
|
|
7494
|
+
this.#take(request);
|
|
7495
|
+
return this.#seal(found.name, "settings");
|
|
7496
|
+
}
|
|
7497
|
+
this.#ended(request);
|
|
7498
|
+
const { kind = "secret", provider, server, settings } = entry.options;
|
|
7499
|
+
return this.#seal(found.name, "settings", kind, {
|
|
7500
|
+
...provider === void 0 ? {} : { provider },
|
|
7501
|
+
...server === void 0 ? {} : { server },
|
|
7502
|
+
...settings === void 0 ? {} : { settings: { ...settings } }
|
|
7503
|
+
});
|
|
7428
7504
|
}
|
|
7429
7505
|
async allow(request) {
|
|
7430
7506
|
await this.#time.sleep(5);
|
|
@@ -7506,6 +7582,12 @@ function vaultRecords(accounts2, mcp) {
|
|
|
7506
7582
|
if (accounts2 === "settings") return [];
|
|
7507
7583
|
return accountRecords(Date.now(), mcp === "moved" ? docsUrl : void 0);
|
|
7508
7584
|
}
|
|
7585
|
+
function dummyVault(vault, entry, time, records) {
|
|
7586
|
+
if (vault === "off") return void 0;
|
|
7587
|
+
const port = new DummyVault(vault, time, records);
|
|
7588
|
+
if (entry === "input") port.enter = void 0;
|
|
7589
|
+
return port;
|
|
7590
|
+
}
|
|
7509
7591
|
function guestsPort(guests, time) {
|
|
7510
7592
|
return guests === "off" ? {} : { guests: new DummyGuests(guests, time) };
|
|
7511
7593
|
}
|
|
@@ -7546,6 +7628,7 @@ function createDummyModel({
|
|
|
7546
7628
|
switches = "off",
|
|
7547
7629
|
follow = "off",
|
|
7548
7630
|
vault = "off",
|
|
7631
|
+
entry = "input",
|
|
7549
7632
|
guests = "off",
|
|
7550
7633
|
accounts: accounts2 = "settings",
|
|
7551
7634
|
providers = "connected",
|
|
@@ -7589,7 +7672,7 @@ function createDummyModel({
|
|
|
7589
7672
|
if (older === "paged") agent.paginate(6, 4);
|
|
7590
7673
|
if (hold !== "off") agent.setHold({ reason: hold, held: 2 });
|
|
7591
7674
|
const noticePort = noticesPort(agent, time, notices, readonly, arrival);
|
|
7592
|
-
const vaultPort =
|
|
7675
|
+
const vaultPort = dummyVault(vault, entry, time, vaultRecords(accounts2, mcp));
|
|
7593
7676
|
if (scenario === "driving") void browser.run(drive);
|
|
7594
7677
|
const base = {
|
|
7595
7678
|
agent,
|