@ai-ecoverse/slicc-spectrum 1.33.0 → 1.34.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 CHANGED
@@ -142,7 +142,7 @@ Text is Adobe Clean Spectrum VF, falling back to Adobe Clean, and code is Spectr
142
142
  | `<slicc-diff-panel>` | The diff of one pending change (`path`), unified or split, with accept and revert. |
143
143
  | `<slicc-terminals>` | Terminal sessions in tabs, each a `<slicc-terminal>` on the session's `TerminalBackend`. Sessions stay alive while hidden; `exit` closes the tab. |
144
144
  | `<slicc-browser>` | Browser tabs with the agent driving each, an address bar, reload, and the active tab's screenshot. |
145
- | `<slicc-settings>` | Theme, the model and thinking level for new cones, composer and diff preferences, and accounts to connect, reconnect or disconnect. |
145
+ | `<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. |
146
146
  | `<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. Also available through View and the rails. |
147
147
  | `<slicc-memory>` | What agents remember, per scope (everyone, a cone or a role), grouped by section: search, filter by tag, expand, edit, add and forget. Free text outside an entry shows “From MEMORY.md”. |
148
148
  | `<slicc-monitor>` | The live monitor: vitals with sparklines (active agents, spend, budget, fullest context), alerts, and the topology (cones and scoops, terminals, tabs, pending changes, sprinkles, tray followers). |
@@ -18,6 +18,7 @@ var DummySettings = class extends Emitter {
18
18
  #models;
19
19
  #storage;
20
20
  #clock;
21
+ #signIns = /* @__PURE__ */ new Map();
21
22
  constructor(defaults, models, accounts, storage, clock2) {
22
23
  super();
23
24
  this.#storage = storage;
@@ -54,13 +55,35 @@ var DummySettings = class extends Emitter {
54
55
  this.emit("accounts", this.accounts());
55
56
  }
56
57
  async connect(id, secret) {
57
- await this.#clock.sleep(10);
58
58
  const account = this.#accounts.find((candidate) => candidate.id === id);
59
+ if (account && account.auth !== "api-key" && !await this.#signIn(account)) return;
60
+ if (account?.auth === "api-key" || !account) await this.#clock.sleep(10);
59
61
  if (account?.auth === "api-key" && !secret)
60
62
  throw new Error(`${account.provider} needs an API key`);
61
63
  const identity = account?.auth === "api-key" ? "API key" : account?.identity || `${id}-user@example.com`;
62
64
  this.#set(id, { status: "connected", identity });
63
65
  }
66
+ #signIn(account) {
67
+ const before = this.#signIns.get(account.id)?.before ?? account.status;
68
+ this.#signIns.get(account.id)?.stop();
69
+ this.#set(account.id, { status: "signing-in" });
70
+ return new Promise((resolve) => {
71
+ const stop = () => {
72
+ this.#signIns.delete(account.id);
73
+ this.#set(account.id, { status: before });
74
+ resolve(false);
75
+ };
76
+ this.#signIns.set(account.id, { before, stop });
77
+ void this.#clock.sleep(100).then(() => {
78
+ if (this.#signIns.get(account.id)?.stop !== stop) return;
79
+ this.#signIns.delete(account.id);
80
+ resolve(true);
81
+ });
82
+ });
83
+ }
84
+ cancel(id) {
85
+ this.#signIns.get(id)?.stop();
86
+ }
64
87
  disconnect(id) {
65
88
  this.#set(id, { status: "disconnected" });
66
89
  }
@@ -70,4 +93,4 @@ export {
70
93
  clock,
71
94
  DummySettings
72
95
  };
73
- //# sourceMappingURL=chunk-43NIJXWH.js.map
96
+ //# sourceMappingURL=chunk-UKNZUMF6.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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\nconst key = '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 #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 ) {\n super();\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 return JSON.parse(this.#storage?.getItem(key) ?? '{}');\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(key, 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(`${account.provider} needs an API key`);\n const identity =\n account?.auth === 'api-key' ? 'API key' : 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
+ "names": ["clock"]
7
+ }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DummySettings,
3
3
  clock
4
- } from "./chunk-43NIJXWH.js";
4
+ } from "./chunk-UKNZUMF6.js";
5
5
  import {
6
6
  Emitter
7
7
  } from "./chunk-T5NHW4KC.js";
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  DummySettings,
6
6
  clock
7
- } from "./chunk-43NIJXWH.js";
7
+ } from "./chunk-UKNZUMF6.js";
8
8
  import {
9
9
  Emitter
10
10
  } from "./chunk-T5NHW4KC.js";
package/dist/slicc-ui.js CHANGED
@@ -23122,7 +23122,8 @@ var SliccMonitor = class extends ModelElement {
23122
23122
  var accountStatus = {
23123
23123
  connected: ["positive", "Connected"],
23124
23124
  expired: ["notice", "Expired"],
23125
- disconnected: ["neutral", "Not connected"]
23125
+ disconnected: ["neutral", "Not connected"],
23126
+ "signing-in": ["info", "Signing in\u2026"]
23126
23127
  };
23127
23128
  var SliccSettings = class extends ModelElement {
23128
23129
  static properties = {
@@ -23280,15 +23281,18 @@ var SliccSettings = class extends ModelElement {
23280
23281
  #checked(event2) {
23281
23282
  return event2.target.checked;
23282
23283
  }
23284
+ #cancelled = /* @__PURE__ */ new Set();
23283
23285
  async connect(id, secret) {
23284
23286
  this.pending = /* @__PURE__ */ new Set([...this.pending, id]);
23285
23287
  this.failure = null;
23288
+ this.#cancelled.delete(id);
23286
23289
  try {
23287
23290
  await this.model?.settings.connect(id, secret);
23288
23291
  if (this.entering === id) this.entering = null;
23289
23292
  } catch (error) {
23290
- this.failure = { id, message: error.message };
23293
+ if (!this.#cancelled.has(id)) this.failure = { id, message: error.message };
23291
23294
  } finally {
23295
+ this.#cancelled.delete(id);
23292
23296
  this.pending = new Set([...this.pending].filter((candidate) => candidate !== id));
23293
23297
  }
23294
23298
  }
@@ -23342,7 +23346,23 @@ var SliccSettings = class extends ModelElement {
23342
23346
  if (await confirm({ title, body: body4, action: "Disconnect", variant: "confirmation" }))
23343
23347
  this.model?.settings.disconnect(account.id);
23344
23348
  }
23349
+ #stopSigningIn(account) {
23350
+ this.#cancelled.add(account.id);
23351
+ this.model?.settings.cancel?.(account.id);
23352
+ }
23353
+ #signingIn(account) {
23354
+ return account.status === "signing-in" || account.auth !== "api-key" && account.status !== "connected" && this.pending.has(account.id);
23355
+ }
23345
23356
  #action(account) {
23357
+ if (this.#signingIn(account) && this.model?.settings.cancel) {
23358
+ return b`<swc-button
23359
+ size="m"
23360
+ variant="secondary"
23361
+ data-action="cancel-sign-in"
23362
+ accessible-label=${`Cancel signing in to ${account.provider}`}
23363
+ @click=${() => this.#stopSigningIn(account)}
23364
+ >Cancel</swc-button>`;
23365
+ }
23346
23366
  if (account.status === "connected") {
23347
23367
  return b`<swc-button
23348
23368
  size="m"
@@ -23354,18 +23374,19 @@ var SliccSettings = class extends ModelElement {
23354
23374
  }
23355
23375
  const label = account.status === "expired" ? "Reconnect" : "Connect";
23356
23376
  const entering = this.entering === account.id;
23377
+ const busy = this.#signingIn(account) || !entering && this.pending.has(account.id);
23357
23378
  return b`<swc-button
23358
23379
  size="m"
23359
23380
  variant="secondary"
23360
23381
  data-action="connect"
23361
23382
  accessible-label=${`${label} ${account.provider}`}
23362
- ?pending=${!entering && this.pending.has(account.id)}
23383
+ ?pending=${busy}
23363
23384
  ?disabled=${entering}
23364
23385
  @click=${() => this.#start(account)}
23365
23386
  >${label}</swc-button>`;
23366
23387
  }
23367
23388
  #account(account) {
23368
- const [variant, label] = accountStatus[account.status];
23389
+ const [variant, label] = accountStatus[this.#signingIn(account) ? "signing-in" : account.status];
23369
23390
  const failure2 = this.failure?.id === account.id ? b`<div class="failure" role="alert">
23370
23391
  <swc-icon-alert-triangle size="s" aria-hidden="true"></swc-icon-alert-triangle>
23371
23392
  <span>${this.failure.message}</span>