agentgui 1.0.1008 → 1.0.1010
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 +4 -0
- package/package.json +1 -1
- package/site/app/js/app.js +47 -12
- package/site/app/vendor/anentrypoint-design/247420.js +5 -5
package/AGENTS.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# AgentGUI — Agent Notes
|
|
2
2
|
|
|
3
|
+
## Mid-thread retry (2026-07-02) — twenty-fifth run
|
|
4
|
+
|
|
5
|
+
Landed the highest-severity finding deferred from the 24th run (gui-completion #7): retry was hard-gated to the LAST message only — a user unhappy with an earlier assistant reply had no way to redo it. Kit `AgentChat` now offers the retry action on every settled assistant turn, not just `i === lastIdx`; `onRetryMessage(m)` passes the specific message. App `retryTurn(m)` truncates+resends from `m`'s preceding user turn (falls back to the trailing assistant turn when called with no argument, preserving the old "retry last" behavior). **Retrying anything before the trailing turn discards every later turn too** — it now arms the same confirm banner edit-and-resend already uses (generalized with a `kind: 'retry'` tag: "Retry this turn? Retrying will remove the later turns") instead of firing immediately; retrying the actual last turn (no data loss) still executes immediately. Caught and fixed a real bug while generalizing: the old `retryLastTurn()` never cleared `state.chat.resumeSid`/`resumeNote` on truncation, unlike `confirmEditAndResend()` which already did per the "never `--resume` a diverged tail" rule — both paths now share `executeTruncateAndResend()` so this can't drift apart again. Kit pushed `834c81d`, agentgui pushed `18cd971`. Browser-witnessed end-to-end (mid-thread retry click -> confirm banner armed with the correct index -> confirm -> truncated to exactly the retried turn -> resent -> resumeSid cleared), 0 console errors, 28/28 tests. **Note:** this run's Deploy GH Pages CI step flaked twice ("Timeout reached" / "Deployment cancelled") before succeeding on a 3rd rerun — an infra flake in the docs-site deploy environment, not a code regression (`npm publish`, the actual consumer artifact, was green from the first attempt every time).
|
|
6
|
+
|
|
3
7
|
## GUI completion sweep (2026-07-02) — twenty-fourth run
|
|
4
8
|
|
|
5
9
|
`gui-completion` workflow wf_4a731841-832 (33 agents, 17 confirmed). Landed 5 of the smaller-scope findings: Live-tab dashboard state (sort/filter/errors-only) now round-trips through the URL hash (`lsort=`/`lfilter=`/`lerr=1`) and localStorage — previously `lv.filter` wasn't persisted anywhere and the whole dashboard view reverted to defaults on reload. Shortcuts overlay (`?`) now wraps its content in the kit's existing `FocusTrap` component instead of a bare `role=dialog` div — Tab could previously escape into the page behind the overlay. History event rows expose copy even when collapsed (was expanded-only). Settings agents panel gives a not-installed direct-runner agent (claude-code/agy) a "re-check" button, matching the ACP rows' "restart" affordance. **Caught and fixed a real CSRF-guard test gap**: adding the regression test for the upload-PUT route (`scripts/validate-mutations.mjs`), the first draft accidentally included the `Authorization` header — matching every other case in the file but testing the wrong scenario (a stolen-token request, not real cross-site CSRF) — and passed trivially at 200. Corrected to omit auth (the path a real cross-site attacker is actually limited to); the server's existing guard correctly rejects it. **Rule for any CSRF/auth regression test: match the real attacker's capability set exactly — copying a nearby test's header pattern can silently test the wrong threat model.** 12 findings deferred to their own re-scoped PRD row (mid-thread retry/fork, file-management move UX with folder-tree picker, event-level permalinks, rolling context-size indicator — each needs a larger architecture change than a targeted fix). No kit changes this run. Pushed `d3e2d1e`. 28/28 tests, 17/17 validate-mutations, 0 console errors.
|
package/package.json
CHANGED
package/site/app/js/app.js
CHANGED
|
@@ -1945,11 +1945,12 @@ function chatMain() {
|
|
|
1945
1945
|
children: [Spinner({ key: 'trspin', size: 'sm' })] }));
|
|
1946
1946
|
}
|
|
1947
1947
|
if (state.chat.confirmingEdit) {
|
|
1948
|
-
|
|
1948
|
+
const isRetry = state.chat.confirmingEdit.kind === 'retry';
|
|
1949
|
+
banners.push(Alert({ key: 'confedit', kind: 'warn', title: isRetry ? 'Retry this turn?' : 'Edit this message?',
|
|
1949
1950
|
children: [
|
|
1950
|
-
h('span', { key: 'cetext' }, 'Editing will remove the later turns - continue? '),
|
|
1951
|
+
h('span', { key: 'cetext' }, (isRetry ? 'Retrying' : 'Editing') + ' will remove the later turns - continue? '),
|
|
1951
1952
|
Btn({ key: 'ceno', onClick: cancelEditAndResend, children: 'cancel' }),
|
|
1952
|
-
Btn({ key: 'ceyes', danger: true, onClick: confirmEditAndResend, children: 'continue' })] }));
|
|
1953
|
+
Btn({ key: 'ceyes', danger: true, onClick: confirmEditAndResend, children: isRetry ? 'retry' : 'continue' })] }));
|
|
1953
1954
|
}
|
|
1954
1955
|
const lastMsg = state.chat.messages.length ? state.chat.messages[state.chat.messages.length - 1] : null;
|
|
1955
1956
|
const lastErr = lastMsg ? lastMsg.error : null;
|
|
@@ -2041,7 +2042,10 @@ function chatMain() {
|
|
|
2041
2042
|
// edit-and-resend a user message (two-step: arm a confirm banner first,
|
|
2042
2043
|
// since the truncation destroys the later turns).
|
|
2043
2044
|
onCopyMessage: (m) => copyMessageText(m),
|
|
2044
|
-
|
|
2045
|
+
// Mid-thread retry (gui-completion #7): the kit now offers retry on
|
|
2046
|
+
// EVERY assistant turn, not just the last - pass the specific message
|
|
2047
|
+
// through so retryTurn truncates from the right position.
|
|
2048
|
+
onRetryMessage: (m) => retryTurn(m),
|
|
2045
2049
|
confirmEdit: true,
|
|
2046
2050
|
onArmEdit: (m) => armEditAndResend(m),
|
|
2047
2051
|
onEditMessage: (m) => armEditAndResend(m),
|
|
@@ -2334,28 +2338,56 @@ function chatFollowups() {
|
|
|
2334
2338
|
return result;
|
|
2335
2339
|
}
|
|
2336
2340
|
// Retry the last assistant turn: drop it and re-send the preceding user message.
|
|
2337
|
-
|
|
2338
|
-
|
|
2341
|
+
// Retry a turn: truncates the transcript back to (and resends) the user
|
|
2342
|
+
// message that preceded the given assistant message, discarding it and
|
|
2343
|
+
// everything after - the same truncate+resend mechanism edit-and-resend
|
|
2344
|
+
// uses. `m` is optional (falls back to the trailing assistant turn) so this
|
|
2345
|
+
// also serves as "retry last turn" for onRetryMessage on a dangling user
|
|
2346
|
+
// message with no reply yet. Mid-thread retry (any assistant turn, not just
|
|
2347
|
+
// the last) was a gui-completion #7 finding - the kit's per-message retry
|
|
2348
|
+
// action is no longer gated to the last message index for assistant turns.
|
|
2349
|
+
function retryTurn(m) {
|
|
2339
2350
|
if (state.chat.busy) return;
|
|
2340
|
-
// The armed edit confirm refers to indices this retry is about to shift.
|
|
2341
|
-
state.chat.confirmingEdit = null;
|
|
2342
2351
|
const msgs = state.chat.messages;
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2352
|
+
let ai = m ? msgs.indexOf(m) : -1;
|
|
2353
|
+
if (ai < 0) {
|
|
2354
|
+
// Fall back to the trailing assistant turn (no specific message given,
|
|
2355
|
+
// or the given message somehow isn't in the current transcript).
|
|
2356
|
+
ai = msgs.length - 1;
|
|
2357
|
+
while (ai >= 0 && msgs[ai].role !== 'assistant') ai--;
|
|
2358
|
+
}
|
|
2346
2359
|
if (ai < 0) return;
|
|
2347
2360
|
let ui = ai - 1;
|
|
2348
2361
|
while (ui >= 0 && msgs[ui].role !== 'user') ui--;
|
|
2349
2362
|
if (ui < 0) return;
|
|
2363
|
+
// Retrying anything before the trailing turn silently discards every LATER
|
|
2364
|
+
// turn too - the same destructive-truncation risk edit-and-resend already
|
|
2365
|
+
// confirms for, so mid-thread retry gets the identical arm-then-confirm
|
|
2366
|
+
// banner instead of firing immediately.
|
|
2367
|
+
const discardsLaterTurns = ai < msgs.length - 1;
|
|
2368
|
+
if (discardsLaterTurns) {
|
|
2369
|
+
state.chat.confirmingEdit = { idx: ui, text: msgs[ui].content || '', kind: 'retry' };
|
|
2370
|
+
render();
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
state.chat.confirmingEdit = null;
|
|
2374
|
+
executeTruncateAndResend(ui);
|
|
2375
|
+
}
|
|
2376
|
+
function executeTruncateAndResend(ui) {
|
|
2377
|
+
const msgs = state.chat.messages;
|
|
2350
2378
|
const userText = msgs[ui].content || '';
|
|
2351
|
-
// Drop the user+assistant pair (and anything after) and resend the user text.
|
|
2352
2379
|
state.chat.messages = msgs.slice(0, ui);
|
|
2353
2380
|
state.chat.totalCost = computeTotalCost(); // discarded turns leave the spend
|
|
2381
|
+
// Never --resume a session whose tail diverged from what the server saw -
|
|
2382
|
+
// a mid-thread retry/edit truncates history the resumed session still has.
|
|
2383
|
+
state.chat.resumeSid = null;
|
|
2384
|
+
state.chat.resumeNote = null;
|
|
2354
2385
|
state.chat.draft = userText;
|
|
2355
2386
|
persistChat();
|
|
2356
2387
|
if (canSend()) sendChat();
|
|
2357
2388
|
else render();
|
|
2358
2389
|
}
|
|
2390
|
+
function retryLastTurn() { retryTurn(); }
|
|
2359
2391
|
// Edit-and-resend a user message: two-step. The edit click ARMS a confirmation
|
|
2360
2392
|
// (truncating destroys the later turns), the banner's continue executes it.
|
|
2361
2393
|
function armEditAndResend(m) {
|
|
@@ -2369,6 +2401,9 @@ function confirmEditAndResend() {
|
|
|
2369
2401
|
const ce = state.chat.confirmingEdit;
|
|
2370
2402
|
if (!ce || state.chat.busy) return;
|
|
2371
2403
|
state.chat.confirmingEdit = null;
|
|
2404
|
+
// Retry resends the SAME text immediately (no edit step); edit-and-resend
|
|
2405
|
+
// populates the draft so the user can change it before sending.
|
|
2406
|
+
if (ce.kind === 'retry') { executeTruncateAndResend(ce.idx); return; }
|
|
2372
2407
|
state.chat.messages = state.chat.messages.slice(0, ce.idx);
|
|
2373
2408
|
state.chat.totalCost = computeTotalCost(); // discarded turns leave the spend
|
|
2374
2409
|
// Never --resume a session whose tail diverged from what the server saw.
|