Skip to content

Commit 9747c49

Browse files
committed
Treat active turn as the source of working state
- Use activeTurnId instead of orchestration status for UI busy/scroll gating - Update settled-turn logic and tests for sessions that are running without an active turn
1 parent 584b7bd commit 9747c49

3 files changed

Lines changed: 25 additions & 13 deletions

File tree

apps/web/src/components/ChatView.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -974,11 +974,14 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
974974
[lockedProvider, providerModelsByProvider, selectableProviders],
975975
);
976976
const phase = derivePhase(activeThread?.session ?? null);
977+
const isTurnActive =
978+
activeThread?.session?.activeTurnId !== undefined &&
979+
activeThread?.session?.activeTurnId !== null;
977980
const isSendBusy = sendPhase !== "idle";
978981
const isPreparingWorktree = sendPhase === "preparing-worktree";
979982
const isTransportReady = transportState === "open";
980983
const isRemoteActionBlocked = !isTransportReady;
981-
const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
984+
const isWorking = isTurnActive || isSendBusy || isConnecting || isRevertingCheckpoint;
982985
const nowIso = new Date(nowTick).toISOString();
983986
const activeWorkStartedAt = deriveActiveWorkStartedAt(
984987
activeLatestTurn,
@@ -2530,10 +2533,10 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
25302533
scheduleStickToBottom();
25312534
}, [messageCount, scheduleStickToBottom]);
25322535
useEffect(() => {
2533-
if (phase !== "running") return;
2536+
if (!isTurnActive) return;
25342537
if (!shouldAutoScrollRef.current) return;
25352538
scheduleStickToBottom();
2536-
}, [phase, scheduleStickToBottom, timelineEntries]);
2539+
}, [isTurnActive, scheduleStickToBottom, timelineEntries]);
25372540

25382541
// Aggressively scroll to bottom after the user submits a new message.
25392542
// The virtualizer may not have settled by the time the first scroll fires,
@@ -2778,14 +2781,14 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
27782781
: "local";
27792782

27802783
useEffect(() => {
2781-
if (phase !== "running") return;
2784+
if (!isTurnActive) return;
27822785
const timer = window.setInterval(() => {
27832786
setNowTick(Date.now());
27842787
}, 1000);
27852788
return () => {
27862789
window.clearInterval(timer);
27872790
};
2788-
}, [phase]);
2791+
}, [isTurnActive]);
27892792

27902793
const beginSendPhase = useCallback((nextPhase: Exclude<SendPhase, "idle">) => {
27912794
setSendStartedAt((current) => current ?? new Date().toISOString());
@@ -2802,7 +2805,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
28022805
return;
28032806
}
28042807
if (
2805-
phase === "running" ||
2808+
isTurnActive ||
28062809
activePendingApproval !== null ||
28072810
activePendingUserInput !== null ||
28082811
activeThread?.error
@@ -2813,7 +2816,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
28132816
activePendingApproval,
28142817
activePendingUserInput,
28152818
activeThread?.error,
2816-
phase,
2819+
isTurnActive,
28172820
resetSendPhase,
28182821
sendPhase,
28192822
]);
@@ -3171,7 +3174,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
31713174
const api = readNativeApi();
31723175
if (!api || !activeThread || isRevertingCheckpoint) return;
31733176

3174-
if (phase === "running" || isSendBusy || isConnecting) {
3177+
if (isTurnActive || isSendBusy || isConnecting) {
31753178
setThreadError(activeThread.id, "Interrupt the current turn before reverting checkpoints.");
31763179
return;
31773180
}
@@ -3204,7 +3207,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
32043207
}
32053208
setIsRevertingCheckpoint(false);
32063209
},
3207-
[activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, phase, setThreadError],
3210+
[activeThread, isConnecting, isRevertingCheckpoint, isSendBusy, isTurnActive, setThreadError],
32083211
);
32093212

32103213
const readLiveComposerDraftSnapshot = useCallback(() => {
@@ -3448,7 +3451,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
34483451
}
34493452

34503453
// ── Queue message if a turn is already running ────────────────────
3451-
if (phase === "running") {
3454+
if (isTurnActive) {
34523455
const composerAttachmentsSnapshot = [...composerAttachmentsForSend];
34533456
const hiddenProviderInput = buildHiddenProviderInput({
34543457
prompt: promptForSend,
@@ -5548,7 +5551,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
55485551
Preparing worktree...
55495552
</span>
55505553
) : null}
5551-
{queuedMessages.length > 0 && phase === "running" ? (
5554+
{queuedMessages.length > 0 && isTurnActive ? (
55525555
<button
55535556
type="button"
55545557
className="flex items-center gap-1 text-muted-foreground/60 text-xs transition-colors hover:text-destructive"
@@ -5605,7 +5608,7 @@ export default function ChatView({ threadId, onMinimize }: ChatViewProps) {
56055608
: "Next question"}
56065609
</Button>
56075610
</div>
5608-
) : phase === "running" ? (
5611+
) : isTurnActive ? (
56095612
<div className="flex items-center gap-1.5">
56105613
<button
56115614
type="button"

apps/web/src/session-logic.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,15 @@ describe("isLatestTurnSettled", () => {
10571057
).toBe(false);
10581058
});
10591059

1060+
it("returns true when the session is running but no turn is active", () => {
1061+
expect(
1062+
isLatestTurnSettled(latestTurn, {
1063+
orchestrationStatus: "running",
1064+
activeTurnId: undefined,
1065+
}),
1066+
).toBe(true);
1067+
});
1068+
10601069
it("returns true once the session is no longer running that turn", () => {
10611070
expect(
10621071
isLatestTurnSettled(latestTurn, {

apps/web/src/session-logic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function isLatestTurnSettled(
146146
if (!latestTurn?.startedAt) return false;
147147
if (!latestTurn.completedAt) return false;
148148
if (!session) return true;
149-
if (session.orchestrationStatus === "running") return false;
149+
if (session.activeTurnId !== undefined && session.activeTurnId !== null) return false;
150150
return true;
151151
}
152152

0 commit comments

Comments
 (0)