@ai-ecoverse/slicc-spectrum 1.65.1 → 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/dist/slicc-ui.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-EN3XGF2M.js";
25
25
  import {
26
26
  define
27
- } from "./chunk-XI74SELH.js";
27
+ } from "./chunk-ZGJM5WUN.js";
28
28
  import {
29
29
  isVaultFailure,
30
30
  nameProblem,
@@ -13843,13 +13843,13 @@ function resolve(panels, raw2, reachable2, cone) {
13843
13843
  return found?.id ?? null;
13844
13844
  }
13845
13845
  function state(info) {
13846
- const shown3 = info.active ? "active" : info.visible ? "shown" : info.open ? "open" : "closed";
13847
- return info.busy ? `${shown3} \xB7 busy: ${info.busy}` : shown3;
13846
+ const shown4 = info.active ? "active" : info.visible ? "shown" : info.open ? "open" : "closed";
13847
+ return info.busy ? `${shown4} \xB7 busy: ${info.busy}` : shown4;
13848
13848
  }
13849
13849
  function table(rows) {
13850
13850
  const lines = [["ID", "TITLE", "STATE"], ...rows.map((row) => [row.id, row.title, state(row)])];
13851
13851
  const widths = [0, 1].map((column) => Math.max(...lines.map((line) => line[column].length)));
13852
- return lines.map(([id, title, shown3]) => `${id.padEnd(widths[0])} ${title.padEnd(widths[1])} ${shown3}`).join("\n");
13852
+ return lines.map(([id, title, shown4]) => `${id.padEnd(widths[0])} ${title.padEnd(widths[1])} ${shown4}`).join("\n");
13853
13853
  }
13854
13854
  var done = (result, note = "") => result.code === 0 ? { stdout: `${result.message}${note}
13855
13855
  `, stderr: "", code: 0 } : { stdout: "", stderr: `panel: ${result.message}
@@ -14105,6 +14105,17 @@ function settled(element2) {
14105
14105
  requestAnimationFrame(frame);
14106
14106
  });
14107
14107
  }
14108
+ function shown2() {
14109
+ if (!document.hidden) return Promise.resolve();
14110
+ return new Promise((resolve2) => {
14111
+ const seen = () => {
14112
+ if (document.hidden) return;
14113
+ document.removeEventListener("visibilitychange", seen);
14114
+ resolve2();
14115
+ };
14116
+ document.addEventListener("visibilitychange", seen);
14117
+ });
14118
+ }
14108
14119
  var ended = { write() {
14109
14120
  }, resize() {
14110
14121
  }, close() {
@@ -14132,6 +14143,9 @@ var KeptSession = class {
14132
14143
  }) : this.#start(size4);
14133
14144
  return session.then((live) => this.#handle(live, sink));
14134
14145
  }
14146
+ replay(write) {
14147
+ for (const chunk of this.#log) write(chunk);
14148
+ }
14135
14149
  dispose() {
14136
14150
  this.#sink = null;
14137
14151
  this.#log = [];
@@ -14310,9 +14324,12 @@ var SliccTerminalPanel = class extends ThemedElement {
14310
14324
  const backend = keep(model.terminals, info.id);
14311
14325
  terminal.backend = {
14312
14326
  open: async (sink) => {
14313
- await this.#placed(info.id, terminal);
14327
+ const hidden = await this.#placed(info.id, terminal);
14314
14328
  terminal.fit();
14315
- return backend.open(sink, { cols: terminal.cols, rows: terminal.rows });
14329
+ const cols = terminal.cols;
14330
+ const session = await backend.open(sink, { cols, rows: terminal.rows });
14331
+ if (hidden) void this.#relayout(terminal, backend, cols);
14332
+ return session;
14316
14333
  }
14317
14334
  };
14318
14335
  terminal.addEventListener("exit", () => model.terminals.close(info.id));
@@ -14333,7 +14350,25 @@ var SliccTerminalPanel = class extends ThemedElement {
14333
14350
  #placed(id, screen) {
14334
14351
  const host4 = this.getRootNode().host;
14335
14352
  const panel = host4?.localName === "slicc-dock" ? host4.api.getPanel(terminalPanel(id)) : void 0;
14336
- if (!panel) return Promise.resolve();
14353
+ if (!panel) return Promise.resolve(false);
14354
+ if (!document.hidden) return this.#seen(panel, screen).then(() => false);
14355
+ if (laid(screen.getBoundingClientRect())) return Promise.resolve(true);
14356
+ return new Promise((resolve2) => {
14357
+ const done2 = (hidden) => {
14358
+ watch.dispose();
14359
+ document.removeEventListener("visibilitychange", visible2);
14360
+ resolve2(hidden);
14361
+ };
14362
+ const watch = panel.api.onDidDimensionsChange((size4) => {
14363
+ if (laid(size4)) done2(true);
14364
+ });
14365
+ const visible2 = () => {
14366
+ if (!document.hidden) done2(false);
14367
+ };
14368
+ document.addEventListener("visibilitychange", visible2);
14369
+ }).then((hidden) => hidden || this.#seen(panel, screen).then(() => false));
14370
+ }
14371
+ #seen(panel, screen) {
14337
14372
  const sized = laid(panel.api) ? Promise.resolve() : new Promise((resolve2) => {
14338
14373
  const watch = panel.api.onDidDimensionsChange((size4) => {
14339
14374
  if (!laid(size4)) return;
@@ -14343,6 +14378,14 @@ var SliccTerminalPanel = class extends ThemedElement {
14343
14378
  });
14344
14379
  return sized.then(() => settled(screen));
14345
14380
  }
14381
+ async #relayout(screen, backend, cols) {
14382
+ await shown2();
14383
+ await settled(screen);
14384
+ screen.fit();
14385
+ if (screen.cols === cols) return;
14386
+ screen.redraw("\x1Bc");
14387
+ backend.replay((data) => screen.redraw(data));
14388
+ }
14346
14389
  #request() {
14347
14390
  this.dispatchEvent(new CustomEvent("new-terminal", { bubbles: true, composed: true }));
14348
14391
  }
@@ -22010,8 +22053,8 @@ var SliccUpdates = class extends ModelElement {
22010
22053
  #checked(at) {
22011
22054
  if (at === null) return b`<span>Not checked yet</span>`;
22012
22055
  const date = new Date(at);
22013
- const shown3 = date.toLocaleString("en-GB", { dateStyle: "medium", timeStyle: "short" });
22014
- return b`<span>Checked <time datetime=${date.toISOString()}>${shown3}</time></span>`;
22056
+ const shown4 = date.toLocaleString("en-GB", { dateStyle: "medium", timeStyle: "short" });
22057
+ return b`<span>Checked <time datetime=${date.toISOString()}>${shown4}</time></span>`;
22015
22058
  }
22016
22059
  #item(item2) {
22017
22060
  const [variant, state3] = states[item2.state];
@@ -22076,14 +22119,14 @@ var SliccUpdates = class extends ModelElement {
22076
22119
  #package(item2, all2, act2) {
22077
22120
  const light = packageLight(item2);
22078
22121
  const busy = this.busy.get(item2.id);
22079
- const shown3 = busy ? [busy] : act2 ? item2.actions : [];
22122
+ const shown4 = busy ? [busy] : act2 ? item2.actions : [];
22080
22123
  const error = this.problems.get(item2.id) ?? item2.error;
22081
22124
  return b`<li data-id=${item2.id} data-state=${item2.state}>
22082
22125
  <div class="name">
22083
22126
  <h3 title=${item2.package}>${item2.label}</h3>
22084
22127
  ${light ? b`<swc-status-light size="s" variant=${light[0]}>${light[1]}</swc-status-light>` : A}
22085
22128
  </div>
22086
- <div class="buttons">${shown3.map(
22129
+ <div class="buttons">${shown4.map(
22087
22130
  (action) => b`<swc-button
22088
22131
  size="s"
22089
22132
  variant="secondary"
@@ -22839,11 +22882,11 @@ var SliccApp = class extends ThemedElement {
22839
22882
  #rail(items, side) {
22840
22883
  const primary = side === "bottom" ? items.filter((item2) => phoneRail.includes(item2.id)) : items;
22841
22884
  const fit = primary.length < items.length ? Math.min(primary.length, railCapacity(this.#width, Infinity)) : railCapacity(this.#width, items.length);
22842
- const shown3 = side === "bottom" ? primary.slice(0, fit) : items;
22843
- const grouped = new Set(shown3.map((item2) => item2.cone).filter(Boolean)).size > 1;
22844
- return b`<nav class=${`rail ${side}`} aria-label=${`Panels, ${side}`} @dragover=${this.#railOver} @dragleave=${this.#railLeave} @drop=${this.#railDrop}>${shown3.map(
22845
- (item2, index) => b`${grouped && index && shown3[index - 1].cone !== item2.cone ? b`<span class="cones" aria-hidden="true"></span>` : A}${this.#railButton(item2, side)}`
22846
- )}${this.#more(items.filter((item2) => !shown3.includes(item2)))}</nav>`;
22885
+ const shown4 = side === "bottom" ? primary.slice(0, fit) : items;
22886
+ const grouped = new Set(shown4.map((item2) => item2.cone).filter(Boolean)).size > 1;
22887
+ return b`<nav class=${`rail ${side}`} aria-label=${`Panels, ${side}`} @dragover=${this.#railOver} @dragleave=${this.#railLeave} @drop=${this.#railDrop}>${shown4.map(
22888
+ (item2, index) => b`${grouped && index && shown4[index - 1].cone !== item2.cone ? b`<span class="cones" aria-hidden="true"></span>` : A}${this.#railButton(item2, side)}`
22889
+ )}${this.#more(items.filter((item2) => !shown4.includes(item2)))}</nav>`;
22847
22890
  }
22848
22891
  #rails() {
22849
22892
  const dock = this.renderRoot.querySelector("slicc-dock");
@@ -26688,7 +26731,7 @@ var fieldStyles = i`
26688
26731
  outline-offset: var(--swc-focus-ring-gap);
26689
26732
  }
26690
26733
  `;
26691
- function shown2(element2, style = getComputedStyle(element2)) {
26734
+ function shown3(element2, style = getComputedStyle(element2)) {
26692
26735
  return !element2.hidden && !element2.inert && style.display !== "none";
26693
26736
  }
26694
26737
  function disabled(element2) {
@@ -26718,10 +26761,10 @@ function tabbables(root2, stop, out = []) {
26718
26761
  continue;
26719
26762
  }
26720
26763
  const style = getComputedStyle(child);
26721
- if (!shown2(child, style)) continue;
26764
+ if (!shown3(child, style)) continue;
26722
26765
  if (child.localName === "details" && !child.open) {
26723
26766
  const summary = child.querySelector(":scope > summary");
26724
- if (summary && shown2(summary) && getComputedStyle(summary).visibility !== "hidden" && !(summary.hasAttribute("tabindex") && summary.tabIndex < 0))
26767
+ if (summary && shown3(summary) && getComputedStyle(summary).visibility !== "hidden" && !(summary.hasAttribute("tabindex") && summary.tabIndex < 0))
26725
26768
  out.push(summary);
26726
26769
  continue;
26727
26770
  }
@@ -28106,8 +28149,8 @@ var SliccMemory = class extends ModelElement {
28106
28149
  const scopes = this.#scopes;
28107
28150
  const scope = this.scope;
28108
28151
  const option2 = (item2) => b`<sp-menu-item value=${item2.id}>${item2.label}</sp-menu-item>`;
28109
- const shown3 = visible(all2, scope, this.tag, this.query);
28110
- return b`<div class="bar"><span>${shown3.length} of ${all2.filter((memory) => memory.scope === scope).length} memories</span><span class="spacer"></span>
28152
+ const shown4 = visible(all2, scope, this.tag, this.query);
28153
+ return b`<div class="bar"><span>${shown4.length} of ${all2.filter((memory) => memory.scope === scope).length} memories</span><span class="spacer"></span>
28111
28154
  <swc-action-button size="s" quiet @click=${() => this.editing = "new"}><swc-icon-add slot="icon"></swc-icon-add>Remember</swc-action-button>
28112
28155
  </div>
28113
28156
  <div class="tools">
@@ -28129,7 +28172,7 @@ var SliccMemory = class extends ModelElement {
28129
28172
  </div>
28130
28173
  <div class="list">
28131
28174
  ${this.editing === "new" ? b`<div class="row">${this.#form({ scope })}</div>` : A}
28132
- ${shown3.length ? sections(shown3).map(
28175
+ ${shown4.length ? sections(shown4).map(
28133
28176
  ([section, items]) => b`<h3>${section}</h3>${items.map((memory) => this.#row(memory))}`
28134
28177
  ) : b`<div class="note">${all2.length ? "No memories match." : "No memories yet."}</div>`}
28135
28178
  </div>`;
@@ -28729,6 +28772,20 @@ function asking2(request2) {
28729
28772
  if (request2.kind === "unseal") return b`${by} asks to remove the ${request2.provider} sign-in`;
28730
28773
  return request2.name ? b`${by} asks for <code>${request2.name}</code>` : b`${by} asks for a value`;
28731
28774
  }
28775
+ var accountLights = {
28776
+ connected: ["positive", "Active"],
28777
+ expired: ["notice", "Expired"],
28778
+ "signing-in": ["info", "Signing in"],
28779
+ disconnected: ["neutral", "Not connected"]
28780
+ };
28781
+ function putOptions(secret) {
28782
+ if (secret.kind === "model") return { kind: "model", provider: secret.provider };
28783
+ if (secret.kind === "mcp") return { kind: "mcp", server: secret.server };
28784
+ return void 0;
28785
+ }
28786
+ function agentUse(secret) {
28787
+ return secret.kind === "model" || secret.kind === "mcp" && secret.rules.length === 0;
28788
+ }
28732
28789
  function byName(a22, b13) {
28733
28790
  return a22.name < b13.name ? -1 : a22.name > b13.name ? 1 : 0;
28734
28791
  }
@@ -29220,7 +29277,8 @@ var SliccSecrets = class extends ThemedElement {
29220
29277
  model.vault.on("availability", update),
29221
29278
  model.vault.on("list", () => this.#listed(model.vault))
29222
29279
  ] : [],
29223
- ...model.network ? [model.network.on("network", update)] : []
29280
+ ...model.network ? [model.network.on("network", update)] : [],
29281
+ model.settings.on("accounts", update)
29224
29282
  ];
29225
29283
  }
29226
29284
  #button(config, action) {
@@ -29306,7 +29364,7 @@ var SliccSecrets = class extends ThemedElement {
29306
29364
  }
29307
29365
  this.busy = true;
29308
29366
  try {
29309
- await vault.put?.(name, value);
29367
+ await vault.put?.(name, value, void 0, form.kind === "replace" ? form.options : void 0);
29310
29368
  if (!this.#mine(vault, form)) return;
29311
29369
  this.form = null;
29312
29370
  this.said = { kind: "saved", name };
@@ -29474,8 +29532,8 @@ var SliccSecrets = class extends ThemedElement {
29474
29532
  const before = entries[entries.length - 1]?.seq;
29475
29533
  const page = await vault.audit(before);
29476
29534
  if (this.model?.vault !== vault || !this.isConnected) return;
29477
- const shown3 = merged(vault.list()?.audit ?? [], this.older);
29478
- if (shown3[shown3.length - 1]?.seq !== before) return;
29535
+ const shown4 = merged(vault.list()?.audit ?? [], this.older);
29536
+ if (shown4[shown4.length - 1]?.seq !== before) return;
29479
29537
  this.older = merged(this.older, page.audit);
29480
29538
  this.more = page.more;
29481
29539
  } catch (error) {
@@ -29586,8 +29644,21 @@ var SliccSecrets = class extends ThemedElement {
29586
29644
  </div>
29587
29645
  </div>`;
29588
29646
  }
29647
+ #reach(secret, gone3) {
29648
+ if (agentUse(secret)) {
29649
+ const use = secret.kind === "model" ? `Model requests to ${this.#providerName(secret.provider ?? "")}, by the agent` : `Requests to the MCP server ${secret.server}, by the agent`;
29650
+ return b`<dt>Used for</dt><dd data-used-for>${use}</dd>`;
29651
+ }
29652
+ const rules = secret.rules.length ? secret.rules.map((rule, index) => b`${index ? b`<br />` : A}${ruleLine(rule)}`) : "Nowhere yet: no rule names it";
29653
+ return b`<dt>${gone3 ? "Was sent to" : "Sent to"}</dt><dd data-rules>${rules}</dd>`;
29654
+ }
29655
+ #account(secret) {
29656
+ const id = secret.kind === "mcp" ? `mcp:${secret.server}` : secret.provider;
29657
+ return this.model?.settings.accounts().find((account) => account.id === id);
29658
+ }
29589
29659
  #state(secret) {
29590
- const [variant, label] = secret.state === "removed" ? ["neutral", "Removed"] : secret.expiresAt !== void 0 && secret.expiresAt <= Date.now() ? ["notice", "Expired"] : secret.state === "inert" ? ["neutral", "No rule yet"] : ["positive", "Active"];
29660
+ const account = agentUse(secret) ? this.#account(secret) : void 0;
29661
+ const [variant, label] = secret.state === "removed" ? ["neutral", "Removed"] : secret.expiresAt !== void 0 && secret.expiresAt <= Date.now() ? ["notice", "Expired"] : account ? accountLights[account.status] : secret.state === "inert" && !agentUse(secret) ? ["neutral", "No rule yet"] : ["positive", "Active"];
29591
29662
  return b`<swc-status-light size="s" variant=${variant}>${label}</swc-status-light>`;
29592
29663
  }
29593
29664
  #controls(vault, secret) {
@@ -29598,15 +29669,18 @@ var SliccSecrets = class extends ThemedElement {
29598
29669
  const replace = vault.put && replaceable(secret);
29599
29670
  if (!replace && !vault.remove) return A;
29600
29671
  return b`<div class="buttons">
29601
- ${replace ? b`<swc-button size="s" variant="secondary" data-action="replace" @click=${() => this.open({ kind: "replace", name })}>Replace value</swc-button>` : A}
29672
+ ${replace ? b`<swc-button size="s" variant="secondary" data-action="replace" @click=${() => this.open({ kind: "replace", name, options: putOptions(secret) })}>Replace value</swc-button>` : A}
29602
29673
  ${vault.remove ? b`<swc-button size="s" variant="secondary" fill-style="outline" data-action="remove" ?pending=${this.acting.has(`remove:${name}`)} @click=${(event2) => this.removeSecret(vault, secret, event2.currentTarget)}>Remove</swc-button>` : A}
29603
29674
  </div>`;
29604
29675
  }
29676
+ #providerName(id) {
29677
+ return this.model?.settings.accounts().find((account) => account.id === id)?.provider ?? id;
29678
+ }
29605
29679
  #secret(vault, secret, entries, devices, more, locked) {
29606
29680
  const kind = kinds[secret.kind];
29607
29681
  const reveal = revealed(secret.name, entries, Date.now(), more);
29608
29682
  const exec = execLine(secret.exec, secret.execArgv);
29609
- const gone2 = secret.state === "removed";
29683
+ const gone3 = secret.state === "removed";
29610
29684
  const form = this.#offline ? null : this.form;
29611
29685
  return b`<li class="secret" data-secret=${secret.name} data-state=${secret.state}>
29612
29686
  <div class="name">
@@ -29616,17 +29690,18 @@ var SliccSecrets = class extends ThemedElement {
29616
29690
  </div>
29617
29691
  ${this.#controls(vault, secret)}
29618
29692
  <dl>
29619
- <dt>${gone2 ? "Was sent to" : "Sent to"}</dt>
29620
- <dd data-rules>${secret.rules.length ? secret.rules.map((rule, index) => b`${index ? b`<br />` : A}${ruleLine(rule)}`) : "Nowhere yet: no rule names it"}</dd>
29621
- ${secret.exits ? b`<dt>${gone2 ? "Left through" : "Leaves through"}</dt><dd data-exits>${secret.exits.length ? list4(secret.exits.map((exit) => exitLabel(exit, devices))) : "No exit"}</dd>` : A}
29693
+ ${this.#reach(secret, gone3)}
29694
+ ${secret.exits ? b`<dt>${gone3 ? "Left through" : "Leaves through"}</dt><dd data-exits>${secret.exits.length ? list4(secret.exits.map((exit) => exitLabel(exit, devices))) : "No exit"}</dd>` : A}
29622
29695
  ${exec || reveal ? b`<dt>Programs</dt><dd data-exec>${exec}${exec && reveal ? b`<br />` : A}${reveal}</dd>` : A}
29696
+ ${secret.kind === "model" && secret.provider ? b`<dt>Provider</dt><dd data-provider>${this.#providerName(secret.provider)}</dd>` : A}
29697
+ ${secret.kind === "mcp" && secret.server ? b`<dt>Server</dt><dd data-server>${secret.server}</dd>` : A}
29623
29698
  <dt>From</dt>
29624
29699
  <dd data-source>${sourceLine(secret, devices)}</dd>
29625
29700
  ${secret.usedAt ? b`<dt>Last used</dt><dd data-used>${since(secret.usedAt)}</dd>` : A}
29626
29701
  ${secret.expiresAt ? b`<dt>Expires</dt><dd data-expires>${until(secret.expiresAt)}</dd>` : A}
29627
29702
  ${secret.removedAt ? b`<dt>Removed</dt><dd>${since(secret.removedAt)}</dd>` : A}
29628
29703
  </dl>
29629
- ${form?.kind === "replace" && form.name === secret.name ? this.#valueForm(vault, form, locked) : A}
29704
+ ${form?.kind === "replace" && form.name === secret.name && (form.options?.kind ?? "secret") === (putOptions(secret)?.kind ?? "secret") ? this.#valueForm(vault, form, locked) : A}
29630
29705
  </li>`;
29631
29706
  }
29632
29707
  #secrets(vault, list5, entries, devices, locked) {
@@ -29657,7 +29732,7 @@ var SliccSecrets = class extends ThemedElement {
29657
29732
  </li>`;
29658
29733
  }
29659
29734
  #audit(vault, list5, entries, devices) {
29660
- const shown3 = this.reveals ? entries.filter((entry) => entry.type === "materialize") : entries;
29735
+ const shown4 = this.reveals ? entries.filter((entry) => entry.type === "materialize") : entries;
29661
29736
  const more = this.more ?? list5.auditMore;
29662
29737
  return b`<div class="audit-head">
29663
29738
  <h3 id="secrets-audit">Activity</h3>
@@ -29665,7 +29740,7 @@ var SliccSecrets = class extends ThemedElement {
29665
29740
  this.reveals = event2.target.checked;
29666
29741
  }}>Only reveals to programs</sp-switch>
29667
29742
  </div>
29668
- ${shown3.length ? b`<ol class="audit" aria-labelledby="secrets-audit">${shown3.map((entry) => this.#entry(entry, devices))}</ol>` : b`<p class="muted" data-audit-empty>${this.reveals ? more ? "No reveals in the activity loaded so far." : "No program has been given a secret." : "Nothing has used a secret yet."}</p>`}
29743
+ ${shown4.length ? b`<ol class="audit" aria-labelledby="secrets-audit">${shown4.map((entry) => this.#entry(entry, devices))}</ol>` : b`<p class="muted" data-audit-empty>${this.reveals ? more ? "No reveals in the activity loaded so far." : "No program has been given a secret." : "Nothing has used a secret yet."}</p>`}
29669
29744
  ${more ? b`<div class="row"><swc-button size="s" variant="secondary" fill-style="outline" data-action="older" ?pending=${this.loading} @click=${() => this.showOlder(vault, entries)}>Show older</swc-button></div>` : A}`;
29670
29745
  }
29671
29746
  #feedback() {
@@ -29747,6 +29822,43 @@ var SliccSecrets = class extends ThemedElement {
29747
29822
  }
29748
29823
  };
29749
29824
 
29825
+ // src/app/records.ts
29826
+ var envNames = {
29827
+ anthropic: "ANTHROPIC_API_KEY",
29828
+ openai: "OPENAI_API_KEY",
29829
+ azure: "AZURE_OPENAI_API_KEY",
29830
+ "azure-openai": "AZURE_OPENAI_API_KEY",
29831
+ "azure-openai-responses": "AZURE_OPENAI_API_KEY",
29832
+ google: "GEMINI_API_KEY",
29833
+ gemini: "GEMINI_API_KEY",
29834
+ "google-vertex": "GOOGLE_CLOUD_API_KEY",
29835
+ "amazon-bedrock": "AWS_BEARER_TOKEN_BEDROCK",
29836
+ mistral: "MISTRAL_API_KEY",
29837
+ groq: "GROQ_API_KEY",
29838
+ cerebras: "CEREBRAS_API_KEY",
29839
+ xai: "XAI_API_KEY",
29840
+ openrouter: "OPENROUTER_API_KEY",
29841
+ "vercel-ai-gateway": "AI_GATEWAY_API_KEY",
29842
+ deepseek: "DEEPSEEK_API_KEY",
29843
+ zai: "ZAI_API_KEY",
29844
+ moonshotai: "MOONSHOT_API_KEY",
29845
+ fireworks: "FIREWORKS_API_KEY",
29846
+ together: "TOGETHER_API_KEY",
29847
+ huggingface: "HF_TOKEN",
29848
+ "github-copilot": "COPILOT_GITHUB_TOKEN"
29849
+ };
29850
+ var upper = (text2) => text2.toUpperCase().replace(/[^A-Z0-9]/g, "_");
29851
+ function serverOf(account) {
29852
+ return account.id.replace(/^mcp:/, "");
29853
+ }
29854
+ function recordKind(account) {
29855
+ return account.group === "mcp" ? "mcp" : "model";
29856
+ }
29857
+ function recordName(account) {
29858
+ if (account.group === "mcp") return `MCP_${upper(serverOf(account))}_TOKEN`;
29859
+ return envNames[account.id] ?? `${upper(account.id)}_API_KEY`;
29860
+ }
29861
+
29750
29862
  // src/app/settings.ts
29751
29863
  var accountStatus = {
29752
29864
  connected: ["positive", "Connected"],
@@ -29760,20 +29872,34 @@ var accountStatus = {
29760
29872
  var popup = (account) => account.group === "mcp" && account.auth === "oauth";
29761
29873
  var secretNames = { "api-key": "API key", token: "token" };
29762
29874
  var rejectedNames = { "api-key": "API key rejected", token: "Token rejected" };
29763
- var SliccSettings = class extends ModelElement {
29875
+ var keptNames = { "api-key": "Key kept in the vault", token: "Token kept in the vault" };
29876
+ var kept2 = (account) => account.auth === "api-key" ? keptNames[account.secret ?? "api-key"] : "Kept in the vault";
29877
+ async function gone2(vault, name, kind) {
29878
+ try {
29879
+ await vault.remove?.(name, kind);
29880
+ } catch (error) {
29881
+ if (!isVaultFailure(error) || error.code !== "ENOENT") throw error;
29882
+ }
29883
+ }
29884
+ var SliccSettings = class extends ThemedElement {
29764
29885
  static properties = {
29765
- ...ModelElement.properties,
29886
+ ...ThemedElement.properties,
29766
29887
  pending: { state: true },
29767
29888
  entering: { state: true },
29768
- failure: { state: true }
29889
+ failure: { state: true },
29890
+ problem: { state: true },
29891
+ blocked: { state: true }
29769
29892
  };
29770
29893
  constructor() {
29771
29894
  super();
29772
29895
  this.pending = /* @__PURE__ */ new Set();
29773
29896
  this.entering = null;
29774
29897
  this.failure = null;
29898
+ this.problem = null;
29899
+ this.blocked = false;
29775
29900
  }
29776
29901
  static styles = [
29902
+ fieldStyles,
29777
29903
  i`
29778
29904
  :host {
29779
29905
  display: block;
@@ -29855,7 +29981,25 @@ var SliccSettings = class extends ModelElement {
29855
29981
  .key sp-textfield {
29856
29982
  flex: 1 1 auto;
29857
29983
  }
29858
- .failure {
29984
+ .key.stacked {
29985
+ display: grid;
29986
+ gap: var(--swc-spacing-200);
29987
+ max-width: 420px;
29988
+ }
29989
+ .buttons,
29990
+ .locked {
29991
+ display: flex;
29992
+ flex-wrap: wrap;
29993
+ align-items: center;
29994
+ gap: var(--swc-spacing-100);
29995
+ }
29996
+ .hint {
29997
+ margin: 0;
29998
+ color: var(--swc-neutral-subdued-content-color-default);
29999
+ font-size: var(--swc-font-size-75);
30000
+ }
30001
+ .failure,
30002
+ .problem {
29859
30003
  display: flex;
29860
30004
  align-items: flex-start;
29861
30005
  gap: var(--swc-spacing-100);
@@ -29902,7 +30046,12 @@ var SliccSettings = class extends ModelElement {
29902
30046
  ];
29903
30047
  subscribe(model) {
29904
30048
  const update = () => this.requestUpdate();
29905
- return [model.settings.on("settings", update), model.settings.on("accounts", update)];
30049
+ return [
30050
+ ...super.subscribe(model),
30051
+ model.settings.on("settings", update),
30052
+ model.settings.on("accounts", update),
30053
+ ...model.vault ? [model.vault.on("status", update)] : []
30054
+ ];
29906
30055
  }
29907
30056
  focus() {
29908
30057
  this.focusOn("sp-picker");
@@ -29917,37 +30066,110 @@ var SliccSettings = class extends ModelElement {
29917
30066
  return event2.target.checked;
29918
30067
  }
29919
30068
  #cancelled = /* @__PURE__ */ new Set();
29920
- async connect(id, secret) {
29921
- this.pending = /* @__PURE__ */ new Set([...this.pending, id]);
30069
+ #busy(id, on) {
30070
+ const rest = [...this.pending].filter((candidate) => candidate !== id);
30071
+ this.pending = new Set(on ? [...rest, id] : rest);
30072
+ }
30073
+ async connect(id, secret, fields) {
30074
+ this.#busy(id, true);
29922
30075
  this.failure = null;
29923
30076
  this.#cancelled.delete(id);
30077
+ const settings = this.model?.settings;
29924
30078
  try {
29925
- await this.model?.settings.connect(id, secret);
30079
+ await (fields ? settings?.connect(id, secret, fields) : secret === void 0 ? settings?.connect(id) : settings?.connect(id, secret));
29926
30080
  if (this.entering === id) this.entering = null;
29927
30081
  } catch (error) {
29928
30082
  if (!this.#cancelled.has(id)) this.failure = { id, message: error.message };
29929
30083
  } finally {
29930
30084
  this.#cancelled.delete(id);
29931
- this.pending = new Set([...this.pending].filter((candidate) => candidate !== id));
30085
+ this.#busy(id, false);
29932
30086
  }
29933
30087
  }
30088
+ #vault(account) {
30089
+ const vault = this.model?.vault;
30090
+ return account.auth === "api-key" && account.store === "vault" && vault?.put ? vault : null;
30091
+ }
30092
+ async keep(account, value, fields) {
30093
+ const model = this.model;
30094
+ const vault = this.#vault(account);
30095
+ const { id } = account;
30096
+ const problem = valueProblem(value);
30097
+ if (problem) {
30098
+ this.#refuse(id, "value", problem);
30099
+ return;
30100
+ }
30101
+ if (vault.status()?.locked) return;
30102
+ const options = account.group === "mcp" ? { kind: "mcp", server: serverOf(account) } : { kind: "model", provider: account.id };
30103
+ this.#busy(id, true);
30104
+ this.failure = null;
30105
+ try {
30106
+ await vault.put?.(recordName(account), value, void 0, options);
30107
+ } catch (error) {
30108
+ this.#busy(id, false);
30109
+ if (this.model === model) this.#refuse(id, "value", failureText(error));
30110
+ return;
30111
+ }
30112
+ if (this.model !== model || this.entering !== id) {
30113
+ this.#busy(id, false);
30114
+ return;
30115
+ }
30116
+ await this.connect(id, void 0, fields);
30117
+ }
30118
+ #refuse(id, field, message) {
30119
+ this.problem = { id, field, message };
30120
+ void this.updateComplete.then(
30121
+ () => this.renderRoot.querySelector(
30122
+ field === "value" ? ".key input[data-field=value]" : `.key input[data-account-field="${field}"]`
30123
+ )?.focus()
30124
+ );
30125
+ }
30126
+ #fields(form, account) {
30127
+ if (!account.fields?.length) return void 0;
30128
+ const values = {};
30129
+ for (const field of account.fields) {
30130
+ const input = form.querySelector(
30131
+ `input[data-account-field="${field.name}"]`
30132
+ );
30133
+ const value = input.value.trim();
30134
+ if (value) values[field.name] = value;
30135
+ else if (field.required) {
30136
+ this.#refuse(account.id, field.name, `${field.label} can\u2019t be empty`);
30137
+ return null;
30138
+ }
30139
+ }
30140
+ return values;
30141
+ }
29934
30142
  #start(account) {
29935
30143
  if (account.auth !== "api-key") {
29936
30144
  void this.connect(account.id);
29937
30145
  return;
29938
30146
  }
29939
30147
  this.failure = null;
30148
+ this.problem = null;
29940
30149
  this.entering = account.id;
29941
- void this.updateComplete.then(() => this.focusOn(".key sp-textfield"));
30150
+ void this.updateComplete.then(() => this.focusOn(".key sp-textfield", ".key input"));
29942
30151
  }
29943
- #submit(event2, id) {
30152
+ #submit(event2, account) {
29944
30153
  event2.preventDefault();
29945
- const field = event2.currentTarget.querySelector(
29946
- "sp-textfield"
29947
- );
30154
+ const form = event2.currentTarget;
30155
+ this.problem = null;
30156
+ if (this.#vault(account)) {
30157
+ const value = take(form.querySelector("input[data-field=value]"));
30158
+ const fields2 = this.#fields(form, account);
30159
+ if (fields2 !== null && !this.pending.has(account.id)) void this.keep(account, value, fields2);
30160
+ return;
30161
+ }
30162
+ const field = form.querySelector("sp-textfield");
29948
30163
  const secret = field.value.trim();
29949
30164
  field.value = "";
29950
- if (secret) void this.connect(id, secret);
30165
+ const fields = this.#fields(form, account);
30166
+ if (secret && fields !== null) void this.connect(account.id, secret, fields);
30167
+ }
30168
+ #submitOnEnter(event2) {
30169
+ const target = event2.target;
30170
+ if (event2.key !== "Enter" || target.localName !== "input") return;
30171
+ event2.preventDefault();
30172
+ this.#send(event2);
29951
30173
  }
29952
30174
  #send(event2) {
29953
30175
  event2.currentTarget.closest("form")?.requestSubmit();
@@ -29956,13 +30178,83 @@ var SliccSettings = class extends ModelElement {
29956
30178
  if (event2.key === "Enter") this.#send(event2);
29957
30179
  }
29958
30180
  #cancel() {
30181
+ clearValues(this.renderRoot);
29959
30182
  this.entering = null;
29960
30183
  this.failure = null;
30184
+ this.problem = null;
30185
+ }
30186
+ #problem(account, field) {
30187
+ const problem = this.problem;
30188
+ if (problem?.id !== account.id || problem.field !== field) return A;
30189
+ return b`<div class="problem" id="key-problem" role="alert">
30190
+ <swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle>
30191
+ <span>${problem.message}</span>
30192
+ </div>`;
30193
+ }
30194
+ #described(account, field) {
30195
+ return this.problem?.id === account.id && this.problem.field === field ? "key-problem" : A;
30196
+ }
30197
+ #invalid(account, field) {
30198
+ return this.#described(account, field) === A ? A : "true";
30199
+ }
30200
+ #extra(account) {
30201
+ return (account.fields ?? []).map(
30202
+ (field) => b`<label class="field"><span>${field.label}</span><input
30203
+ type="text"
30204
+ autocomplete="off"
30205
+ spellcheck="false"
30206
+ data-account-field=${field.name}
30207
+ aria-required=${field.required ? "true" : A}
30208
+ aria-invalid=${this.#invalid(account, field.name)}
30209
+ aria-describedby=${this.#described(account, field.name)}
30210
+ /></label>
30211
+ ${this.#problem(account, field.name)}`
30212
+ );
30213
+ }
30214
+ #unlock(vault) {
30215
+ return b`<div class="locked" data-locked>
30216
+ <span>Unlock the vault first</span>
30217
+ <slicc-vault-button
30218
+ data-action="unlock"
30219
+ .vault=${vault}
30220
+ .config=${{ intent: { kind: "unlock" }, label: "Unlock", size: "m", variant: "accent", theme: this.color }}
30221
+ @vault-popup=${(event2) => {
30222
+ this.blocked = event2.detail.state === "blocked";
30223
+ }}
30224
+ ></slicc-vault-button>
30225
+ </div>
30226
+ ${this.blocked ? b`<div class="problem" role="alert" data-popup="blocked"><swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle><span>The browser blocked the vault window. Allow pop-ups for this site, then try again.</span></div>` : A}`;
30227
+ }
30228
+ #kept(account, vault) {
30229
+ const busy = this.pending.has(account.id);
30230
+ const locked = !!vault.status()?.locked;
30231
+ const name = secretNames[account.secret ?? "api-key"];
30232
+ return b`<form class="key stacked" data-id=${account.id} data-store="vault" @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
30233
+ <label class="field"><span>${account.provider} ${name}</span><input
30234
+ type="password"
30235
+ autocomplete="off"
30236
+ spellcheck="false"
30237
+ data-field="value"
30238
+ placeholder=${`Paste the ${name}`}
30239
+ aria-invalid=${this.#invalid(account, "value")}
30240
+ aria-describedby=${this.#described(account, "value")}
30241
+ /></label>
30242
+ ${this.#problem(account, "value")}
30243
+ ${this.#extra(account)}
30244
+ ${locked ? this.#unlock(vault) : b`<p class="hint">Saved in the vault</p>`}
30245
+ <div class="buttons">
30246
+ <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} ?disabled=${locked} @click=${(event2) => this.#send(event2)}>Save</swc-button>
30247
+ <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>
30248
+ </div>
30249
+ </form>`;
29961
30250
  }
29962
30251
  #key(account) {
29963
30252
  if (this.entering !== account.id) return A;
30253
+ const vault = this.#vault(account);
30254
+ if (vault) return this.#kept(account, vault);
29964
30255
  const busy = this.pending.has(account.id);
29965
- return b`<form class="key" data-id=${account.id} @submit=${(event2) => this.#submit(event2, account.id)}>
30256
+ const extra = account.fields?.length;
30257
+ return b`<form class=${extra ? "key stacked" : "key"} data-id=${account.id} @submit=${(event2) => this.#submit(event2, account)} @keydown=${(event2) => this.#submitOnEnter(event2)}>
29966
30258
  <sp-textfield
29967
30259
  size="m"
29968
30260
  type="password"
@@ -29971,15 +30263,61 @@ var SliccSettings = class extends ModelElement {
29971
30263
  placeholder=${`Paste the ${secretNames[account.secret ?? "api-key"]}`}
29972
30264
  @keydown=${(event2) => this.#enter(event2)}
29973
30265
  ></sp-textfield>
29974
- <swc-button size="m" variant="accent" data-action="save" ?pending=${busy} @click=${(event2) => this.#send(event2)}>Save</swc-button>
29975
- <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>
30266
+ ${extra ? this.#extra(account) : A}
30267
+ ${extra ? b`<div class="buttons">${this.#buttons(busy)}</div>` : this.#buttons(busy)}
29976
30268
  </form>`;
29977
30269
  }
30270
+ #buttons(busy) {
30271
+ return b`<swc-button size="m" variant="accent" data-action="save" ?pending=${busy} @click=${(event2) => this.#send(event2)}>Save</swc-button>
30272
+ <swc-button size="m" variant="secondary" data-action="cancel" @click=${() => this.#cancel()}>Cancel</swc-button>`;
30273
+ }
30274
+ #inVault(account) {
30275
+ const vault = this.model?.vault;
30276
+ if (account.store !== "vault" || account.status !== "connected" || !vault) return false;
30277
+ return account.auth !== "api-key" || !!vault.put;
30278
+ }
30279
+ #records(account, vault) {
30280
+ if (account.auth === "api-key") return [[recordName(account), recordKind(account)]];
30281
+ const server = serverOf(account);
30282
+ const names = (vault.list()?.secrets ?? []).filter(
30283
+ (secret) => secret.kind === "mcp" && secret.server === server && secret.state !== "removed"
30284
+ ).map((secret) => secret.name);
30285
+ const tokens = names.length ? names : [`${upper(server)}_ACCESS_TOKEN`];
30286
+ return tokens.flatMap(
30287
+ (name) => [
30288
+ [name, "mcp"],
30289
+ [`${name}_GRANT`, "grant"]
30290
+ ]
30291
+ );
30292
+ }
29978
30293
  async #disconnect(account) {
29979
- const body4 = `Agents can\u2019t use ${account.provider} until you connect it again.`;
30294
+ const model = this.model;
30295
+ const vault = model?.vault;
30296
+ const vaulted = account.store === "vault" && !!vault?.remove;
30297
+ const key2 = vaulted && account.auth === "api-key";
30298
+ const signIn = vaulted && account.auth !== "api-key" && account.group === "mcp";
30299
+ const stored = key2 || signIn ? vault : void 0;
30300
+ const body4 = `Agents can\u2019t use ${account.provider} until you connect it again.${key2 ? " You can restore its key from Removed in Secrets for 30 days." : ""}`;
29980
30301
  const title = `Disconnect ${account.provider}?`;
29981
- if (await confirm({ title, body: body4, action: "Disconnect", variant: "confirmation" }))
29982
- this.model?.settings.disconnect(account.id);
30302
+ if (!await confirm({ title, body: body4, action: "Disconnect", variant: "confirmation" })) return;
30303
+ clearValues(this.renderRoot);
30304
+ if (this.model !== model) return;
30305
+ if (stored && !await this.#forget(account, stored)) return;
30306
+ if (this.model === model) model?.settings.disconnect(account.id);
30307
+ }
30308
+ async #forget(account, vault) {
30309
+ this.#busy(account.id, true);
30310
+ this.failure = null;
30311
+ try {
30312
+ for (const [name, kind] of this.#records(account, vault)) await gone2(vault, name, kind);
30313
+ return true;
30314
+ } catch (error) {
30315
+ if (this.model?.vault === vault)
30316
+ this.failure = { id: account.id, message: failureText(error) };
30317
+ return false;
30318
+ } finally {
30319
+ this.#busy(account.id, false);
30320
+ }
29983
30321
  }
29984
30322
  #stopSigningIn(account) {
29985
30323
  this.#cancelled.add(account.id);
@@ -30009,6 +30347,7 @@ var SliccSettings = class extends ModelElement {
30009
30347
  variant="secondary"
30010
30348
  data-action="disconnect"
30011
30349
  accessible-label=${`Disconnect ${account.provider}`}
30350
+ ?pending=${this.pending.has(account.id)}
30012
30351
  @click=${() => this.#disconnect(account)}
30013
30352
  >Disconnect</swc-button>`;
30014
30353
  }
@@ -30042,7 +30381,7 @@ var SliccSettings = class extends ModelElement {
30042
30381
  </div>` : A;
30043
30382
  return b`<div class="account" data-id=${account.id}>
30044
30383
  <span class="provider">${account.provider}</span>
30045
- <span class="identity">${account.identity}</span>
30384
+ <span class="identity">${account.identity}${this.#inVault(account) ? b`<span class="kept" data-store="vault">${account.identity ? " \xB7 " : ""}${kept2(account)}</span>` : A}</span>
30046
30385
  <swc-status-light class="state" variant=${variant}>${label}</swc-status-light>
30047
30386
  ${this.#action(account)}
30048
30387
  </div>
@@ -30843,10 +31182,10 @@ var SliccValueRequest = class extends ThemedElement {
30843
31182
  if (!vault || !request2) return A;
30844
31183
  const locked = !!vault.status()?.locked;
30845
31184
  const rest = queue.length - 1;
30846
- const shown3 = this.problem?.id === request2.id ? this.problem : null;
30847
- const problem = shown3?.text ?? "";
30848
- const name = this.#aria("name", shown3);
30849
- const field = this.#aria("value", shown3);
31185
+ const shown4 = this.problem?.id === request2.id ? this.problem : null;
31186
+ const problem = shown4?.text ?? "";
31187
+ const name = this.#aria("name", shown4);
31188
+ const field = this.#aria("value", shown4);
30850
31189
  return i17(
30851
31190
  request2.id,
30852
31191
  b`<dialog role="alertdialog" aria-labelledby="title" aria-describedby="asked" data-request=${request2.id} @cancel=${(event2) => {
@@ -41891,12 +42230,12 @@ function getIgnoreCaseMatchChars(char) {
41891
42230
  }
41892
42231
  const set = /* @__PURE__ */ new Set();
41893
42232
  const lower2 = char.toLowerCase();
41894
- const upper = lower2.toUpperCase();
42233
+ const upper2 = lower2.toUpperCase();
41895
42234
  const title = LowerToTitleCaseMap.get(lower2);
41896
42235
  const altLower = LowerToAlternativeLowerCaseMap.get(lower2);
41897
42236
  const altUpper = LowerToAlternativeUpperCaseMap.get(lower2);
41898
- if ([...upper].length === 1) {
41899
- set.add(upper);
42237
+ if ([...upper2].length === 1) {
42238
+ set.add(upper2);
41900
42239
  }
41901
42240
  altUpper && set.add(altUpper);
41902
42241
  title && set.add(title);