agentgui 1.0.940 → 1.0.942

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
@@ -16,14 +16,9 @@ Dependencies:
16
16
  - `ccsniff` (>=1.1.0) — exports `createHistoryRouter({projectsDir})` mountable on Express; serves `/v1/history/{sessions,sessions/:sid/events,search,snapshot,reindex,stream}`. Reads `~/.claude/projects` (override via `CLAUDE_PROJECTS_DIR`).
17
17
  - `anentrypoint-design` (>=0.0.119) — kit library, single-file ESM from unpkg
18
18
 
19
- ## Browser Witness (2026-05-19)
19
+ ## Browser Witness
20
20
 
21
- Local server on PORT=3056 (default), `bun server.js`:
22
- - `GET /health` → 200 JSON
23
- - `GET /v1/history/sessions` → `{"sessions":[]}` from ccsniff
24
- - `GET /` → site/app/index.html
25
- - WS `/sync` → opens, sync_connected
26
- - Browser at `localhost:3056/`: AppShell renders, nav=[chat,history,settings], SSE `hello` received (live.connected=true, eventCount=1), 0 console errors, backend resolves to `''` (same origin).
21
+ `bun server.js`. Default `PORT=3000` (server.js); the SPA is served under `BASE_URL` (default `/gm/`), so the live app is **http://localhost:3000/gm/** — `/health` and `/` answer at root, the app is under `/gm/`. First request to `/gm/` or `/v1/history/*` triggers a 30-90s ccsniff JSONL walk (curl with a short timeout returns 000 during warmup). AppShell renders nav=[chat,history,settings], SSE `hello`, 0 console errors, backend resolves to `''` (same origin).
27
22
 
28
23
  ## Learning audit
29
24
 
@@ -149,3 +144,7 @@ The GUI runs fully offline. `site/app/vendor/cdn/` holds marked, dompurify, pris
149
144
  - `agents.list` (WS) returns `available` + `npxInstallable` per agent; `agents.models` returns model choices (claude-code → sonnet/opus/haiku). The chat picker is **agent-then-model**, not a flat model list. Unavailable agents are disabled/gated.
150
145
  - `chat.sendMessage` accepts `cwd` (defaults to STARTUP_CWD) and `model`/`agentId` separately. `chat.active` (WS) lists in-flight chats with agentId/model/cwd/startedAt/pid; the history tab polls it (3s) and shows a running panel with per-session stop.
151
146
  - Client (`app.js`): chat transcript persists to `localStorage[agentgui.chat]` and restores on load; tool_use/result events render as chat parts; keyboard shortcuts (g+c/h/s, n, /, ?); settings has an agents-status panel from `health.acp[]`.
147
+
148
+ ## DS CSS cascade — overriding component styles (2026-05-28)
149
+
150
+ **`installStyles()` injects DS CSS into a runtime `<style>` after the head `<style>`, so local overrides need `!important` or higher specificity than the DS's `.ds-247420`-prefixed rules.** Full detail (font vars `--ff-display`/`--ff-mono`, the `.chat-head .sub` "00 msgs" quirk, EventList `.row[role=button]`, `[data-prog-focus]` focus suppression, `projectLabel()` for cwd slugs) is in rs-learn (recall "agentgui GUI styling DS cascade installStyles").
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.940",
3
+ "version": "1.0.942",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -173,9 +173,18 @@ const onServerListenStart = () => {
173
173
  loadPluginExtensions();
174
174
  };
175
175
 
176
+ let _portRetries = 0;
177
+ const MAX_PORT_RETRIES = 5;
176
178
  server.on('error', (err) => {
177
179
  if (err.code === 'EADDRINUSE') {
178
- console.error(`Port ${PORT} already in use. Waiting 3 seconds before retry...`);
180
+ _portRetries++;
181
+ if (_portRetries > MAX_PORT_RETRIES) {
182
+ // Bail instead of retrying forever — an unbounded retry loop leaks a
183
+ // live process that holds no useful port and accumulates on each launch.
184
+ console.error(`Port ${PORT} still in use after ${MAX_PORT_RETRIES} retries; exiting. Free the port or set PORT to a different value.`);
185
+ process.exit(1);
186
+ }
187
+ console.error(`Port ${PORT} already in use. Retry ${_portRetries}/${MAX_PORT_RETRIES} in 3s...`);
179
188
  setTimeout(() => { server.listen(PORT, onServerListenStart); }, 3000);
180
189
  } else {
181
190
  console.error('[SERVER] Error (contained):', err.message);
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
- <html lang="en" class="ds-247420" data-theme="dark">
2
+ <html lang="en" class="ds-247420" data-theme="dark" data-typescale="app">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -21,11 +21,39 @@
21
21
  height: 100%;
22
22
  background: var(--bg, var(--agentgui-bg));
23
23
  color: var(--fg, var(--agentgui-fg));
24
- font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);
24
+ /* The DS exposes --ff-display / --ff-mono (there is no --font-sans), so the
25
+ old reference fell through to the system font. Use the DS display face. */
26
+ font-family: var(--ff-display, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);
25
27
  }
26
28
  #app { height: 100vh; height: 100dvh; }
27
29
  #app > * { height: 100%; }
28
30
 
31
+ /* Themed thin scrollbars — the native chrome scrollbar is chunky/light and
32
+ clashes with the dark theme on the history sidebar and settings column. */
33
+ * {
34
+ scrollbar-width: thin;
35
+ scrollbar-color: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 22%, transparent) transparent;
36
+ }
37
+ *::-webkit-scrollbar { width: 9px; height: 9px; }
38
+ *::-webkit-scrollbar-track { background: transparent; }
39
+ *::-webkit-scrollbar-thumb {
40
+ background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 20%, transparent);
41
+ border-radius: 999px;
42
+ border: 2px solid transparent;
43
+ background-clip: padding-box;
44
+ }
45
+ *::-webkit-scrollbar-thumb:hover {
46
+ background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 34%, transparent);
47
+ background-clip: padding-box;
48
+ }
49
+
50
+ /* We move focus to the page heading on tab change for AT users; suppress the
51
+ resulting green outline box on those programmatically-focused elements.
52
+ !important + .ds-247420 prefix to beat the runtime-injected DS focus rule. */
53
+ .ds-247420 [data-prog-focus]:focus,
54
+ .ds-247420 [data-prog-focus]:focus-visible,
55
+ [data-prog-focus]:focus, [data-prog-focus]:focus-visible { outline: none !important; box-shadow: none !important; }
56
+
29
57
  /* skip link for keyboard/AT users */
30
58
  .skip-link {
31
59
  position: absolute; left: -9999px; top: 0; z-index: 1000;
@@ -90,32 +118,17 @@
90
118
 
91
119
  .field-error { color: var(--warn, var(--agentgui-warn)); }
92
120
 
93
- /* chat control cluster in the crumb bar: keep model picker, +new, status
94
- on one tidy baseline-aligned row instead of wrapping under the nav. */
95
- .chat-controls { display: flex; align-items: center; gap: .6em; flex-wrap: nowrap; }
121
+ /* chat control cluster in the crumb bar: model picker, +new, status.
122
+ Allowed to wrap so it never overflows the crumb on tablet widths. */
123
+ .chat-controls { display: flex; align-items: center; gap: .6em; flex-wrap: wrap; }
96
124
  .chat-controls > * { flex: 0 0 auto; }
97
125
  .chat-controls select,
98
126
  .chat-controls .select,
99
- .chat-controls [role="combobox"] { min-width: 150px; max-width: 220px; }
127
+ .chat-controls [role="combobox"] { min-width: 130px; max-width: 220px; }
100
128
  .chat-controls .status-dot { white-space: nowrap; }
101
129
 
102
- /* Tame the design-system hero-sized PageHeader h1 to a sensible page title.
103
- The DS default (--fs-h1, cqi-scaled to ~64-80px) overpowers app content. */
104
- .agentgui-main .ds-section > h1 {
105
- font-size: clamp(22px, 2.2vw, 30px);
106
- line-height: 1.15;
107
- margin-bottom: .35em;
108
- }
109
130
  .agentgui-main .ds-section { margin: 0 0 var(--space-4, 16px); }
110
131
 
111
- /* TextField: stack label above the input so the label can't overlap it. */
112
- .agentgui-main .ds-field {
113
- display: flex; flex-direction: column; gap: .35em; align-items: stretch;
114
- }
115
- .agentgui-main .ds-field-label { font-size: .8rem; color: var(--fg-2, #ccc); }
116
- .agentgui-main .ds-field input,
117
- .agentgui-main .ds-field textarea { width: 100%; }
118
-
119
132
  /* readable backend health summary (replaces raw JSON dump) */
120
133
  .health-summary { display: flex; flex-wrap: wrap; gap: .4em; margin: .6em 0; }
121
134
  .health-chip {
@@ -130,20 +143,8 @@
130
143
  border-color: color-mix(in srgb, var(--accent, var(--agentgui-accent)) 40%, transparent);
131
144
  }
132
145
 
133
- /* search input: match the dark theme instead of a bare white box */
134
- .app input[type="search"],
135
- .app .search-input input,
136
- .app input.search,
137
- input[type="search"] {
138
- background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 6%, transparent);
139
- color: var(--fg, var(--agentgui-fg));
140
- border: 1px solid color-mix(in srgb, var(--fg, var(--agentgui-fg)) 16%, transparent);
141
- border-radius: 8px; padding: .5em .7em;
142
- }
143
- .app input[type="search"]::placeholder { color: var(--fg-3, #888); }
144
- .app input[type="search"]:focus-visible {
145
- outline: 2px solid var(--accent, var(--agentgui-accent)); outline-offset: 1px;
146
- }
146
+ /* (search-input theming now provided by the design system's .ds-search-input
147
+ + base input rules; the local reskin hack was removed.) */
147
148
 
148
149
  /* empty-state: keep it from wrapping awkwardly in the narrow sidebar */
149
150
  .empty-state { white-space: normal; }
@@ -154,6 +155,92 @@
154
155
  outline: 2px solid var(--accent, var(--agentgui-accent)); outline-offset: 2px;
155
156
  }
156
157
 
158
+ /* Topbar nav: the DS renders the active tab as a large filled green pill that
159
+ sits taller than the inactive text links and reads as misaligned. Make all
160
+ tabs consistent — equal padding, the active one a subtle tinted underline-pill
161
+ rather than an oversized oval. */
162
+ /* Prefix with .ds-247420 (the <html> class) to match the DS selector's
163
+ specificity; source order then lets these win. */
164
+ .ds-247420 .app-topbar nav { display: flex; align-items: center; gap: .15em; }
165
+ .ds-247420 .app-topbar nav a {
166
+ padding: .35em .75em; border-radius: 8px; line-height: 1.4;
167
+ color: var(--fg-2, color-mix(in srgb, var(--fg, var(--agentgui-fg)) 75%, transparent));
168
+ text-decoration: none; background: transparent;
169
+ transition: background-color .15s ease, color .15s ease;
170
+ }
171
+ .ds-247420 .app-topbar nav a:hover { background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 8%, transparent); color: var(--fg, var(--agentgui-fg)); }
172
+ /* installStyles() injects the DS CSS into a runtime <style> appended after
173
+ this block, so equal-specificity rules lose on source order; !important is
174
+ the targeted override for the few props we reshape on the active tab. */
175
+ .ds-247420 .app-topbar nav a.active {
176
+ background: color-mix(in srgb, var(--accent, var(--agentgui-accent)) 16%, transparent) !important;
177
+ color: var(--accent, var(--agentgui-accent)) !important;
178
+ box-shadow: inset 0 -2px 0 0 var(--accent, var(--agentgui-accent)) !important;
179
+ border-radius: 8px !important;
180
+ font-weight: 600;
181
+ }
182
+
183
+ /* Compact working-directory bar (replaces the full-width tall banner box). */
184
+ .cwd-bar {
185
+ display: flex; align-items: center; gap: .5em; flex-wrap: wrap;
186
+ padding: .15em 0 .5em; font-size: .85rem;
187
+ }
188
+ .cwd-bar-text {
189
+ font-family: var(--ff-mono, ui-monospace, monospace);
190
+ color: var(--fg-3, #999); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60vw;
191
+ }
192
+ .cwd-bar-btn {
193
+ cursor: pointer; font: inherit; line-height: 1.3;
194
+ padding: .15em .55em; border-radius: 6px;
195
+ background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 8%, transparent);
196
+ border: 1px solid color-mix(in srgb, var(--fg, var(--agentgui-fg)) 14%, transparent);
197
+ color: var(--fg-2, var(--agentgui-fg));
198
+ }
199
+ .cwd-bar-btn:hover { background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 14%, transparent); }
200
+ .cwd-bar-btn:focus-visible { outline: 2px solid var(--accent, var(--agentgui-accent)); outline-offset: 2px; }
201
+
202
+ /* History no-session empty state: fill the void with a centered prompt. */
203
+ .history-empty {
204
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
205
+ gap: .4em; text-align: center; min-height: 50vh; color: var(--fg-3, #888);
206
+ }
207
+ .history-empty-glyph { font-size: 3rem; opacity: .25; line-height: 1; }
208
+ .history-empty-title { margin: 0; font-size: 1.05rem; color: var(--fg-2, var(--agentgui-fg)); }
209
+ .history-empty-sub { margin: 0; max-width: 42ch; }
210
+
211
+ /* Settings: two-column on wide screens (backend + agents) so it uses the
212
+ full width instead of a cramped centered measure with empty margins. */
213
+ .settings-grid {
214
+ display: grid; gap: var(--space-4, 16px);
215
+ grid-template-columns: minmax(0, 1fr);
216
+ align-items: start;
217
+ }
218
+ @media (min-width: 900px) {
219
+ .settings-grid { grid-template-columns: minmax(0, 420px) minmax(0, 1fr); }
220
+ }
221
+ /* The DS PageHeader carries large vertical margins that leave a big empty
222
+ band above the heading and between the lede and the panels. Neutralize
223
+ them so settings/history read as normal top-aligned scrolling pages. */
224
+ .agentgui-main [class*="page-header"],
225
+ .agentgui-main .ds-page-header { margin-top: 0 !important; margin-bottom: var(--space-4, 16px) !important; }
226
+ .agentgui-main > :first-child { margin-top: 0 !important; }
227
+ .agentgui-main-settings .settings-grid { margin-top: 0; }
228
+
229
+ /* The DS Chat head computes its own zero-padded count ("00 msgs") and ignores
230
+ our sub prop; it reads as a bug. Hide the DS sub — streaming state shows via
231
+ the title and the busy banner. Also hide the DS's empty decorative head dot. */
232
+ .chat-head .sub { display: none; }
233
+ .chat-head .dot { display: none; }
234
+
235
+ /* EventList rows are role=button (click to expand) but the DS doesn't give
236
+ them a pointer cursor, so the affordance is invisible. */
237
+ .ds-event-list .row[role="button"] { cursor: pointer; }
238
+ .ds-event-list .row[role="button"]:hover { background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 5%, transparent); }
239
+
240
+ /* Chat composer: hide the idle scrollbar on the (empty/short) textarea. */
241
+ .chat-composer textarea { overflow-y: auto; scrollbar-width: thin; }
242
+ .chat-composer textarea:not(:focus) { overflow-y: hidden; }
243
+
157
244
  /* touch targets on small screens */
158
245
  @media (max-width: 640px) {
159
246
  .pill { min-height: 36px; padding: .4em .8em; }
@@ -161,9 +248,13 @@
161
248
  }
162
249
 
163
250
  @media print {
164
- #app { min-height: auto; display: block; }
165
- .skip-link, .status-dot, .history-actions { display: none !important; }
166
- * { background: #fff !important; color: #000 !important; box-shadow: none !important; }
251
+ #app { min-height: auto; display: block; height: auto; }
252
+ .skip-link, .status-dot, .history-actions, .chat-composer { display: none !important; }
253
+ /* Drop chrome backgrounds/shadows for ink-saving, but preserve code
254
+ highlighting and rail colors (don't nuke every color to black). */
255
+ .app, .app-main, .panel, .chat, .chat-thread {
256
+ background: #fff !important; color: #000 !important; box-shadow: none !important;
257
+ }
167
258
  }
168
259
  </style>
169
260
  </head>