fix(frontend): nouvelle conversation par défaut à l'ouverture et au lancement d'un agent — #371 (QA verte)

cause: l'ouverture d'IdeA ou le lancement ordinaire d'un agent dérivait la sélection UI du conversationId persisté sur la leaf (T4b), exposant l'ancien fil par défaut au retour d'application ; la persistance du pivot runtime/historique était confondue avec un choix de reprise.

fix: le lancement ordinaire d'une cellule agent passe désormais freshStart: true sans transmettre le conversationId de la leaf — seul un resume explicite (menu Conversations / sélection intentional) fournit un id ; CustomAgentChatView reçoit freshOnOpen du LeafView, donc une ouverture normale démarre sur une nouvelle conversation quand aucune session structurée live n'existe ; garde de continuité préservée : si une session structurée est toujours vivante (remount transitoire), réattach live sans fresh ; l'id assigné par un lancement reste persisté sur la leaf via setCellConversation mais uniquement comme pivot runtime/historique, jamais comme sélection UI implicite (#371) ; contrats ports/domain/adapters/useLayout réalignés (assignedConversationId, launchAgent, LeafCell.conversationId).

tests: ouverture ordinaire = nouvelle conversation sans readPage ni reprise ; resume explicite toujours fonctionnel ; continuité live au remontage avec session vivante préservée.

QA verte: git diff --check ; npm run typecheck ; vitest ciblé setCellConversation/CustomAgentChatView/resumeProject/LayoutGrid/LayoutGrid.chat 144/144 ; npm test src/features/agents src/features/layout 350/350.

fichiers .ideai/* et scripts/ hors périmètre ; aucun push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 18:53:20 +02:00
parent 70998a30bc
commit f347698239
9 changed files with 147 additions and 61 deletions

View File

@ -1069,8 +1069,8 @@ export function CustomAgentChatView({
() => new Set(),
);
const [currentSession, setCurrentSession] = useState(sessionId);
// `freshOnOpen` is reserved for an explicit reset intent. Ordinary React
// remounts keep the durable conversation selected by the hosting cell.
// A normal cell opening starts fresh when no live session exists. React
// remounts with a live session still preserve that runtime continuity.
const [activeConversationId, setActiveConversationId] = useState<string | null>(
sessionId || !freshOnOpen ? conversationId : null,
);
@ -2005,6 +2005,7 @@ export function CustomAgentChatView({
options.conversationId === undefined
? activeConversationIdRef.current ?? undefined
: options.conversationId ?? undefined,
...(options.conversationId === null ? { freshStart: true } : {}),
...(nodeId ? { nodeId } : {}),
};
console.debug("[ticket149] recoverStructuredSession:launchAgentChat:start", {
@ -2266,6 +2267,29 @@ export function CustomAgentChatView({
setSavedConversationLoadState("idle");
setLiveSessionAttached(false);
if (freshOnOpen) {
// A missing layout session can be a transient remount while this
// agent is still active. Preserve that live continuity without
// treating the leaf's persisted conversation id as a UI choice.
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);
setResumedConversation(false);
return;
}
await recoverStructuredSession({
applyScrollback: false,
retryAttachNotFound: true,