@ai-ecoverse/slicc-spectrum 1.108.0 → 1.108.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
@@ -438,7 +438,7 @@ Disconnect and Remove delete its key record first, as for any vault-backed key,
438
438
  Every other account calls `connect` without `options`, as before.
439
439
  Accounts with `group: 'mcp'` (MCP servers) get their own **MCP servers** heading (`.accounts[data-group=mcp]`).
440
440
  With `settings.addServer` the heading shows even with no servers, and **Add server** (`data-action="add-server"`) opens a form (`form[data-form=add-server]`) with Name and URL (`input[data-server-field=name|url]`), **Uses a token** (`sp-switch[data-server-field=token]`, with its help line `[data-token-help]` only while it's on), **Add** (`data-action="save"`) and Cancel (`data-action="cancel"`). The page checks only the obvious, on the field (`[data-problem=name|url]`, focused): an empty field, a name outside letters, digits, `_` and `-`, a URL that isn't `https:`. Then it calls `addServer(name, { url })`, or `{ url, token: true }`: Add is pending and Cancel disabled until the answer, a refusal shows in the form and keeps what was typed (`code: 'url-secret'` on the URL field, invalid and focused, as “This URL contains a secret. Remove it, turn on Uses a token, and paste the token when you connect.”; any other `code`, or none, as `reason` with a capital first letter in `[data-problem=form]`, and a rejection there in `failureText`'s words), and `{ ok: true }` closes and resets it. The row comes with the `accounts` event, never from the answer.
441
- With `settings.removeServer` every MCP row adds **Remove** (`data-action="remove-server"`, a quiet delete icon named “Remove {server}”), which asks “Remove {server}?” (“Its token is deleted from the vault. Agents can’t use it any more.”, “Its sign-in is deleted…” for OAuth, and only “Agents can’t use it any more.” when the vault holds no record for it, whatever the row's `auth`) with a destructive **Remove** and Cancel focused. It removes the row's vault records (`MCP_<SERVER>_TOKEN` on a token row, plus any live `mcp` record of that server, which a disabled or refused row can still hold), takes back the page's registration or, with none, the URL of a token it held and that isn't a pin, drops the `slicc-spectrum.mcp-endpoints` entry, then calls `removeServer(id)`. The row reads **Removing…** with its buttons disabled until an `accounts` event no longer lists it; `{ ok: false, reason }` (with a capital first letter), a rejection or a vault failure shows in the row (`.failure`) and keeps it. There is no edit: remove and add again.
441
+ With `settings.removeServer` every MCP row adds **Remove** (`data-action="remove-server"`, a quiet delete icon named “Remove {server}”), which asks “Remove {server}?” (“Its token is deleted from the vault. Agents can’t use it any more.”, “Its sign-in is deleted…” for OAuth, and only “Agents can’t use it any more.” when the vault holds no record for it, whatever the row's `auth`) with a destructive **Remove** and Cancel focused. It calls `removeServer(id)` first, since the agent decides: `{ ok: false, reason }` (with a capital first letter) or a rejection shows in the row (`.failure`), keeps it, and leaves the vault, the registration and the `slicc-spectrum.mcp-endpoints` entry as they were. Only after `{ ok: true }` does it remove the row's vault records (`MCP_<SERVER>_TOKEN` on a token row, plus any live `mcp` record of that server, which a disabled or refused row can still hold), take back the page's registration or, with none, the URL of a token it held and that isn't a pin, and drop the `slicc-spectrum.mcp-endpoints` entry; if the vault refuses to take the URL back, the entry keeps that URL, so removing the server again after adding it takes it back. It tries every step, using what the row held when Remove was confirmed, and a failure shows below the rows (`[data-failure=servers]`), because the row is about to go. The line names what stayed, with “sign-in” for “token” on an OAuth row: “{server} is removed, but its token stayed in the vault.” and “Open Secrets to remove it.”, or “Unlock the vault, then remove it under Secrets.” when the vault was locked; a URL reads “the vault kept its URL”, both read “its token and URL stayed in the vault”, and either of those ends “Add the server and remove it again.”, after “Unlock the vault, then” when locked, since Secrets can't remove a URL. The row reads **Removing…** with its buttons disabled until an `accounts` event no longer lists it. There is no edit: remove and add again.
442
442
  `Account.state` shows on MCP rows in words, after **Token rejected** and **Sign-in expired**: `connecting` **Connecting…**, `needs-secret` **Needs a token**, `needs-auth` **Needs sign-in**, `failed` **Couldn’t connect**, `disabled` **Disabled**, `refused` **Refused**, `connected` as `status`, and rows with no `state` as before. A row with `auth: 'local'`, `state: 'disabled'` or `state: 'refused'` has no sign-in, so it offers neither Connect nor Disconnect nor the moved note, whatever its `status`; a token or OAuth row that failed keeps Connect, which is how a new token gets in. A refused row shows its reason from `settings.refusals()` with a capital first letter (`[data-refusal]`), read again on every `accounts` event while a row is refused, or “This entry was refused. Remove it and add it again here.” when the method is missing, fails or has no entry for it, in the host's place, after the host when it has one.
443
443
  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**.
444
444
  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`.
@@ -791,7 +791,7 @@ Theme, model, thinking level, composer and diff preferences, the models on offer
791
791
  A model can say it is its provider's `default`.
792
792
  Emit `settings` or `accounts` when the models on offer change.
793
793
  The optional `providers()` returns the `ProviderOption`s that can be added (`{ id, name, description?, auth: 'api-key' | 'oauth' | 'fields' | 'local', fields?, keyUrl?, needs?, blocked?, vaultName? }`, sorted by name, added ones included), with a `providers` event when they change; `id` is pi-ai's provider id and becomes the `Account.id`. With it, `accounts()` lists only added accounts, `connect(id, secret?, fields?)` adds one (`group: 'providers'`; a `fields` provider becomes an `api-key` account with `store: 'vault'` and only its non-secret `fields`), and the optional `remove(id)` drops one. `Account` adds `keyUrl`, `description`, `vaultName`, the vault record for its key (else pi-ai's environment-variable name), and `url`, an `mcp:<server>` account's server URL. `needs: 'cors-free-transport'` on either is static (the provider doesn't answer a browser); `blocked: 'cors-free-transport'` is live, set while the current transport can't reach it, and cleared by a `providers` and an `accounts` event when an extension or a linked slicc appears.
794
- The optional `addServer(name, entry)` and `removeServer(id)` exist only on the owner's connection and answer `McpEdit` (`{ ok: true }` or `{ ok: false, code?, reason }`, an answer and not a rejection, with nothing written; `code` is an `McpRefusalCode`); `name` is the `mcp.json` key, `entry` is `{ url, token? }` plus any `mcp.json` field, `id` is the row's `mcp:<server>`, and the row changes with the `accounts` event once the file watch reconciles. `removeServer` forgets the server's local OAuth state only; the vault records and the page's registration are Settings' to remove first. The optional owner-only `refusals()` answers `{ [server]: { code, reason } }` for the refused entries, which aren't on the replicated rows. `Account.state` (`McpState`: `connecting`, `connected`, `needs-secret`, `needs-auth`, `failed`, `disabled` or `refused`) says how an MCP server is doing, and `auth: 'local'` on one means it has no sign-in; `identity` is its host (`''` for a refused entry with no URL), and `url` has no user, password, query or fragment.
794
+ The optional `addServer(name, entry)` and `removeServer(id)` exist only on the owner's connection and answer `McpEdit` (`{ ok: true }` or `{ ok: false, code?, reason }`, an answer and not a rejection, with nothing written; `code` is an `McpRefusalCode`); `name` is the `mcp.json` key, `entry` is `{ url, token? }` plus any `mcp.json` field, `id` is the row's `mcp:<server>`, and the row changes with the `accounts` event once the file watch reconciles. `removeServer` forgets the server's local OAuth state only; the vault records and the page's registration are Settings' to remove once it answers `{ ok: true }`. The optional owner-only `refusals()` answers `{ [server]: { code, reason } }` for the refused entries, which aren't on the replicated rows. `Account.state` (`McpState`: `connecting`, `connected`, `needs-secret`, `needs-auth`, `failed`, `disabled` or `refused`) says how an MCP server is doing, and `auth: 'local'` on one means it has no sign-in; `identity` is its host (`''` for a refused entry with no URL), and `url` has no user, password, query or fragment.
795
795
  `Account.signIn` (`{ url, code? }`) is what a `signing-in` account's sign-in page and device code are, and `connect` takes an optional fourth `options` (`{ method: 'sign-in' | 'key' }`) for an account that signs in or takes a key (`auth: 'oauth'` with `secret: 'api-key'`).
796
796
 
797
797
  #### `changes` (optional)
package/dist/slicc-ui.js CHANGED
@@ -53845,6 +53845,16 @@ var removeBodies = {
53845
53845
  local: "Models from it stop working."
53846
53846
  };
53847
53847
  var serverGone = "Agents can\u2019t use it any more.";
53848
+ var serversFailure = "mcp";
53849
+ function serverLeft(account, left) {
53850
+ const url = left.some(([part2]) => part2 === "url");
53851
+ const record = left.some(([part2]) => part2 === "record");
53852
+ const held2 = account.auth === "oauth" ? "sign-in" : "token";
53853
+ const locked = left.some(([, error]) => isVaultFailure(error) && error.code === "EACCES");
53854
+ const what = !url ? `its ${held2} stayed in the vault` : record ? `its ${held2} and URL stayed in the vault` : "the vault kept its URL";
53855
+ const next = url ? locked ? "Unlock the vault, then add the server and remove it again." : "Add the server and remove it again." : locked ? "Unlock the vault, then remove it under Secrets." : "Open Secrets to remove it.";
53856
+ return `${account.provider} is removed, but ${what}. ${next}`;
53857
+ }
53848
53858
  var refusedEntry = "This entry was refused. Remove it and add it again here.";
53849
53859
  var urlSecret = "This URL contains a secret. Remove it, turn on Uses a token, and paste the token when you connect.";
53850
53860
  var sentence2 = (text4) => text4.charAt(0).toUpperCase() + text4.slice(1);
@@ -54339,6 +54349,7 @@ var SliccSettings = class extends ThemedElement {
54339
54349
  queueMicrotask(() => settleModels(model));
54340
54350
  this.removing = /* @__PURE__ */ new Set();
54341
54351
  this.refusals = {};
54352
+ if (this.failure?.id === serversFailure) this.failure = null;
54342
54353
  void this.#readRefusals(model);
54343
54354
  if (model.updates !== this.#developerPort) {
54344
54355
  this.#developerPort = model.updates;
@@ -55163,16 +55174,26 @@ var SliccSettings = class extends ThemedElement {
55163
55174
  const held2 = account.auth === "oauth" ? "sign-in" : "token";
55164
55175
  return `Its ${held2} is deleted from the vault. ${serverGone}`;
55165
55176
  }
55166
- async #clearServer(model, account) {
55177
+ #serverClear(model, account) {
55167
55178
  const vault = model.vault;
55168
55179
  const server = serverOf(account);
55169
55180
  const records = vault?.remove ? this.#serverRecords(account, vault) : [];
55170
55181
  const held2 = !!vault?.remove && this.#heldRecords(account, vault).length > 0;
55171
55182
  const pinned = !!this.#pinned(account);
55172
55183
  const url = registrations2()[server] ?? (held2 && !pinned && this.#mcpUrl(account));
55173
- for (const [name, kind] of records) await gone2(vault, name, kind);
55174
- if (url) await vault?.endpoints?.({ remove: { mcp: [{ server, url }] } });
55175
- register(server, void 0);
55184
+ return async () => {
55185
+ const left = [];
55186
+ for (const [name, kind] of records)
55187
+ await gone2(vault, name, kind).catch((error) => left.push(["record", error]));
55188
+ try {
55189
+ if (url) await vault?.endpoints?.({ remove: { mcp: [{ server, url }] } });
55190
+ register(server, void 0);
55191
+ } catch (error) {
55192
+ register(server, url);
55193
+ left.push(["url", error]);
55194
+ }
55195
+ return left;
55196
+ };
55176
55197
  }
55177
55198
  #markRemoving(id, on2) {
55178
55199
  const rest = [...this.removing].filter((candidate) => candidate !== id);
@@ -55210,10 +55231,13 @@ var SliccSettings = class extends ThemedElement {
55210
55231
  if (this.model === model) this.failure = { id, message };
55211
55232
  };
55212
55233
  try {
55213
- await this.#clearServer(model, account);
55234
+ const clear2 = this.#serverClear(model, account);
55214
55235
  const edit = await model.settings.removeServer?.(id);
55215
- if (!edit.ok) refused3(sentence2(edit.reason));
55216
- else if (this.model === model) this.focusOn("[data-action=add-server]");
55236
+ if (!edit.ok) return refused3(sentence2(edit.reason));
55237
+ const left = await clear2();
55238
+ if (this.model !== model) return;
55239
+ if (left.length) this.failure = { id: serversFailure, message: serverLeft(account, left) };
55240
+ this.focusOn("[data-action=add-server]");
55217
55241
  } catch (error) {
55218
55242
  refused3(failureText(error));
55219
55243
  }
@@ -55317,13 +55341,14 @@ var SliccSettings = class extends ThemedElement {
55317
55341
  }
55318
55342
  #serverList(servers) {
55319
55343
  const add = !!this.model?.settings.addServer;
55320
- if (!(servers.length || add)) return A;
55344
+ const failed2 = this.failure?.id === serversFailure;
55345
+ if (!(servers.length || add || failed2)) return A;
55321
55346
  return b`<h2>MCP servers</h2>
55322
55347
  <div class="accounts" data-group="mcp">${c(
55323
55348
  servers,
55324
55349
  (account) => account.id,
55325
55350
  (account) => this.#account(account)
55326
- )}</div>
55351
+ )}${failed2 ? b`<div class="failure" role="alert" data-failure="servers"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>${this.failure?.message}</span></div>` : A}</div>
55327
55352
  ${add && this.serverOpen ? this.#serverForm() : add ? b`<div class="add"><swc-action-button size="m" quiet data-action="add-server" @click=${() => this.#openServerForm()}><swc-icon-add slot="icon"></swc-icon-add>Add server</swc-action-button></div>` : A}`;
55328
55353
  }
55329
55354
  #notice(row, size4 = "long") {