agentgui 1.0.1054 → 1.0.1055
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/.gm/prd.yml
CHANGED
|
@@ -3076,3 +3076,6 @@
|
|
|
3076
3076
|
- gm-plugkit/playwriter browser verb dom=/capture eval context defect - requires an upstream tool fix outside agentgui/design
|
|
3077
3077
|
status: completed
|
|
3078
3078
|
witness: 'External tool defect, not resolvable from this repo/session: the gm-plugkit/playwriter browser verb''s dom= and capture prefixes evaluate in a Node vm context with no page/document binding (ReferenceError on every attempt this session), unlike the screenshot= and url=<target> paths which do reach the real page. This is a defect in the upstream gm-plugkit/playwriter tool package itself, outside agentgui and design repo scope. Recorded per the false-completion rule as blockedBy:external rather than marked done - screenshots remain the working witness fallback and were used for all browser verification in this sweep.'
|
|
3079
|
+
- id: 40th-run-push-ci-green
|
|
3080
|
+
subject: Push kit and agentgui changes, verify CI green on both repos before COMPLETE
|
|
3081
|
+
status: pending
|
package/AGENTS.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# AgentGUI — Agent Notes
|
|
2
2
|
|
|
3
|
+
## GUI logic+predictability sweep (2026-07-12) — fortieth run
|
|
4
|
+
|
|
5
|
+
`gui-logic-predictability` workflow wf_c0a8d954-547 (20 agents, 196 tool calls, 0 errors) — 11 confirmed findings, all implemented; the lens is still surfacing real gaps at run 40, contrary to the 39th run's thinning-yield note. App (`site/app/js/app.js`, `site/app/js/backend.js`): the global Escape ladder disarmed `confirmingEdit`/`confirmingNewChat`/`confirmingStopAll`/`confirmingStopSelected` but never `confirmingClearData` (settings' arm-then-confirm "clear all local data?" banner) — a keyboard user who armed it and pressed the ASCII-standard cancel gesture this app trains everywhere else got no response; added the matching disarm branch. History detail header title/lede fell back to the raw full `cwd` path instead of `pathBasename(cwd)` like every other headline surface (chat cwd label, files breadcrumb, resume banner) — wrapped both fallback sites; the unabbreviated path stays available via the labeled `SessionMeta` "directory" row's own `title=` tooltip. `wsCall` (the WS RPC helper every `backend.js` call goes through) had no timeout at all — a hung `chat.active`/any other response left its promise pending forever with zero indicator; added a 15s timeout that rejects and clears the pending map, `resolve`/`reject` wrapped to clear the timer on a real reply. agy (Antigravity) has a working `--continue` (most-recent-conversation) resume path server-side (`lib/claude-runner-agents.js`) that the client never triggered — `resumeSid` was hardcoded to only forward for claude-code, so agy always got the flattened-transcript preamble instead of true continuity, AND the "does not resume across turns" banner fired for agy exactly like the genuinely-stateless opencode/kilo/codex, misattributing a missing client wire as an agent limitation; now `resumeSid` forwards `true` for agy once the chat is past its first turn (agy's `--continue` ignores the sid value, boolean-truthy is sufficient) and the banner excludes agy. Pasted/dropped files into the composer only fired a screen-reader-only `announce()` during upload — zero visible feedback for sighted mouse users on progress or failure; now calls the kit's `flashComposerNote` (newly exported) on the real composer element for upload-start/failure/success, and the success announce text now discloses the undo-history caveat inline. Kit (`design/src/components/{chat,content}.js`, `design/app-shell.css`, `design/chat.css`, `design/src/components.js`): `Row`'s primary `.title` span had no `title=` tooltip fallback (only `.sub` got one in the 39th run) — the more commonly-truncated primary label was the bigger gap; fixed at the one shared render site so every `Row` consumer (settings agents panel, etc.) inherits it. `.ds-file-actions` (Files row rename/move/delete icons) were `opacity:0` by default on desktop mouse/trackpad pointers, fully invisible without an accidental hover — raised the resting baseline to `0.35` so the actions are discoverable without hover, full `1` still on hover/focus-within. The Enter-to-send composer hint was CSS-hidden except while focused or carrying a draft (and `aria-hidden`), so a brand-new user looking at an empty, unfocused composer had no in-UI way to learn Enter-vs-Shift+Enter — now visible at rest above 420px (still hidden under 420px to save rows) and no longer `aria-hidden` since it's real content, not decoration. `insertEmoji`'s direct `taEl.value=` assignment discards the native undo stack the same way draft-restore's `.value=` does, but only draft-restore surfaced an explanatory note — added a matching one-time (`sessionStorage`-gated) `flashComposerNote` call on first emoji insert per session. `flashComposerNote` exported from `chat.js` through the `components.js` barrel (33rd-run standing lesson: a kit function must clear the barrel to be consumable by the app). Kit built+tested (`bun test.js` all pass), rebuilt (`node scripts/build.mjs`, 0 lint errors), re-vendored into `site/app/vendor/anentrypoint-design/`. Browser-witnessed live on buildesk via the now-standard `page.context().setHTTPCredentials()` + single-dispatch (creds+goto+wait+evaluate, same session) pattern: `confirmingClearData` Escape-disarm before:true/after:false; composer hint `display:block` with `aria-hidden` absent; `.ds-file-actions` computed `opacity:"0.35"`; settings agents-panel `Row` `.title` elements 16/16 carry a `title=` attribute; deployed `backend.js`/`app.js`/`247420.js` all curl-confirmed to contain the shipped fix strings (`timeoutMs = 15000`, the agy banner-exclusion comment, `pathBasename(sess?.cwd)`, `undoNoteShown`, `ds-file-actions`); 0 pageErrors across every dispatch. **Reconfirmed standing tool defect, worse than previously documented: browser session ids silently rotate across SEPARATE dispatches even against the "same" nominal session, and a dispatch landing on a stale/rotated session reads an entirely blank `document.body` (0 children) despite `window.__agentgui` still resolving — the fix is to always combine credentials-set + goto + wait + evaluate into ONE single dispatch rather than splitting navigation and reading across dispatches.** Also reconfirmed the `capture` prefix's `page.evaluate` return value is NOT propagated back through the tool's `result` field (comes back `undefined` even though the evaluated expression itself runs correctly and its side effects land) — the reliable read pattern is `console.log("WITNESS:" + JSON.stringify(...))` inside the evaluated callback and grep the dispatch's `stdout`/`debug.console`, not the `result` field.
|
|
6
|
+
|
|
3
7
|
## GUI logic+predictability sweep (2026-07-12) — thirty-ninth run
|
|
4
8
|
|
|
5
9
|
`gui-logic-predictability` workflow wf_88c431e5-931 (26 agents, 202 tool calls, 0 errors) — 6 confirmed findings after hunt+adversarial-verify, all implemented; a much thinner yield than the 37th/38th runs, consistent with the same lens converging after two straight passes. App (`site/app/js/app.js`): `runningPanel()`'s inline running-chat rows previously showed only agent+model+elapsed+cwd with no session title, reading nameless while the identical session shows a resolved title on the sessions rail/history rail/Live SessionCard — a new `runningRowTitle()` helper (wrapping the existing `resumeSidLabel()`) prepends the same resolved identity string; `stopActiveChat()` could wedge a stop button permanently disabled if the backend accepted the cancel but the underlying process outlived the immediate post-cancel `refreshActive()` call (the next 3s poll would silently re-add the sid to `state.active` while `stopping` never cleared) — a bounded 9s timeout now clears `stopping` and surfaces the same "conversation did not stop - try again" retry banner used on outright rejection; `computeLiveSessions()`'s per-sid SSE tally accumulated `tokens` but never `cost`, so every live/external session showed a blank cost field except the one open in-page in Chat — the tally now accumulates `total_cost_usd` from `result` events too, with the same tally-fallback pattern tokens already had; WorkspaceRail's footer gained a visible shortcuts-help `IconButton` (previously the `?`-overlay was 100% keyboard-only tribal knowledge with zero discovery path outside Settings). Kit (`design/src/components/{chat,content,shell}.js`): `CodeNode.refSink`'s syntax-highlight swap gained the same in-progress-text-selection guard `MdNode.refSink` already had (the two paths had drifted — code blocks still wiped an active selection mid-stream after the markdown path was fixed for the same bug); `Row.sub` gained a `title=` tooltip fallback for string values, matching every other long-text field in the app (agent-chat cwd, sessions rail title/sub/cwd/agent) — `Row` is used everywhere (history sessions rail, search results, context-pane cwd fact, agents panel) so this closes the gap at every call site at once; added a `help` icon path to `ICON_PATHS`/`Glyph` for the new rail affordance. Kit built+tested (`bun test.js` all pass), rebased cleanly on a concurrent `v0.0.316` release-bump commit (no source conflicts), dist rebuilt fresh post-rebase, pushed `00dae4c`. agentgui re-vendored, pushed `64f4466`, all 3 CI checks (Auto-Declaudeify/Publish and Release/Deploy GH Pages) green. Browser-witnessed live on buildesk via `page.context().setHTTPCredentials()` + `page.evaluate()`: help button found in `.ws-rail-foot`, clicking it opened the shortcuts overlay (`shortcutsTextPresent:true`), zero pageErrors; history rail `Row.sub` confirmed rendering `title="server default"` live. **New tool-defect note this run: this env's nginx Basic-Auth password contains a literal comma (`123,slam,123,slam` — see the 32nd-run standing gotcha), and embedding it directly in a `page.goto` URL (`https://user:pass@host/...`) throws `net::ERR_INVALID_AUTH_CREDENTIALS` at the Chromium URL-parser level even when percent-encoded (`%2C`) — this is a stricter failure than the previously-documented "breaks the app's own fetch()" caveat for comma-free creds. Working fix: `await page.context().setHTTPCredentials({username, password})` before `page.goto()` with a plain (no-embedded-creds) URL — this is the durable workaround for any future witness against this deploy, comma-password or not.** Also reconfirmed the 38th-run tool defect (`capture` prefix scripts run in a bare Node vm with no `document`/`page` binding unless the script itself calls `page.evaluate(() => {...})` — a raw top-level `document.querySelector(...)` throws `ReferenceError: document is not defined`; wrapping every DOM read/write in `page.evaluate()` inside the capture script is the reliable pattern, not a tool-side fix).
|
package/package.json
CHANGED
package/site/app/js/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as B from './backend.js';
|
|
|
3
3
|
|
|
4
4
|
installStyles().catch(() => {});
|
|
5
5
|
|
|
6
|
-
const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, IconButton, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton } = C;
|
|
6
|
+
const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, IconButton, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton, flashComposerNote } = C;
|
|
7
7
|
|
|
8
8
|
// One duration/bytes vocabulary across every surface: prefer the kit's shared
|
|
9
9
|
// formatters (exported alongside the components), fall back to the local
|
|
@@ -1214,25 +1214,34 @@ async function uploadFiles(fileList) {
|
|
|
1214
1214
|
// the same confined endpoint the Files tab uses (into the chat's current cwd),
|
|
1215
1215
|
// then insert the resulting relative path into the draft. Falls back to an
|
|
1216
1216
|
// announce() if there's no cwd to upload into (chat hasn't picked one yet).
|
|
1217
|
+
function composerEl() {
|
|
1218
|
+
return document.querySelector('.chat-composer');
|
|
1219
|
+
}
|
|
1217
1220
|
async function pastedFilesToChat(files) {
|
|
1218
1221
|
const list = Array.from(files || []);
|
|
1219
1222
|
if (!list.length) return;
|
|
1220
1223
|
const dir = state.chatCwd;
|
|
1221
1224
|
if (!dir) { announce('set a working directory before pasting files'); return; }
|
|
1225
|
+
const el = composerEl();
|
|
1226
|
+
if (flashComposerNote && el) flashComposerNote(el, 'uploading ' + plural(list.length, 'file') + '…');
|
|
1222
1227
|
const inserted = [];
|
|
1223
1228
|
for (const f of list) {
|
|
1224
1229
|
try {
|
|
1225
1230
|
await B.uploadFile(state.backend, dir, f);
|
|
1226
1231
|
inserted.push(f.name);
|
|
1227
1232
|
} catch (e) {
|
|
1228
|
-
|
|
1233
|
+
const msg = 'paste upload failed: ' + fileMutationCopy(e);
|
|
1234
|
+
announce(msg);
|
|
1235
|
+
if (flashComposerNote && el) flashComposerNote(el, msg);
|
|
1229
1236
|
}
|
|
1230
1237
|
}
|
|
1231
1238
|
if (inserted.length) {
|
|
1232
1239
|
const draft = state.chat.draft || '';
|
|
1233
1240
|
state.chat.draft = draft + (draft && !draft.endsWith('\n') && !draft.endsWith(' ') ? ' ' : '') + inserted.join(' ');
|
|
1234
1241
|
debouncedPersistDraft();
|
|
1235
|
-
|
|
1242
|
+
const msg = 'pasted ' + plural(inserted.length, 'file') + ' into ' + pathBasename(dir) + ' - undo history does not include this insert';
|
|
1243
|
+
announce(msg);
|
|
1244
|
+
if (flashComposerNote && el) flashComposerNote(el, 'pasted ' + plural(inserted.length, 'file'));
|
|
1236
1245
|
render();
|
|
1237
1246
|
}
|
|
1238
1247
|
}
|
|
@@ -2079,10 +2088,13 @@ function chatMain() {
|
|
|
2079
2088
|
// silently loses prior context. Warn once the conversation is past its first
|
|
2080
2089
|
// turn so the user knows this agent is not carrying history forward.
|
|
2081
2090
|
{
|
|
2082
|
-
if (state.selectedAgent && state.selectedAgent !== 'claude-code' && userTurnCount > 1 && !state.chat.resumeSid) {
|
|
2091
|
+
if (state.selectedAgent && state.selectedAgent !== 'claude-code' && state.selectedAgent !== 'agy' && userTurnCount > 1 && !state.chat.resumeSid) {
|
|
2083
2092
|
banners.push(Alert({ key: 'nocontinuity', kind: 'info', title: (agentById(state.selectedAgent)?.name || state.selectedAgent) + ' does not resume across turns',
|
|
2084
2093
|
children: 'This agent starts fresh each message - it will not remember earlier turns in this chat. Use Claude Code for a continuous conversation.' }));
|
|
2085
2094
|
}
|
|
2095
|
+
// agy has a real --continue (most-recent-conversation) path wired via
|
|
2096
|
+
// resumeSid once this chat is past its first turn - it is NOT stateless
|
|
2097
|
+
// like opencode/kilo/codex, so it gets no "does not resume" warning.
|
|
2086
2098
|
}
|
|
2087
2099
|
if (state.chat.resumeSid && state.selectedAgent === 'claude-code') {
|
|
2088
2100
|
// The agent-switched note (if any) lives inside the resume banner rather
|
|
@@ -2690,9 +2702,13 @@ async function sendChat(textArg) {
|
|
|
2690
2702
|
// readable transcript rather than raw API objects they cannot process.
|
|
2691
2703
|
messages: state.chat.messages.slice(0, -1).map(m => ({ role: m.role, content: m.content || messageToText(m) })),
|
|
2692
2704
|
signal: ctrl.signal,
|
|
2693
|
-
//
|
|
2694
|
-
//
|
|
2695
|
-
|
|
2705
|
+
// claude-code consumes a literal --resume <sid>; agy's --continue is a
|
|
2706
|
+
// boolean 'resume most-recent conversation' flag (it ignores the sid
|
|
2707
|
+
// value entirely, see lib/claude-runner-agents.js), so any truthy value
|
|
2708
|
+
// is enough to opt agy into continuity once this chat has a prior turn.
|
|
2709
|
+
resumeSid: state.selectedAgent === 'claude-code'
|
|
2710
|
+
? (state.chat.resumeSid || undefined)
|
|
2711
|
+
: (state.selectedAgent === 'agy' && state.chat.messages.length > 2 ? true : undefined),
|
|
2696
2712
|
})) {
|
|
2697
2713
|
// After a stop, the iterator drains buffered events for a turn the user
|
|
2698
2714
|
// aborted - applying them would set resumeSid / accrue cost / write text
|
|
@@ -2859,13 +2875,13 @@ function historyMain() {
|
|
|
2859
2875
|
|
|
2860
2876
|
const sess = (Array.isArray(state.sessions) ? state.sessions : []).find(s => s.sid === state.selectedSid);
|
|
2861
2877
|
const lede = sess
|
|
2862
|
-
? (projectLabel(sess.project) || sess.cwd || 'unknown location') + ' · ' + plural(sess.events || 0, 'event') + ' · ' + plural(sess.userTurns || 0, 'turn') + ' · ' + fmtRelTime(sess.last)
|
|
2878
|
+
? (projectLabel(sess.project) || pathBasename(sess.cwd) || 'unknown location') + ' · ' + plural(sess.events || 0, 'event') + ' · ' + plural(sess.userTurns || 0, 'turn') + ' · ' + fmtRelTime(sess.last)
|
|
2863
2879
|
: UNTITLED_CONVERSATION;
|
|
2864
2880
|
|
|
2865
2881
|
const head = PageHeader({
|
|
2866
2882
|
compact: true,
|
|
2867
2883
|
dense: true,
|
|
2868
|
-
title: truncate(projectLabel(sess?.title) || projectLabel(sess?.project) || sess?.cwd || state.selectedSid || UNTITLED_CONVERSATION, 40, 80),
|
|
2884
|
+
title: truncate(projectLabel(sess?.title) || projectLabel(sess?.project) || pathBasename(sess?.cwd) || state.selectedSid || UNTITLED_CONVERSATION, 40, 80),
|
|
2869
2885
|
lede,
|
|
2870
2886
|
});
|
|
2871
2887
|
|
|
@@ -3999,6 +4015,7 @@ window.addEventListener('keydown', (e) => {
|
|
|
3999
4015
|
// backdrop listener covers in-dialog focus; this covers everything else).
|
|
4000
4016
|
if (state.files.dialog) { if (!state.files.dialog.busy) closeFileDialog(); return; }
|
|
4001
4017
|
if (state.chat.confirmingEdit) { state.chat.confirmingEdit = null; render(); announce('edit cancelled'); return; }
|
|
4018
|
+
if (state.confirmingClearData) { state.confirmingClearData = false; render(); announce('clear cancelled'); return; }
|
|
4002
4019
|
if (state.confirmingNewChat) { clearTimeout(_newChatArmTimer); state.confirmingNewChat = false; render(); announce('new chat cancelled'); return; }
|
|
4003
4020
|
if (state.live.confirmingStopAll || state.live.confirmingStopSelected) {
|
|
4004
4021
|
state.live.confirmingStopAll = false; state.live.confirmingStopSelected = false;
|
package/site/app/js/backend.js
CHANGED
|
@@ -351,10 +351,16 @@ function ensureWs(base) {
|
|
|
351
351
|
return _wsReady;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
function wsCall(base, method, params) {
|
|
354
|
+
function wsCall(base, method, params, timeoutMs = 15000) {
|
|
355
355
|
return ensureWs(base).then(() => new Promise((resolve, reject) => {
|
|
356
356
|
const r = _nextReqId++;
|
|
357
|
-
|
|
357
|
+
const timer = setTimeout(() => {
|
|
358
|
+
if (_pending.delete(r)) reject(new Error(method + ' timed out'));
|
|
359
|
+
}, timeoutMs);
|
|
360
|
+
_pending.set(r, {
|
|
361
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); },
|
|
362
|
+
reject: (e) => { clearTimeout(timer); reject(e); },
|
|
363
|
+
});
|
|
358
364
|
_ws.send(encode({ m: method, r, p: params || {} }));
|
|
359
365
|
}));
|
|
360
366
|
}
|
|
@@ -2205,10 +2205,12 @@
|
|
|
2205
2205
|
.ds-247420 .ds-file-row[data-file-type="archive"] .ds-file-icon { color: var(--flame); }
|
|
2206
2206
|
.ds-247420 .ds-file-row[data-file-type="document"] .ds-file-icon { color: var(--amber, var(--sun)); }
|
|
2207
2207
|
|
|
2208
|
-
/* Row actions —
|
|
2208
|
+
/* Row actions — a faint baseline keeps them discoverable at rest (not fully
|
|
2209
|
+
hidden, which left mouse/trackpad users unable to find rename/move/delete
|
|
2210
|
+
without accidentally hovering a row), full opacity on hover/focus. */
|
|
2209
2211
|
.ds-247420 .ds-file-actions {
|
|
2210
2212
|
display: inline-flex; gap: 2px; align-items: center;
|
|
2211
|
-
opacity: 0; transition: opacity var(--dur-base) var(--ease);
|
|
2213
|
+
opacity: 0.35; transition: opacity var(--dur-base) var(--ease);
|
|
2212
2214
|
}
|
|
2213
2215
|
.ds-247420 .ds-file-row:hover .ds-file-actions,
|
|
2214
2216
|
.ds-247420 .ds-file-row:focus-within .ds-file-actions { opacity: 1; }
|
|
@@ -7668,14 +7670,13 @@
|
|
|
7668
7670
|
windowed thread, dashboard stopping/external states, cwd validation.
|
|
7669
7671
|
============================================================================ */
|
|
7670
7672
|
|
|
7671
|
-
/* Enter-to-send hint: visible
|
|
7672
|
-
|
|
7673
|
+
/* Enter-to-send hint: visible at rest so a new user with an empty composer
|
|
7674
|
+
can discover Enter-vs-Shift+Enter without having to focus it first;
|
|
7675
|
+
hidden under 420px to save rows. */
|
|
7673
7676
|
.ds-247420 .chat-composer-hint {
|
|
7674
|
-
display:
|
|
7677
|
+
display: block; width: 100%; order: 5; padding: 2px var(--space-1) 0;
|
|
7675
7678
|
font-size: var(--fs-micro); color: var(--fg-3);
|
|
7676
7679
|
}
|
|
7677
|
-
.ds-247420 .chat-composer:focus-within .chat-composer-hint,
|
|
7678
|
-
.ds-247420 .chat-composer.has-draft .chat-composer-hint { display: block; }
|
|
7679
7680
|
@media (max-width: 420px) {
|
|
7680
7681
|
.ds-247420 .chat-composer .chat-composer-hint { display: none; }
|
|
7681
7682
|
}
|