mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
feat(approvals): let a watching session keep quiet, and fix the tui gate
The badge alone left the row in NEEDS YOU, which is the thing the issue was about. The fix is the alert that does not fire. An idle prompt from a session that is watching its own background work now opens ALREADY acknowledged. `hook-event-routes` passes `Session.watching` to `notePrompt()`, which sets `acknowledgedAt` and records why in a new `acknowledgedReason`. Nothing new suppresses anything: `acknowledge()` has always meant "the alert this prompt armed is spent", and the prompt itself stays pending, answerable and available as Read My Mind context. A wrong label therefore costs a card that does not blink, never an alert that was never created. Every surface follows from that. The broadcast carries the reason, so a live page declines to arm the tab alert and raises no desktop notification. The push is skipped, since a false alarm is hardest to ignore on a phone. A reloading page reads `acknowledgedAt` in `seedApprovals()`, which it already did. And `classifySession()` now reads it too, which is a pre-existing bug fixed here: acknowledging on one device cleared the alert everywhere except `codeman tui`. It re-arms for free, because the next idle prompt supersedes the item and is built fresh. Only `idle` is eligible, so a dialog that blocks the agent still goes red whatever else it started. The label is pane-derived and therefore prompt-injectable, so it is now read from the last two rows of the screen only, with Claude's pattern anchored on the `·` its footer joins items with, ANSI-stripped and length-capped at the source. An agent that prints `· 1 monitor ·` into its own output finds no match. Verified on an isolated beta: a session that armed a monitor took its idle prompt acknowledged with no alert on any surface, wore the badge, and showed "quiet, watching 1 monitor" on its still-answerable card; the same session with the monitor killed alerted normally on the next prompt. `test/watching-no-alert.test.ts` pins both directions across all four surfaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3f2cde2db7
commit
74884a20eb
@@ -71,6 +71,15 @@ export interface ApprovalItem {
|
||||
* and reach the user's other devices. See `acknowledge()`.
|
||||
*/
|
||||
acknowledgedAt?: number;
|
||||
/**
|
||||
* Why the item arrived already acknowledged, for display only: the inbox
|
||||
* writes `watching 1 monitor` for a session that went quiet because work it
|
||||
* started itself is still running. A human acknowledgement leaves this unset,
|
||||
* so a card can say "quiet, watching 1 monitor" rather than implying somebody
|
||||
* looked. ⚠️ Pane-derived text, so it is bounded at the source and must not
|
||||
* reach the DOM as markup — see `watchingLabel()` in `session-activity.ts`.
|
||||
*/
|
||||
acknowledgedReason?: string;
|
||||
/**
|
||||
* Present only when the frame parsed confidently. Gates which digits the
|
||||
* answer endpoint accepts; absent → only approve('1')/deny(Esc) are allowed.
|
||||
@@ -93,6 +102,12 @@ interface NotePromptArgs {
|
||||
toolSummary?: string;
|
||||
message?: string;
|
||||
cwd?: string;
|
||||
/**
|
||||
* What the session's pane says is still running in the background
|
||||
* (`Session.watching`, e.g. `1 monitor`). An idle prompt from such a session
|
||||
* opens ALREADY acknowledged: see `notePrompt()`.
|
||||
*/
|
||||
watching?: string | null;
|
||||
/** Returns the raw (ANSI-bearing) pane frame, or null when unavailable. */
|
||||
capture?: () => string | null;
|
||||
}
|
||||
@@ -217,6 +232,22 @@ export class ApprovalInbox {
|
||||
* Record a prompt for a session, superseding any previous item, and return
|
||||
* the new item. Captures context immediately and once more after a short
|
||||
* delay (see RECAPTURE_DELAY_MS).
|
||||
*
|
||||
* ⚠️ An idle prompt from a session that is WATCHING its own background work
|
||||
* opens already acknowledged (`args.watching`). Claude Code ends the turn
|
||||
* after arming a monitor or backgrounding a shell and then reports the pane
|
||||
* idle a minute later, so the alert that follows asks a human to look at a
|
||||
* session that wants nothing from them. Acknowledging is deliberately what
|
||||
* happens here rather than skipping the item: the prompt is real and stays
|
||||
* pending, answerable and available as Read My Mind context, and only the
|
||||
* alert it would have armed is spent. A wrong label therefore costs a card
|
||||
* that does not blink, never an alert that was never created.
|
||||
*
|
||||
* It re-arms by itself. The next idle prompt supersedes this item and builds
|
||||
* a fresh one, so once the background work ends and the session goes quiet
|
||||
* for an ordinary reason, that item carries no acknowledgement and alerts
|
||||
* normally. Only `idle` is eligible: a permission or question dialog blocks
|
||||
* the agent whatever else it started, so its alert must survive.
|
||||
*/
|
||||
notePrompt(args: NotePromptArgs): ApprovalItem {
|
||||
this.resolveForSession(args.sessionId, 'superseded');
|
||||
@@ -231,6 +262,10 @@ export class ApprovalInbox {
|
||||
message: args.message,
|
||||
cwd: args.cwd,
|
||||
};
|
||||
if (args.kind === 'idle' && args.watching) {
|
||||
item.acknowledgedAt = item.createdAt;
|
||||
item.acknowledgedReason = `watching ${args.watching}`;
|
||||
}
|
||||
this.applyCapture(item, args.capture);
|
||||
this.items.set(args.sessionId, item);
|
||||
if (args.capture) this.captures.set(args.sessionId, args.capture);
|
||||
|
||||
Reference in New Issue
Block a user