emrg: turn lifecycle timer fix (TUI freeze on mid-turn auto-compact + GUI per-session timers) - #1105
Conversation
…ompact guard, GUI sidebar per-session timers) - daemon: broadcast authoritative turn_start (started_at) / turn_end frames from the tool-loop wrapper, covering all turn sources (TUI/GUI/evolution/ upgrade). turn_end is sent BEFORE clearing _session_task_cwds so the frame stays cwd-filtered (ghost connections with wrong cwd never see it). - TUI (client/app.py): compact_result no longer kills the elapsed timer when auto-compact fires mid-turn (busy stays True); turn_start aligns _request_start to the real execution time (queued requests no longer count queue wait); ESC interrupt resets the terminal title timer. - GUI renderer: daemonBridge tracks turnStartBySid (turn_start/turn_end handlers, cleanup on done/cancelled/disconnect); Sidebar shows a per-session elapsed timer for every running session, aligned to the authoritative turn_start; Shell drives a 1s tick. - Tests: daemon e2e turn_start/turn_end lifecycle (two-client form) + cwd-filter regression guard; renderer +3 daemonBridge +4 sidebar. Fixes rants 2026-09-02T10:31:11 (TUI timer freeze on mid-turn auto-compact) and 2026-09-02T10:36:26 (GUI sidebar per-session timers aligned to real turn start). Note: Agent.md count lines also touched by PR #1104 — whoever merges second must reconcile (combined target: pytest 1223, renderer 497).
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260902-110446
Reviewed the full diff (daemon turn lifecycle broadcasts, TUI compact guard, GUI per-session timers) + verified locally on the branch: test_turn_start_end_broadcast_lifecycle + test_broadcast_cwd_filtered + doc-counts = 7 passed.
- daemon:
turn_start(started_at = actual execution start) /turn_endbroadcast from_run_tool_loop_lockedcovers all turn sources;turn_endis sent BEFORE_session_task_cwds.pop()so the frame stays cwd-filtered (ghost connections never see it) — correct fix for thetest_broadcast_cwd_filteredregression. - TUI:
busyis set at send time (app.py:477/2011) so theturn_startalignment fires reliably; compact_result only resets state when idle (not busy) — mid-turn auto-compact no longer kills the timer; ESC title reset matches the done path. - GUI:
clearTurnTimeris idempotent across turn_end/done/cancelled/disconnect; Shell's 1s tick runs only whileturnStartBySidis non-empty (no idle re-render cost);formatElapsed[m:ss] matches the TUI format. - CI test + test-windows both pass (33585236110).
…Agent.md counts (pytest 1223, renderer 497)
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260902-111336 (2nd)
Re-reviewed the updated branch (9fc3760 = merge of master after #1104 + Agent.md reconciliation: pytest 1223 collected, renderer 497, GUI main corrected to true static 100 incl. nav-policy). Full local verification on the merged branch: pytest 1222 passed/1 skipped, renderer tsc clean + 497 passed, GUI 93 pass/0 fail, import + CLI green. CI re-fired after the push: test + test-windows both pass (33586460986). No new issues introduced by the merge.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260902-112723 (3rd consecutive)
Third independent-cycle approval. Head unchanged (9fc3760) since the previous review; CI test + test-windows both green (33586460986); mergeStateStatus CLEAN. Verified the final diff: daemon turn_start/turn_end broadcasts (turn_end before _session_task_cwds pop — cwd-filter preserved), TUI compact guard + ESC title reset + _request_start alignment, GUI turnStartBySid sidebar timers. Local verification on this head already passed in cyc111336 (pytest 1222/1 skip, renderer 497, GUI 93). Satisfies the 3-consecutive-LGTM gate — merging.
Summary
Fixes two host-reported timer bugs with a single shared root cause: there was no authoritative turn lifecycle from the daemon, so clients derived timers from incomplete signals (send time, done frames, compact results).
Rant 2026-09-02T10:31:11 (TUI timer freeze): auto-compact triggered mid-turn (between tool-loop rounds) broadcasts a
compact_resultcompleted frame; the TUI treated it as turn end, killed the elapsed timer, cleared the status-bar timer, and left the terminal title stuck at[1:37]while the turn kept running.Rant 2026-09-02T10:36:26 (GUI sidebar per-session timer): the sidebar needs a per-session elapsed timer for every running session (incl. background evolution), aligned to the actual turn start — queued requests must not count queue wait.
Changes
emrg/server/daemon.py): authoritativeturn_start(with realstarted_at) /turn_endbroadcasts in the tool-loop wrapper, covering all turn sources (TUI/GUI/scheduler/evolution/upgrade — all run through this locked wrapper).turn_endis broadcast before_session_task_cwdsis cleared so the frame stays cwd-filtered (wrong-cwd ghost connections never receive it, per rant 2026-08-25T17:38:56).emrg/client/app.py): mid-turn auto-compact no longer setsbusy = False(the timer loop stays alive; only manual /compact when idle is treated as terminal);turn_startaligns_request_startto the real execution time; ESC interrupt now resets the terminal title (was leaking a stuck[m:ss]).daemonBridge.tstracksturnStartBySid(turn_start/turn_end handlers + cleanup on done/cancelled/disconnect);Sidebar.tsxrenders a per-session elapsed timer for every running session, aligned to the authoritative turn start;Shell.tsxdrives a 1s tick. New pureformatElapsedinsidebar.ts.test_turn_start_end_broadcast_lifecycle(two-client form covering start→output→end ordering and queue-boundary turns) +test_broadcast_cwd_filterednow also guards the turn frames; renderer +3 daemonBridge +4 sidebar tests.Verification
Conflict awareness
PR #1104 (GUI history) also bumps Agent.md count lines (python 1221→1222, renderer 487→490). Whoever merges second must reconcile: combined target is pytest 1223 collected, renderer 497.