Skip to content

fix(status-bar): emit explicit Running event on turn start#121

Merged
yishuiliunian merged 1 commit intomainfrom
fix/status-bar-running-event
Apr 19, 2026
Merged

fix(status-bar): emit explicit Running event on turn start#121
yishuiliunian merged 1 commit intomainfrom
fix/status-bar-running-event

Conversation

@yishuiliunian
Copy link
Copy Markdown
Contributor

Summary

The TUI status bar stays on "Idle" after the user presses Enter until the LLM's first streamed chunk arrives (1-5s TTFB). Root cause: runner.transition(AgentStatus::Running) was a no-op — session-layer observable.status only flipped when Stream/ToolCall/etc. events arrived, so the UI had no signal between "input accepted" and "first token back."

Fix by making the agent the authoritative source of status transitions:

  • Add AgentEventPayload::Running — emitted the moment the runner enters active processing, before any LLM call.
  • Hub broadcasts it to all observers (TUI, ACP, MetaHub shadows).
  • Session layer handles it by calling begin_turn() and setting observable.status = Running.
  • TUI reads the projection as before — no client-side optimistic state.

Also fixes a secondary issue: the turn-elapsed timer now starts when the turn begins, not on the first Stream/ToolCall event.

Architecture rationale

Status is data-plane state; the agent owns it. Any observer (TUI, IDE via ACP, external Hub clients) gets the same authoritative view via event broadcast. No lossy shortcut (TUI-side optimistic updates) that would desync from the server model.

Test plan

  • New runtime test asserts Running is emitted before the first Stream
  • New session tests cover status flip, turn timer start, sub-agent creation
  • bazel test //... — all 52 test targets pass
  • bazel build //... --config=clippy — zero warnings

The status bar stayed on "Idle" until the LLM's first streaming chunk
arrived, because `transition(Running)` was a no-op and the session's
`observable.status` only flipped on Stream/ToolCall events. This made the
UI look unresponsive for 1-5s after pressing Enter while waiting on TTFB.

Make the agent the authoritative source: emit a dedicated `Running` event
when the runner enters active processing. Hub broadcasts it to all
observers (TUI, ACP, MetaHub shadows); session layer updates
`observable.status` and starts the turn timer. The TUI reads the
projection as before — no optimistic state on the client.

Also starts the turn-elapsed timer as soon as the turn begins (previously
only on the first Stream/ToolCall).

Changes:
- protocol: add `AgentEventPayload::Running` variant
- runtime: `transition(Running)` now emits the event
- session: handle `Running` — begin_turn + set status
- acp: `Running` has no ACP counterpart (activity already signaled via Stream)

Tests: runtime asserts Running precedes first Stream; session covers
status transition, turn timer, and sub-agent creation.
@yishuiliunian yishuiliunian merged commit e9e2f95 into main Apr 19, 2026
4 checks passed
@yishuiliunian yishuiliunian deleted the fix/status-bar-running-event branch April 19, 2026 11:56
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