Skip to content

Commit b3cd3bc

Browse files
authored
Merge pull request #7834 from Agenta-AI/fix/frontend-lost-message-on-session-switch
fix(frontend): keep a sent message when the user switches sessions
2 parents dd43c87 + 0d036f9 commit b3cd3bc

8 files changed

Lines changed: 599 additions & 16 deletions

File tree

‎web/mobile/src/features/chat/LiveConversation.tsx‎

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
getInteractionAvailability,
2727
getLivePendingApprovals,
2828
isSessionTurnStopping,
29+
refusedSendReason,
2930
type TurnViewModel,
3031
} from "@agenta/chat/model"
3132
import {getSessionTurnId} from "@agenta/chat/state"
@@ -51,13 +52,19 @@ import type {RichChatInputHandle} from "@agenta/ui/rich-chat-input"
5152
import {isAltChord} from "@agenta/ui/shortcuts"
5253
import {Button} from "@agenta/ui/ui"
5354
import {useQueryClient} from "@tanstack/react-query"
55+
import type {FileUIPart} from "ai"
5456
import {useAtomValue, useSetAtom, useStore} from "jotai"
5557

5658
import {ContentRail} from "@/components/ContentRail"
5759
import {ScreenScaffold} from "@/components/ScreenScaffold"
5860

5961
import {useProjectPermission} from "../context/useProjectPermission"
60-
import {failPendingTaskAtom, pendingTasksAtom, sendPendingTaskAtom} from "../home/pendingTask"
62+
import {
63+
failPendingTaskAtom,
64+
keepFailedSendAtom,
65+
pendingTasksAtom,
66+
sendPendingTaskAtom,
67+
} from "../home/pendingTask"
6168
import {AppShell} from "../nav/AppShell"
6269
import {livenessQueryKey, useLivenessUpdatedAt} from "../sessions/useLivenessPoll"
6370
import {useWalletSummary} from "../wallet/useWalletSummary"
@@ -179,6 +186,34 @@ export const LiveConversation = ({
179186
const registerLocalSession = useSetAtom(registerLocalSessionAtom)
180187
const markLocalSessionAccepted = useSetAtom(markLocalSessionAcceptedAtom)
181188
const dropUnacceptedLocalSession = useSetAtom(dropUnacceptedLocalSessionAtom)
189+
// A send outlives this screen. One that fails after the user left has no composer to go back
190+
// to, so its session stays listed and the message waits as a failed task for their return.
191+
const keepFailedSend = useSetAtom(keepFailedSendAtom)
192+
const onScreenRef = useRef(false)
193+
useEffect(() => {
194+
onScreenRef.current = true
195+
return () => {
196+
onScreenRef.current = false
197+
}
198+
}, [])
199+
const sendFailed = useCallback(
200+
(message: {text: string; fileParts?: FileUIPart[]}, error?: unknown) => {
201+
if (onScreenRef.current) {
202+
dropUnacceptedLocalSession(sessionId)
203+
return
204+
}
205+
keepFailedSend({
206+
sessionId,
207+
task: {
208+
agentId: agentId ?? entityId,
209+
text: message.text,
210+
parts: message.fileParts,
211+
failureReason: refusedSendReason(error) ?? undefined,
212+
},
213+
})
214+
},
215+
[agentId, dropUnacceptedLocalSession, entityId, keepFailedSend, sessionId],
216+
)
182217
const followCommitRef = useRef<(revisionId: string) => void>(() => undefined)
183218
const conversation = useAgentConversation({
184219
entityId,
@@ -189,7 +224,7 @@ export const LiveConversation = ({
189224
sharedReaderLivenessUpdatedAt: livenessUpdatedAt,
190225
restoreRefusedSend,
191226
onSendAccepted: () => markLocalSessionAccepted(sessionId),
192-
onSendFailed: () => dropUnacceptedLocalSession(sessionId),
227+
onSendFailed: sendFailed,
193228
})
194229
const canEditSecrets = useProjectPermission(projectId, "edit_secret")
195230
const store = useStore()
@@ -356,17 +391,17 @@ export const LiveConversation = ({
356391
await sendToConversation(input)
357392
} catch (error) {
358393
// The durable path reports this through `onSendFailed` too; this covers the rest.
359-
dropUnacceptedLocalSession(sessionId)
394+
sendFailed({text: input.text, fileParts: input.parts}, error)
360395
throw error
361396
}
362397
},
363398
[
364399
agentId,
365400
buildKitReadyAtom,
366-
dropUnacceptedLocalSession,
367401
firstTurn,
368402
projectId,
369403
registerLocalSession,
404+
sendFailed,
370405
sendToConversation,
371406
sessionId,
372407
store,

‎web/mobile/src/features/home/pendingTask.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ export const failPendingTaskAtom = atom(null, (get, set, sessionId: string) => {
5959
}
6060
})
6161

62+
/**
63+
* Keep a send that failed after its chat screen closed. No composer is left to take the text back,
64+
* so it waits here as a failed task, and the chat screen offers it again with a retry when the user
65+
* comes back. A slot already in use is a Home task, whose own send marks it failed.
66+
*/
67+
export const keepFailedSendAtom = atom(
68+
null,
69+
(get, set, {sessionId, task}: {sessionId: string; task: PendingTask}) => {
70+
const tasks = get(pendingTasksAtom)
71+
if (tasks[sessionId]) return
72+
set(pendingTasksAtom, {...tasks, [sessionId]: {...task, delivery: "failed"}})
73+
},
74+
)
75+
6276
export const sendPendingTaskAtom = atom(
6377
null,
6478
async (

0 commit comments

Comments
 (0)