diff --git a/frontend/app/app/assistant.tsx b/frontend/app/app/assistant.tsx index 5ae90cf..acf177d 100644 --- a/frontend/app/app/assistant.tsx +++ b/frontend/app/app/assistant.tsx @@ -20,6 +20,7 @@ import { SidebarTrigger, } from "@/components/ui/sidebar"; import { + consumePendingWorkspaceSessionId, readActiveSessionId, writeActiveSessionId, } from "@/lib/claw-active-session"; @@ -37,10 +38,21 @@ export const Assistant = () => { const messages = options.messages as UIMessage[]; const selectedSessionId = readActiveSessionId(); const lastSessionId = getLastSessionId(messages); + const pendingWorkspaceSessionId = + messages.length <= 1 ? consumePendingWorkspaceSessionId() : null; + const usablePendingWorkspaceSessionId = + pendingWorkspaceSessionId && + (!selectedSessionId || + selectedSessionId === pendingWorkspaceSessionId) + ? pendingWorkspaceSessionId + : null; const selectedResumeSessionId = messages.length > 1 ? selectedSessionId : null; const outgoingSessionId = - lastSessionId ?? selectedResumeSessionId ?? options.id; + lastSessionId ?? + selectedResumeSessionId ?? + usablePendingWorkspaceSessionId ?? + options.id; const lastUserMessage = [...messages] .reverse() .find((message) => message.role === "user"); @@ -49,7 +61,7 @@ export const Assistant = () => { return { body: { ...body, - id: options.id, + id: outgoingSessionId, messages: lastUserMessage ? [lastUserMessage] : [], resumeSessionId: lastSessionId ?? selectedResumeSessionId ?? undefined, diff --git a/frontend/app/components/assistant-ui/thread.tsx b/frontend/app/components/assistant-ui/thread.tsx index 9326d74..10ca8a0 100644 --- a/frontend/app/components/assistant-ui/thread.tsx +++ b/frontend/app/components/assistant-ui/thread.tsx @@ -83,6 +83,7 @@ import { clearActiveSessionId, readActiveSessionId, writeActiveSessionId, + writePendingWorkspaceSessionId, } from "@/lib/claw-active-session"; import { useClawSessionReplay } from "@/lib/claw-session-replay"; import { cn } from "@/lib/utils"; @@ -437,6 +438,7 @@ function WorkspaceSwitchDialog({ setSubmitting(true); const progressTimer = startWorkspaceProgress(setProgress); try { + const needsFirstMessageSession = !normalizeSessionId(sessionId); const response = await fetch("/api/claw/jupyter", { method: "POST", headers: { "content-type": "application/json" }, @@ -455,6 +457,9 @@ function WorkspaceSwitchDialog({ return; } writeActiveSessionId(sessionIdForRequest); + if (needsFirstMessageSession) { + writePendingWorkspaceSessionId(sessionIdForRequest); + } setStatus(payload as JupyterWorkspaceStatus); setProgress({ percent: 100, label: "切换完成" }); setMessage("工作区切换成功,后续工具会在远端 Jupyter 工作区执行。"); diff --git a/frontend/app/lib/claw-active-session.ts b/frontend/app/lib/claw-active-session.ts index 5227b98..252f9e1 100644 --- a/frontend/app/lib/claw-active-session.ts +++ b/frontend/app/lib/claw-active-session.ts @@ -1,6 +1,7 @@ "use client"; const ACTIVE_SESSION_ID_KEY = "claw.activeSessionId"; +const PENDING_WORKSPACE_SESSION_ID_KEY = "claw.pendingWorkspaceSessionId"; export const ACTIVE_SESSION_CHANGED_EVENT = "claw-active-session-changed"; export function readActiveSessionId() { @@ -20,6 +21,10 @@ export function writeActiveSessionId(sessionId: string | null | undefined) { } window.sessionStorage.setItem(ACTIVE_SESSION_ID_KEY, normalized); window.localStorage.setItem(ACTIVE_SESSION_ID_KEY, normalized); + const pendingSessionId = readPendingWorkspaceSessionId(); + if (pendingSessionId && pendingSessionId !== normalized) { + clearPendingWorkspaceSessionId(); + } window.dispatchEvent( new CustomEvent(ACTIVE_SESSION_CHANGED_EVENT, { detail: { sessionId: normalized }, @@ -31,6 +36,7 @@ export function clearActiveSessionId() { if (typeof window === "undefined") return; window.sessionStorage.removeItem(ACTIVE_SESSION_ID_KEY); window.localStorage.removeItem(ACTIVE_SESSION_ID_KEY); + clearPendingWorkspaceSessionId(); window.dispatchEvent( new CustomEvent(ACTIVE_SESSION_CHANGED_EVENT, { detail: { sessionId: null }, @@ -38,6 +44,43 @@ export function clearActiveSessionId() { ); } +export function writePendingWorkspaceSessionId( + sessionId: string | null | undefined, +) { + if (typeof window === "undefined") return; + const normalized = normalizeSessionId(sessionId); + if (!normalized) { + clearPendingWorkspaceSessionId(); + return; + } + window.sessionStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized); + window.localStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized); +} + +export function consumePendingWorkspaceSessionId() { + const sessionId = readPendingWorkspaceSessionId(); + clearPendingWorkspaceSessionId(); + return sessionId; +} + +function readPendingWorkspaceSessionId() { + if (typeof window === "undefined") return null; + return ( + normalizeSessionId( + window.sessionStorage.getItem(PENDING_WORKSPACE_SESSION_ID_KEY), + ) ?? + normalizeSessionId( + window.localStorage.getItem(PENDING_WORKSPACE_SESSION_ID_KEY), + ) + ); +} + +function clearPendingWorkspaceSessionId() { + if (typeof window === "undefined") return; + window.sessionStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY); + window.localStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY); +} + function normalizeSessionId(value: unknown) { if (typeof value !== "string") return null; const trimmed = value.trim();