Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions agents/specs/APP-4871: LRC elapsed timer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
*Proposed change: LRC elapsed timer after 30 seconds*

*Summary:* While an agent-monitored long-running command (LRC) is still executing, hide the generic prompt-label duration counter for that LRC and append the command's elapsed duration to the LRC status row (for example, `Waiting for command to exit... (30s)`) once the elapsed whole-second duration reaches 30 seconds. Keep the existing prompt-label counter for ordinary commands and keep the existing final-duration display after any command completes.

*Key design choices:* Use the existing `Block` start timestamp and whole-second duration formatting; use an inclusive `>= 30s` threshold so the first visible value is `(30s)` rather than delaying to `(31s)`; render the timer as a non-shimmering suffix on the LRC status row while preserving the existing one-second repaint patterns; do not add a feature flag or persisted timer state.

*Design alternatives:* Rendering only on the prompt-label row would reuse the existing `LiveElement`, but it would not put the timer on the LRC surface shown in the request and would leave an unwanted sub-30-second counter visible. Gating the prompt-label counter for every executing block would satisfy the threshold but regress ordinary command UX. Storing a second timer/state in the AI block or deriving elapsed time from viewer join time would duplicate the canonical `Block` timing data and break shared-session/restore semantics. The selected design gates only active LRC prompt-label rendering and adds the same canonical duration to the existing LRC status-row suffix, derived from the command's original `start_ts`.

*Root cause / approach:* `Block::elapsed_duration_whole_secs()` / `formatted_duration_string()` / `is_duration_live()` already provide a live whole-second counter for every executing block (`app/src/terminal/model/block.rs:2468-2724 @ 8d9f2e08ad9020c88293a71d369b6b1d58581b34`). `TerminalView::render_label_element` unconditionally consumes that value for the prompt-label row and uses `LiveElement` to repaint it (`app/src/terminal/view.rs:23453-23526 @ 8d9f2e08ad9020c88293a71d369b6b1d58581b34`), so an LRC currently exposes a counter from about one second onward. The LRC status bar already selects `Waiting for command to exit...` / `Executing command...` and supports a separate non-shimmering suffix plus periodic refresh (`app/src/ai/blocklist/block/view_impl/common.rs:128-546 @ 8d9f2e08ad9020c88293a71d369b6b1d58581b34`, `app/src/ai/blocklist/block/status_bar.rs:133-331, 680-730 @ 8d9f2e08ad9020c88293a71d369b6b1d58581b34`). Add a clearly named LRC/threshold accessor or equivalent predicate in `Block`, use it to suppress the live prompt-label text only for an executing block with `agent_interaction_metadata`, and feed the gated formatted duration into the status bar's non-shimmering suffix. Add a dedicated one-second status-bar notification/repaint while that LRC remains active, stopping it on completion or when the LRC status disappears. Preserve the existing prompt-label `LiveElement` kickoff for ordinary executing blocks and for any LRC path that needs to cross the threshold.

*Affected files:* `app/src/terminal/model/block.rs`; `app/src/terminal/model/block_tests.rs`; `app/src/terminal/view.rs`; `app/src/ai/blocklist/block/status_bar.rs`; `app/src/ai/blocklist/block/view_impl/common.rs`; and the nearest existing terminal/status-bar view-test file if one is needed for deterministic rendering assertions.

*Open questions resolved:*
- *Threshold:* Treat the request's “over 30s” wording as an inclusive whole-second UI threshold: no timer at 29 whole seconds, the first value is `(30s)`, and it continues at `(31s)`, `(1m 0.00s)`, etc. This follows the existing whole-second API and avoids a one-second UX delay.
- *LRC identity:* An LRC is an executing, active block with `agent_interaction_metadata().is_some()`, matching the current status-bar predicate; ordinary shell commands and completed blocks are not LRCs for this feature.
- *Placement and existing counter:* The LRC timer is appended to the active LRC status row (including the requested `Waiting for command to exit...` row and any other active LRC status message), while the prompt-label live counter is suppressed only for that active LRC. Ordinary blocks retain their existing prompt-label counter from about one second onward; completed blocks retain their existing final duration.
- *Shared sessions and restore:* Elapsed time is always computed from the command's serialized/exchanged `start_ts`, not from a viewer's join time or a restored UI's construction time. A shared-session viewer that joins before or after 30 seconds sees the same threshold relative to the command start once its block metadata arrives. Restored completed blocks show their stored final duration and never start a new live timer; a currently executing shared command follows the normal execution-start event path.
- *Feature flagging:* No new feature flag is required. Existing LRC/AI status-surface flags continue to control whether that surface exists; when it exists, the timer behavior is unconditional.

*Risks / blast radius:* The two UI surfaces must not show duplicate or contradictory timers; the status-bar repaint must not leak after completion; the threshold must not flicker because of sub-second notifications; shared-session viewers must not reset the elapsed origin; and adding a suffix must not clip the existing “Next check in …” or agent-tip content. Keep the existing duration formatter and non-shimmering suffix layout, and cover ordinary, completed, shared, restored, and LRC-blocked states in tests/verification.

*Validation & verification criteria* (must ALL pass before merge):
1. Add a `Block`-level regression test (extend the `test_elapsed_duration_*` group in `app/src/terminal/model/block_tests.rs`) that constructs an executing LRC block with agent metadata and verifies the gated accessor/predicate returns no display duration at 0s, 1s, and 29s, returns exactly the existing formatted `(30s)` duration at 30s and `(31s)` at 31s, and stops being live after `finish`. The test must fail before the implementation and pass after it.
2. In the same block-level tests, verify the gate is scoped to LRCs: an executing ordinary block still returns the existing live duration at 1s, a block with agent metadata that is not executing is not live, and a completed LRC keeps its final `duration()` / `formatted_duration_string()` value.
3. Add a deterministic terminal/status-bar rendering test (or the closest existing view-test seam) proving that an active LRC's prompt-label row contains no duration before 30s, while its status row contains no suffix at 29s and contains the exact existing formatted suffix `(30s)` at the threshold. Prove that the suffix remains attached when the base status changes between `Waiting for command to exit...`, `Executing command...`, and a `Next check in ...` suffix.
4. Preserve ordinary prompt-label rendering: an executing non-LRC block continues to show its existing `(1s)`-style live counter and its one-second `LiveElement` repaint kickoff; a completed ordinary or LRC block still shows the final duration in the prompt-label row exactly as before.
5. Prove live updates and teardown: the LRC status row changes from `(30s)` to `(31s)` on the one-second refresh cadence without requiring command output, and the dedicated refresh handle/timer stops (or becomes inert) when the block completes, the LRC status bar is removed, or the active block changes.
6. Prove shared-session and restoration semantics with focused model/view tests or existing fixtures: a viewer joining before 30s remains timer-free until the original command `start_ts` reaches 30s; a viewer joining after 30s shows the duration based on that same original timestamp rather than join time; and a restored completed block never starts a live LRC timer and retains its persisted final duration.
7. Exercise the real user-facing flow with `computer_use`: start an agent-monitored command that remains in the LRC running state, capture proof while it is at 29s (or otherwise demonstrably below 30s) showing no elapsed timer on either the prompt label or status row, then capture proof at/after 30s showing the timer on the `Waiting for command to exit...` status row, and observe at least one live increment while the command remains running. Attach the screenshots to the task record and PR; do not treat code/tests alone as UI proof.
8. Run `./script/presubmit` from the Warp client repository and resolve all formatting, lint, build, and test failures before the implementation PR is marked ready.