Skip to content
Draft
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
8 changes: 7 additions & 1 deletion apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
const phase = derivePhase(activeThread?.session ?? null);
const isSendBusy = sendPhase !== "idle";
const isPreparingWorktree = sendPhase === "preparing-worktree";
const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
const nowIso = new Date(nowTick).toISOString();
const activeWorkStartedAt = deriveActiveWorkStartedAt(
activeLatestTurn,
Expand All @@ -684,6 +683,13 @@ export default function ChatView({ threadId }: ChatViewProps) {
() => derivePendingUserInputs(threadActivities),
[threadActivities],
);
// phase === "running" alone isn't sufficient: the session can stay "running" even after
// the latest turn has completed (server lifecycle guard can reject status updates while
// completedAt is set through a separate path) or while waiting for user input.
// Use completedAt on the latest turn as the reliable completion signal.
const turnDone = !!activeLatestTurn?.completedAt;
const isActivelyRunning = phase === "running" && pendingUserInputs.length === 0 && !turnDone;
const isWorking = isActivelyRunning || isSendBusy || isConnecting || isRevertingCheckpoint;
const activePendingUserInput = pendingUserInputs[0] ?? null;
const activePendingDraftAnswers = useMemo(
() =>
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/Sidebar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ export function resolveThreadStatusPill(input: {
};
}

if (thread.session?.status === "running") {
// Use completedAt as the reliable completion signal — activeTurnId can be permanently
// stale when the server lifecycle guard rejects status updates.
const sidebarTurnDone = !!thread.latestTurn?.completedAt;

if (thread.session?.status === "running" && !sidebarTurnDone) {
return {
label: "Working",
colorClass: "text-sky-600 dark:text-sky-300/80",
Expand Down