agentgui 1.0.1001 → 1.0.1003

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/AGENTS.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # AgentGUI — Agent Notes
2
2
 
3
- ## CRITICAL ACP is managed ONLY by `lib/acp-sdk-manager.js` (2026-06-20) — twentieth run
3
+ ## GUI cohesion sweep (2026-07-02) — twenty-first run
4
4
 
5
- A destructive quality run found a second, redundant ACP path: `lib/plugins/acp-plugin.js` eager-spawned all four ACP tools at boot on **conflicting/mismatched ports** (it mapped gemini=18101 / kilo=18102 / codex=18103, but the canonical `acp-sdk-manager.js` uses opencode=18100 / kilo=18101 / codex=18102), implemented no restart/health logic despite a `/api/acp/status` route that promised `restarts`, and **crashed server boot** with `[FATAL] Uncaught exception: Executable not found in $PATH: "codex"` whenever an ACP binary was missing. Its route was consumed by nobody (the client `acpStatusFor` reads `state.health.acp` from `getACPStatus()` → `acp-sdk-manager.js`); no plugin declared an `acp` dependency. **Deleted the file.** Rule: ACP lifecycle (on-demand start, health via `/provider`, restart-backoff, status) lives in `lib/acp-sdk-manager.js` alonenever add a second eager-spawn ACP manager. Also: `spawn()` for a missing binary surfaces ENOENT as an **async `'error'` event** under Bun (it escapes a synchronous try/catch), so every `spawn()` callsite needs a `proc.on('error', …)` handler. `acp-sdk-manager.js startProcess` was the only remaining callsite missing it (every other terminal.js, claude-runner-direct.js, gm-agent-configs.js, claude-runner-acp.js already had one); added `proc.on('error')` routing into the CRASHED + `scheduleRestart` backoff path. Witnessed: clean boot (0 FATAL), `[PLUGINS] Loaded extensions` no longer lists `acp`, `/api/acp/status` 404, tests 46/46, browser localhost:3009/gm/ 0 console errors. Pushed this run.
5
+ `gui-cohesion` workflow wf_6b9f1890-320 (25 agents, 12 confirmed). Kit (`../design`): `WorkspaceRail` items gain an optional `rail` attention-dot tone (a background surface, e.g. an errored Live session, can flag its nav item even while the user is elsewhere) new `.ws-rail-item-flag` CSS. `WorkspaceShell` extends the pane-track `stableFrame` treatment to the sessions column (`keepSessionsTrack`, mirrors `keepPaneTrack`) so navigating chat/history/files/live/settings no longer reflows the 4-column grid to 3 and back. Mobile `ws-sessions`/`ws-pane` drawers now trap Tab/Shift+Tab focus (exported `trapTab` from `overlay-primitives.js`, reused in `shell.js`'s `toggleWsDrawer`)previously Tab could walk focus out into the scrim. `ConversationList` rows accept a richer optional `status` ('error'|'stale'|'running'|'stopping') mirroring `SessionCard`'s `STATUS_DISC`, so a session pinned to the rail's "Running" group shows stuck-vs-busy. `ContextPane` gains a `recentFiles`/`onOpenFile` "recent files" panel. Also picked up inherited uncommitted 19th/20th-run work sitting dirty in the kit tree (agent-chat/chat/files-modals/files a11y+dropzone fixes) commit and push before starting new work if `git status --porcelain` is non-empty at PLAN entry.
6
+
7
+ App (`site/app/js/app.js`): new `computeLiveSessions()` is the single source of truth for the live session set (owned `chat.active` rows + external SSE-observed sessions) — shared by the `WorkspaceRail` Live badge count/error-flame tone AND `liveMain()`'s dashboard, closing a prior gap where the nav badge undercounted external sessions the dashboard showed. Sessions rail now renders on the Live tab too (previously only chat/history), so the "jump between conversations" rail and "all sessions at once" oversight view coexist. Follow-up chip "Open X in files" relabeled "Explain X" — it only ever called `sendChat`, never navigated, so the label lied about its behavior. `recentSessionFiles()` scans Edit/Write/MultiEdit/Read/NotebookEdit tool-call args for `file_path`/`path`/`notebook_path`, deduped, most-recent-first, feeding `ContextPane`. "disconnected" -> "offline" in two remaining spots (live-stream reconnect banner, ACP agent status) to match the connected/connecting/offline vocabulary used everywhere else. `refreshActive()` now skips its network poll while `document.hidden` (resumes immediately on `visibilitychange`) instead of polling every ~3.3s from backgrounded tabs.
8
+
9
+ Server (`lib/http-handler.js`): `/health` now reports `corsWildcardOpen` (true when `CORS_ORIGIN=*` and no `PASSWORD`); the settings panel renders a persistent error `Alert` from it instead of relying on the console-only boot warning an operator not tailing logs would never see.
10
+
11
+ Witnessed: kit build (`node scripts/build.mjs`) — lint-tokens/lint-glyphs/lint-null-children/lint-classes all OK; `test.js` 28/28; browser localhost:3010/gm/ chat/live/settings tabs, 0 console errors, sessions column confirmed present on Live tab post-fix. Kit pushed `anentrypoint-design` `6cfd115` (rebased onto concurrent-writer `fe50db5`, dist regenerated rather than hand-merging the conflicting minified bundle). agentgui pushed `3410178`. Both repos' CI (Test/Publish/Deploy GH Pages) green.
12
+
13
+ ## CRITICAL — ACP is managed ONLY by `lib/acp-sdk-manager.js`
14
+
15
+ Twentieth run deleted a redundant eager-spawn `lib/plugins/acp-plugin.js` (conflicting ports, no restart logic, crashed boot on a missing binary). Rule: ACP lifecycle lives in `lib/acp-sdk-manager.js` alone; every `spawn()` callsite needs a `proc.on('error', …)` handler (ENOENT surfaces async under Bun). Full detail in rs-learn (recall "agentgui 20th run ACP redundant manager").
6
16
 
7
17
  ## GUI quality sweep (2026-06-19) — nineteenth run
8
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.1001",
3
+ "version": "1.0.1003",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -13,6 +13,20 @@
13
13
  `node scripts/build.mjs`, copy dist/247420.{js,css} here, then publish the
14
14
  kit so unpkg stays in sync. -->
15
15
  <link rel="stylesheet" href="./vendor/anentrypoint-design/247420.css">
16
+ <!-- Resolve the saved theme BEFORE first paint, synchronously (not type=module,
17
+ which would defer until the kit's module graph loads) - otherwise a user
18
+ with a stored 'paper'/light preference sees the hardcoded data-theme="dark"
19
+ shell flash before the kit's theme.js applies their real choice. Mirrors
20
+ theme.js's own KEY/VALID set; keep in sync if that module changes. -->
21
+ <script>
22
+ (function () {
23
+ try {
24
+ var v = window.localStorage.getItem('247420:theme');
25
+ var valid = { auto: 1, paper: 1, ink: 1, thebird: 1 };
26
+ document.documentElement.setAttribute('data-theme', valid[v] ? v : 'auto');
27
+ } catch (e) {}
28
+ })();
29
+ </script>
16
30
  <script type="importmap">
17
31
  { "imports": { "anentrypoint-design": "./vendor/anentrypoint-design/247420.js" } }
18
32
  </script>
@@ -738,7 +738,7 @@ function sessionsColumn() {
738
738
  const hits = (state.searchHits.results || []).slice(0, state.sessionsLimit);
739
739
  const items = hits.map((r, i) => ({
740
740
  sid: r.sid,
741
- title: r.snippet || r.title || r.sid,
741
+ title: r.snippet || r.title || ('session ' + String(r.sid).slice(0, 8) + '…'),
742
742
  project: projectLabel(r.project) || '',
743
743
  time: r.ts ? fmtRelTime(r.ts) : '',
744
744
  running: false,
@@ -1902,13 +1902,13 @@ function chatMain() {
1902
1902
  children: [
1903
1903
  h('span', { key: 'cntxt' }, 'This cannot be undone. '),
1904
1904
  Btn({ key: 'cnno', onClick: () => { clearTimeout(_newChatArmTimer); state.confirmingNewChat = false; render(); }, children: 'cancel' }),
1905
- Btn({ key: 'cnyes', danger: true, onClick: confirmNewChat, children: 'yes, clear chat' })] }));
1905
+ Btn({ key: 'cnyes', danger: true, onClick: confirmNewChat, children: 'yes, clear history' })] }));
1906
1906
  }
1907
1907
  if (state.cwdError) {
1908
1908
  banners.push(Alert({ key: 'cwderr', kind: 'warn', title: 'Invalid working directory', children: state.cwdError }));
1909
1909
  }
1910
1910
  if (state.chat.externalUpdate) {
1911
- banners.push(Alert({ key: 'xupd', kind: 'info', title: 'This conversation was updated in another tab',
1911
+ banners.push(Alert({ key: 'xupd', kind: 'info', title: 'This chat was updated in another tab',
1912
1912
  children: [
1913
1913
  h('span', { key: 'xutxt' }, 'Reload it to see the latest turns, or dismiss to keep this tab\'s view. '),
1914
1914
  Btn({ key: 'xureload', disabled: state.chat.busy, onClick: () => {
@@ -3139,9 +3139,9 @@ function preferencesPanel() {
3139
3139
  'local data: ' + fmtBytes(lsBytes) + ' across ' + lsKeys + ' key' + (lsKeys === 1 ? '' : 's')),
3140
3140
  h('div', { key: 'expchatrow', class: 'agentgui-field-my' },
3141
3141
  Btn({ key: 'expchat', disabled: !savedChat,
3142
- title: savedChat ? 'Download the saved chat transcript as JSON' : 'no saved chat',
3142
+ title: savedChat ? 'Download the saved chat transcript as JSON' : 'no saved chat yet',
3143
3143
  onClick: savedChat ? () => downloadBlob(savedChat, 'agentgui-chat-' + dateStamp() + '.json', 'application/json') : undefined,
3144
- children: savedChat ? 'export chat' : 'no saved chat' })),
3144
+ children: 'export chat' })),
3145
3145
  h('div', { key: 'cwdnote', class: 'lede agentgui-field-my' },
3146
3146
  'working directory: set per-chat in the chat composer\'s cwd bar' + (state.chatCwd ? ' (current: ' + state.chatCwd + ')' : ' (currently: server default)')),
3147
3147
  state.confirmingClearData
@@ -3179,10 +3179,10 @@ function agentsPanel() {
3179
3179
  const avail = a.available !== false;
3180
3180
  const usable = avail || a.npxInstallable; // selectable from this row
3181
3181
  const bits = [PROTOCOL_WORDS[a.protocol] || 'agent'];
3182
- if (!avail) bits.push(a.npxInstallable ? 'runs via npx' : 'not installed');
3182
+ if (!avail) bits.push(a.npxInstallable ? 'installs automatically when used' : 'not installed');
3183
3183
  if (acp) bits.push(acp.healthy ? 'connected' : (acp.running ? 'connecting' : 'offline'));
3184
3184
  if (acp && acp.restartCount >= 1) bits.push('restarted ' + acp.restartCount + (acp.restartCount === 1 ? ' time' : ' times'));
3185
- if (acp && !acp.healthy && acp.providerInfo?.error) bits.push(acp.providerInfo.error);
3185
+ if (acp && !acp.healthy && acp.providerInfo?.error) bits.push('provider error: ' + String(acp.providerInfo.error).slice(0, 80));
3186
3186
  if (acp && acp.idleMs > 3_600_000) bits.push(fmtDuration(acp.idleMs) + ' idle');
3187
3187
  return Row({
3188
3188
  key: 'ag' + a.id,
@@ -1129,7 +1129,7 @@
1129
1129
  .ds-247420 .row.rail-green::before,
1130
1130
  .ds-247420 .row.rail-purple::before,
1131
1131
  .ds-247420 .row.rail-flame::before { height: 56%; opacity: 1; }
1132
- .ds-247420 .row.rail-green::before { background: var(--accent); }
1132
+ .ds-247420 .row.rail-green::before { background: var(--accent-ink); }
1133
1133
  .ds-247420 .row.rail-purple::before { background: var(--purple-2); }
1134
1134
  .ds-247420 .row.rail-flame::before { background: var(--flame); }
1135
1135
  /* Differentiate the rail by SHAPE, not hue alone (color-blind safety): error
@@ -3849,7 +3849,7 @@
3849
3849
  /* Crumb band is the top chrome of the content column: a stable height so the
3850
3850
  top edge aligns with the rail head, and a left gutter matching .ws-main so
3851
3851
  the trail text is not flush against the rail border. */
3852
- .ds-247420 .ws-crumb { flex: 0 0 auto; min-height: 48px; padding: 0 var(--space-5); display: flex; align-items: center; gap: var(--space-1); border-bottom: var(--bw-hair) solid var(--bg-3); }
3852
+ .ds-247420 .ws-crumb { flex: 0 0 auto; min-height: calc(var(--app-crumb-h) + var(--space-3)); padding: 0 var(--space-5); display: flex; align-items: center; gap: var(--space-1); border-bottom: var(--bw-hair) solid var(--bg-3); }
3853
3853
  .ds-247420 .ws-crumb-main { flex: 1 1 auto; min-width: 0; }
3854
3854
  /* Content column gutter. Claude-Code-class calm: file grid / dashboard / event
3855
3855
  list / history get a generous, consistent inner gutter instead of butting
@@ -4044,6 +4044,7 @@
4044
4044
  /* Disabled file actions (read-only row / in-flight mutation) stay visible but inert. */
4045
4045
  .ds-247420 .ds-file-act:disabled { opacity: .45; cursor: default; }
4046
4046
  .ds-247420 .ds-file-act:disabled:hover { background: transparent; color: var(--fg-3); }
4047
+ .ds-247420 .ds-file-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
4047
4048
 
4048
4049
  /* ============================================================
4049
4050
  Print — linearize WorkspaceShell so main content prints in full.
@@ -5939,7 +5940,7 @@
5939
5940
  /* Live: a persistent faint concentric ring (resting shape channel, independent
5940
5941
  of motion) with the pulse layered on top — so even at the pulse trough the
5941
5942
  disc reads as solid-fill + ring, never a bare solid that aliases stale. */
5942
- .ds-247420 .status-dot-disc.status-dot-live { background: var(--accent); box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
5943
+ .ds-247420 .status-dot-disc.status-dot-live { background: var(--accent-ink); box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent-ink) 30%, transparent); animation: status-disc-pulse 1.8s ease-in-out infinite; }
5943
5944
  .ds-247420 .status-dot-disc.status-dot-error { background: var(--flame); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--flame) 38%, transparent); }
5944
5945
  .ds-247420 .status-dot-disc.status-dot-connecting { background: transparent; box-shadow: inset 0 0 0 2px var(--amber); }
5945
5946
  /* Stale: a muted ring with a hollow centre — silhouette distinct from live's
@@ -5977,7 +5978,7 @@
5977
5978
  .ds-247420 .ds-session-new:hover { background: var(--bg-2); color: var(--fg); }
5978
5979
  /* Rail filter uses the shared .ds-search-input primitive (provides bg/border/
5979
5980
  radius/focus-ring); only the rail layout + touch floor live here. */
5980
- .ds-247420 .ds-session-head .ds-search-input { order: 1; flex: 1 1 auto; min-width: 0; }
5981
+ .ds-247420 .ds-session-head .ds-search-input { order: 1; flex: 1 1 auto; min-width: 0; min-height: 36px; padding: 0 10px; }
5981
5982
  .ds-247420 .ds-session-new:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
5982
5983
  /* Touch floor (must FOLLOW the base rules - same specificity, order decides). */
5983
5984
  @media (pointer: coarse) {
@@ -5989,7 +5990,7 @@
5989
5990
  label sticks to the top of the scroll area for Claude-Desktop-style headers. */
5990
5991
  .ds-247420 .ds-session-group { display: flex; flex-direction: column; }
5991
5992
  .ds-247420 .ds-session-group-rows { display: flex; flex-direction: column; gap: 2px; }
5992
- .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps); padding: var(--space-2) var(--space-2) var(--space-1); }
5993
+ .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-xs); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps); padding: var(--space-2) var(--space-2) var(--space-1); }
5993
5994
  .ds-247420 .ds-session-row {
5994
5995
  position: relative; display: flex; align-items: center; gap: var(--space-2);
5995
5996
  width: 100%; padding: var(--space-2); min-height: 52px; margin-bottom: 2px;
@@ -6004,7 +6005,7 @@
6004
6005
  }
6005
6006
  /* Rail tones MUST match .row.rail-* in app-shell.css so the same semantic state
6006
6007
  reads as one colour across the conversation list and content rows. */
6007
- .ds-247420 .ds-session-row.rail-green::before { background: var(--accent); }
6008
+ .ds-247420 .ds-session-row.rail-green::before { background: var(--accent-ink); }
6008
6009
  .ds-247420 .ds-session-row.rail-purple::before { background: var(--purple-2); }
6009
6010
  .ds-247420 .ds-session-row.rail-flame::before { background: var(--flame); }
6010
6011
  .ds-247420 .ds-session-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
@@ -6067,8 +6068,8 @@
6067
6068
  ---------------------------------------------------------------------------- */
6068
6069
  .ds-247420 .ds-context { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-3); }
6069
6070
  .ds-247420 .ds-context .panel { background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-2); }
6070
- .ds-247420 .ds-context .row .meta { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-2); }
6071
- .ds-247420 .ds-context .row .sub { font-family: var(--ff-mono); font-size: var(--fs-tiny); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
6071
+ .ds-247420 .ds-context .row .meta { font-family: var(--ff-mono); font-size: var(--fs-xs); color: var(--fg-2); }
6072
+ .ds-247420 .ds-context .row .sub { font-size: var(--fs-xs); color: var(--fg-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
6072
6073
  .ds-247420 .ds-context-actions { display: flex; gap: var(--space-2); }
6073
6074
 
6074
6075
  /* ============================================================================
@@ -6090,7 +6091,7 @@
6090
6091
  opacity: 0; transition: opacity var(--dur-snap, .12s) var(--ease, ease);
6091
6092
  }
6092
6093
  .ds-247420 .chat-code-block:hover .chat-code-copy, .ds-247420 .chat-code-block:focus-within .chat-code-copy { opacity: 1; }
6093
- .ds-247420 .chat-code-copy.is-copied { color: var(--accent); border-color: var(--accent); }
6094
+ .ds-247420 .chat-code-copy.is-copied { color: var(--accent-ink); border-color: var(--accent); }
6094
6095
  .ds-247420 .chat-code-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1; }
6095
6096
  /* Head-placed copy (structured CodeNode + FilePreviewCode) is always visible. */
6096
6097
  .ds-247420 .chat-code-head .spread { flex: 1; }
@@ -6137,7 +6138,7 @@
6137
6138
 
6138
6139
  /* --- C1: stale/idle dashboard status (static, NOT pulsing). --- */
6139
6140
  .ds-247420 .ds-dash-status.is-stale { color: var(--stale); }
6140
- .ds-247420 .ds-dash-status.is-running { color: var(--accent); }
6141
+ .ds-247420 .ds-dash-status.is-running { color: var(--accent-ink); }
6141
6142
 
6142
6143
  /* --- C2: dashboard sort/filter toolbar + stream-state line. --- */
6143
6144
  /* The toolbar is part of the ONE header row (count | heartbeat ... filter,
@@ -6153,7 +6154,7 @@
6153
6154
  width: auto; border-radius: var(--r-1);
6154
6155
  }
6155
6156
  .ds-247420 .ds-dash-stream { font-size: var(--fs-tiny); color: var(--fg-3); }
6156
- .ds-247420 .ds-dash-stream.is-connected { color: var(--accent); }
6157
+ .ds-247420 .ds-dash-stream.is-connected { color: var(--accent-ink); }
6157
6158
  .ds-247420 .ds-dash-stream.is-lost { color: var(--flame); }
6158
6159
  .ds-247420 .ds-dash-stream.is-connecting { color: var(--amber); }
6159
6160
  .ds-247420 .ds-dash-header .spread { flex: 1; }
@@ -6172,7 +6173,7 @@
6172
6173
  border: none; background: none; color: var(--fg-3);
6173
6174
  font-family: var(--ff-mono); font-size: var(--fs-sm);
6174
6175
  }
6175
- .ds-247420 .ds-dash-select[aria-checked="true"] { color: var(--accent); }
6176
+ .ds-247420 .ds-dash-select[aria-checked="true"] { color: var(--accent-ink); }
6176
6177
  .ds-247420 .ds-dash-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
6177
6178
 
6178
6179
  /* --- C5: active dashboard card (current conversation). --- */
@@ -6336,7 +6337,7 @@
6336
6337
 
6337
6338
  /* Dashboard: shared session title heading (same string as the rails). */
6338
6339
  .ds-247420 .ds-dash-title {
6339
- font-size: var(--fs-body); font-weight: 600; color: var(--fg);
6340
+ font-size: var(--fs-sm); font-weight: 600; color: var(--fg);
6340
6341
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
6341
6342
  }
6342
6343
 
@@ -6370,7 +6371,7 @@
6370
6371
  .ds-247420 .chat-msg-flat .chat-stack { max-width: var(--measure); width: 100%; margin: 0; align-items: stretch; }
6371
6372
  .ds-247420 .chat-msg-flat.you .chat-stack { align-items: stretch; }
6372
6373
  .ds-247420 .chat-role { font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); margin-bottom: var(--space-1); }
6373
- .ds-247420 .chat-msg-flat.you .chat-role { color: var(--accent); }
6374
+ .ds-247420 .chat-msg-flat.you .chat-role { color: var(--accent-ink); }
6374
6375
  .ds-247420 .chat-msg-flat .chat-bubble { background: none; border: none; border-radius: 0; padding: 0; max-width: 100%; box-shadow: none; transform: none; }
6375
6376
  .ds-247420 .chat-msg-flat.you .chat-bubble { background: none; color: inherit; }
6376
6377
  /* Flat user turns reset their bubble to transparent, but the messenger rule
@@ -6443,7 +6444,7 @@
6443
6444
  padding: 2px var(--space-2); border-radius: var(--r-pill);
6444
6445
  background: color-mix(in oklab, var(--fg) 6%, transparent);
6445
6446
  }
6446
- .ds-247420 .chat-msg .chat-tool.tool-running .chat-tool-status { color: var(--accent); background: color-mix(in oklab, var(--accent) 12%, transparent); }
6447
+ .ds-247420 .chat-msg .chat-tool.tool-running .chat-tool-status { color: var(--accent-ink); background: color-mix(in oklab, var(--accent) 12%, transparent); }
6447
6448
  .ds-247420 .chat-msg .chat-tool.tool-error .chat-tool-status { color: var(--flame); background: color-mix(in oklab, var(--flame) 12%, transparent); }
6448
6449
  /* Completed tool: a positive (success) tone so done reads distinctly from the
6449
6450
  neutral pill and from the accent-toned running pill (--success = green-2). */
@@ -6508,7 +6509,7 @@
6508
6509
  shape, not hue alone. CSS-drawn discs, not glyphs (glyph policy clean). */
6509
6510
  .ds-247420 .ds-dash-breakdown .seg { display: inline-flex; align-items: center; gap: var(--space-1); font-weight: 600; }
6510
6511
  .ds-247420 .ds-dash-breakdown .seg::before { content: ''; width: 7px; height: 7px; border-radius: 50%; flex: none; }
6511
- .ds-247420 .ds-dash-breakdown .seg.is-running { color: var(--accent); }
6512
+ .ds-247420 .ds-dash-breakdown .seg.is-running { color: var(--accent-ink); }
6512
6513
  .ds-247420 .ds-dash-breakdown .seg.is-running::before { background: var(--accent); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--accent) 30%, transparent); }
6513
6514
  .ds-247420 .ds-dash-breakdown .seg.is-error { color: var(--flame); }
6514
6515
  .ds-247420 .ds-dash-breakdown .seg.is-error::before { background: var(--flame); box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--flame) 38%, transparent); }