fix(frontend): le changement d'onglet ne déclenche plus de reprise de la conversation active — au remontage d'une cellule avec conversationId, réattache d'abord la session structurée vivante (sessionId du cache de conversation, puis registre listLiveAgents/attachLiveAgent avec rebinding de node) via reattachStructuredSession preserveViewOnEmptyScrollback avant tout chargement d'historique durable ; le transcript persisté (conversation.readPage) n'est consulté qu'en l'absence de runtime live et la bannière « Conversation reprise » reste réservée à la reprise explicite depuis le menu Conversations — éviction du cache de session morte (chatSessionViewCache.delete) réconciliée avec le cache de conversation pour ne pas rejouer un transcript obsolète ; tests: réattach live au remontage sans readPage ni bannière (#370), retour depuis une vue non conversationnelle = replay live complet (second reattach, pas de readPage, pas de bannière) — #370 (QA verte: npm ci, npm run typecheck, npx vitest run src/features/agents/CustomAgentChatView.test.tsx src/features/layout/LayoutGrid.chat.test.tsx, git diff --check ; fichiers .ideai/* et scripts/ hors périmètre ; aucun push)
This commit is contained in:
@ -1885,6 +1885,7 @@ export function CustomAgentChatView({
|
||||
applyScrollback?: boolean;
|
||||
stableIdentity?: boolean;
|
||||
preserveRestartTurns?: boolean;
|
||||
preserveViewOnEmptyScrollback?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
if (!agent.reattachAgentChat) throw new Error("Structured reattach unavailable");
|
||||
@ -1921,7 +1922,10 @@ export function CustomAgentChatView({
|
||||
if (reattached.conversationId) {
|
||||
publishConversationId(reattached.conversationId);
|
||||
}
|
||||
if (options.applyScrollback) {
|
||||
if (
|
||||
options.applyScrollback &&
|
||||
(reattached.scrollback.length > 0 || !options.preserveViewOnEmptyScrollback)
|
||||
) {
|
||||
scrollbackChunkCountRef.current = reattached.scrollback.length;
|
||||
let replayed = reattached.scrollback.reduce(
|
||||
(state, chunk) => foldChunk(state, chunk),
|
||||
@ -2214,6 +2218,21 @@ export function CustomAgentChatView({
|
||||
if (!isNotFound(stableError)) throw stableError;
|
||||
}
|
||||
if (!cancelled) {
|
||||
chatSessionViewCache.delete(externalSessionId);
|
||||
const cacheKey = conversationCacheKey(
|
||||
projectId,
|
||||
agentId,
|
||||
activeConversationIdRef.current,
|
||||
);
|
||||
const cached = cacheKey ? chatConversationViewCache.get(cacheKey) : undefined;
|
||||
if (cacheKey && cached?.sessionId === externalSessionId) {
|
||||
chatConversationViewCache.set(cacheKey, {
|
||||
turns: cached.turns,
|
||||
progressIndex: cached.progressIndex,
|
||||
progressScope: cached.progressScope,
|
||||
chunkCount: cached.chunkCount,
|
||||
});
|
||||
}
|
||||
setCurrentSession(null);
|
||||
setExternalSessionId(null);
|
||||
setLiveSessionAttached(false);
|
||||
@ -2256,6 +2275,51 @@ export function CustomAgentChatView({
|
||||
return;
|
||||
}
|
||||
|
||||
// A cell can remount while its structured runtime remains live (for
|
||||
// example after switching tabs). The layout session can temporarily
|
||||
// be absent, but the conversation cache and live registry still carry
|
||||
// the runtime identity. Reattach that runtime before considering the
|
||||
// durable transcript: a conversation id alone is not a request to
|
||||
// replace an active view with saved history (#370).
|
||||
const cachedSessionId = cached?.sessionId;
|
||||
if (cachedSessionId) {
|
||||
try {
|
||||
const attachedSessionId = await reattachStructuredSession(cachedSessionId, {
|
||||
applyScrollback: true,
|
||||
stableIdentity: true,
|
||||
preserveViewOnEmptyScrollback: true,
|
||||
});
|
||||
if (cancelled) return;
|
||||
publishSessionId(attachedSessionId);
|
||||
setSavedConversationLoadState("idle");
|
||||
setResumedConversation(false);
|
||||
return;
|
||||
} catch (e) {
|
||||
if (!isNotFound(e)) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const live = agent.listLiveAgents
|
||||
? (await agent.listLiveAgents(projectId)).find(
|
||||
(candidate) => candidate.agentId === agentId && candidate.kind === "structured",
|
||||
)
|
||||
: undefined;
|
||||
if (live) {
|
||||
const rebound =
|
||||
nodeId && live.nodeId !== nodeId && agent.attachLiveAgent
|
||||
? await agent.attachLiveAgent(projectId, agentId, nodeId)
|
||||
: live;
|
||||
const attachedSessionId = await reattachStructuredSession(rebound.sessionId, {
|
||||
applyScrollback: true,
|
||||
preserveViewOnEmptyScrollback: true,
|
||||
});
|
||||
if (cancelled) return;
|
||||
publishSessionId(attachedSessionId);
|
||||
setSavedConversationLoadState("idle");
|
||||
setResumedConversation(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setSavedConversationLoadState("loading");
|
||||
setLiveSessionAttached(false);
|
||||
try {
|
||||
@ -2311,8 +2375,10 @@ export function CustomAgentChatView({
|
||||
publishSessionId,
|
||||
applyChatViewState,
|
||||
conversation,
|
||||
agent,
|
||||
projectId,
|
||||
agentId,
|
||||
nodeId,
|
||||
freshOnOpen,
|
||||
openRetryNonce,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user