Skip to content

fix(runtime): unfreeze TUI after /compact and resume; observability sweep#190

Merged
yishuiliunian merged 2 commits into
mainfrom
fix/unfreeze-tui-compact-resume
Jun 2, 2026
Merged

fix(runtime): unfreeze TUI after /compact and resume; observability sweep#190
yishuiliunian merged 2 commits into
mainfrom
fix/unfreeze-tui-compact-resume

Conversation

@yishuiliunian
Copy link
Copy Markdown
Contributor

Summary

  • Fix a frozen-UI bug cluster: ESC dead, /compact stuck on "Streaming" with stale ctx:, and post-resume continue never consumed.
  • Root causes: view-state usurping backend-authoritative status (Bug A) and a resume/cold-start turn-lifecycle gap (Bug D, keystone; Bug C is its downstream).
  • Observability sweep: default log level → debug, filter rewritten to cover all crates, empty-LLM-response warning, and key-path debug anchors.

Changes

  • loopal-runtime/agent_loop: ensure_resume_turn_record opens a turn record before User-tail resume runs (turn_record.rs); recovery split into turn_recover.rs; post_compact emits reliable TokenUsage; empty-response warn in llm_record.rs; status-transition debug.
  • loopal-view-state: compact mutator no longer writes status; conversation_display syncs tokens from Compacted.
  • loopal-telemetry: new build_env_filter (global-default + third-party blacklist).
  • loopal-tui: user-message routing debug log.
  • src/logging.rs: default level → debug via shared filter builder.
  • Tests: resume_then_followup_message_runs_second_turn, resume_user_tail_records_turn_with_llm_step, compact_token_sync_test, compact_idle_e2e_test, extended compact-banner mutator tests.

Test plan

  • bazel test //... — 94/94 pass
  • bazel build //... --config=clippy — zero warnings
  • bazel build //... --config=rustfmt — pass
  • CI passes

…weep

Two root causes behind a frozen UI cluster (ESC dead, /compact stuck on
"Streaming", resumed "continue" never consumed):

- Bug A: view-state compact mutator usurped backend-authoritative status by
  flipping it to Running without restoring it. Status is now backend-only;
  the compact_banner alone conveys compacting.
- Bug D (keystone): resume/cold-start with a User-tail history skipped the
  idle phase, so no turn record was opened and every append_step hit
  NoCurrentTurn. ensure_resume_turn_record() now opens the record before the
  turn runs, letting the loop return to idle and drain queued input (Bug C).
- Bug B: post_compact now emits a reliable TokenUsage so the ctx counter
  refreshes; conversation_display defensively syncs tokens from Compacted.

Observability: default log level -> debug; telemetry build_env_filter switches
to global-default + third-party blacklist (was a whitelist that dropped
tui/session/hub/view-state); empty-LLM-response warn; status-transition and
user-message-routing debug anchors.

Regression coverage (e2e): resume_then_followup_message_runs_second_turn,
resume_user_tail_records_turn_with_llm_step, compact_token_sync, and
compact_idle_e2e reproduce the exact frozen-UI event sequences.
The followup Envelope was pre-sent before run(), landing inside the resume
turn's mid-turn inject_pending_messages drain window. Whether it was absorbed
into turn 1 or ran as turn 2 depended on scheduling — flaky (failed ~70% on
macOS CI, passed locally only when bundled in the full suite).

Gate delivery on the agent's own AwaitingInput event: send "continue" only
after the resume turn completes and the loop returns to idle, then close both
mailbox + control senders on the second idle to end the Persistent loop. The
helper now returns control_tx (5-tuple); existing tests drop both senders up
front to keep their closed-channel break behavior. Deterministic 15/15.
@yishuiliunian yishuiliunian merged commit e59deb5 into main Jun 2, 2026
4 checks passed
@yishuiliunian yishuiliunian deleted the fix/unfreeze-tui-compact-resume branch June 2, 2026 05:17
yishuiliunian added a commit that referenced this pull request Jun 3, 2026
…191)

* fix(tui): show "Compacting" status during compaction instead of Idle

#190 made AgentStatus backend-only and declared compact_banner the sole
signal for compacting, but the unified status line never consumed it.
Manual /compact runs as a control command in the idle phase, so status
stays WaitingForInput and the decision tree fell through to Idle with a
frozen spinner — covering auto-compaction and resume-rehydrate too.

Extract the status-label decision into a pure pick_label() and add a
Compacting tier (after Thinking, before Streaming). Feed it
compact_banner.is_some(), and add the same flag to is_agent_active so the
spinner animates for the whole compaction rather than freezing after the
750ms activity grace.

Presentation-layer only: no AgentStatus mutation, no protocol change, so
it does not reintroduce the #189/#190 turn-lifecycle desync risk.

* fix: address CI failure - rustfmt struct-update layout in tests

rustfmt expands single-line `ActivityInputs { field: x, ..base() }` to
multi-line; apply the canonical formatting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant