mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
fix(approvals): clear the red tab alert when a dialog is answered in the terminal
Confirming an AskUserQuestion left its tab flowing red for the rest of the turn (owner report: ~8 minutes on a running session, with no dialog anywhere on screen). Two separate bugs, both live-verified. The re-capture erased the evidence the staleness check runs on. Claude Code fires the Notification behind the dialog (measured 6-7s on v2.1.237, documented up to ~30s), so the 600ms re-capture routinely lands on a frame the user has ALREADY answered, parses nothing, and applyCapture overwrote item.options with undefined. A MISSING options is how "we never could read this dialog" is expressed, and those items stay answerable by design, so a cleared field was indistinguishable from a never-parsed one and the item became permanently unsweepable: it survived every GET /api/approvals and every page reload, cleared only on `stop`, and still accepted an answer, sending a bare `1` into a composer with no dialog under it. applyCapture is now ADD-ONLY for options. Nothing ran the staleness check while a page was open. It lived only in GET /api/approvals, which seedApprovals() calls on init and reconnect, so `stop` was the first thing that ever cleared an answered dialog. The `working` signal now runs the pane-VERIFIED variant (resolveIfDialogGone -> verifyStillAnswerable): the heuristic only decides when to look, the screen decides the outcome, so the existing "working can flap" rule is respected. A frame that parses no options is now conclusive in two cases, and only those, so an unreadable capture still keeps the alert: the item once parsed options, or the frame shows Claude actively running a turn. A modal dialog BLOCKS the turn, so the two cannot coexist - measured, a live-dialog frame carries neither the elapsed-timer spinner nor the "esc to interrupt" footer, which the dialog replaces with "Enter to select". That second signal is reached by a delayed staleness pass (3s) scheduled alongside the re-capture, which closes the late-hook case where the prompt is answered before the hook lands: nothing ever parses, `stop` may have gone by already, and the alert outlived reloads until the 12h TTL. The pass is deliberately later than RECAPTURE_DELAY_MS, whose whole reason for existing is that the hook can beat Ink to the screen. Frontend: _onHookElicitationComplete cleared only the elicitation entry, but an AskUserQuestion arrives as permission_prompt, so it was clearing the wrong alert; it now clears both, matching the server's kind-agnostic APPROVAL_RESOLVING_EVENTS. Verified end to end on an isolated beta instance, not just in unit tests: before, resolution could only come from the stop route (approval:resolved always immediately preceding hook:stop); after, it arrives from the new paths, and a simulated late hook resolves at +3.12s with no stop, no working signal and no GET, while the pane is still working. Tests use frames captured off a live pane and each new one was confirmed to fail against the old behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,8 +41,17 @@ Object.assign(CodemanApp.prototype, {
|
||||
_onHookElicitationComplete(data) {
|
||||
// Question answered in the terminal: clear the action alert without
|
||||
// waiting for `stop` (the turn may keep running for a long time).
|
||||
// ⚠️ BOTH action kinds, matching the server's APPROVAL_RESOLVING_EVENTS,
|
||||
// which resolves a session's pending item whatever its kind. An
|
||||
// AskUserQuestion dialog arrives as `permission_prompt` (only MCP
|
||||
// elicitation is `elicitation_dialog`), so clearing just the elicitation
|
||||
// entry left the red alert armed on exactly the dialog these events are
|
||||
// most often about. Normally the server's `approval:resolved` broadcast
|
||||
// clears it too; this is the path that still works when the store holds no
|
||||
// item for the session (restart, superseded).
|
||||
if (data.sessionId) {
|
||||
this.clearPendingHooks(data.sessionId, 'elicitation_dialog');
|
||||
this.clearPendingHooks(data.sessionId, 'permission_prompt');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user