Skip to content

Commit 4c5a6e0

Browse files
committed
cli: Pause timer when using ask user tool
1 parent b06c44f commit 4c5a6e0

File tree

8 files changed

+96
-11
lines changed

8 files changed

+96
-11
lines changed

cli/src/chat.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ export const Chat = ({
252252
const isConnected = useConnectionStatus(handleReconnection)
253253
const mainAgentTimer = useElapsedTime()
254254
const { ad } = useGravityAd()
255+
// Use startTime for active timer display; when paused, timer hook maintains frozen value
255256
const timerStartTime = mainAgentTimer.startTime
256257

257258
// Set initial mode from CLI flag on mount
@@ -437,6 +438,15 @@ export const Chat = ({
437438
const setInputMode = useChatStore((state) => state.setInputMode)
438439
const askUserState = useChatStore((state) => state.askUserState)
439440

441+
// Pause/resume timer when ask_user tool becomes active/inactive
442+
useEffect(() => {
443+
if (askUserState !== null) {
444+
mainAgentTimer.pause()
445+
} else if (mainAgentTimer.isPaused) {
446+
mainAgentTimer.resume()
447+
}
448+
}, [askUserState, mainAgentTimer])
449+
440450
// Filter slash commands based on current ads state - only show the option that changes state
441451
const filteredSlashCommands = useMemo(() => {
442452
const adsEnabled = getAdsEnabled()

cli/src/components/status-bar.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,28 @@ export const StatusBar = ({
2626
const theme = useTheme()
2727
const [elapsedSeconds, setElapsedSeconds] = useState(0)
2828

29-
// Show timer only when actively working (streaming or waiting for response)
29+
// Show timer when actively working (streaming or waiting for response) or paused (ask_user)
3030
// This uses statusIndicatorState as the single source of truth for "is the LLM working?"
3131
const shouldShowTimer =
3232
statusIndicatorState?.kind === 'waiting' ||
33-
statusIndicatorState?.kind === 'streaming'
33+
statusIndicatorState?.kind === 'streaming' ||
34+
statusIndicatorState?.kind === 'paused'
3435

3536
useEffect(() => {
3637
if (!timerStartTime || !shouldShowTimer) {
3738
setElapsedSeconds(0)
3839
return
3940
}
4041

42+
// When paused, don't update the timer - just keep the frozen value
43+
if (statusIndicatorState?.kind === 'paused') {
44+
// Calculate current elapsed time once and freeze it
45+
const now = Date.now()
46+
const elapsed = Math.floor((now - timerStartTime) / 1000)
47+
setElapsedSeconds(elapsed)
48+
return
49+
}
50+
4151
const updateElapsed = () => {
4252
const now = Date.now()
4353
const elapsed = Math.floor((now - timerStartTime) / 1000)
@@ -48,7 +58,7 @@ export const StatusBar = ({
4858
const interval = setInterval(updateElapsed, 1000)
4959

5060
return () => clearInterval(interval)
51-
}, [timerStartTime, shouldShowTimer])
61+
}, [timerStartTime, shouldShowTimer, statusIndicatorState?.kind])
5262

5363
const renderStatusIndicator = () => {
5464
switch (statusIndicatorState.kind) {
@@ -96,6 +106,9 @@ export const StatusBar = ({
96106
/>
97107
)
98108

109+
case 'paused':
110+
return null
111+
99112
case 'idle':
100113
return null
101114
}

cli/src/hooks/__tests__/use-send-message-timer.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ describe('createSendMessageTimerController', () => {
3131
mainAgentTimer = {
3232
start: startMock,
3333
stop: stopMock,
34+
pause: mock(() => {}),
35+
resume: mock(() => {}),
3436
elapsedSeconds: 0,
3537
startTime: null,
38+
isPaused: false,
3639
}
3740
})
3841

cli/src/hooks/helpers/__tests__/send-message.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const createMockTimerController = (): SendMessageTimerController & {
5353
stopCalls.push(outcome)
5454
return { finishedAt: Date.now(), elapsedMs: 100 }
5555
},
56+
pause: () => {},
57+
resume: () => {},
5658
isActive: () => startCalls.length > stopCalls.length,
5759
}
5860
}

cli/src/hooks/use-elapsed-time.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ export interface ElapsedTimeTracker {
99
* Stop tracking and reset to 0
1010
*/
1111
stop: () => void
12+
/**
13+
* Pause tracking, freezing the current elapsed time
14+
*/
15+
pause: () => void
16+
/**
17+
* Resume tracking from the frozen elapsed time
18+
*/
19+
resume: () => void
1220
/**
1321
* Get the current elapsed seconds
1422
*/
@@ -17,6 +25,10 @@ export interface ElapsedTimeTracker {
1725
* Get the start time timestamp (null if not started)
1826
*/
1927
startTime: number | null
28+
/**
29+
* Whether the timer is currently paused
30+
*/
31+
isPaused: boolean
2032
}
2133

2234
/**
@@ -37,19 +49,47 @@ export interface ElapsedTimeTracker {
3749
export const useElapsedTime = (): ElapsedTimeTracker => {
3850
const [startTime, setStartTime] = useState<number | null>(null)
3951
const [elapsedSeconds, setElapsedSeconds] = useState<number>(0)
52+
const [isPaused, setIsPaused] = useState(false)
53+
// Track accumulated time from previous pause/resume cycles
54+
const [accumulatedSeconds, setAccumulatedSeconds] = useState(0)
4055

4156
const start = useCallback(() => {
4257
setStartTime(Date.now())
58+
setAccumulatedSeconds(0)
59+
setIsPaused(false)
4360
}, [])
4461

4562
const stop = useCallback(() => {
4663
setStartTime(null)
4764
setElapsedSeconds(0)
65+
setAccumulatedSeconds(0)
66+
setIsPaused(false)
4867
}, [])
4968

69+
const pause = useCallback(() => {
70+
if (startTime && !isPaused) {
71+
// Capture current elapsed time before pausing
72+
const currentElapsed = Math.floor((Date.now() - startTime) / 1000)
73+
setAccumulatedSeconds(currentElapsed)
74+
setElapsedSeconds(currentElapsed)
75+
setIsPaused(true)
76+
}
77+
}, [startTime, isPaused])
78+
79+
const resume = useCallback(() => {
80+
if (isPaused) {
81+
// Set a new start time adjusted for accumulated time
82+
setStartTime(Date.now() - accumulatedSeconds * 1000)
83+
setIsPaused(false)
84+
}
85+
}, [isPaused, accumulatedSeconds])
86+
5087
useEffect(() => {
51-
if (!startTime) {
52-
setElapsedSeconds(0)
88+
if (!startTime || isPaused) {
89+
// When paused, keep showing the frozen elapsed time (don't reset)
90+
if (!isPaused && !startTime) {
91+
setElapsedSeconds(0)
92+
}
5393
return
5494
}
5595

@@ -65,11 +105,11 @@ export const useElapsedTime = (): ElapsedTimeTracker => {
65105
const interval = setInterval(updateElapsed, 1000)
66106

67107
return () => clearInterval(interval)
68-
}, [startTime])
108+
}, [startTime, isPaused])
69109

70110
const timer = useMemo(
71-
() => ({ start, stop, elapsedSeconds, startTime }),
72-
[start, stop, elapsedSeconds, startTime],
111+
() => ({ start, stop, pause, resume, elapsedSeconds, startTime, isPaused }),
112+
[start, stop, pause, resume, elapsedSeconds, startTime, isPaused],
73113
)
74114

75115
return timer

cli/src/utils/__tests__/send-message-timer.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ describe('createSendMessageTimerController', () => {
99
const mainAgentTimer = {
1010
start: mock(() => {}),
1111
stop: mock(() => {}),
12+
pause: mock(() => {}),
13+
resume: mock(() => {}),
14+
isPaused: false,
1215
}
1316

1417
const controller = createSendMessageTimerController({
@@ -37,6 +40,9 @@ describe('createSendMessageTimerController', () => {
3740
const mainAgentTimer = {
3841
start: mock(() => {}),
3942
stop: mock(() => {}),
43+
pause: mock(() => {}),
44+
resume: mock(() => {}),
45+
isPaused: false,
4046
}
4147

4248
const controller = createSendMessageTimerController({

cli/src/utils/send-message-timer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface SendMessageTimerController {
2626
finishedAt: number
2727
elapsedMs: number
2828
} | null
29+
pause: () => void
30+
resume: () => void
2931
isActive: () => boolean
3032
}
3133

@@ -94,7 +96,15 @@ export const createSendMessageTimerController = (
9496
return { finishedAt, elapsedMs }
9597
}
9698

99+
const pause = () => {
100+
mainAgentTimer.pause()
101+
}
102+
103+
const resume = () => {
104+
mainAgentTimer.resume()
105+
}
106+
97107
const isActive = () => timerActive
98108

99-
return { start, stop, isActive }
109+
return { start, stop, pause, resume, isActive }
100110
}

cli/src/utils/status-indicator-state.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type StatusIndicatorState =
99
| { kind: 'waiting' }
1010
| { kind: 'streaming' }
1111
| { kind: 'reconnected' }
12+
| { kind: 'paused' }
1213

1314
export type AuthStatus = 'ok' | 'retrying' | 'unreachable'
1415

@@ -82,9 +83,9 @@ export const getStatusIndicatorState = ({
8283
return { kind: 'connecting' }
8384
}
8485

85-
// Hide working/thinking indicators when ask_user is active
86+
// Show paused state when ask_user is active (timer stays visible but frozen)
8687
if (isAskUserActive) {
87-
return { kind: 'idle' }
88+
return { kind: 'paused' }
8889
}
8990

9091
if (streamStatus === 'waiting') {

0 commit comments

Comments
 (0)