@ai-ecoverse/slicc-spectrum 1.86.6 → 1.87.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
@@ -280,7 +280,7 @@ Under 480 px of column width the label takes a row of its own under the icons, w
280
280
  A cluster's label comes from the agent port's optional `toolLabel(agentId, callIds)`, asked once for each agent port, agent and set of runs; while a growing run waits for its new label, it keeps its last one, and without `toolLabel` the label is empty.
281
281
  A command's icon is the favicon of the site it names when an open browser tab already has a `data:` favicon for it (nothing is fetched; otherwise the globe), else the file browser's icon for the file it names, else the code icon; other tools show their kind's icon.
282
282
  A running run pulses (still, with a dashed ring, under `prefers-reduced-motion`), a done or cancelled one is grey and a failed one red, each with a corner glyph.
283
- The card (`.tool[data-tool][data-status][data-kind]`, a `details.run` under a pill and a `div[role=region]` named by its `title` and status under a strip) has no header of its own, no chevron, title, tool name or status: its input (a shell command after `$`), an edit's `diff` shown as a diff view, its output wrapped rather than cut off, an `image`, such as a screenshot, an optional `meta` such as a timeout, a standalone tool message's time ("Ran at 16:12", `.ran`, by the two-minute rule), and its `notes` (`{ severity, text }`, such as "Exited with code 5" or "Full output in /tmp/…") as lines under the output, `li[data-note][data-severity]`: `error` with an alert icon in the negative colour, `warn` with an alert icon and `info` with an info icon, both subdued; media galleries with images, video and audio; action cards for tools, pull requests and status; plans and checklists; inline diffs; question prompts (as decision cards, below); delegations; link previews; errors with a fix-it action, as a grey card (`.error-card[role=alert]`, `data-id` on system errors) inset by a wide margin on each side, a narrower one under 480 px: a red alert icon, the title in bold, the detail subdued and the action on the right, or under the detail below 360 px, announced as "Error: " and the title; a content-filter stop offers **Drop the last turn**, which rewinds that turn and puts its prompt back into the composer; when the turns right before it failed too, the button reads **Drop N failed turns** and rewinds to before the first of them, putting the latest prompt back), system messages (error cards; compactions and notices, rewinds and model changes included, as full-width dividers with a line on each side, like the day: a compaction shows only its title, its `text` kept for screen readers, one that lost messages (`state: 'fallback'`, by default "Compaction failed, transcript truncated", or `discarded`, "Context discarded") an alert icon in the negative colour, and a notice its title and text on one line, `role="note"`; a divider that wraps keeps a short line on each side) and licks, SLICC's events into a cone, on every channel, on the right (`.lick-turn`): a byline with the title and the time by the two-minute rule, then a dashed box (`.lick[data-id][data-channel]`) with the text and the count (×3); emoji pills on its bottom edge (`.reactions .reaction`, `aria-hidden`, each named by its `title`) give the channel (⏰ Schedule, 🪝 Webhook, ✨ Sprinkle, …) and the outcome (👍 Allowed, 👎 Denied, also said in the box's hidden text).
283
+ The card (`.tool[data-tool][data-status][data-kind]`, a `details.run` under a pill and a `div[role=region]` named by its `title` and status under a strip) has no header of its own, no chevron, title, tool name or status: its input (a shell command after `$`), an edit's `diff` shown as a diff view, its output wrapped rather than cut off, an `image`, such as a screenshot, an optional `meta` such as a timeout, a standalone tool message's time ("Ran at 16:12", `.ran`, by the two-minute rule), and its `notes` (`{ severity, text }`, such as "Exited with code 5" or "Full output in /tmp/…") as lines under the output, `li[data-note][data-severity]`: `error` with an alert icon in the negative colour, `warn` with an alert icon and `info` with an info icon, both subdued; media galleries with images, video and audio; action cards for tools, pull requests and status; plans and checklists; inline diffs; question prompts (as decision cards, below); delegations; link previews; errors with a fix-it action, as a grey card (`.error-card[role=alert]`, `data-id` on system errors) inset by a wide margin on each side, a narrower one under 480 px: a red alert icon, the title in bold, the detail subdued and the action on the right, or under the detail below 360 px, announced as "Error: " and the title; an error with `action: 'login'` and an `account` (an `Account.id`) signs that account in: **Log in** calls `settings.connect(account)` synchronously in the click when the account is OAuth or needs no secret or fields, so a sign-in pop-up opens within the user gesture, and otherwise, or when `accounts()` doesn't have it, opens Settings at its row; the card follows the account's status from `accounts` (`data-login` is `idle`, `signing-in` or `connected`): while it is `signing-in` or `connect` is pending, **Log in** is pending and reads “Signing in…”, with **Cancel** (`data-action="cancel-login"`) when the port has `cancel`; once `connected`, the card is no longer an alert: a neutral info icon in the detail colour replaces the red one, the "Error: " prefix goes, the detail reads “Signed in. Retry to send it again.” (a polite status) and the button becomes **Retry**, which sends the last prompt again only when clicked; a rejected `connect` shows its message as negative help text (`[data-error=login]`), keeps **Log in** and adds a quiet **Open settings** (`data-action="settings"`) to that account; without an `account`, **Log in** opens Settings at Accounts, and watch-only and guest seats keep their gating; a content-filter stop offers **Drop the last turn**, which rewinds that turn and puts its prompt back into the composer; when the turns right before it failed too, the button reads **Drop N failed turns** and rewinds to before the first of them, putting the latest prompt back), system messages (error cards; compactions and notices, rewinds and model changes included, as full-width dividers with a line on each side, like the day: a compaction shows only its title, its `text` kept for screen readers, one that lost messages (`state: 'fallback'`, by default "Compaction failed, transcript truncated", or `discarded`, "Context discarded") an alert icon in the negative colour, and a notice its title and text on one line, `role="note"`; a divider that wraps keeps a short line on each side) and licks, SLICC's events into a cone, on every channel, on the right (`.lick-turn`): a byline with the title and the time by the two-minute rule, then a dashed box (`.lick[data-id][data-channel]`) with the text and the count (×3); emoji pills on its bottom edge (`.reactions .reaction`, `aria-hidden`, each named by its `title`) give the channel (⏰ Schedule, 🪝 Webhook, ✨ Sprinkle, …) and the outcome (👍 Allowed, 👎 Denied, also said in the box's hidden text).
284
284
  A lick with a body is a `details.lick` whose head (`summary.lick-head`, with a quiet chevron that turns when open) toggles it with a click, <kbd>Enter</kbd> or <kbd>Space</kbd>; clicking the body doesn't close it.
285
285
  Open licks and open tool runs hold for every view of the chat on the same model, so a layout switch keeps them.
286
286
  A lick's `severity` (`warn` or `error`) adds an alert icon to its byline, a coloured edge to its box and a spoken prefix.
@@ -409,6 +409,7 @@ An MCP server with `auth: 'oauth'` signs in in a pop-up: its button reads **Sign
409
409
  An account with `fields` (`{ name, label, required? }`) shows a plain labelled input for each, and their values go to `connect(id, secret?, fields)`.
410
410
  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.
411
411
  Without `store: 'vault'` or `vault.put`, the key goes to `connect(id, secret)` as before.
412
+ A `show-surface` whose `section` is an account id scrolls to that row and focuses its button, and `accounts` scrolls to the Accounts heading (`h2[data-section=accounts]`).
412
413
  With a `vault` port, **Secrets** (`<slicc-secrets>`) follows.
413
414
  When the model for new cones was moved off a provider that isn't set up, a quiet note under its picker (`p[data-switched][role=status]`) says “Switched to {model} because the previous model's provider isn't set up.” until a model is picked.
414
415
 
@@ -482,6 +483,7 @@ A live row offers **Open**, which selects that agent; a stopped one offers **Tha
482
483
  A thawed scoop or agent run keeps its row, which says what it was thawed as.
483
484
  **Delete cone**, in the agents list (or <kbd>Delete</kbd> on a cone's row) and the header's cone picker, stops a cone after a confirmation; `/freeze` does it at once.
484
485
  Either way its conversation stays here, and a row without a `kind` counts as a cone.
486
+ A title longer than two lines ends in an ellipsis and carries its full text as a `title`; the dummy's titles are the whole first line of the first message, so only the card cuts them.
485
487
 
486
488
  #### `<slicc-sprinkle>`
487
489
 
@@ -544,10 +546,13 @@ Not for direct use.
544
546
  #### `<slicc-file-tree>`
545
547
 
546
548
  Generic.
547
- [`@pierre/trees`](https://www.npmjs.com/package/@pierre/trees) through its `web-components` entry: `paths` (folders end in `/`), `gitStatus`, `expanded`, `query` (shows only the paths that match it; empty shows all), `reveal(path, focus)`.
549
+ [`@pierre/trees`](https://www.npmjs.com/package/@pierre/trees) through its `web-components` entry: `paths` (folders end in `/`), `gitStatus`, `expanded`, `query` (shows only the paths that match it; empty shows all), `reveal(path, focus, options)`.
548
550
  `expanded` also opens and closes the folders of a mounted tree, a `tree-expand` event (`detail.expanded`) reports the open folders when the user changes them, and the tree keeps them when it's moved, such as into a floating group.
549
551
  It has no search field of its own.
550
552
  A folder name or flattened folder chain too long for the row ends in an ellipsis instead of losing its middle, and every row carries its full name as a `title`, with its indent and chevron kept.
553
+ A file name too long for the row ends its name in an ellipsis and keeps its extension, as in "tide-summary… md": the library splits the name after the dot, and the browser's ellipsis drops whole letters, so the dot goes with the name and a sliver of space can stay before the extension.
554
+ `@pierre/trees` middle-truncates names by laying an "…" marker over the text, painted with `--trees-bg`; with the transparent background the tree uses here, the letters showed through it, so the tree hides that marker and lets the browser's own `text-overflow: ellipsis` cut the name before the extension.
555
+ A row's focus ring shows only with keyboard focus: the library draws it on the row that holds focus (`data-item-focused`), mouse click or not, so the tree hides it when the row isn't `:focus-visible`, and `focus(options)` passes `focusVisible` down to the row. Under forced colors, where the selection fill drops out, a selected row keeps a `Highlight` outline.
551
556
  Fires `file-open` on click and on <kbd>Enter</kbd>, and `tree-search` (`detail.key`) when a letter or digit is typed in it, for the panel's search field.
552
557
 
553
558
  #### `<slicc-code-view>`
@@ -669,6 +674,7 @@ Every port is subscribable with `on(type, listener)`, which returns an unsubscri
669
674
 
670
675
  Later backed by: `@ai-ecoverse/slicc-agent`.
671
676
  Cones and scoops, the active agent, conversations, `send` (with attachments, as a send, a steer or a queued follow-up) and `stop`, the queue, suggestions, answers to questions, permission decisions, compaction, clearing (`clear(agentId)` starts a new conversation: on a cone, a port with `freeze` first puts the old one in the Freezer as its own row, which thaws into a cone of its own; a scoop's is just reset), the model per agent and new scoops.
677
+ An error part (`{ type: 'error', message, detail?, action?, account? }`) and an error `SystemMessage` may carry `account`, the `Account.id` that the card's **Log in** signs in; slicc-agent sets it on the expired-session card.
672
678
  The optional `rewind(agentId, messageId)` drops the user turn before that message and everything after it, and resolves with the turn's prompt (text and attachments) for the composer, or `null`.
673
679
  The optional `hold()` returns an `AgentHold` (`{ reason: waiting | locked, held }`) while tool results wait for the vault (slicc-agent's `connection.vault`: `waiting` while the vault port doesn't answer, `locked` while it needs a passkey), or `null`, with a `hold` event when it changes.
674
680
  The optional `drop(agentId)` removes a scoop: it stops and its working folder is deleted, while its conversation and reports are kept, and `createCone(name)` resolves with a new cone; the dummy rejects a cone name that is taken.
@@ -948,6 +954,7 @@ The SLICC you follow runs a newer version.
948
954
  Update this one to send prompts.", and shows a warning notice without a close button (`[data-notice="readonly"]`) in place of any other notices.
949
955
  `model.agent.setReadOnly(text)` changes it, and `null` ends it.
950
956
 
957
+ The kitchen sink's “docs-pass failed” card is an expired-session error for the invented `ai-adobe` account (Adobe AI, harbor-ops@example.com, `expired`, OAuth): **Log in** goes `signing-in`, then `connected` after a short wait, and the dummy records each `connect` in `settings.connects` (`{ id, gesture }`, whether `navigator.userActivation.isActive` was true at the call); inbox-triage's “Tickets connector” card names `mcp:tickets`, a token account, so its **Log in** opens Settings at that row; `createDummyModel({ login: 'fail' })` (`?login=fail` on `/ui/`) makes OAuth sign-ins reject with “The sign-in window was closed before it finished.”
951
958
  `createDummyModel({ providers: 'none' })` (`?providers=none` on `/ui/`) starts with no model provider connected: `models()` offers only the models of connected accounts, and connecting OpenAI offers the invented GPT-6 and GPT-6 mini (its `default`).
952
959
 
953
960
  `createDummyModel({ monitor: 'agent' })` (`?monitor=agent` on `/ui/`) adds the agent's Tasks (a tree by `depth`), Processes and Recent problems sections to the monitor, and its Running vital.
@@ -12,6 +12,7 @@ function clock(delay) {
12
12
 
13
13
  // src/dummy/settings.ts
14
14
  var dummySettingsKey = "slicc-ui.settings";
15
+ var signInClosed = "The sign-in window was closed before it finished.";
15
16
  var DummySettings = class extends Emitter {
16
17
  #settings;
17
18
  #accounts;
@@ -21,8 +22,11 @@ var DummySettings = class extends Emitter {
21
22
  #keys;
22
23
  #signIns = /* @__PURE__ */ new Map();
23
24
  #gated;
24
- constructor(defaults, models, accounts, storage, clock2, keys = [dummySettingsKey], gated = false) {
25
+ #fails;
26
+ connects = [];
27
+ constructor(defaults, models, accounts, storage, clock2, keys = [dummySettingsKey], gated = false, login) {
25
28
  super();
29
+ this.#fails = login === "fail";
26
30
  this.#gated = gated;
27
31
  this.#keys = keys;
28
32
  this.#storage = storage;
@@ -64,8 +68,14 @@ var DummySettings = class extends Emitter {
64
68
  this.emit("accounts", this.accounts());
65
69
  }
66
70
  async connect(id, secret) {
71
+ const activation = globalThis.navigator?.userActivation;
72
+ this.connects.push({ id, gesture: activation ? activation.isActive : null });
67
73
  const account = this.#accounts.find((candidate) => candidate.id === id);
68
74
  if (account && account.auth !== "api-key" && !await this.#signIn(account)) return;
75
+ if (account && account.auth !== "api-key" && this.#fails) {
76
+ this.#set(id, { status: account.status });
77
+ throw new Error(signInClosed);
78
+ }
69
79
  if (account?.auth === "api-key" || !account) await this.#clock.sleep(10);
70
80
  if (account?.auth === "api-key" && account.store !== "vault" && !secret)
71
81
  throw new Error(
@@ -105,4 +115,4 @@ export {
105
115
  dummySettingsKey,
106
116
  DummySettings
107
117
  };
108
- //# sourceMappingURL=chunk-X4C3AZJT.js.map
118
+ //# sourceMappingURL=chunk-VRFAMBWK.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\nexport const dummySettingsKey = 'slicc-ui.settings';\nexport const signInClosed = 'The sign-in window was closed before it finished.';\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 #gated: boolean;\n #fails: boolean;\n readonly connects: Array<{ id: string; gesture: boolean | null }> = [];\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 gated = false,\n login?: 'ok' | 'fail'\n ) {\n super();\n this.#fails = login === 'fail';\n this.#gated = gated;\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 if (!this.#gated) return this.#models;\n const live = new Set(\n this.#accounts\n .filter((account) => account.status === 'connected')\n .map((account) => account.provider)\n );\n return this.#models.filter((option) => live.has(option.provider));\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 activation = (globalThis.navigator as Navigator | undefined)?.userActivation;\n this.connects.push({ id, gesture: activation ? activation.isActive : null });\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 && account.auth !== 'api-key' && this.#fails) {\n this.#set(id, { status: account.status });\n throw new Error(signInClosed);\n }\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
+ "names": ["clock"]
7
+ }
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  DummySettings,
8
8
  clock
9
- } from "./chunk-X4C3AZJT.js";
9
+ } from "./chunk-VRFAMBWK.js";
10
10
  import {
11
11
  Emitter
12
12
  } from "./chunk-T5NHW4KC.js";
@@ -1209,6 +1209,7 @@ function events() {
1209
1209
  title: "docs-pass failed",
1210
1210
  text: "The Adobe session expired. Log in again to continue.",
1211
1211
  action: "login",
1212
+ account: "ai-adobe",
1212
1213
  createdAt: at(1476)
1213
1214
  },
1214
1215
  {
@@ -1578,6 +1579,13 @@ var accounts = [
1578
1579
  { id: "anthropic", provider: "Anthropic", identity: "API key ending 3f2a", status: "connected" },
1579
1580
  { id: "github", provider: "GitHub", identity: "@example-dev", status: "connected" },
1580
1581
  { id: "adobe", provider: "Adobe", identity: "dev@example.com", status: "expired" },
1582
+ {
1583
+ id: "ai-adobe",
1584
+ provider: "Adobe AI",
1585
+ identity: "harbor-ops@example.com",
1586
+ status: "expired",
1587
+ auth: "oauth"
1588
+ },
1581
1589
  { id: "openai", provider: "OpenAI", identity: "", status: "disconnected", auth: "api-key" },
1582
1590
  {
1583
1591
  id: "mcp:docs",
@@ -1994,6 +2002,16 @@ var conversations = {
1994
2002
  title: "Mail connector",
1995
2003
  text: "The connector returned 401. Reconnect the account in Settings.",
1996
2004
  createdAt: at2(-19)
2005
+ },
2006
+ {
2007
+ id: "m-t4",
2008
+ role: "system",
2009
+ kind: "error",
2010
+ action: "login",
2011
+ account: "mcp:tickets",
2012
+ title: "Tickets connector",
2013
+ text: "The tickets server rejected its token.",
2014
+ createdAt: at2(-18)
1997
2015
  }
1998
2016
  ]
1999
2017
  };
@@ -2807,6 +2825,9 @@ function script(prompt, attachments = []) {
2807
2825
  }
2808
2826
 
2809
2827
  // src/dummy/agent.ts
2828
+ function firstLine(text) {
2829
+ return text.split("\n")[0];
2830
+ }
2810
2831
  var paced = /* @__PURE__ */ new Map([["cone-kitchen", 2e4]]);
2811
2832
  function chunks(text) {
2812
2833
  return text.match(/\S+\s*|\s+/g) ?? [];
@@ -3176,7 +3197,7 @@ var DummyAgent = class extends Emitter {
3176
3197
  id,
3177
3198
  kind: "cone",
3178
3199
  name: agent.name,
3179
- title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
3200
+ title: first?.role === "user" ? firstLine(first.text) : agent.name,
3180
3201
  model: agent.model,
3181
3202
  messages: messages.length,
3182
3203
  frozenAt: Date.now()
@@ -3303,7 +3324,7 @@ var DummyAgent = class extends Emitter {
3303
3324
  kind: agent.kind,
3304
3325
  live: true,
3305
3326
  name: agent.name,
3306
- title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
3327
+ title: first?.role === "user" ? firstLine(first.text) : agent.name,
3307
3328
  model: agent.model,
3308
3329
  messages: messages.length,
3309
3330
  frozenAt: messages.at(-1)?.createdAt ?? 0
@@ -3324,7 +3345,7 @@ var DummyAgent = class extends Emitter {
3324
3345
  id: agentId,
3325
3346
  kind: "cone",
3326
3347
  name: agent.name,
3327
- title: first?.role === "user" ? first.text.slice(0, 80) : agent.name,
3348
+ title: first?.role === "user" ? firstLine(first.text) : agent.name,
3328
3349
  model: agent.model,
3329
3350
  messages: messages.length,
3330
3351
  frozenAt: Date.now()
@@ -7206,7 +7227,8 @@ function createDummyModel({
7206
7227
  readonly = "off",
7207
7228
  browser: scenario = "idle",
7208
7229
  sprinkles: sprinkles2 = "builtin",
7209
- monitor = "dummy"
7230
+ monitor = "dummy",
7231
+ login
7210
7232
  } = {}) {
7211
7233
  const time = clock(delay);
7212
7234
  const mount = mounts === "off" ? null : { scenario: mounts, point: mountPoint, files: mounted };
@@ -7251,7 +7273,8 @@ function createDummyModel({
7251
7273
  storage,
7252
7274
  time,
7253
7275
  void 0,
7254
- providers === "none"
7276
+ providers === "none",
7277
+ login
7255
7278
  ),
7256
7279
  memory: new DummyMemory(memories, () => [
7257
7280
  { id: "global", label: "Everyone" },