Skip to content

Commit d6ba03f

Browse files
refactor: extract reconcileConfirmedPrompt into dedicated module
1 parent 1133b10 commit d6ba03f

2 files changed

Lines changed: 40 additions & 28 deletions

File tree

frontend/src/hooks/useOpenCode.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { showToast } from "../lib/toast";
1414
import { useSendErrorStore } from "../stores/sendErrorStore";
1515
import { useSessionStatus } from "../stores/sessionStatusStore";
1616
import { invalidateSessionListCaches, messagesQueryKey } from "../lib/queryInvalidation";
17+
import { reconcileConfirmedPrompt } from "../lib/sendErrorReconcile";
1718

1819
type AssistantMessage = components["schemas"]["AssistantMessage"];
1920

@@ -156,34 +157,6 @@ export const useMessages = (opcodeUrl: string | null | undefined, sessionID: str
156157
});
157158
};
158159

159-
const getUserMessageText = (message: MessageWithParts) => {
160-
if (message.info.role !== 'user') return ''
161-
return message.parts
162-
.map((part) => part.type === 'text' ? part.text || '' : '')
163-
.join('')
164-
.trim()
165-
}
166-
167-
const hasUserMessageText = (messages: MessageWithParts[], prompt: string) => {
168-
const expected = prompt.trim()
169-
if (!expected) return false
170-
return messages.some((message) => getUserMessageText(message) === expected)
171-
}
172-
173-
const reconcileConfirmedPrompt = (sessionID: string, messages: MessageWithParts[]) => {
174-
const sendErrorStore = useSendErrorStore.getState()
175-
const sendError = sendErrorStore.getError(sessionID)
176-
const queuedPrompt = sendErrorStore.queuedPrompts[sessionID]
177-
178-
if (sendError?.kind === 'network' && sendError.failedPrompt && hasUserMessageText(messages, sendError.failedPrompt)) {
179-
sendErrorStore.clearNetworkError(sessionID)
180-
}
181-
182-
if (queuedPrompt && hasUserMessageText(messages, queuedPrompt)) {
183-
sendErrorStore.clearQueuedPrompt(sessionID)
184-
}
185-
}
186-
187160
export const useCreateSession = (
188161
opcodeUrl: string | null | undefined,
189162
directory?: string,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { MessageWithParts } from '../api/types'
2+
import { useSendErrorStore } from '../stores/sendErrorStore'
3+
4+
const getUserMessageText = (message: MessageWithParts): string => {
5+
if (message.info.role !== 'user') return ''
6+
return message.parts
7+
.map((part) => (part.type === 'text' ? part.text || '' : ''))
8+
.join('')
9+
.trim()
10+
}
11+
12+
const hasUserMessageText = (messages: MessageWithParts[], prompt: string): boolean => {
13+
const expected = prompt.trim()
14+
if (!expected) return false
15+
return messages.some((message) => getUserMessageText(message) === expected)
16+
}
17+
18+
/**
19+
* Strict backstop for clearing stale send state when a prompt is confirmed delivered.
20+
*
21+
* The SSE `message.updated` handler is the realtime authority and clears send errors
22+
* unconditionally because it only receives message metadata (text parts stream in
23+
* separately). This helper runs against a fully fetched message list — where text parts
24+
* are present — and clears only when the failed/queued prompt text actually appears,
25+
* recovering sessions whose confirming SSE events were missed (e.g. backgrounded tab).
26+
*/
27+
export const reconcileConfirmedPrompt = (sessionID: string, messages: MessageWithParts[]): void => {
28+
const sendErrorStore = useSendErrorStore.getState()
29+
const sendError = sendErrorStore.getError(sessionID)
30+
const queuedPrompt = sendErrorStore.queuedPrompts[sessionID]
31+
32+
if (sendError?.kind === 'network' && sendError.failedPrompt && hasUserMessageText(messages, sendError.failedPrompt)) {
33+
sendErrorStore.clearNetworkError(sessionID)
34+
}
35+
36+
if (queuedPrompt && hasUserMessageText(messages, queuedPrompt)) {
37+
sendErrorStore.clearQueuedPrompt(sessionID)
38+
}
39+
}

0 commit comments

Comments
 (0)