Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 05fc461

Browse files
authored
feat(code): pause timer when awaiting user input (#1233)
## problem agent timer does not pause when awaiting user input, so if you take a break, you'll get crazy times on the display closes https://github.com/PostHog/code/issues/1227 ## changes tracks `pausedDurationMs` , accumulated while the agent is waiting for input, and subtracted from display times ## testing manually tested locally - started a task - let it sit on permissions for a long time - verified timer did not include the waiting time after approving
1 parent 7068b57 commit 05fc461

6 files changed

Lines changed: 50 additions & 17 deletions

File tree

apps/code/src/renderer/features/sessions/components/ConversationView.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
useOptimisticItemsForTask,
44
usePendingPermissionsForTask,
55
useQueuedMessagesForTask,
6+
useSessionForTask,
67
} from "@features/sessions/stores/sessionStore";
78
import { useSettingsStore } from "@features/settings/stores/settingsStore";
89
import { useFeatureFlag } from "@hooks/useFeatureFlag";
@@ -72,6 +73,8 @@ export function ConversationView({
7273
const pendingPermissionsCount = pendingPermissions.size;
7374
const queuedMessages = useQueuedMessagesForTask(taskId);
7475
const optimisticItems = useOptimisticItemsForTask(taskId);
76+
const session = useSessionForTask(taskId);
77+
const pausedDurationMs = session?.pausedDurationMs ?? 0;
7578

7679
const queuedItems = useMemo<Extract<ConversationItem, { type: "queued" }>[]>(
7780
() =>
@@ -188,11 +191,14 @@ export function ConversationView({
188191
isPromptPending={isPromptPending}
189192
promptStartedAt={promptStartedAt}
190193
lastGenerationDuration={
191-
lastTurnInfo?.isComplete ? lastTurnInfo.durationMs : null
194+
lastTurnInfo?.isComplete
195+
? Math.max(0, lastTurnInfo.durationMs - pausedDurationMs)
196+
: null
192197
}
193198
lastStopReason={lastTurnInfo?.stopReason}
194199
queuedCount={queuedMessages.length}
195200
hasPendingPermission={pendingPermissionsCount > 0}
201+
pausedDurationMs={pausedDurationMs}
196202
/>
197203
</div>
198204
}

apps/code/src/renderer/features/sessions/components/GeneratingIndicator.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Brain, Circle } from "@phosphor-icons/react";
22
import { Flex, Text } from "@radix-ui/themes";
3-
import { useEffect, useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44

55
const THINKING_MESSAGES = [
66
"Booping",
@@ -110,16 +110,24 @@ export function formatDuration(ms: number): string {
110110
interface GeneratingIndicatorProps {
111111
/** Timestamp (ms) when the prompt started. Only render this component while a prompt is pending. */
112112
startedAt?: number | null;
113+
/** Accumulated time (ms) spent waiting for user input, subtracted from elapsed display. */
114+
pausedDurationMs?: number;
113115
}
114116

115-
export function GeneratingIndicator({ startedAt }: GeneratingIndicatorProps) {
117+
export function GeneratingIndicator({
118+
startedAt,
119+
pausedDurationMs,
120+
}: GeneratingIndicatorProps) {
116121
const [elapsed, setElapsed] = useState(0);
117122
const [activity, setActivity] = useState(getRandomThinkingMessage);
118123

124+
const pausedRef = useRef(pausedDurationMs ?? 0);
125+
pausedRef.current = pausedDurationMs ?? 0;
126+
119127
useEffect(() => {
120128
const startTime = startedAt ?? Date.now();
121129
const interval = setInterval(() => {
122-
setElapsed(Date.now() - startTime);
130+
setElapsed(Math.max(0, Date.now() - startTime - pausedRef.current));
123131
}, 50);
124132

125133
return () => clearInterval(interval);

apps/code/src/renderer/features/sessions/components/SessionFooter.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface SessionFooterProps {
1010
lastStopReason?: string;
1111
queuedCount?: number;
1212
hasPendingPermission?: boolean;
13+
pausedDurationMs?: number;
1314
}
1415

1516
export function SessionFooter({
@@ -19,6 +20,7 @@ export function SessionFooter({
1920
lastStopReason,
2021
queuedCount = 0,
2122
hasPendingPermission = false,
23+
pausedDurationMs,
2224
}: SessionFooterProps) {
2325
if (isPromptPending) {
2426
// Show static "waiting" state when permission is pending
@@ -41,7 +43,10 @@ export function SessionFooter({
4143
return (
4244
<Box className="pt-3 pb-1">
4345
<Flex align="center" gap="2">
44-
<GeneratingIndicator startedAt={promptStartedAt} />
46+
<GeneratingIndicator
47+
startedAt={promptStartedAt}
48+
pausedDurationMs={pausedDurationMs}
49+
/>
4550
{queuedCount > 0 && (
4651
<Text size="1" color="gray">
4752
({queuedCount} queued)

apps/code/src/renderer/features/sessions/service/service.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ const createMockSession = (
207207
isPromptPending: false,
208208
promptStartedAt: null,
209209
pendingPermissions: new Map(),
210+
pausedDurationMs: 0,
210211
messageQueue: [],
211212
optimisticItems: [],
212213
...overrides,

apps/code/src/renderer/features/sessions/service/service.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ export class SessionService {
803803
sessionStoreSetters.updateSession(taskRunId, {
804804
isPromptPending: true,
805805
promptStartedAt: acpMsg.ts,
806+
pausedDurationMs: 0,
806807
});
807808
}
808809
if (
@@ -1077,6 +1078,7 @@ export class SessionService {
10771078
sessionStoreSetters.updateSession(session.taskRunId, {
10781079
isPromptPending: true,
10791080
promptStartedAt: Date.now(),
1081+
pausedDurationMs: 0,
10801082
});
10811083

10821084
sessionStoreSetters.appendOptimisticItem(session.taskRunId, {
@@ -1457,6 +1459,24 @@ export class SessionService {
14571459

14581460
// --- Permissions ---
14591461

1462+
private resolvePermission(session: AgentSession, toolCallId: string): void {
1463+
const permission = session.pendingPermissions.get(toolCallId);
1464+
const newPermissions = new Map(session.pendingPermissions);
1465+
newPermissions.delete(toolCallId);
1466+
sessionStoreSetters.setPendingPermissions(
1467+
session.taskRunId,
1468+
newPermissions,
1469+
);
1470+
1471+
if (permission?.receivedAt) {
1472+
sessionStoreSetters.updateSession(session.taskRunId, {
1473+
pausedDurationMs:
1474+
(session.pausedDurationMs ?? 0) +
1475+
(Date.now() - permission.receivedAt),
1476+
});
1477+
}
1478+
}
1479+
14601480
/**
14611481
* Respond to a permission request.
14621482
*/
@@ -1473,12 +1493,7 @@ export class SessionService {
14731493
return;
14741494
}
14751495

1476-
const newPermissions = new Map(session.pendingPermissions);
1477-
newPermissions.delete(toolCallId);
1478-
sessionStoreSetters.setPendingPermissions(
1479-
session.taskRunId,
1480-
newPermissions,
1481-
);
1496+
this.resolvePermission(session, toolCallId);
14821497

14831498
try {
14841499
await trpcClient.agent.respondToPermission.mutate({
@@ -1515,12 +1530,7 @@ export class SessionService {
15151530
return;
15161531
}
15171532

1518-
const newPermissions = new Map(session.pendingPermissions);
1519-
newPermissions.delete(toolCallId);
1520-
sessionStoreSetters.setPendingPermissions(
1521-
session.taskRunId,
1522-
newPermissions,
1523-
);
1533+
this.resolvePermission(session, toolCallId);
15241534

15251535
try {
15261536
await trpcClient.agent.cancelPermission.mutate({
@@ -2164,6 +2174,7 @@ export class SessionService {
21642174
isPromptPending: false,
21652175
promptStartedAt: null,
21662176
pendingPermissions: new Map(),
2177+
pausedDurationMs: 0,
21672178
messageQueue: [],
21682179
optimisticItems: [],
21692180
};

apps/code/src/renderer/features/sessions/stores/sessionStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export interface AgentSession {
5555
/** Session configuration options (model, mode, thought level, etc.) */
5656
configOptions?: SessionConfigOption[];
5757
pendingPermissions: Map<string, PermissionRequest>;
58+
/** Accumulated time (ms) spent waiting for user input (permissions, questions, etc.) */
59+
pausedDurationMs: number;
5860
messageQueue: QueuedMessage[];
5961
/** Whether this session is for a cloud run */
6062
isCloud?: boolean;

0 commit comments

Comments
 (0)