Fix Jupyter workspace session scoping

This commit is contained in:
wuyang6
2026-05-13 16:56:14 +08:00
parent 9984d35d1f
commit 9a42b00bfe
2 changed files with 57 additions and 14 deletions
+44 -13
View File
@@ -81,7 +81,9 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { import {
clearActiveSessionId, clearActiveSessionId,
PENDING_WORKSPACE_SESSION_CHANGED_EVENT,
readActiveSessionId, readActiveSessionId,
readPendingWorkspaceSessionId,
writeActiveSessionId, writeActiveSessionId,
writePendingWorkspaceSessionId, writePendingWorkspaceSessionId,
} from "@/lib/claw-active-session"; } from "@/lib/claw-active-session";
@@ -273,17 +275,46 @@ function useReplayedRunState() {
readMetadataValue(replayRunMessage?.metadata, "runId"), readMetadataValue(replayRunMessage?.metadata, "runId"),
); );
}); });
const localSessionId = const sessionId = replaySessionId ?? latestSessionId;
typeof window !== "undefined"
? normalizeSessionId(readActiveSessionId())
: null;
const sessionId = replaySessionId ?? latestSessionId ?? localSessionId;
return useMemo( return useMemo(
() => ({ active, sessionId, runId }), () => ({ active, sessionId, runId }),
[active, sessionId, runId], [active, sessionId, runId],
); );
} }
function usePendingWorkspaceSessionId() {
const [sessionId, setSessionId] = useState<string | null>(() =>
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) { async function cancelLatestRun(sessionId: string, runId?: string | null) {
await fetch("/api/claw/runs/cancel", { await fetch("/api/claw/runs/cancel", {
method: "POST", method: "POST",
@@ -298,8 +329,9 @@ async function cancelLatestRun(sessionId: string, runId?: string | null) {
const ChatTopActions: FC = () => { const ChatTopActions: FC = () => {
const { openFiles } = useActivityPanel(); const { openFiles } = useActivityPanel();
const [workspaceOpen, setWorkspaceOpen] = useState(false); const [workspaceOpen, setWorkspaceOpen] = useState(false);
const currentRun = useReplayedRunState(); const normalizedSessionId = useCurrentThreadSessionId({
const normalizedSessionId = currentRun.sessionId; includePending: true,
});
const workspaceStatus = useJupyterWorkspaceStatus(normalizedSessionId); const workspaceStatus = useJupyterWorkspaceStatus(normalizedSessionId);
return ( return (
@@ -444,11 +476,7 @@ function WorkspaceSwitchDialog({
null, null,
); );
const fieldId = useId(); const fieldId = useId();
const effectiveSessionId = const effectiveSessionId = sessionId;
sessionId ??
(typeof window !== "undefined"
? normalizeSessionId(readActiveSessionId())
: null);
useEffect(() => { useEffect(() => {
if (!open || !effectiveSessionId) return; if (!open || !effectiveSessionId) return;
@@ -726,6 +754,9 @@ const ThreadSuggestionItem: FC = () => {
const Composer: FC = () => { const Composer: FC = () => {
const replayedRun = useReplayedRunState(); const replayedRun = useReplayedRunState();
const workspaceSessionId = useCurrentThreadSessionId({
includePending: true,
});
return ( return (
<ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col"> <ComposerPrimitive.Root className="aui-composer-root relative flex w-full flex-col">
<ComposerPrimitive.AttachmentDropzone asChild> <ComposerPrimitive.AttachmentDropzone asChild>
@@ -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" 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"
> >
<ComposerAttachments /> <ComposerAttachments />
<ComposerWorkspaceStatus sessionId={replayedRun.sessionId} /> <ComposerWorkspaceStatus sessionId={workspaceSessionId} />
<ImeComposerInput <ImeComposerInput
placeholder="Send a message..." placeholder="Send a message..."
className="aui-composer-input max-h-32 min-h-10 w-full resize-none bg-transparent px-1.75 py-1 text-sm outline-none placeholder:text-muted-foreground/80" className="aui-composer-input max-h-32 min-h-10 w-full resize-none bg-transparent px-1.75 py-1 text-sm outline-none placeholder:text-muted-foreground/80"
+13 -1
View File
@@ -3,6 +3,8 @@
const ACTIVE_SESSION_ID_KEY = "claw.activeSessionId"; const ACTIVE_SESSION_ID_KEY = "claw.activeSessionId";
const PENDING_WORKSPACE_SESSION_ID_KEY = "claw.pendingWorkspaceSessionId"; const PENDING_WORKSPACE_SESSION_ID_KEY = "claw.pendingWorkspaceSessionId";
export const ACTIVE_SESSION_CHANGED_EVENT = "claw-active-session-changed"; export const ACTIVE_SESSION_CHANGED_EVENT = "claw-active-session-changed";
export const PENDING_WORKSPACE_SESSION_CHANGED_EVENT =
"claw-pending-workspace-session-changed";
export function readActiveSessionId() { export function readActiveSessionId() {
if (typeof window === "undefined") return null; if (typeof window === "undefined") return null;
@@ -55,6 +57,11 @@ export function writePendingWorkspaceSessionId(
} }
window.sessionStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized); window.sessionStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized);
window.localStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized); window.localStorage.setItem(PENDING_WORKSPACE_SESSION_ID_KEY, normalized);
window.dispatchEvent(
new CustomEvent(PENDING_WORKSPACE_SESSION_CHANGED_EVENT, {
detail: { sessionId: normalized },
}),
);
} }
export function consumePendingWorkspaceSessionId() { export function consumePendingWorkspaceSessionId() {
@@ -63,7 +70,7 @@ export function consumePendingWorkspaceSessionId() {
return sessionId; return sessionId;
} }
function readPendingWorkspaceSessionId() { export function readPendingWorkspaceSessionId() {
if (typeof window === "undefined") return null; if (typeof window === "undefined") return null;
return ( return (
normalizeSessionId( normalizeSessionId(
@@ -79,6 +86,11 @@ function clearPendingWorkspaceSessionId() {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
window.sessionStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY); window.sessionStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY);
window.localStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY); window.localStorage.removeItem(PENDING_WORKSPACE_SESSION_ID_KEY);
window.dispatchEvent(
new CustomEvent(PENDING_WORKSPACE_SESSION_CHANGED_EVENT, {
detail: { sessionId: null },
}),
);
} }
function normalizeSessionId(value: unknown) { function normalizeSessionId(value: unknown) {