fix(domain,application,infrastructure,backend,app-tauri,frontend): Stop demande agent préserve la conversation ouverte — arrêt sans fermeture/clôture du chat, états cohérents après cancel sur les 3 adapters de session, cellule et workstate synchronisés — #343 (QA verte)
This commit is contained in:
@ -1009,6 +1009,7 @@ export function CustomAgentChatView({
|
||||
>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [stopping, setStopping] = useState(false);
|
||||
const [stopTerminalOverride, setStopTerminalOverride] = useState(false);
|
||||
const [error, setError] = useState<ChatErrorState | null>(null);
|
||||
const [projectIcon, setProjectIcon] = useState<ProjectIconRef | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -1032,6 +1033,11 @@ export function CustomAgentChatView({
|
||||
const draftSaveSeqRef = useRef(0);
|
||||
const interactionSeqRef = useRef(0);
|
||||
const activeInteractionIdRef = useRef<string | null>(null);
|
||||
const cancelledInteractionIdsRef = useRef(new Set<string>());
|
||||
const ignoreUnscopedChunksAfterStopRef = useRef(false);
|
||||
const stopInFlightRef = useRef(false);
|
||||
const stopTerminalOverrideRef = useRef(false);
|
||||
const requesterRefreshSeqRef = useRef(0);
|
||||
const draftHydratedRef = useRef(false);
|
||||
const draftEditedBeforeHydrationRef = useRef(false);
|
||||
const lastPersistedDraftRef = useRef("");
|
||||
@ -1185,18 +1191,24 @@ export function CustomAgentChatView({
|
||||
busy ||
|
||||
stopping ||
|
||||
Boolean(restart && restart.outcome !== "failed") ||
|
||||
requesterDelegation.currentAgentBusy ||
|
||||
delegationActive;
|
||||
(!stopTerminalOverride &&
|
||||
(requesterDelegation.currentAgentBusy || delegationActive));
|
||||
const delegationFallbackText = requesterDelegation.delegationTargetName
|
||||
? `${agentName} attend ${requesterDelegation.delegationTargetName}`
|
||||
: tr("Délégation en cours.", "Delegation in progress.");
|
||||
|
||||
const refreshRequesterDelegation = useCallback(async () => {
|
||||
const refreshSeq = ++requesterRefreshSeqRef.current;
|
||||
if (!workState) {
|
||||
if (refreshSeq !== requesterRefreshSeqRef.current) return;
|
||||
setRequesterDelegation({
|
||||
currentAgentBusy: false,
|
||||
delegationTargetName: null,
|
||||
});
|
||||
if (stopTerminalOverrideRef.current) {
|
||||
stopTerminalOverrideRef.current = false;
|
||||
setStopTerminalOverride(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -1214,11 +1226,17 @@ export function CustomAgentChatView({
|
||||
ticket.source.agentId === agentId,
|
||||
),
|
||||
);
|
||||
if (refreshSeq !== requesterRefreshSeqRef.current) return;
|
||||
setRequesterDelegation({
|
||||
currentAgentBusy,
|
||||
delegationTargetName: target ? workAgentDisplayName(target) : null,
|
||||
});
|
||||
if (stopTerminalOverrideRef.current && !currentAgentBusy && !target) {
|
||||
stopTerminalOverrideRef.current = false;
|
||||
setStopTerminalOverride(false);
|
||||
}
|
||||
} catch {
|
||||
if (refreshSeq !== requesterRefreshSeqRef.current) return;
|
||||
setRequesterDelegation({
|
||||
currentAgentBusy: false,
|
||||
delegationTargetName: null,
|
||||
@ -1446,7 +1464,20 @@ export function CustomAgentChatView({
|
||||
}, []);
|
||||
|
||||
const receive = useCallback(
|
||||
(chunk: ReplyChunk) => {
|
||||
(chunk: ReplyChunk, sourceInteractionId?: string) => {
|
||||
if (
|
||||
sourceInteractionId &&
|
||||
cancelledInteractionIdsRef.current.has(sourceInteractionId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!sourceInteractionId && ignoreUnscopedChunksAfterStopRef.current) {
|
||||
if (chunk.kind === "userPrompt") {
|
||||
ignoreUnscopedChunksAfterStopRef.current = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setTranscriptUnavailableReason(null);
|
||||
scrollbackChunkCountRef.current += 1;
|
||||
const activeInteractionId = activeInteractionIdRef.current;
|
||||
@ -2284,6 +2315,9 @@ export function CustomAgentChatView({
|
||||
const attachmentLabels = outgoingAttachments.map((item) => item.label);
|
||||
const displayText = text || "Pièce jointe";
|
||||
const interactionId = `${agentId}:${++interactionSeqRef.current}`;
|
||||
ignoreUnscopedChunksAfterStopRef.current = false;
|
||||
stopTerminalOverrideRef.current = false;
|
||||
setStopTerminalOverride(false);
|
||||
activeInteractionIdRef.current = interactionId;
|
||||
markProjectCliInteractionStarted(projectId, interactionId);
|
||||
const finishInteraction = () => {
|
||||
@ -2312,8 +2346,10 @@ export function CustomAgentChatView({
|
||||
}));
|
||||
const sendTurn = (sid: string) =>
|
||||
attachmentInputs.length > 0
|
||||
? agent.sendAgentChat!(sid, text, receive, { attachments: attachmentInputs })
|
||||
: agent.sendAgentChat!(sid, text, receive);
|
||||
? agent.sendAgentChat!(sid, text, (chunk) => receive(chunk, interactionId), {
|
||||
attachments: attachmentInputs,
|
||||
})
|
||||
: agent.sendAgentChat!(sid, text, (chunk) => receive(chunk, interactionId));
|
||||
try {
|
||||
const sid =
|
||||
currentSession ??
|
||||
@ -2322,8 +2358,10 @@ export function CustomAgentChatView({
|
||||
retryAttachNotFound: true,
|
||||
}));
|
||||
await sendTurn(sid);
|
||||
if (cancelledInteractionIdsRef.current.has(interactionId)) return;
|
||||
finishInteraction();
|
||||
} catch (e) {
|
||||
if (cancelledInteractionIdsRef.current.has(interactionId)) return;
|
||||
if (isNotFound(e)) {
|
||||
try {
|
||||
setCurrentSession(null);
|
||||
@ -2361,11 +2399,31 @@ export function CustomAgentChatView({
|
||||
|
||||
async function stopCurrentRequest() {
|
||||
const sid = sessionRef.current;
|
||||
if (!sid || !agent.cancelAgentChat || stopping) return;
|
||||
if (
|
||||
!sid ||
|
||||
!agent.cancelAgentChat ||
|
||||
stopping ||
|
||||
stopInFlightRef.current ||
|
||||
stopTerminalOverrideRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
stopInFlightRef.current = true;
|
||||
ignoreUnscopedChunksAfterStopRef.current = true;
|
||||
const interactionBeingCancelled = activeInteractionIdRef.current;
|
||||
if (interactionBeingCancelled) {
|
||||
cancelledInteractionIdsRef.current.add(interactionBeingCancelled);
|
||||
}
|
||||
setStopping(true);
|
||||
setError(null);
|
||||
try {
|
||||
await agent.cancelAgentChat(sid);
|
||||
const settleStoppedRequest = (alreadyStopped: boolean) => {
|
||||
if (stopTerminalOverrideRef.current) return;
|
||||
stopTerminalOverrideRef.current = true;
|
||||
setStopTerminalOverride(true);
|
||||
setRequesterDelegation({
|
||||
currentAgentBusy: false,
|
||||
delegationTargetName: null,
|
||||
});
|
||||
if (activeInteractionIdRef.current) {
|
||||
markProjectCliInteractionStopped(projectId, activeInteractionIdRef.current);
|
||||
activeInteractionIdRef.current = null;
|
||||
@ -2380,32 +2438,28 @@ export function CustomAgentChatView({
|
||||
...completed.turns,
|
||||
{
|
||||
role: "tool",
|
||||
label: tr("Demande arrêtée.", "Request stopped."),
|
||||
label: alreadyStopped
|
||||
? tr("Demande déjà arrêtée.", "Request already stopped.")
|
||||
: tr("Demande arrêtée.", "Request stopped."),
|
||||
receivedAtMs: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
void refreshRequesterDelegation();
|
||||
};
|
||||
try {
|
||||
const result = await agent.cancelAgentChat(sid);
|
||||
settleStoppedRequest(result.outcome !== "cancelled");
|
||||
} catch (e) {
|
||||
if (isNotFound(e)) {
|
||||
if (activeInteractionIdRef.current) {
|
||||
markProjectCliInteractionStopped(projectId, activeInteractionIdRef.current);
|
||||
activeInteractionIdRef.current = null;
|
||||
}
|
||||
setBusy(false);
|
||||
setOpening(false);
|
||||
updateChatViewState((prev) => {
|
||||
const completed = completeChatViewProgress(prev);
|
||||
return {
|
||||
...completed,
|
||||
turns: [
|
||||
...completed.turns,
|
||||
{ role: "tool", label: tr("Demande déjà arrêtée.", "Request already stopped."), receivedAtMs: null },
|
||||
],
|
||||
};
|
||||
});
|
||||
settleStoppedRequest(true);
|
||||
return;
|
||||
}
|
||||
if (interactionBeingCancelled) {
|
||||
cancelledInteractionIdsRef.current.delete(interactionBeingCancelled);
|
||||
}
|
||||
ignoreUnscopedChunksAfterStopRef.current = false;
|
||||
setError({ message: describe(e), openFailure: false });
|
||||
updateChatViewState((prev) => ({
|
||||
...prev,
|
||||
@ -2419,6 +2473,7 @@ export function CustomAgentChatView({
|
||||
],
|
||||
}));
|
||||
} finally {
|
||||
stopInFlightRef.current = false;
|
||||
setStopping(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user