Skip to content

Commit 7e5f12f

Browse files
committed
ship: iteration 3 — disable stale recovery actions
1 parent 83bf6a2 commit 7e5f12f

3 files changed

Lines changed: 51 additions & 6 deletions

File tree

apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,7 @@ function renderEvent(
26432643
surfaceProfile?: ChatSurfaceProfile;
26442644
assistantLabel?: string;
26452645
turnActive?: boolean;
2646+
sessionTurnActive?: boolean;
26462647
sessionEnded?: boolean;
26472648
onOpenWorkspacePath?: (path: string | WorkspacePathLocation) => void;
26482649
respondingApprovalIds?: Set<string>;
@@ -3687,7 +3688,7 @@ function renderEvent(
36873688
{recovery ? (
36883689
<ProviderFailureRecoveryCard
36893690
recovery={recovery}
3690-
disabled={Boolean(options?.turnActive)}
3691+
disabled={Boolean(options?.sessionTurnActive)}
36913692
onRetry={options?.onRetryProviderFailure
36923693
? () => options.onRetryProviderFailure?.(event.turnId ?? null)
36933694
: undefined}
@@ -4167,6 +4168,7 @@ type EventRowProps = {
41674168
surfaceProfile?: ChatSurfaceProfile;
41684169
assistantLabel?: string;
41694170
turnActive?: boolean;
4171+
sessionTurnActive?: boolean;
41704172
sessionEnded?: boolean;
41714173
isLatestWorkLog?: boolean;
41724174
onOpenWorkspacePath?: (path: string | WorkspacePathLocation) => void;
@@ -4202,6 +4204,7 @@ const EventRow = React.memo(function EventRow({
42024204
surfaceProfile = "standard",
42034205
assistantLabel,
42044206
turnActive,
4207+
sessionTurnActive,
42054208
sessionEnded,
42064209
isLatestWorkLog,
42074210
onOpenWorkspacePath,
@@ -4272,6 +4275,7 @@ const EventRow = React.memo(function EventRow({
42724275
surfaceProfile,
42734276
assistantLabel,
42744277
turnActive,
4278+
sessionTurnActive,
42754279
sessionEnded,
42764280
onOpenWorkspacePath,
42774281
respondingApprovalIds,
@@ -4600,6 +4604,7 @@ function AgentChatMessageListMain({
46004604
surfaceMode = "standard",
46014605
surfaceProfile = "standard",
46024606
assistantLabel,
4607+
sessionTurnActive = false,
46034608
onOpenWorkspacePath,
46044609
respondingApprovalIds,
46054610
pendingApprovalIds,
@@ -4626,6 +4631,7 @@ function AgentChatMessageListMain({
46264631
surfaceMode?: ChatSurfaceMode;
46274632
surfaceProfile?: ChatSurfaceProfile;
46284633
assistantLabel?: string;
4634+
sessionTurnActive?: boolean;
46294635
onOpenWorkspacePath?: (path: string, laneId?: string | null) => void;
46304636
onInsertDraft?: (text: string) => void;
46314637
onRevealChatTerminal?: (terminal: { terminalId: string; ptyId: string; label: string }) => void;
@@ -5431,6 +5437,7 @@ function AgentChatMessageListMain({
54315437
surfaceProfile={surfaceProfile}
54325438
assistantLabel={assistantLabel}
54335439
turnActive={rowTurnActive}
5440+
sessionTurnActive={sessionTurnActive}
54345441
sessionEnded={sessionEnded}
54355442
isLatestWorkLog={isLatestWorkLog}
54365443
onOpenWorkspacePath={openWorkspacePath}
@@ -5469,6 +5476,7 @@ function AgentChatMessageListMain({
54695476
surfaceProfile={surfaceProfile}
54705477
assistantLabel={assistantLabel}
54715478
turnActive={rowTurnActive}
5479+
sessionTurnActive={sessionTurnActive}
54725480
sessionEnded={sessionEnded}
54735481
isLatestWorkLog={isLatestWorkLog}
54745482
onOpenWorkspacePath={openWorkspacePath}
@@ -5490,7 +5498,7 @@ function AgentChatMessageListMain({
54905498
assistantTurnCopy={assistantTurnCopy}
54915499
/>
54925500
);
5493-
}, [activeTurnId, anchoredRowKey, assistantLabel, assistantTurnCopyByRowKey, surfaceMode, surfaceProfile, groupedRows, latestWorkLogIndex, turnModelState, handleApproval, handleMeasure, openWorkspacePath, handleNavigateSuggestion, handleReviewChanges, onCodexRecovery, onRetryProviderFailure, onChooseProviderFailureModel, onInsertDraft, onRevealChatTerminal, onRewindFiles, turnDiffSummaries, respondingApprovalIds, pendingApprovalIds, resolvedInputStates, laneId, sessionId, sessionEnded, runtimeName, mosaic, scrollToRowKey]);
5501+
}, [activeTurnId, anchoredRowKey, assistantLabel, assistantTurnCopyByRowKey, surfaceMode, surfaceProfile, groupedRows, latestWorkLogIndex, turnModelState, handleApproval, handleMeasure, openWorkspacePath, handleNavigateSuggestion, handleReviewChanges, onCodexRecovery, onRetryProviderFailure, onChooseProviderFailureModel, onInsertDraft, onRevealChatTerminal, onRewindFiles, turnDiffSummaries, respondingApprovalIds, pendingApprovalIds, resolvedInputStates, laneId, sessionId, sessionTurnActive, sessionEnded, runtimeName, mosaic, scrollToRowKey]);
54945502

54955503
// Compute the bottom spacer height for virtualized mode.
54965504
const bottomSpacerHeight = useMemo(() => {

apps/desktop/src/renderer/components/chat/AgentChatPane.test.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,7 +2656,43 @@ describe("AgentChatPane submit recovery", () => {
26562656
await waitFor(() => expect(modelTrigger.getAttribute("aria-expanded")).toBe("true"));
26572657
fireEvent.click(modelTrigger);
26582658

2659-
fireEvent.click(screen.getByRole("button", { name: "Retry turn" }));
2659+
const retryButton = screen.getByRole("button", { name: "Retry turn" }) as HTMLButtonElement;
2660+
const chooseModelButton = screen.getByRole("button", { name: "Choose model" }) as HTMLButtonElement;
2661+
act(() => {
2662+
emitChatEvent({
2663+
sessionId: session.sessionId,
2664+
timestamp: "2026-07-10T18:18:54.000Z",
2665+
sequence: 6,
2666+
event: { type: "status", turnStatus: "started", turnId: "turn-next" },
2667+
});
2668+
});
2669+
await waitFor(() => {
2670+
expect(retryButton.disabled).toBe(true);
2671+
expect(chooseModelButton.disabled).toBe(true);
2672+
});
2673+
fireEvent.click(retryButton);
2674+
expect(send).not.toHaveBeenCalled();
2675+
2676+
act(() => {
2677+
emitChatEvent({
2678+
sessionId: session.sessionId,
2679+
timestamp: "2026-07-10T18:18:55.000Z",
2680+
sequence: 7,
2681+
event: { type: "status", turnStatus: "completed", turnId: "turn-next" },
2682+
});
2683+
emitChatEvent({
2684+
sessionId: session.sessionId,
2685+
timestamp: "2026-07-10T18:18:55.001Z",
2686+
sequence: 8,
2687+
event: { type: "done", status: "completed", turnId: "turn-next", model: "gpt-5.4" },
2688+
});
2689+
});
2690+
await waitFor(() => {
2691+
expect(retryButton.disabled).toBe(false);
2692+
expect(chooseModelButton.disabled).toBe(false);
2693+
});
2694+
2695+
fireEvent.click(retryButton);
26602696
await waitFor(() => {
26612697
expect(send).toHaveBeenCalledWith(expect.objectContaining({
26622698
sessionId: session.sessionId,
@@ -2670,8 +2706,8 @@ describe("AgentChatPane submit recovery", () => {
26702706
act(() => {
26712707
emitChatEvent({
26722708
sessionId: session.sessionId,
2673-
timestamp: "2026-07-10T18:18:54.000Z",
2674-
sequence: 6,
2709+
timestamp: "2026-07-10T18:18:56.000Z",
2710+
sequence: 9,
26752711
event: { type: "system_notice", noticeKind: "info", message: "Session metadata refreshed." },
26762712
});
26772713
});

apps/desktop/src/renderer/components/chat/AgentChatPane.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11109,6 +11109,7 @@ export function AgentChatPane({
1110911109
: subagentView
1111011110
? subagentTranscriptLoading || subagentViewSnapshot?.status === "running"
1111111111
: turnActive && selectedSession?.status !== "ended"}
11112+
sessionTurnActive={turnActive}
1111211113
sessionEnded={selectedSession?.status === "ended"}
1111311114
className="min-h-0 border-0"
1111411115
surfaceMode={surfaceMode}
@@ -11143,7 +11144,7 @@ export function AgentChatPane({
1114311144
onCodexRecovery={(args: AgentChatRecoverCodexTurnArgs) =>
1114411145
window.ade.agentChat.recoverCodexTurn(args)}
1114511146
onRetryProviderFailure={(failedTurnId) => {
11146-
if (!selectedSessionId) return;
11147+
if (!selectedSessionId || turnActive) return;
1114711148
void resendLastUserMessage(selectedSessionId, failedTurnId);
1114811149
}}
1114911150
onChooseProviderFailureModel={() => {

0 commit comments

Comments
 (0)