From 029ed97bffa783fb03775312887f627d482f59e0 Mon Sep 17 00:00:00 2001 From: Blomios Date: Wed, 5 Aug 2026 15:23:45 +0200 Subject: [PATCH] chore(chat): instrumentation session CustomAgentChatView pour diagnostic #149 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute des points de traçage ciblés sur le cycle de vie de session CustomAgentChatView/LayoutGrid pour isoler la cause du fallback silencieux CLI custom avant tentative de fix. Co-Authored-By: Claude Opus 4.8 --- .../features/agents/CustomAgentChatView.tsx | 114 +++++++++++++++++- frontend/src/features/layout/LayoutGrid.tsx | 55 ++++++--- 2 files changed, 149 insertions(+), 20 deletions(-) diff --git a/frontend/src/features/agents/CustomAgentChatView.tsx b/frontend/src/features/agents/CustomAgentChatView.tsx index bb65809..82dd1fc 100644 --- a/frontend/src/features/agents/CustomAgentChatView.tsx +++ b/frontend/src/features/agents/CustomAgentChatView.tsx @@ -40,6 +40,19 @@ function describe(e: unknown): string { return String(e); } +function instrumentationError(e: unknown): Record { + if (e && typeof e === "object") { + const err = e as GatewayError & { name?: string }; + return { + code: err.code, + message: err.message, + name: err.name, + raw: e, + }; + } + return { message: String(e), raw: e }; +} + function isNotFound(e: unknown): boolean { return Boolean(e && typeof e === "object" && (e as GatewayError).code === "NOT_FOUND"); } @@ -127,6 +140,7 @@ export function CustomAgentChatView({ const sessionRef = useRef(sessionId); sessionRef.current = currentSession; const mountedRef = useRef(false); + const openOrAttachCountRef = useRef(0); const onSessionIdRef = useRef(onSessionId); onSessionIdRef.current = onSessionId; const onConversationIdRef = useRef(onConversationId); @@ -156,7 +170,28 @@ export function CustomAgentChatView({ const reattachStructuredSession = useCallback( async (sid: string, options: { applyScrollback?: boolean } = {}) => { if (!agent.reattachAgentChat) throw new Error("Structured reattach unavailable"); - const reattached = await agent.reattachAgentChat(sid, receive); + console.debug("[ticket149] reattachStructuredSession:start", { + timestamp: new Date().toISOString(), + sessionId: sid, + options, + }); + let reattached: Awaited>>; + try { + reattached = await agent.reattachAgentChat(sid, receive); + } catch (e) { + console.error("[ticket149] reattachStructuredSession:error", { + timestamp: new Date().toISOString(), + sessionId: sid, + error: instrumentationError(e), + }); + throw e; + } + console.debug("[ticket149] reattachStructuredSession:success", { + timestamp: new Date().toISOString(), + requestedSessionId: sid, + returnedSessionId: reattached.sessionId, + scrollbackCount: reattached.scrollback.length, + }); if (!mountedRef.current) return reattached.sessionId; setCurrentSession(reattached.sessionId); if (options.applyScrollback) { @@ -170,14 +205,47 @@ export function CustomAgentChatView({ const recoverStructuredSession = useCallback( async (options: { applyScrollback?: boolean; retryAttachNotFound?: boolean } = {}) => { if (!agent.launchAgentChat) throw new Error("Structured launch unavailable"); + console.debug("[ticket149] recoverStructuredSession:start", { + timestamp: new Date().toISOString(), + currentSession: sessionRef.current, + conversationId, + options, + }); let retryAttachNotFound = Boolean(options.retryAttachNotFound); for (;;) { - const launched = await agent.launchAgentChat(projectId, agentId, { + const launchRequest = { cwd, rows: 24, cols: 80, conversationId: conversationId ?? undefined, nodeId, + }; + console.debug("[ticket149] recoverStructuredSession:launchAgentChat:start", { + timestamp: new Date().toISOString(), + projectId, + agentId, + request: launchRequest, + options, + retryAttachNotFound, + }); + let launched: Awaited>>; + try { + launched = await agent.launchAgentChat(projectId, agentId, launchRequest); + } catch (e) { + console.error("[ticket149] recoverStructuredSession:launchAgentChat:error", { + timestamp: new Date().toISOString(), + projectId, + agentId, + request: launchRequest, + error: instrumentationError(e), + }); + throw e; + } + console.debug("[ticket149] recoverStructuredSession:launchAgentChat:success", { + timestamp: new Date().toISOString(), + projectId, + agentId, + launched, }); if (!mountedRef.current) return launched.sessionId; setCurrentSession(launched.sessionId); @@ -186,9 +254,15 @@ export function CustomAgentChatView({ onConversationIdRef.current(launched.assignedConversationId); } try { - return await reattachStructuredSession(launched.sessionId, { + const recoveredSessionId = await reattachStructuredSession(launched.sessionId, { applyScrollback: options.applyScrollback, }); + console.debug("[ticket149] recoverStructuredSession:success", { + timestamp: new Date().toISOString(), + launchedSessionId: launched.sessionId, + recoveredSessionId, + }); + return recoveredSessionId; } catch (e) { if (isNotFound(e) && retryAttachNotFound) { retryAttachNotFound = false; @@ -198,6 +272,11 @@ export function CustomAgentChatView({ } continue; } + console.error("[ticket149] recoverStructuredSession:reattach:error", { + timestamp: new Date().toISOString(), + launchedSessionId: launched.sessionId, + error: instrumentationError(e), + }); throw e; } } @@ -223,6 +302,13 @@ export function CustomAgentChatView({ let cancelled = false; async function openOrAttach() { + const execution = openOrAttachCountRef.current + 1; + openOrAttachCountRef.current = execution; + console.debug("[ticket149] openOrAttach:start", { + timestamp: new Date().toISOString(), + execution, + sessionId, + }); setOpening(true); setError(null); try { @@ -230,6 +316,11 @@ export function CustomAgentChatView({ try { await reattachStructuredSession(sessionId, { applyScrollback: true }); if (cancelled) return; + console.debug("[ticket149] openOrAttach:reattach-existing:success", { + timestamp: new Date().toISOString(), + execution, + sessionId, + }); return; } catch (e) { // The backend contract for `reattach_agent_chat` (NOT_FOUND) is that @@ -240,6 +331,12 @@ export function CustomAgentChatView({ setCurrentSession(null); onSessionIdRef.current(null); } + console.debug("[ticket149] openOrAttach:reattach-existing:not-found", { + timestamp: new Date().toISOString(), + execution, + sessionId, + error: instrumentationError(e), + }); } } await recoverStructuredSession({ @@ -247,7 +344,18 @@ export function CustomAgentChatView({ retryAttachNotFound: true, }); if (cancelled) return; + console.debug("[ticket149] openOrAttach:recover:success", { + timestamp: new Date().toISOString(), + execution, + receivedSessionId: sessionId, + }); } catch (e) { + console.error("[ticket149] openOrAttach:error", { + timestamp: new Date().toISOString(), + execution, + sessionId, + error: instrumentationError(e), + }); if (!cancelled) setError(describe(e)); } finally { if (!cancelled) setOpening(false); diff --git a/frontend/src/features/layout/LayoutGrid.tsx b/frontend/src/features/layout/LayoutGrid.tsx index c6980a3..a00a4cb 100644 --- a/frontend/src/features/layout/LayoutGrid.tsx +++ b/frontend/src/features/layout/LayoutGrid.tsx @@ -624,19 +624,24 @@ function LeafView({ legitChange?.agentId === pinnedAgent.id && legitChange.profileId === pinnedAgent.profileId, ); - if ( - shouldFallbackCustomCliMode({ - agentsLoaded, - profilesLoaded, - cellMode, - hasPinnedAgent: Boolean(pinnedAgent), - hasPinnedProfile: Boolean(pinnedProfile), - customCliAvailable, - effectiveCustomCliAvailable, - hasTrustedCustomCli: Boolean(trustedCustomCli), - hasLegitProfileChange: hasLegitDowngrade, - }) - ) { + const fallbackInput = { + agentsLoaded, + profilesLoaded, + cellMode, + hasPinnedAgent: Boolean(pinnedAgent), + hasPinnedProfile: Boolean(pinnedProfile), + customCliAvailable, + effectiveCustomCliAvailable, + hasTrustedCustomCli: Boolean(trustedCustomCli), + hasLegitProfileChange: hasLegitDowngrade, + }; + const shouldFallback = shouldFallbackCustomCliMode(fallbackInput); + console.debug("[ticket149] shouldFallbackCustomCliMode", { + timestamp: new Date().toISOString(), + input: fallbackInput, + result: shouldFallback, + }); + if (shouldFallback) { setTrustedCustomCli(null); setCellMode("tui"); } @@ -693,6 +698,22 @@ function LeafView({ else setCellMode(target); } + async function setSessionWithInstrumentation( + site: "custom" | "plain" | "plain-background-attach" | "mode-switch", + nextSession: string | null, + ): Promise { + console.debug("[ticket149] vm.setSession", { + timestamp: new Date().toISOString(), + nodeId: id, + site, + previousSession: session, + nextSession, + cellMode, + agentId, + }); + await vm.setSession(id, nextSession); + } + async function stopCurrentSessionForSwitch(): Promise { if (!session) return; if (agentId && agentGateway?.stopLiveAgent) { @@ -708,7 +729,7 @@ function LeafView({ } else { await terminal?.closeTerminal(session); } - await vm.setSession(id, null); + await setSessionWithInstrumentation("mode-switch", null); refreshLive(); } @@ -863,7 +884,7 @@ function LeafView({ if (background?.sessionId && agentGateway!.attachLiveAgent) { const attached = await agentGateway!.attachLiveAgent(projectId, agentId!, id); const sessionId = attached.sessionId ?? background.sessionId; - void vm.setSession(id, sessionId); + void setSessionWithInstrumentation("plain-background-attach", sessionId); const result = await agentGateway!.reattach(sessionId, onData); refreshLive(); return result.handle; @@ -1299,7 +1320,7 @@ function LeafView({ nodeId={id} sessionId={session} conversationId={conversationId} - onSessionId={(sid) => void vm.setSession(id, sid)} + onSessionId={(sid) => void setSessionWithInstrumentation("custom", sid)} onConversationId={(cid) => void vm.setCellConversation(id, cid)} /> ) : ( @@ -1309,7 +1330,7 @@ function LeafView({ open={terminalOpener} reattach={reattachOpener} sessionId={session} - onSessionId={(sid) => void vm.setSession(id, sid)} + onSessionId={(sid) => void setSessionWithInstrumentation("plain", sid)} agentMode={agentId != null} portal={agentId != null ? portal : undefined} refitSignal={refitSignal}