agentgui 1.0.952 → 1.0.953
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: 'gui-audit',
|
|
3
3
|
description: 'Sweep every agentgui GUI surface for jank, verify findings adversarially, and emit kit-update tasks',
|
|
4
|
-
whenToUse: 'Run a maximum-effort GUI audit of agentgui: review chat, history, settings, a11y, security, performance,
|
|
4
|
+
whenToUse: 'Run a maximum-effort GUI audit of agentgui: review chat, history, settings, a11y, security, performance, glyph hygiene, responsive, consistency, and CSS-override-elimination across the client (site/app) and the design kit (c:\\dev\\anentrypoint-design). Fans out one reviewer per surface, adversarially verifies each finding (with a kept-typography guard so the middot/ellipsis/dash are never wrongly flagged), then synthesizes a prioritized kit-vs-app punch-list. GUI fixes land in the design kit; index.html !important overrides are converted to kit defaults.',
|
|
5
5
|
phases: [
|
|
6
6
|
{ title: 'Review', detail: 'one reviewer agent per GUI surface/aspect' },
|
|
7
7
|
{ title: 'Verify', detail: 'independent skeptic confirms each finding is real' },
|
|
@@ -18,7 +18,8 @@ const SURFACES = [
|
|
|
18
18
|
{ key: 'a11y', prompt: 'Audit agentgui ACCESSIBILITY across site/app and the kit. Check: skip-link target reachability/focus, tab order on each tab, agent/model select labels, EventList row role=button keyboard activation + expand announcement, aria-live over/under-announcing, color contrast on the dark theme (WCAG AA 4.5:1 text / 3:1 UI), focus-visible rings, reduced-motion. Report each issue with file:line.' },
|
|
19
19
|
{ key: 'security', prompt: 'Audit agentgui GUI-reachable SECURITY. Read lib/http-handler.js. Check: Access-Control-Allow-Origin:* with Authorization + ?token= cross-origin/leak risk; /api/image arbitrary-file-read (the .. check runs after path.normalize); markdown XSS (kit marked+dompurify) — does DOMPurify actually neutralize script/javascript: URLs in agent output. Report each issue with file:line, severity, and the concrete exploit path.' },
|
|
20
20
|
{ key: 'perf', prompt: 'Audit agentgui GUI PERFORMANCE. Check: per-token render+scroll thrash on the chat stream (should be rAF-coalesced via scheduleStreamRender), history EventList rebuild cost on burst SSE loads (last-300 map per rAF), webjsx keying stability, markdown/Prism cache warmup, resize/relTime tick cost. Report each hotspot with file:line and a concrete fix.' },
|
|
21
|
-
{ key: 'glyph', prompt: 'Sweep EVERY GUI source file (site/app/**, c:\\dev\\anentrypoint-design/src/**, *.css) for decorative tell-tale glyphs: arrows, box-drawing, bullets, status dots, checks, emoji, any non-ASCII decorative symbol.
|
|
21
|
+
{ key: 'glyph', prompt: 'Sweep EVERY GUI source file (site/app/**, c:\\dev\\anentrypoint-design/src/**, *.css) for decorative tell-tale glyphs: arrows, box-drawing, bullets, status dots, checks, emoji, any non-ASCII decorative symbol. CRITICAL KEPT-SET GUARD: the middot separator, the ellipsis, and em/en-dashes in prose are ESTABLISHED PRODUCT DESIGN per AGENTS.md - they are NOT decorative tells and you MUST NOT flag or convert them. Also keep SVG line-icons via Icon(), CSS-drawn discs (.status-dot-disc), and intentional ascii-art (AICAT_FACE). Report ONLY genuine decorative tells (arrows/box-drawing/bullets/checks/status-dot CHARS/emoji) with file:line and the ASCII replacement.' },
|
|
22
|
+
{ key: 'overrides', prompt: 'Audit site/app/index.html\'s inline <style> for CSS rules that exist ONLY to fight a design-kit (anentrypoint-design) default - especially !important rules and fragile [class*=...] substring selectors. Each such override is a symptom: the kit ships a poor default. For each override report the rule, the kit default it fights, and the concrete KIT change (a component prop or a better default) that would let agentgui DROP the override entirely. These are the highest-value kit fixes. Read c:\\dev\\anentrypoint-design/src/components/{shell.js,content.js,chat.js} and app-shell.css to ground the proposed kit change. Report fixLocation=kit for the kit change and note the app override to remove.' },
|
|
22
23
|
{ key: 'responsive', prompt: 'Audit agentgui RESPONSIVE behavior from site/app/index.html media queries + the kit AppShell. Check: chat controls cluster wrap at 360/640/900px, cwd path overflow, history sidebar drawer (open/close/scrim/select-closes-it), touch-target sizes, settings two-column collapse. Report each issue with the breakpoint and file:line.' },
|
|
23
24
|
{ key: 'consistency', prompt: 'Audit agentgui PREDICTABILITY/CONSISTENCY. Check that the same state reads the same way everywhere: connection/activity status vocabulary across crumb dot, chat status, AgentControls status, settings health, agentsPanel; ellipsis convention (… vs ...); rail tone semantics (green=ok/selected, purple=subagent, flame=error); button labels. Report each divergence with file:line.' },
|
|
24
25
|
];
|
|
@@ -66,7 +67,7 @@ const reviewed = await pipeline(
|
|
|
66
67
|
(review, surface) =>
|
|
67
68
|
parallel((review.findings || []).map((f) => () =>
|
|
68
69
|
agent(
|
|
69
|
-
'Adversarially verify this GUI audit finding. Default to isReal=false if you cannot confirm it from the actual source. Finding: "' + f.title + '" at ' + f.where + ' — ' + f.why,
|
|
70
|
+
'Adversarially verify this GUI audit finding. Default to isReal=false if you cannot confirm it from the actual source. KEPT-SET GUARD: if the finding proposes converting/removing the middot separator, the ellipsis, or an em/en-dash in prose, it is WRONG by policy (these are kept product typography) - return isReal=false with reason "kept typographic set, not a decorative tell". Finding: "' + f.title + '" at ' + f.where + ' — ' + f.why,
|
|
70
71
|
{ label: 'verify:' + surface.key, phase: 'Verify', schema: VERDICT_SCHEMA }
|
|
71
72
|
).then((v) => ({ ...f, surface: surface.key, verdict: v }))
|
|
72
73
|
))
|
package/package.json
CHANGED
package/site/app/index.html
CHANGED
|
@@ -175,30 +175,9 @@
|
|
|
175
175
|
outline: 2px solid var(--accent, var(--agentgui-accent)); outline-offset: 2px;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
/* Topbar
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
rather than an oversized oval. */
|
|
182
|
-
/* Prefix with .ds-247420 (the <html> class) to match the DS selector's
|
|
183
|
-
specificity; source order then lets these win. */
|
|
184
|
-
.ds-247420 .app-topbar nav { display: flex; align-items: center; gap: .15em; }
|
|
185
|
-
.ds-247420 .app-topbar nav a {
|
|
186
|
-
padding: .35em .75em; border-radius: 8px; line-height: 1.4;
|
|
187
|
-
color: var(--fg-2, color-mix(in srgb, var(--fg, var(--agentgui-fg)) 75%, transparent));
|
|
188
|
-
text-decoration: none; background: transparent;
|
|
189
|
-
transition: background-color .15s ease, color .15s ease;
|
|
190
|
-
}
|
|
191
|
-
.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)); }
|
|
192
|
-
/* installStyles() injects the DS CSS into a runtime <style> appended after
|
|
193
|
-
this block, so equal-specificity rules lose on source order; !important is
|
|
194
|
-
the targeted override for the few props we reshape on the active tab. */
|
|
195
|
-
.ds-247420 .app-topbar nav a.active {
|
|
196
|
-
background: color-mix(in srgb, var(--accent, var(--agentgui-accent)) 16%, transparent) !important;
|
|
197
|
-
color: var(--accent, var(--agentgui-accent)) !important;
|
|
198
|
-
box-shadow: inset 0 -2px 0 0 var(--accent, var(--agentgui-accent)) !important;
|
|
199
|
-
border-radius: 8px !important;
|
|
200
|
-
font-weight: 600;
|
|
201
|
-
}
|
|
178
|
+
/* (Topbar active-tab styling - the subtle tinted underline-pill instead of an
|
|
179
|
+
oversized filled oval - now ships as the kit default in app-shell.css, so
|
|
180
|
+
agentgui no longer overrides it here.) */
|
|
202
181
|
|
|
203
182
|
/* Compact working-directory bar (replaces the full-width tall banner box). */
|
|
204
183
|
.cwd-bar {
|
|
@@ -237,23 +216,19 @@
|
|
|
237
216
|
@media (min-width: 900px) {
|
|
238
217
|
.settings-grid { grid-template-columns: minmax(0, 420px) minmax(0, 1fr); }
|
|
239
218
|
}
|
|
240
|
-
/*
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
.agentgui-main [class*="page-header"],
|
|
244
|
-
.agentgui-main .ds-page-header { margin-top: 0 !important; margin-bottom: var(--space-4, 16px) !important; }
|
|
245
|
-
.agentgui-main > :first-child { margin-top: 0 !important; }
|
|
219
|
+
/* (PageHeader top/bottom margin is now handled by the kit's `compact` prop -
|
|
220
|
+
agentgui passes compact:true on its leading PageHeaders, so the fragile
|
|
221
|
+
[class*=page-header] !important overrides are gone.) */
|
|
246
222
|
.agentgui-main-settings .settings-grid { margin-top: 0; }
|
|
247
223
|
|
|
248
224
|
/* The DS Chat head computes its own zero-padded count ("00 msgs") and ignores
|
|
249
225
|
our sub prop; it reads as a bug. Hide the DS sub - streaming state shows via
|
|
250
|
-
the title and the busy banner.
|
|
226
|
+
the title and the busy banner. (The decorative head dot was removed from the
|
|
227
|
+
kit's Chat component, so it no longer needs hiding here.) */
|
|
251
228
|
.chat-head .sub { display: none; }
|
|
252
|
-
.chat-head .dot { display: none; }
|
|
253
229
|
|
|
254
|
-
/*
|
|
255
|
-
|
|
256
|
-
.ds-event-list .row[role="button"] { cursor: pointer; }
|
|
230
|
+
/* (Row role=button cursor:pointer now ships as a kit default in app-shell.css.)
|
|
231
|
+
A slightly stronger hover tint for event rows specifically remains local. */
|
|
257
232
|
.ds-event-list .row[role="button"]:hover { background: color-mix(in srgb, var(--fg, var(--agentgui-fg)) 5%, transparent); }
|
|
258
233
|
/* Flash the row a search result landed on, so the match is obvious. */
|
|
259
234
|
.ds-event-list .row.event-flash { animation: agentgui-event-flash 2s ease-out; }
|
package/site/app/js/app.js
CHANGED
|
@@ -437,6 +437,13 @@ function toolLabel(inp) {
|
|
|
437
437
|
if (inp && typeof inp === 'object') {
|
|
438
438
|
const a = inp.command || inp.file_path || inp.path || inp.pattern || inp.query || inp.url || '';
|
|
439
439
|
if (a) return String(a).slice(0, 160);
|
|
440
|
+
// No salient arg: surface the first scalar property so the card still carries
|
|
441
|
+
// a hint of what it does, rather than an empty, affordance-less label.
|
|
442
|
+
for (const [k, v] of Object.entries(inp)) {
|
|
443
|
+
if (v != null && (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {
|
|
444
|
+
return (k + ': ' + String(v)).slice(0, 160);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
440
447
|
}
|
|
441
448
|
return '';
|
|
442
449
|
}
|
|
@@ -748,6 +755,7 @@ function historyMain() {
|
|
|
748
755
|
return [
|
|
749
756
|
reconnectAlert(),
|
|
750
757
|
PageHeader({
|
|
758
|
+
compact: true,
|
|
751
759
|
title: 'history',
|
|
752
760
|
lede: 'pick a session from the sidebar - events stream live from ccsniff /v1/history.',
|
|
753
761
|
}),
|
|
@@ -767,6 +775,7 @@ function historyMain() {
|
|
|
767
775
|
: state.selectedSid;
|
|
768
776
|
|
|
769
777
|
const head = PageHeader({
|
|
778
|
+
compact: true,
|
|
770
779
|
title: truncate(projectLabel(sess?.title) || projectLabel(sess?.project) || state.selectedSid, 40, 80),
|
|
771
780
|
lede,
|
|
772
781
|
});
|
|
@@ -803,7 +812,7 @@ function historyMain() {
|
|
|
803
812
|
if (allExpanded) { shownKeys.forEach(k => state.expandedEvents.delete(k)); }
|
|
804
813
|
else { shownKeys.forEach(k => state.expandedEvents.add(k)); }
|
|
805
814
|
render();
|
|
806
|
-
}, children: allExpanded ? 'collapse
|
|
815
|
+
}, children: allExpanded ? 'collapse shown' : 'expand shown' }),
|
|
807
816
|
hiddenCount > 0
|
|
808
817
|
? Btn({ key: 'older', onClick: () => { state.eventsLimit += 300; render(); }, children: 'load ' + Math.min(300, hiddenCount) + ' older (' + hiddenCount + ' hidden)' })
|
|
809
818
|
: null,
|
|
@@ -936,7 +945,7 @@ function runningPanel() {
|
|
|
936
945
|
const agentName = agentById(r.agentId)?.name || r.agentId || 'agent';
|
|
937
946
|
const elapsed = r.startedAt ? Math.round((Date.now() - r.startedAt) / 1000) : 0;
|
|
938
947
|
return h('div', { key: 'run' + r.sessionId, class: 'resume-banner', role: 'group' },
|
|
939
|
-
h('span', { key: 'rd', class: 'status-dot-disc status-dot-live', 'aria-hidden': 'true' }),
|
|
948
|
+
h('span', { key: 'rd-' + r.sessionId, class: 'status-dot-disc status-dot-live', 'aria-hidden': 'true' }),
|
|
940
949
|
h('span', { class: 'lede' }, agentName + (r.model ? ' · ' + r.model : '') + ' · ' + elapsed + 's' + (r.cwd ? ' · ' + r.cwd.split(/[/\\]/).filter(Boolean).slice(-1)[0] : '')),
|
|
941
950
|
Btn({ key: 'stop' + r.sessionId, onClick: () => stopActiveChat(r.sessionId), children: 'stop' }));
|
|
942
951
|
}),
|
|
@@ -1107,6 +1116,7 @@ function settingsMain() {
|
|
|
1107
1116
|
const isValid = isValidUrl(state.backendDraft);
|
|
1108
1117
|
return [
|
|
1109
1118
|
PageHeader({
|
|
1119
|
+
compact: true,
|
|
1110
1120
|
title: 'settings',
|
|
1111
1121
|
lede: 'point agentgui at any backend. blank = same-origin (ccsniff in-process). ?backend=... or the field below persists via localStorage.',
|
|
1112
1122
|
}),
|
|
@@ -1128,9 +1138,9 @@ function settingsMain() {
|
|
|
1128
1138
|
onInput: (v) => { state.backendDraft = v; render(); },
|
|
1129
1139
|
}),
|
|
1130
1140
|
!isValid ? h('p', { key: 'err', id: 'backend-url-error', class: 'lede field-error', role: 'alert' }, 'Invalid URL format') : null,
|
|
1131
|
-
state.backendStatus === 'connecting' ? h('p', { key: 'bst', class: 'lede', role: 'status' }, 'connecting…') : null,
|
|
1132
|
-
state.backendStatus === 'ok' ? h('p', { key: 'bst', class: 'lede', role: 'status' }, 'connected') : null,
|
|
1133
|
-
state.backendStatus === 'failed' ? h('p', { key: 'bst', class: 'lede field-error', role: 'alert' }, 'connection failed - check the URL') : null,
|
|
1141
|
+
state.backendStatus === 'connecting' ? h('p', { key: 'bst-connecting', class: 'lede', role: 'status' }, 'connecting…') : null,
|
|
1142
|
+
state.backendStatus === 'ok' ? h('p', { key: 'bst-ok', class: 'lede', role: 'status' }, 'connected') : null,
|
|
1143
|
+
state.backendStatus === 'failed' ? h('p', { key: 'bst-failed', class: 'lede field-error', role: 'alert' }, 'connection failed - check the URL') : null,
|
|
1134
1144
|
state.confirmingBackend ? h('p', { key: 'bcw', class: 'lede field-error', role: 'alert' }, 'changing backend discards this browser\'s chat transcript - press save again to confirm') : null,
|
|
1135
1145
|
healthSummary(),
|
|
1136
1146
|
Btn({
|
|
@@ -1239,6 +1249,15 @@ async function refreshHistory() {
|
|
|
1239
1249
|
// Index by sid so each live SSE event is an O(1) lookup, not an O(sessions)
|
|
1240
1250
|
// linear scan per event during a burst load.
|
|
1241
1251
|
state.sessionsBySid = new Map((state.sessions || []).map(s => [s.sid, s]));
|
|
1252
|
+
// If the selected session vanished from the list (deleted/aged out server-side),
|
|
1253
|
+
// drop the selection so the main pane doesn't sit on stale events that can no
|
|
1254
|
+
// longer be reloaded; fall back to the no-selection empty state.
|
|
1255
|
+
if (state.selectedSid && !state.sessionsBySid.has(state.selectedSid)) {
|
|
1256
|
+
state.selectedSid = null;
|
|
1257
|
+
state.events = [];
|
|
1258
|
+
state.eventsLoaded = false;
|
|
1259
|
+
writeHash(null);
|
|
1260
|
+
}
|
|
1242
1261
|
state.historyError = null;
|
|
1243
1262
|
render();
|
|
1244
1263
|
} catch (e) {
|
|
@@ -1253,6 +1272,10 @@ async function runSearch() {
|
|
|
1253
1272
|
const q = state.searchQ.trim();
|
|
1254
1273
|
if (!q) { state.searchHits = null; state.searchBusy = false; render(); return; }
|
|
1255
1274
|
if (q.length < 2) { state.searchHits = null; state.searchBusy = false; render(); return; }
|
|
1275
|
+
// The project-filter pills are hidden while searching; clear the filter so it
|
|
1276
|
+
// doesn't silently re-apply (and surprise the user) when they later clear the
|
|
1277
|
+
// search and the now-visible session list is unexpectedly narrowed.
|
|
1278
|
+
state.projectFilter = '';
|
|
1256
1279
|
state.searchBusy = true;
|
|
1257
1280
|
render();
|
|
1258
1281
|
try {
|
|
@@ -1274,6 +1297,9 @@ async function loadSession(sid, { focusEventI = null, focusEventTs = null } = {}
|
|
|
1274
1297
|
state.eventsLoaded = false;
|
|
1275
1298
|
state.eventsLimit = 300; // reset the render window per session
|
|
1276
1299
|
state.expandedEvents = new Set(); // don't carry expansion to the new session
|
|
1300
|
+
// The live "live · N" crumb counter reads as the selected session's activity,
|
|
1301
|
+
// so reset it per selection rather than letting it accrue across all sessions.
|
|
1302
|
+
state.live.eventCount = 0;
|
|
1277
1303
|
writeHash(sid, { push: true });
|
|
1278
1304
|
// Close the mobile sidebar drawer on selection. The DS only auto-closes when
|
|
1279
1305
|
// the clicked element is an <a>; agentgui's session rows are onClick divs, so
|
|
@@ -579,6 +579,14 @@
|
|
|
579
579
|
.ds-247420 .fd-root .app { width: 100%; height: 100%; min-height: 0; flex: 1 1 auto; }
|
|
580
580
|
.ds-247420 .fd-root .app-body { min-width: 0; }
|
|
581
581
|
|
|
582
|
+
/* Full-bleed canvas host (e.g. a game/3D viewport that fills the page behind the
|
|
583
|
+
shell): the shell's opaque var(--bg) would paint OVER the canvas and hide it.
|
|
584
|
+
Mark the host with `canvas-host` to make the shell surfaces transparent so the
|
|
585
|
+
live canvas shows through. The canvas itself is the backdrop. */
|
|
586
|
+
.ds-247420 .canvas-host,
|
|
587
|
+
.ds-247420 .canvas-host .app,
|
|
588
|
+
.ds-247420 body.canvas-host { background: transparent !important; }
|
|
589
|
+
|
|
582
590
|
.ds-247420 {
|
|
583
591
|
--app-status-h: var(--size-base);
|
|
584
592
|
--app-topbar-h: var(--size-lg);
|
|
@@ -638,9 +646,14 @@
|
|
|
638
646
|
outline: 2px solid var(--accent);
|
|
639
647
|
outline-offset: 2px;
|
|
640
648
|
}
|
|
649
|
+
/* Active tab reads as a subtle tinted pill with an accent underline rather than
|
|
650
|
+
an oversized filled oval that sits taller than its inactive siblings and reads
|
|
651
|
+
as misaligned. Same height as the inactive links — only the tint + underline
|
|
652
|
+
change. */
|
|
641
653
|
.ds-247420 .app-topbar nav a.active {
|
|
642
|
-
color: var(--accent
|
|
643
|
-
background: var(--accent);
|
|
654
|
+
color: var(--accent);
|
|
655
|
+
background: color-mix(in oklab, var(--accent) 16%, transparent);
|
|
656
|
+
box-shadow: inset 0 -2px 0 0 var(--accent);
|
|
644
657
|
font-weight: 600;
|
|
645
658
|
}
|
|
646
659
|
|
|
@@ -933,6 +946,9 @@
|
|
|
933
946
|
background: transparent; border-radius: 3px;
|
|
934
947
|
transition: background var(--dur-snap) var(--ease);
|
|
935
948
|
}
|
|
949
|
+
/* A clickable row (onClick -> role=button, or a link row) is interactive, so it
|
|
950
|
+
carries a pointer cursor; without this the click affordance is invisible. */
|
|
951
|
+
.ds-247420 .row[role="button"], .ds-247420 a.row { cursor: pointer; }
|
|
936
952
|
.ds-247420 .row:hover { background: var(--bg-2); }
|
|
937
953
|
.ds-247420 .row:hover::before { background: var(--rule-strong); }
|
|
938
954
|
.ds-247420 .row.active { background: color-mix(in oklab, var(--accent) 10%, var(--bg-2)); }
|
|
@@ -981,6 +997,9 @@
|
|
|
981
997
|
Section grouping
|
|
982
998
|
============================================================ */
|
|
983
999
|
.ds-247420 .ds-section { margin: var(--space-8) 0; }
|
|
1000
|
+
/* Compact section (e.g. a PageHeader used as a page's first element): no large
|
|
1001
|
+
leading margin, modest trailing margin — top-aligns without consumer overrides. */
|
|
1002
|
+
.ds-247420 .ds-section-compact { margin-top: 0; margin-bottom: var(--space-4); }
|
|
984
1003
|
.ds-247420 .ds-section > h3 {
|
|
985
1004
|
font-family: var(--ff-body); font-weight: 600; font-size: var(--fs-h3);
|
|
986
1005
|
margin: 0 0 var(--space-5);
|