Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
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
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ import {
useOptimisticItemsForTask,
usePendingPermissionsForTask,
useQueuedMessagesForTask,
useSessionForTask,
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { useFeatureFlag } from "@hooks/useFeatureFlag";
Expand DownExpand Up@@ -72,6 +73,8 @@ export function ConversationView({
const pendingPermissionsCount = pendingPermissions.size;
const queuedMessages = useQueuedMessagesForTask(taskId);
const optimisticItems = useOptimisticItemsForTask(taskId);
const session = useSessionForTask(taskId);
const pausedDurationMs = session?.pausedDurationMs ?? 0;

const queuedItems = useMemo<Extract<ConversationItem, { type: "queued" }>[]>(
() =>
Expand DownExpand Up@@ -188,11 +191,14 @@ export function ConversationView({
isPromptPending={isPromptPending}
promptStartedAt={promptStartedAt}
lastGenerationDuration={
lastTurnInfo?.isComplete ? lastTurnInfo.durationMs : null
lastTurnInfo?.isComplete
? Math.max(0, lastTurnInfo.durationMs - pausedDurationMs)
: null
}
lastStopReason={lastTurnInfo?.stopReason}
queuedCount={queuedMessages.length}
hasPendingPermission={pendingPermissionsCount > 0}
pausedDurationMs={pausedDurationMs}
/>
</div>
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
import { Brain, Circle } from "@phosphor-icons/react";
import { Flex, Text } from "@radix-ui/themes";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";

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

export function GeneratingIndicator({ startedAt }: GeneratingIndicatorProps) {
export function GeneratingIndicator({
startedAt,
pausedDurationMs,
}: GeneratingIndicatorProps) {
const [elapsed, setElapsed] = useState(0);
const [activity, setActivity] = useState(getRandomThinkingMessage);

const pausedRef = useRef(pausedDurationMs ?? 0);
pausedRef.current = pausedDurationMs ?? 0;

useEffect(() => {
const startTime = startedAt ?? Date.now();
const interval = setInterval(() => {
setElapsed(Date.now() - startTime);
setElapsed(Math.max(0, Date.now() - startTime - pausedRef.current));
}, 50);

return () => clearInterval(interval);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ interface SessionFooterProps {
lastStopReason?: string;
queuedCount?: number;
hasPendingPermission?: boolean;
pausedDurationMs?: number;
}

export function SessionFooter({
Expand All@@ -19,6 +20,7 @@ export function SessionFooter({
lastStopReason,
queuedCount = 0,
hasPendingPermission = false,
pausedDurationMs,
}: SessionFooterProps) {
if (isPromptPending) {
// Show static "waiting" state when permission is pending
Expand All@@ -41,7 +43,10 @@ export function SessionFooter({
return (
<Box className="pt-3 pb-1">
<Flex align="center" gap="2">
<GeneratingIndicator startedAt={promptStartedAt} />
<GeneratingIndicator
startedAt={promptStartedAt}
pausedDurationMs={pausedDurationMs}
/>
{queuedCount > 0 && (
<Text size="1" color="gray">
({queuedCount} queued)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -207,6 +207,7 @@ const createMockSession = (
isPromptPending: false,
promptStartedAt: null,
pendingPermissions: new Map(),
pausedDurationMs: 0,
messageQueue: [],
optimisticItems: [],
...overrides,
Expand Down
35 changes: 23 additions & 12 deletions apps/code/src/renderer/features/sessions/service/service.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -803,6 +803,7 @@ export class SessionService {
sessionStoreSetters.updateSession(taskRunId, {
isPromptPending: true,
promptStartedAt: acpMsg.ts,
pausedDurationMs: 0,
});
}
if (
Expand DownExpand Up@@ -1077,6 +1078,7 @@ export class SessionService {
sessionStoreSetters.updateSession(session.taskRunId, {
isPromptPending: true,
promptStartedAt: Date.now(),
pausedDurationMs: 0,
});

sessionStoreSetters.appendOptimisticItem(session.taskRunId, {
Expand DownExpand Up@@ -1457,6 +1459,24 @@ export class SessionService {

// --- Permissions ---

private resolvePermission(session: AgentSession, toolCallId: string): void {
const permission = session.pendingPermissions.get(toolCallId);
const newPermissions = new Map(session.pendingPermissions);
newPermissions.delete(toolCallId);
sessionStoreSetters.setPendingPermissions(
session.taskRunId,
newPermissions,
);

if (permission?.receivedAt) {
sessionStoreSetters.updateSession(session.taskRunId, {
pausedDurationMs:
(session.pausedDurationMs ?? 0) +
(Date.now() - permission.receivedAt),
});
}
}

/**
* Respond to a permission request.
*/
Expand All@@ -1473,12 +1493,7 @@ export class SessionService {
return;
}

const newPermissions = new Map(session.pendingPermissions);
newPermissions.delete(toolCallId);
sessionStoreSetters.setPendingPermissions(
session.taskRunId,
newPermissions,
);
this.resolvePermission(session, toolCallId);

try {
await trpcClient.agent.respondToPermission.mutate({
Expand DownExpand Up@@ -1515,12 +1530,7 @@ export class SessionService {
return;
}

const newPermissions = new Map(session.pendingPermissions);
newPermissions.delete(toolCallId);
sessionStoreSetters.setPendingPermissions(
session.taskRunId,
newPermissions,
);
this.resolvePermission(session, toolCallId);

try {
await trpcClient.agent.cancelPermission.mutate({
Expand DownExpand Up@@ -2164,6 +2174,7 @@ export class SessionService {
isPromptPending: false,
promptStartedAt: null,
pendingPermissions: new Map(),
pausedDurationMs: 0,
messageQueue: [],
optimisticItems: [],
};
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,6 +55,8 @@ export interface AgentSession {
/** Session configuration options (model, mode, thought level, etc.) */
configOptions?: SessionConfigOption[];
pendingPermissions: Map<string, PermissionRequest>;
/** Accumulated time (ms) spent waiting for user input (permissions, questions, etc.) */
pausedDurationMs: number;
messageQueue: QueuedMessage[];
/** Whether this session is for a cloud run */
isCloud?: boolean;
Expand Down
Loading