mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
fix(session): decide working/idle from the pane, not the composer redraw
Every working Claude session reported `status: "idle"` about two seconds into its turn. Measured on live workers: two sessions mid-tool-call at 13 and 17 minutes both read `idle` while their panes showed `✻ Actualizing… (13m 23s · ↓ 47.5k tokens)`. Two things had drifted apart: 1. The working indicator changed. Claude animates the glyph through `· ✢ ✳ ∗ ✻ ✽` and randomizes the gerund per turn, so neither SPINNER_PATTERN (braille, no longer drawn) nor the keyword list (Thinking/Writing/Reading/Running) matches a turn anymore. 2. A `❯` sighting is not the end of a turn. Claude redraws the composer roughly once a second all the way through one, and that redraw armed the "2s later, call it idle" timer. Matching the new status line in the STREAM does not fix it either: tmux ships partial repaints, so the complete line reached the PTY about once every 20 seconds while the `❯` arrived every second. So the decision moves off the stream: - An unbroken run of repaints marks a turn as started. Sampled once a second for 12s over six live sessions, the two working ones produced output in 12/12 windows and the four idle ones in 0/12. Pure helpers in session-activity.ts carry the thresholds. - Idle now needs the pane to go quiet AND the screen to agree. `_confirmIdle()` asks tmux what is rendered (new `capturePaneText()`, one plain `capture-pane`, floored at 1.5s per session and only ever at a transition) and re-checks every 5s while the screen still shows work. A turn can sit silent for tens of seconds inside one tool call, so silence alone proves nothing. - The same screen check vetoes keystroke echo, which is a steady stream of repaints too but is not work. CLAUDE_WORKING_LINE_PATTERN matches the `… (elapsed)` shape rather than the glyph, because the FINISHED line (`✻ Cooked for 2m 49s`) carries the same glyph and would otherwise pin a session at working forever. Claude mode only. An external CLI has no `❯`, so nothing would arm the confirmation and such a session would latch busy. respawn-patterns.hasWorkingPattern() had the same blind spot (its gerund list cannot see "Actualizing"), so it takes the pattern as an extra signal. That can only make respawn less eager, never more. Idle now lands about 3 to 5 seconds after a turn ends instead of 2 seconds into one. Verified end to end against a live worker, sampled against the CLI's own "esc to interrupt" footer as independent ground truth: busy for all 25s of a turn, idle 3s after it ended. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -186,6 +186,8 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
|
||||
|
||||
**Idle detection**: Multi-layer (completion message → AI check → output silence → token stability). See `docs/respawn-state-machine.md`.
|
||||
|
||||
⚠️ **A `❯` sighting is NOT the end of a turn, and neither is silence.** Claude redraws the composer (`❯`) about once a second all through a turn, so the old "saw a ❯, wait 2s → idle" rule flipped every working session to idle two seconds in (measured: a session mid-tool-call at 17 minutes reporting `status:"idle"`). Its working indicator is `✻ Actualizing… (13m 23s · ↓ 47.5k tokens)`: the glyph animates through `· ✢ ✳ ∗ ✻ ✽`, the gerund is randomized, and the finished line (`✻ Cooked for 2m 49s`) carries the same glyph, so neither `SPINNER_PATTERN` (braille, not what current versions draw) nor a keyword list can see it. Matching the new line in the STREAM does not work either: tmux ships partial repaints, so the whole line reaches the PTY only every few tens of seconds. So: `_confirmIdle()` (session.ts) requires the pane to go quiet, and then asks the SCREEN via `capturePaneText()` + `CLAUDE_WORKING_LINE_PATTERN` before believing it; a sustained run of repaints (`session-activity.ts`, pure + unit tested) is what marks a turn as started, with the same screen probe vetoing keystroke echo. Idle now lands ~3-5s after a turn ends instead of 2s into one. Claude-mode only, since an external CLI has no `❯`, so nothing would ever arm the confirmation and the session would latch busy.
|
||||
|
||||
**Auto-resume on usage limit** (opt-in per session, top of the Respawn tab): when Claude halts on a subscription limit, `usage-limit-patterns.ts` (pure, unit-tested) parses the reset time and `SessionAutoOps` arms a timer for reset+2min, then sends Esc + `continue`. ⚠️ Respawn cycles are blocked while paused (`isLimitPaused` guard in `onIdleDetected`), which is what prevents `/clear` from wiping the paused conversation. Claude-mode only. → [architecture-invariants#auto-resume-on-usage-limit](docs/architecture-invariants.md#auto-resume-on-usage-limit)
|
||||
|
||||
**Plan-usage chip** (statusLine telemetry, `showPlanUsageLimits`, per-device: desktop default **ON**, handhelds OFF via the mobile block in `getDefaultSettings()`): resolve it ONLY through `planUsageChipEnabled()` in settings-ui.js, which backs all three call sites (the App Settings checkbox, the chip's visibility, and the `statusLineTelemetry` flag on session create). A chip shown without telemetry renders `—` forever. Codeman injects its own `statusLine.command` exporter which POSTs Claude's `rate_limits` blob to `POST /api/status-telemetry`. The exporter is identified by a marker, so it only ever adds/updates/removes a statusLine that is **ours**, never a user's hand-authored one, and it prints the footer through so the in-terminal statusline is not blanked. Claude-mode only; distinct from auto-resume, which reacts to the limit *message* rather than showing live %. → [architecture-invariants#plan-usage-chip-statusline-telemetry](docs/architecture-invariants.md#plan-usage-chip-statusline-telemetry), `docs/usage-limits-display-plan.md`
|
||||
|
||||
Reference in New Issue
Block a user