@ai-ecoverse/slicc-spectrum 1.65.2 → 1.66.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 +2 -2
- package/dist/{chunk-ZS4XIKFR.js → chunk-2PKRU2CL.js} +2 -2
- package/dist/{chunk-ZS4XIKFR.js.map → chunk-2PKRU2CL.js.map} +2 -2
- package/dist/slicc-dummy.js +51 -11
- package/dist/slicc-dummy.js.map +3 -3
- package/dist/slicc-kernel-model.js +1 -1
- package/dist/slicc-ui.js +330 -34
- package/dist/slicc-ui.js.map +4 -4
- package/dist/types/app/records.d.ts +5 -0
- package/dist/types/app/secrets.d.ts +4 -1
- package/dist/types/app/settings.d.ts +17 -4
- package/dist/types/dummy/index.d.ts +4 -2
- package/dist/types/dummy/vault.d.ts +5 -3
- package/dist/types/model/types.d.ts +17 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -148,8 +148,8 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
|
|
|
148
148
|
| `<slicc-diff-panel>` | The diff of one pending change (`path`), unified or split, with accept and revert. A change with `before` and `after` both `null` (a binary file, or text over 1 MB) shows “No diff for this file” and Open the file instead, and keeps Accept and Revert. |
|
|
149
149
|
| `<slicc-terminals>` | Terminal sessions in tabs, each a `<slicc-terminal>` on the session's `TerminalBackend`. Sessions stay alive while hidden; `exit` closes the tab. |
|
|
150
150
|
| `<slicc-browser>` | What the agents' browser control is doing. It opens on the most recently driven tab (`.focused`), or on the tab overview: cards (`li[data-id]`, `data-controlled` while an agent drives the tab, `data-pulse` when another tab starts being driven, without switching to it) with a thumbnail, the agent and a small **Stop <agent>** (`data-action="stop-agent"`), and an address bar to open a URL. The focused tab has **All tabs** (`data-action="all-tabs"`), a tab picker (a list of tabs at 720 px and wider), the URL with **Copy** (`data-action="copy-url"`) and **Open** (`data-action="open-tab"`, activates the tab), who's using it (`.driving[data-driving=agent|user|none]`; a controlled tab without an agent reads **In use** and has no Stop) with **Stop**, a view-only preview (`figure[data-view]`: `live` while watching with a frame under 3 s old, `shot` with "Updated N s ago", `none`) and the recent actions (`li[data-action-id][data-kind][data-status]`, failures with their error inline), folded into **Recent actions (n)** below 480 px with the last one showing. Labels, URLs and errors are text only. With no tabs and `browser.via` `null` in the network status, it says browser control isn't connected (`data-browser="none"`) with the extension link and the slicc-node command. The Browser rail button carries a dot (`data-driven`) while a tab is in use. |
|
|
151
|
-
| `<slicc-settings>` | Theme, the model and thinking level for new cones, composer and diff preferences, and accounts to connect, reconnect or disconnect. An account whose status is `signing-in` (or a non-API-key account while its `connect` is pending) reads “Signing in…”; when the port has `cancel(id)`, its row offers Cancel (`data-action="cancel-sign-in"`), and the rejected `connect` that follows shows no error. Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`). 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**. 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`. With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
|
|
152
|
-
| `<slicc-secrets>` | Settings › Secrets from the model's `vault`. It never shows a value or a placeholder. Value fields are `type=password` with no reveal toggle, are read once on submit and cleared at once (on success, refusal, error, cancel, close and disconnect), and are checked before the vault is called: under 8 UTF-8 bytes, CR, LF or NUL, or containing `fnox_proxy_` are refused with the vault's own wording. Values go only to `vault.put` and are never kept on the element or put in an event. The head shows **Locked** or **Unlocked** and the lock mode; locked, the vault's **Unlock** button (`slicc-vault-button[data-action=unlock]`); unlocked, **Lock** (`data-action="lock"`, with `lock`) and the vault's **Protect with a passkey** (lock `auto`) or **Change protection** button (`data-action="protect"`). 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`. 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. With `backupReminder: 'due'`, an alert (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`). **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`). Saved shows "Saved NAME in the vault" (`[data-said]`). 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]`) 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"). Locked, value forms say "Unlock the vault first" and disable Save. Vault errors show their message, EACCES as "Unlock the vault first" and ENOTSUP as "This arrives with a later vault version". Each action shows only when the port has its method. 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 `mcp` entry "Sent to the MCP server SERVER at DOMAIN" with its request and exit, and a policy entry lists its changes, its warning and, when accepted, how to change the file back; **Only reveals to programs**, `data-action="reveals"`, and **Show older**, `data-action="older"`). The section's `data-vault` is `locked`, `unlocked` or `loading`. A popup the browser blocked shows how to allow it (`[data-popup=blocked]`). Widened-rule notices aren't shown. |
|
|
151
|
+
| `<slicc-settings>` | Theme, the model and thinking level for new cones, composer and diff preferences, and accounts to connect, reconnect or disconnect. An account whose status is `signing-in` (or a non-API-key account while its `connect` is pending) reads “Signing in…”; when the port has `cancel(id)`, its row offers Cancel (`data-action="cancel-sign-in"`), and the rejected `connect` that follows shows no error. Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`). 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**. 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`. An account with `fields` (`{ name, label, required? }`) shows a plain labelled input for each, and their values go to `connect(id, secret?, fields)`. 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. Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before. With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
|
|
152
|
+
| `<slicc-secrets>` | Settings › Secrets from the model's `vault`. It never shows a value or a placeholder. Value fields are `type=password` with no reveal toggle, are read once on submit and cleared at once (on success, refusal, error, cancel, close and disconnect), and are checked before the vault is called: under 8 UTF-8 bytes, CR, LF or NUL, or containing `fnox_proxy_` are refused with the vault's own wording. Values go only to `vault.put` and are never kept on the element or put in an event. The head shows **Locked** or **Unlocked** and the lock mode; locked, the vault's **Unlock** button (`slicc-vault-button[data-action=unlock]`); unlocked, **Lock** (`data-action="lock"`, with `lock`) and the vault's **Protect with a passkey** (lock `auto`) or **Change protection** button (`data-action="protect"`). 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`. 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. With `backupReminder: 'due'`, an alert (`[data-backup]`) offers **Export backup** (`data-action="export"`) and **Not now** (`data-action="dismiss-backup"`, with `dismissBackupReminder`). **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`). Saved shows "Saved NAME in the vault" (`[data-said]`). 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"). Locked, value forms say "Unlock the vault first" and disable Save. Vault errors show their message, EACCES as "Unlock the vault first" and ENOTSUP as "This arrives with a later vault version". Each action shows only when the port has its method. 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 `mcp` entry "Sent to the MCP server SERVER at DOMAIN" with its request and exit, and a policy entry lists its changes, its warning and, when accepted, how to change the file back; **Only reveals to programs**, `data-action="reveals"`, and **Show older**, `data-action="older"`). The section's `data-vault` is `locked`, `unlocked` or `loading`. A popup the browser blocked shows how to allow it (`[data-popup=blocked]`). Widened-rule notices aren't shown. |
|
|
153
153
|
| `<slicc-vault-button>` | Places `vault.button()`'s element (the vault's own button frame), posts its `config` (`ButtonConfig` plus `theme`) once the button is `ready`, takes the reported `size` (with the focus-ring `inset` as negative margin) and `label` (as its title), moves focus to the control before or after it when the frame reports `focus` (`previous` or `next`), and fires `vault-popup` (bubbles, composed) with each `popup` event's `{ intent, state }`. spectrum sends only the `unlock` and `protect` intents. |
|
|
154
154
|
| `<slicc-value-request>` | The vault's waiting `value` requests (`fnox set` on a terminal) as a modal dialog, one at a time, oldest first, with "N more waiting"; `<slicc-app>` mounts it. `dialog[data-request]`: "Enter a value for NAME" (a Name field when the request has no name), who asks, as on the Settings card ("docs-pass asks for NAME", or "A program running as uid N asks for NAME"), the prompt under "From the agent, not checked by SLICC:", a masked value field with the same rules as Settings › Secrets, "Expires in N min", and **Later** (`data-action="later"`, or Escape: closes, the request stays in Settings › Secrets, whose **Enter value** reopens it), **Cancel request** (`data-action="cancel-request"`, with `cancel`) and **Save** (`data-action="save"`, `put(name, value, request.id)`, with `put`). Locked, it shows the vault's Unlock button and disables Save. It closes, clearing the field, when the request leaves `waiting` or while the vault is away. |
|
|
155
155
|
| `<slicc-updates>` | Install / Update: component states, version changes, last checks, download and link counts, action buttons, failures with Retry, and collapsible install logs. Opens at boot until the agent is ready without failures. Background updates stay in the header's update status; new failures reopen the panel, and after a reload into a boot that isn't ready yet such a panel closes once the agent is ready. Every automatic close is saved to the layout. When the port offers packages, an Optional packages section lists them below as compact rows, each with its state, version, download size, requirements, commands, progress, errors and Install log; packages never count in the header or keep the panel open at boot. Packages the user added or pinned with pnpm show up in their own Your packages section below it, with a neutral Pinned light (a hold, not a success) or a positive Added light, their version against seven's, and Unpin or Remove. Also available through View and the rails. |
|
|
@@ -61,7 +61,7 @@ var DummySettings = class extends Emitter {
|
|
|
61
61
|
const account = this.#accounts.find((candidate) => candidate.id === id);
|
|
62
62
|
if (account && account.auth !== "api-key" && !await this.#signIn(account)) return;
|
|
63
63
|
if (account?.auth === "api-key" || !account) await this.#clock.sleep(10);
|
|
64
|
-
if (account?.auth === "api-key" && !secret)
|
|
64
|
+
if (account?.auth === "api-key" && account.store !== "vault" && !secret)
|
|
65
65
|
throw new Error(
|
|
66
66
|
`${account.provider} needs ${account.secret === "token" ? "a token" : "an API key"}`
|
|
67
67
|
);
|
|
@@ -99,4 +99,4 @@ export {
|
|
|
99
99
|
dummySettingsKey,
|
|
100
100
|
DummySettings
|
|
101
101
|
};
|
|
102
|
-
//# sourceMappingURL=chunk-
|
|
102
|
+
//# sourceMappingURL=chunk-2PKRU2CL.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/dummy/clock.ts", "../src/dummy/settings.ts"],
|
|
4
|
-
"sourcesContent": ["export interface Clock {\n delay: number;\n sleep(ticks?: number): Promise<void>;\n}\n\nexport function clock(delay: number): Clock {\n return {\n delay,\n sleep: (ticks = 1) => new Promise((resolve) => setTimeout(resolve, delay * ticks)),\n };\n}\n", "import { Emitter } from '../model/emitter.ts';\nimport type {\n Account,\n AccountStatus,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '../model/types.ts';\nimport type { Clock } from './clock.ts';\n\nexport const dummySettingsKey = 'slicc-ui.settings';\n\nexport class DummySettings extends Emitter<SettingsEvents> implements SettingsPort {\n #settings: Settings;\n #accounts: Account[];\n #models: readonly ModelOption[];\n #storage: Storage | null;\n #clock: Clock;\n #keys: readonly string[];\n #signIns = new Map<string, { before: AccountStatus; stop: () => void }>();\n\n constructor(\n defaults: Settings,\n models: readonly ModelOption[],\n accounts: readonly Account[],\n storage: Storage | null,\n clock: Clock,\n keys: readonly string[] = [dummySettingsKey]\n ) {\n super();\n this.#keys = keys;\n this.#storage = storage;\n this.#clock = clock;\n this.#models = models;\n this.#accounts = accounts.map((account) => ({ ...account }));\n this.#settings = { ...defaults, ...this.#load() };\n }\n\n #load(): Partial<Settings> {\n try {\n const saved = this.#keys\n .map((key) => this.#storage?.getItem(key))\n .find((value) => value != null);\n return JSON.parse(saved ?? '{}');\n } catch {\n return {};\n }\n }\n\n get(): Settings {\n return { ...this.#settings };\n }\n\n update(patch: Partial<Settings>): void {\n this.#settings = { ...this.#settings, ...patch };\n this.#storage?.setItem(this.#keys[0], JSON.stringify(this.#settings));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n return this.#models;\n }\n\n accounts(): readonly Account[] {\n return this.#accounts.map((account) => ({ ...account }));\n }\n\n #set(id: string, patch: Partial<Account>): void {\n this.#accounts = this.#accounts.map((account) =>\n account.id === id ? { ...account, ...patch } : account\n );\n this.emit('accounts', this.accounts());\n }\n\n async connect(id: string, secret?: string): Promise<void> {\n const account = this.#accounts.find((candidate) => candidate.id === id);\n if (account && account.auth !== 'api-key' && !(await this.#signIn(account))) return;\n if (account?.auth === 'api-key' || !account) await this.#clock.sleep(10);\n if (account?.auth === 'api-key' && !secret)\n throw new Error(\n `${account.provider} needs ${account.secret === 'token' ? 'a token' : 'an API key'}`\n );\n const identity =\n account?.group === 'mcp'\n ? account.identity\n : account?.auth === 'api-key'\n ? 'API key'\n : account?.identity || `${id}-user@example.com`;\n this.#set(id, { status: 'connected', identity });\n }\n\n #signIn(account: Account): Promise<boolean> {\n const before = this.#signIns.get(account.id)?.before ?? account.status;\n this.#signIns.get(account.id)?.stop();\n this.#set(account.id, { status: 'signing-in' });\n return new Promise((resolve) => {\n const stop = () => {\n this.#signIns.delete(account.id);\n this.#set(account.id, { status: before });\n resolve(false);\n };\n this.#signIns.set(account.id, { before, stop });\n void this.#clock.sleep(100).then(() => {\n if (this.#signIns.get(account.id)?.stop !== stop) return;\n this.#signIns.delete(account.id);\n resolve(true);\n });\n });\n }\n\n cancel(id: string): void {\n this.#signIns.get(id)?.stop();\n }\n\n disconnect(id: string): void {\n this.#set(id, { status: 'disconnected' });\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;AAKO,SAAS,MAAM,OAAsB;AAC1C,SAAO;AAAA,IACL;AAAA,IACA,OAAO,CAAC,QAAQ,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,QAAQ,KAAK,CAAC;AAAA,EACnF;AACF;;;ACCO,IAAM,mBAAmB;AAEzB,IAAM,gBAAN,cAA4B,QAAgD;AAAA,EACjF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAAyD;AAAA,EAExE,YACE,UACA,QACA,UACA,SACAA,QACA,OAA0B,CAAC,gBAAgB,GAC3C;AACA,UAAM;AACN,SAAK,QAAQ;AACb,SAAK,WAAW;AAChB,SAAK,SAASA;AACd,SAAK,UAAU;AACf,SAAK,YAAY,SAAS,IAAI,CAAC,aAAa,EAAE,GAAG,QAAQ,EAAE;AAC3D,SAAK,YAAY,EAAE,GAAG,UAAU,GAAG,KAAK,MAAM,EAAE;AAAA,EAClD;AAAA,EAEA,QAA2B;AACzB,QAAI;AACF,YAAM,QAAQ,KAAK,MAChB,IAAI,CAAC,QAAQ,KAAK,UAAU,QAAQ,GAAG,CAAC,EACxC,KAAK,CAAC,UAAU,SAAS,IAAI;AAChC,aAAO,KAAK,MAAM,SAAS,IAAI;AAAA,IACjC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAgB;AACd,WAAO,EAAE,GAAG,KAAK,UAAU;AAAA,EAC7B;AAAA,EAEA,OAAO,OAAgC;AACrC,SAAK,YAAY,EAAE,GAAG,KAAK,WAAW,GAAG,MAAM;AAC/C,SAAK,UAAU,QAAQ,KAAK,MAAM,CAAC,GAAG,KAAK,UAAU,KAAK,SAAS,CAAC;AACpE,SAAK,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,EAClC;AAAA,EAEA,SAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAA+B;AAC7B,WAAO,KAAK,UAAU,IAAI,CAAC,aAAa,EAAE,GAAG,QAAQ,EAAE;AAAA,EACzD;AAAA,EAEA,KAAK,IAAY,OAA+B;AAC9C,SAAK,YAAY,KAAK,UAAU;AAAA,MAAI,CAAC,YACnC,QAAQ,OAAO,KAAK,EAAE,GAAG,SAAS,GAAG,MAAM,IAAI;AAAA,IACjD;AACA,SAAK,KAAK,YAAY,KAAK,SAAS,CAAC;AAAA,EACvC;AAAA,EAEA,MAAM,QAAQ,IAAY,QAAgC;AACxD,UAAM,UAAU,KAAK,UAAU,KAAK,CAAC,cAAc,UAAU,OAAO,EAAE;AACtE,QAAI,WAAW,QAAQ,SAAS,aAAa,CAAE,MAAM,KAAK,QAAQ,OAAO,EAAI;AAC7E,QAAI,SAAS,SAAS,aAAa,CAAC,QAAS,OAAM,KAAK,OAAO,MAAM,EAAE;AACvE,QAAI,SAAS,SAAS,aAAa,CAAC;
|
|
4
|
+
"sourcesContent": ["export interface Clock {\n delay: number;\n sleep(ticks?: number): Promise<void>;\n}\n\nexport function clock(delay: number): Clock {\n return {\n delay,\n sleep: (ticks = 1) => new Promise((resolve) => setTimeout(resolve, delay * ticks)),\n };\n}\n", "import { Emitter } from '../model/emitter.ts';\nimport type {\n Account,\n AccountStatus,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '../model/types.ts';\nimport type { Clock } from './clock.ts';\n\nexport const dummySettingsKey = 'slicc-ui.settings';\n\nexport class DummySettings extends Emitter<SettingsEvents> implements SettingsPort {\n #settings: Settings;\n #accounts: Account[];\n #models: readonly ModelOption[];\n #storage: Storage | null;\n #clock: Clock;\n #keys: readonly string[];\n #signIns = new Map<string, { before: AccountStatus; stop: () => void }>();\n\n constructor(\n defaults: Settings,\n models: readonly ModelOption[],\n accounts: readonly Account[],\n storage: Storage | null,\n clock: Clock,\n keys: readonly string[] = [dummySettingsKey]\n ) {\n super();\n this.#keys = keys;\n this.#storage = storage;\n this.#clock = clock;\n this.#models = models;\n this.#accounts = accounts.map((account) => ({ ...account }));\n this.#settings = { ...defaults, ...this.#load() };\n }\n\n #load(): Partial<Settings> {\n try {\n const saved = this.#keys\n .map((key) => this.#storage?.getItem(key))\n .find((value) => value != null);\n return JSON.parse(saved ?? '{}');\n } catch {\n return {};\n }\n }\n\n get(): Settings {\n return { ...this.#settings };\n }\n\n update(patch: Partial<Settings>): void {\n this.#settings = { ...this.#settings, ...patch };\n this.#storage?.setItem(this.#keys[0], JSON.stringify(this.#settings));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n return this.#models;\n }\n\n accounts(): readonly Account[] {\n return this.#accounts.map((account) => ({ ...account }));\n }\n\n #set(id: string, patch: Partial<Account>): void {\n this.#accounts = this.#accounts.map((account) =>\n account.id === id ? { ...account, ...patch } : account\n );\n this.emit('accounts', this.accounts());\n }\n\n async connect(id: string, secret?: string): Promise<void> {\n const account = this.#accounts.find((candidate) => candidate.id === id);\n if (account && account.auth !== 'api-key' && !(await this.#signIn(account))) return;\n if (account?.auth === 'api-key' || !account) await this.#clock.sleep(10);\n if (account?.auth === 'api-key' && account.store !== 'vault' && !secret)\n throw new Error(\n `${account.provider} needs ${account.secret === 'token' ? 'a token' : 'an API key'}`\n );\n const identity =\n account?.group === 'mcp'\n ? account.identity\n : account?.auth === 'api-key'\n ? 'API key'\n : account?.identity || `${id}-user@example.com`;\n this.#set(id, { status: 'connected', identity });\n }\n\n #signIn(account: Account): Promise<boolean> {\n const before = this.#signIns.get(account.id)?.before ?? account.status;\n this.#signIns.get(account.id)?.stop();\n this.#set(account.id, { status: 'signing-in' });\n return new Promise((resolve) => {\n const stop = () => {\n this.#signIns.delete(account.id);\n this.#set(account.id, { status: before });\n resolve(false);\n };\n this.#signIns.set(account.id, { before, stop });\n void this.#clock.sleep(100).then(() => {\n if (this.#signIns.get(account.id)?.stop !== stop) return;\n this.#signIns.delete(account.id);\n resolve(true);\n });\n });\n }\n\n cancel(id: string): void {\n this.#signIns.get(id)?.stop();\n }\n\n disconnect(id: string): void {\n this.#set(id, { status: 'disconnected' });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["clock"]
|
|
7
7
|
}
|
package/dist/slicc-dummy.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
DummySettings,
|
|
8
8
|
clock
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-2PKRU2CL.js";
|
|
10
10
|
import {
|
|
11
11
|
Emitter
|
|
12
12
|
} from "./chunk-T5NHW4KC.js";
|
|
@@ -5592,6 +5592,24 @@ function vaultSecrets(now2 = Date.now()) {
|
|
|
5592
5592
|
}
|
|
5593
5593
|
];
|
|
5594
5594
|
}
|
|
5595
|
+
function accountRecords(now2 = Date.now()) {
|
|
5596
|
+
return [
|
|
5597
|
+
{
|
|
5598
|
+
name: "MCP_DOCS_TOKEN",
|
|
5599
|
+
kind: "mcp",
|
|
5600
|
+
placeholder: "fnox_proxy_docs0006",
|
|
5601
|
+
state: "active",
|
|
5602
|
+
source: "settings",
|
|
5603
|
+
sourceDevice: null,
|
|
5604
|
+
server: "docs",
|
|
5605
|
+
domains: ["docs.example"],
|
|
5606
|
+
rules: [{ domain: "docs.example", header: "Authorization", methods: [], paths: [] }],
|
|
5607
|
+
exec: false,
|
|
5608
|
+
createdAt: now2 - 6 * day2,
|
|
5609
|
+
usedAt: now2 - 20 * minute2
|
|
5610
|
+
}
|
|
5611
|
+
];
|
|
5612
|
+
}
|
|
5595
5613
|
function vaultAudit(now2 = Date.now()) {
|
|
5596
5614
|
const who = { uid: 1e3, tag: "cone" };
|
|
5597
5615
|
return [
|
|
@@ -5944,11 +5962,11 @@ var DummyVault = class extends Emitter {
|
|
|
5944
5962
|
imports = [];
|
|
5945
5963
|
exported = 0;
|
|
5946
5964
|
acknowledged = [];
|
|
5947
|
-
constructor(scenario, time) {
|
|
5965
|
+
constructor(scenario, time, records = []) {
|
|
5948
5966
|
super();
|
|
5949
5967
|
const { status, list } = vaultFixtures(scenario);
|
|
5950
5968
|
this.#status = status;
|
|
5951
|
-
this.#list = list;
|
|
5969
|
+
this.#list = { ...list, secrets: [...list.secrets, ...records] };
|
|
5952
5970
|
this.#time = time;
|
|
5953
5971
|
this.#available = scenario === "waiting" ? "waiting" : "ready";
|
|
5954
5972
|
}
|
|
@@ -6014,18 +6032,23 @@ var DummyVault = class extends Emitter {
|
|
|
6014
6032
|
#save(secret, rotated) {
|
|
6015
6033
|
this.#list = {
|
|
6016
6034
|
...this.#list,
|
|
6017
|
-
secrets: [
|
|
6035
|
+
secrets: [
|
|
6036
|
+
...this.#list.secrets.filter(
|
|
6037
|
+
(item) => item.name !== secret.name || item.kind !== secret.kind
|
|
6038
|
+
),
|
|
6039
|
+
secret
|
|
6040
|
+
]
|
|
6018
6041
|
};
|
|
6019
6042
|
this.#log(secret.name, { type: "seal", source: secret.source, rotated });
|
|
6020
6043
|
this.emit("list", this.#list);
|
|
6021
6044
|
return { name: secret.name, placeholder: secret.placeholder, state: secret.state };
|
|
6022
6045
|
}
|
|
6023
|
-
#seal(name2, source, kind = "secret") {
|
|
6046
|
+
#seal(name2, source, kind = "secret", binding = {}) {
|
|
6024
6047
|
const old = this.#list.secrets.find((item) => item.name === name2 && item.kind === kind);
|
|
6025
6048
|
if (old) {
|
|
6026
6049
|
const state = old.state === "removed" ? old.rules.length ? "active" : "inert" : old.state;
|
|
6027
6050
|
const { removedAt: _, ...kept } = old;
|
|
6028
|
-
return this.#save({ ...kept, state, source }, true);
|
|
6051
|
+
return this.#save({ ...kept, ...binding, state, source }, true);
|
|
6029
6052
|
}
|
|
6030
6053
|
this.#serial += 1;
|
|
6031
6054
|
return this.#save(
|
|
@@ -6036,6 +6059,7 @@ var DummyVault = class extends Emitter {
|
|
|
6036
6059
|
state: "inert",
|
|
6037
6060
|
source,
|
|
6038
6061
|
sourceDevice: null,
|
|
6062
|
+
...binding,
|
|
6039
6063
|
domains: [],
|
|
6040
6064
|
rules: [],
|
|
6041
6065
|
exec: false,
|
|
@@ -6044,14 +6068,22 @@ var DummyVault = class extends Emitter {
|
|
|
6044
6068
|
false
|
|
6045
6069
|
);
|
|
6046
6070
|
}
|
|
6047
|
-
async put(name2, value, request) {
|
|
6071
|
+
async put(name2, value, request, options) {
|
|
6048
6072
|
await this.#time.sleep(5);
|
|
6049
6073
|
this.#unlocked();
|
|
6050
6074
|
const problem = nameProblem(name2) ?? valueProblem(value);
|
|
6051
6075
|
if (problem) throw vaultFailure("EINVAL", problem);
|
|
6052
6076
|
if (request !== void 0) this.#take(request, name2);
|
|
6053
|
-
this.puts.push({
|
|
6054
|
-
|
|
6077
|
+
this.puts.push({
|
|
6078
|
+
name: name2,
|
|
6079
|
+
...request === void 0 ? {} : { request },
|
|
6080
|
+
...options ? { options: { ...options } } : {}
|
|
6081
|
+
});
|
|
6082
|
+
const { kind = "secret", provider, server } = options ?? {};
|
|
6083
|
+
return this.#seal(name2, "settings", kind, {
|
|
6084
|
+
...provider === void 0 ? {} : { provider },
|
|
6085
|
+
...server === void 0 ? {} : { server }
|
|
6086
|
+
});
|
|
6055
6087
|
}
|
|
6056
6088
|
#take(request, name2) {
|
|
6057
6089
|
const found = this.#status.waiting.find((item) => item.id === request);
|
|
@@ -6160,6 +6192,11 @@ var DummyVault = class extends Emitter {
|
|
|
6160
6192
|
};
|
|
6161
6193
|
|
|
6162
6194
|
// src/dummy/index.ts
|
|
6195
|
+
function vaultAccounts(accounts2) {
|
|
6196
|
+
return accounts2.map(
|
|
6197
|
+
(account) => account.auth === "api-key" || account.id === "github" ? { ...account, store: "vault" } : account
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6163
6200
|
function createDummyModel({
|
|
6164
6201
|
delay = 30,
|
|
6165
6202
|
storage = null,
|
|
@@ -6176,6 +6213,7 @@ function createDummyModel({
|
|
|
6176
6213
|
approvals = "off",
|
|
6177
6214
|
follow = "off",
|
|
6178
6215
|
vault = "off",
|
|
6216
|
+
accounts: accounts2 = "settings",
|
|
6179
6217
|
hold = "off",
|
|
6180
6218
|
browser: scenario = "idle",
|
|
6181
6219
|
sprinkles: sprinkles2 = "builtin",
|
|
@@ -6212,7 +6250,7 @@ function createDummyModel({
|
|
|
6212
6250
|
settings: new DummySettings(
|
|
6213
6251
|
defaults,
|
|
6214
6252
|
models,
|
|
6215
|
-
accounts,
|
|
6253
|
+
accounts2 === "vault" ? vaultAccounts(accounts) : accounts,
|
|
6216
6254
|
storage,
|
|
6217
6255
|
time
|
|
6218
6256
|
),
|
|
@@ -6237,7 +6275,9 @@ function createDummyModel({
|
|
|
6237
6275
|
...tabs2 === "off" ? {} : { tabs: new DummyTabs(tabs2, time) },
|
|
6238
6276
|
...approvals === "off" ? {} : { approvals: new DummyApprovals(approvals, time) },
|
|
6239
6277
|
...follow === "off" ? {} : { follow: new DummyFollow(follow, time) },
|
|
6240
|
-
...vault === "off" ? {} : {
|
|
6278
|
+
...vault === "off" ? {} : {
|
|
6279
|
+
vault: new DummyVault(vault, time, accounts2 === "vault" ? accountRecords() : [])
|
|
6280
|
+
}
|
|
6241
6281
|
};
|
|
6242
6282
|
base.vault?.on("status", (status) => {
|
|
6243
6283
|
if (!status.locked && agent.hold()?.reason === "locked") agent.setHold(null);
|