feat(cli,tui): show time since last final agent response on the status bar (#44265)

Adds an idle clock to the context/status bar in both the prompt_toolkit CLI
and the Ink TUI: once a turn completes, a dim '✓ <elapsed>' segment shows how
long the session has been idle since the last final agent response. Hidden
while a turn is live (the per-prompt elapsed timer covers that) and before
the first turn completes.

- cli.py: track _last_turn_finished_at when the agent thread exits, surface
  it via _format_idle_since() in the snapshot, render in both the wide
  fragments path and the plain-text fallback.
- ui-tui: stamp lastTurnEndedAt when busy flips false after a live turn,
  thread it through appStatus -> StatusRule, render via a ticking IdleSince
  segment sharing the duration breakpoint/width budget.
This commit is contained in:
Teknium
2026-06-11 06:06:19 -07:00
committed by GitHub
parent a2d7f538d4
commit 8972a151a4
7 changed files with 186 additions and 2 deletions
+1
View File
@@ -368,6 +368,7 @@ export interface AppLayoutProgressProps {
export interface AppLayoutStatusProps {
cwdLabel: string
goodVibesTick: number
lastTurnEndedAt: null | number
sessionStartedAt: null | number
showStickyPrompt: boolean
statusColor: string
+9 -2
View File
@@ -173,6 +173,7 @@ export function useMainApp(gw: GatewayClient) {
const [voiceRecordKey, setVoiceRecordKey] = useState<ParsedVoiceRecordKey>(DEFAULT_VOICE_RECORD_KEY)
const [sessionStartedAt, setSessionStartedAt] = useState(() => Date.now())
const [turnStartedAt, setTurnStartedAt] = useState<null | number>(null)
const [lastTurnEndedAt, setLastTurnEndedAt] = useState<null | number>(null)
const [goodVibesTick, setGoodVibesTick] = useState(0)
const [bellOnComplete, setBellOnComplete] = useState(false)
@@ -500,10 +501,14 @@ export function useMainApp(gw: GatewayClient) {
useEffect(() => {
if (ui.busy) {
setTurnStartedAt(prev => prev ?? Date.now())
} else {
} else if (turnStartedAt != null) {
// Only stamp the idle marker when a turn was actually live — busy is
// also false on mount and we don't want a phantom "done" timestamp
// before the first turn has completed.
setLastTurnEndedAt(Date.now())
setTurnStartedAt(null)
}
}, [ui.busy])
}, [ui.busy, turnStartedAt])
useConfigSync({ gw, setBellOnComplete, setVoiceEnabled, setVoiceRecordKey, sid: ui.sid })
@@ -1090,6 +1095,7 @@ export function useMainApp(gw: GatewayClient) {
// essentials and truncates this further on narrow terminals.
cwdLabel: fmtCwdBranch(cwd, gitBranch, 28),
goodVibesTick,
lastTurnEndedAt: ui.sid ? lastTurnEndedAt : null,
sessionStartedAt: ui.sid ? sessionStartedAt : null,
showStickyPrompt: !!stickyPrompt,
statusColor: statusColorOf(ui.status, ui.theme.color),
@@ -1103,6 +1109,7 @@ export function useMainApp(gw: GatewayClient) {
cwd,
gitBranch,
goodVibesTick,
lastTurnEndedAt,
sessionStartedAt,
stickyPrompt,
turnStartedAt,