From 9a42b00bfea4016a6af02dd06e2c21d526e59a05 Mon Sep 17 00:00:00 2001 From: wuyang6 Date: Wed, 13 May 2026 16:56:14 +0800 Subject: [PATCH] Fix Jupyter workspace session scoping --- .../app/components/assistant-ui/thread.tsx | 57 ++++++++++++++----- frontend/app/lib/claw-active-session.ts | 14 ++++- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index 32ba6db..61332cf 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -81,7 +81,9 @@ import { } from "@/components/ui/tooltip"; import { clearActiveSessionId, + PENDING_WORKSPACE_SESSION_CHANGED_EVENT, readActiveSessionId, + readPendingWorkspaceSessionId, writeActiveSessionId, writePendingWorkspaceSessionId, } from "@/lib/claw-active-session"; @@ -273,17 +275,46 @@ function useReplayedRunState() { readMetadataValue(replayRunMessage?.metadata, "runId"), ); }); - const localSessionId = - typeof window !== "undefined" - ? normalizeSessionId(readActiveSessionId()) - : null; - const sessionId = replaySessionId ?? latestSessionId ?? localSessionId; + const sessionId = replaySessionId ?? latestSessionId; return useMemo( () => ({ active, sessionId, runId }), [active, sessionId, runId], ); } +function usePendingWorkspaceSessionId() { + const [sessionId, setSessionId] = useState(() => + typeof window !== "undefined" + ? normalizeSessionId(readPendingWorkspaceSessionId()) + : null, + ); + + useEffect(() => { + const refresh = () => { + setSessionId(normalizeSessionId(readPendingWorkspaceSessionId())); + }; + window.addEventListener(PENDING_WORKSPACE_SESSION_CHANGED_EVENT, refresh); + window.addEventListener("storage", refresh); + window.addEventListener("focus", refresh); + return () => { + window.removeEventListener( + PENDING_WORKSPACE_SESSION_CHANGED_EVENT, + refresh, + ); + window.removeEventListener("storage", refresh); + window.removeEventListener("focus", refresh); + }; + }, []); + + return sessionId; +} + +function useCurrentThreadSessionId({ includePending = false } = {}) { + const currentRun = useReplayedRunState(); + const pendingSessionId = usePendingWorkspaceSessionId(); + return currentRun.sessionId ?? (includePending ? pendingSessionId : null); +} + async function cancelLatestRun(sessionId: string, runId?: string | null) { await fetch("/api/claw/runs/cancel", { method: "POST", @@ -298,8 +329,9 @@ async function cancelLatestRun(sessionId: string, runId?: string | null) { const ChatTopActions: FC = () => { const { openFiles } = useActivityPanel(); const [workspaceOpen, setWorkspaceOpen] = useState(false); - const currentRun = useReplayedRunState(); - const normalizedSessionId = currentRun.sessionId; + const normalizedSessionId = useCurrentThreadSessionId({ + includePending: true, + }); const workspaceStatus = useJupyterWorkspaceStatus(normalizedSessionId); return ( @@ -444,11 +476,7 @@ function WorkspaceSwitchDialog({ null, ); const fieldId = useId(); - const effectiveSessionId = - sessionId ?? - (typeof window !== "undefined" - ? normalizeSessionId(readActiveSessionId()) - : null); + const effectiveSessionId = sessionId; useEffect(() => { if (!open || !effectiveSessionId) return; @@ -726,6 +754,9 @@ const ThreadSuggestionItem: FC = () => { const Composer: FC = () => { const replayedRun = useReplayedRunState(); + const workspaceSessionId = useCurrentThreadSessionId({ + includePending: true, + }); return ( @@ -734,7 +765,7 @@ const Composer: FC = () => { className="flex w-full flex-col gap-2 rounded-(--composer-radius) border bg-background p-(--composer-padding) transition-shadow focus-within:border-ring/75 focus-within:ring-2 focus-within:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50" > - +