Fix active session replay state

This commit is contained in:
wuyang6
2026-05-11 21:35:23 +08:00
parent 413a37b32c
commit 535e4b2c83
7 changed files with 88 additions and 46 deletions
+7 -11
View File
@@ -19,6 +19,10 @@ import {
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import {
readActiveSessionId,
writeActiveSessionId,
} from "@/lib/claw-active-session";
import { ClawSessionReplayProvider } from "@/lib/claw-session-replay";
import { ClawAccountGate, useClawAccount } from "./claw-account-gate";
@@ -31,21 +35,13 @@ export const Assistant = () => {
prepareSendMessagesRequest: async (options) => {
const body = options.body as Record<string, unknown>;
const messages = options.messages as UIMessage[];
const selectedSessionId =
typeof window !== "undefined"
? window.localStorage.getItem("claw.activeSessionId")
: null;
const selectedSessionId = readActiveSessionId();
const lastSessionId = getLastSessionId(messages);
const selectedResumeSessionId =
messages.length > 1 ? selectedSessionId : null;
const outgoingSessionId =
lastSessionId ?? selectedResumeSessionId ?? options.id;
if (typeof window !== "undefined" && outgoingSessionId) {
window.localStorage.setItem(
"claw.activeSessionId",
outgoingSessionId,
);
}
writeActiveSessionId(outgoingSessionId);
return {
body: {
@@ -65,7 +61,7 @@ export const Assistant = () => {
});
const replaySession = useCallback(
(sessionId: string, repository: ExportedMessageRepository) => {
window.localStorage.setItem("claw.activeSessionId", sessionId);
writeActiveSessionId(sessionId);
runtime.thread.import(repository);
},
[runtime],
+2 -1
View File
@@ -5,6 +5,7 @@ import type { ReactNode } from "react";
import { useCallback, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { clearActiveSessionId } from "@/lib/claw-active-session";
export type ClawAccount = {
id: string;
@@ -131,7 +132,7 @@ export function ClawAccountBar({
}) {
async function logout() {
await fetch("/api/claw/auth/logout", { method: "POST" });
window.localStorage.removeItem("claw.activeSessionId");
clearActiveSessionId();
onLogout();
}
@@ -37,6 +37,10 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
readActiveSessionId,
writeActiveSessionId,
} from "@/lib/claw-active-session";
import { cn } from "@/lib/utils";
type ActivityContextValue = {
@@ -71,12 +75,7 @@ export function ActivityProvider({ children }: { children: ReactNode }) {
}, []);
const openFiles = useCallback((nextSessionId?: string | null) => {
setMode("files");
setSessionId(
nextSessionId ??
(typeof window !== "undefined"
? window.localStorage.getItem("claw.activeSessionId")
: null),
);
setSessionId(nextSessionId ?? readActiveSessionId());
setFilesRefreshToken((value) => value + 1);
setOpen(true);
}, []);
@@ -257,7 +256,7 @@ function SessionFilesPanel({
let cancelled = false;
async function loadFiles() {
const activeSessionId = normalizeSessionId(
sessionId ?? window.localStorage.getItem("claw.activeSessionId"),
sessionId ?? readActiveSessionId(),
);
setStatus("正在读取聊天中的文件...");
try {
@@ -285,10 +284,7 @@ function SessionFilesPanel({
return;
}
if (nextPayload.session_id) {
window.localStorage.setItem(
"claw.activeSessionId",
nextPayload.session_id,
);
writeActiveSessionId(nextPayload.session_id);
}
setPayload(nextPayload);
setStatus("");
@@ -19,6 +19,11 @@ import {
useState,
} from "react";
import { Button } from "@/components/ui/button";
import {
clearActiveSessionId,
readActiveSessionId,
writeActiveSessionId,
} from "@/lib/claw-active-session";
import { useClawSessionReplay } from "@/lib/claw-session-replay";
type ClawSession = {
@@ -106,7 +111,7 @@ const ThreadListNew: FC = () => {
return (
<div
onClickCapture={() => {
window.localStorage.removeItem("claw.activeSessionId");
clearActiveSessionId();
window.dispatchEvent(new Event("claw-active-session-cleared"));
}}
>
@@ -141,7 +146,7 @@ const ClawSessionList: FC = () => {
const renameInputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
setActiveSessionId(window.localStorage.getItem("claw.activeSessionId"));
setActiveSessionId(readActiveSessionId());
const refreshSessions = () => {
fetch("/api/claw/sessions")
.then((res) => (res.ok ? res.json() : []))
@@ -247,10 +252,7 @@ const ClawSessionList: FC = () => {
className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
onClick={async () => {
setOpenMenuSessionId(null);
window.localStorage.setItem(
"claw.activeSessionId",
session.session_id,
);
writeActiveSessionId(session.session_id);
setActiveSessionId(session.session_id);
setLoadingSessionId(session.session_id);
try {
@@ -333,9 +335,7 @@ const ClawSessionList: FC = () => {
);
setOpenMenuSessionId(null);
if (activeSessionId === session.session_id) {
window.localStorage.removeItem(
"claw.activeSessionId",
);
clearActiveSessionId();
setActiveSessionId(null);
window.dispatchEvent(
new Event("claw-active-session-cleared"),
@@ -62,6 +62,11 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
clearActiveSessionId,
readActiveSessionId,
writeActiveSessionId,
} from "@/lib/claw-active-session";
import { useClawSessionReplay } from "@/lib/claw-session-replay";
import { cn } from "@/lib/utils";
@@ -193,6 +198,15 @@ function useRefreshReplayedRun() {
const runStatus = await fetchLatestRunStatus(activeSessionId);
if (cancelled) return;
if (runStatus?.status === "queued" || runStatus?.status === "running") {
const response = await fetch(`/api/claw/sessions/${activeSessionId}`, {
cache: "no-store",
});
if (!response.ok || cancelled) return;
const payload = await response.json();
replaySession(
activeSessionId,
toReplayRepository(payload, activeSessionId, runStatus),
);
return;
}
const response = await fetch(`/api/claw/sessions/${activeSessionId}`, {
@@ -243,7 +257,7 @@ function useReplayedRunState() {
});
const localSessionId =
typeof window !== "undefined"
? normalizeSessionId(window.localStorage.getItem("claw.activeSessionId"))
? normalizeSessionId(readActiveSessionId())
: null;
const sessionId = replaySessionId ?? latestSessionId ?? localSessionId;
return useMemo(
@@ -722,7 +736,7 @@ function useComposerContextStatus() {
useEffect(() => {
const cleanLatestSessionId = normalizeSessionId(latestSessionId);
if (cleanLatestSessionId) {
window.localStorage.setItem("claw.activeSessionId", cleanLatestSessionId);
writeActiveSessionId(cleanLatestSessionId);
window.setTimeout(() => {
window.dispatchEvent(new Event("claw-sessions-changed"));
}, 0);
@@ -752,13 +766,10 @@ function useComposerContextStatus() {
: {};
let sessionId = normalizeSessionId(
normalizeSessionId(latestSessionId) ||
normalizeSessionId(
window.localStorage.getItem("claw.activeSessionId"),
) ||
normalizeSessionId(readActiveSessionId()) ||
normalizeSessionId(statePayload.active_session_id),
);
if (sessionId)
window.localStorage.setItem("claw.activeSessionId", sessionId);
if (sessionId) writeActiveSessionId(sessionId);
let sessionPayload: ClawStoredSession | null = null;
let contextBudget: ContextBudget | undefined;
if (sessionId) {
@@ -772,7 +783,7 @@ function useComposerContextStatus() {
sessionPayload =
(await sessionResponse.json()) as ClawStoredSession;
} else if (sessionResponse.status === 404) {
window.localStorage.removeItem("claw.activeSessionId");
clearActiveSessionId();
sessionId = null;
}
}
@@ -43,6 +43,11 @@ import {
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
import {
clearActiveSessionId,
readActiveSessionId,
writeActiveSessionId,
} from "@/lib/claw-active-session";
import { useClawSessionReplay } from "@/lib/claw-session-replay";
type ThreadListSidebarProps = React.ComponentProps<typeof Sidebar> & {
@@ -157,7 +162,7 @@ function CollapsedSidebarRail({
<div className="flex flex-col items-center gap-3">
<div
onClickCapture={() => {
window.localStorage.removeItem("claw.activeSessionId");
clearActiveSessionId();
window.dispatchEvent(new Event("claw-active-session-cleared"));
}}
>
@@ -360,7 +365,7 @@ function useSidebarSessions(open: boolean) {
) {
const cleanSessionId = normalizeSessionId(sessionId);
if (!cleanSessionId) return;
window.localStorage.setItem("claw.activeSessionId", cleanSessionId);
writeActiveSessionId(cleanSessionId);
setLoadingSessionId(sessionId);
try {
const response = await fetch(`/api/claw/sessions/${cleanSessionId}`, {
@@ -438,14 +443,13 @@ function AccountMenu({
setState(nextState);
setSessions(nextSessions);
const activeSessionId = normalizeSessionId(
window.localStorage.getItem("claw.activeSessionId") ??
nextState?.active_session_id,
readActiveSessionId() ?? nextState?.active_session_id,
);
if (!activeSessionId) {
setActiveSession(null);
return;
}
window.localStorage.setItem("claw.activeSessionId", activeSessionId);
writeActiveSessionId(activeSessionId);
const response = await fetch(`/api/claw/sessions/${activeSessionId}`, {
cache: "no-store",
});
@@ -460,7 +464,7 @@ function AccountMenu({
async function logout() {
await fetch("/api/claw/auth/logout", { method: "POST" });
window.localStorage.removeItem("claw.activeSessionId");
clearActiveSessionId();
onLogout();
}
+34
View File
@@ -0,0 +1,34 @@
"use client";
const ACTIVE_SESSION_ID_KEY = "claw.activeSessionId";
export function readActiveSessionId() {
if (typeof window === "undefined") return null;
return (
normalizeSessionId(window.sessionStorage.getItem(ACTIVE_SESSION_ID_KEY)) ??
normalizeSessionId(window.localStorage.getItem(ACTIVE_SESSION_ID_KEY))
);
}
export function writeActiveSessionId(sessionId: string | null | undefined) {
if (typeof window === "undefined") return;
const normalized = normalizeSessionId(sessionId);
if (!normalized) {
clearActiveSessionId();
return;
}
window.sessionStorage.setItem(ACTIVE_SESSION_ID_KEY, normalized);
window.localStorage.setItem(ACTIVE_SESSION_ID_KEY, normalized);
}
export function clearActiveSessionId() {
if (typeof window === "undefined") return;
window.sessionStorage.removeItem(ACTIVE_SESSION_ID_KEY);
window.localStorage.removeItem(ACTIVE_SESSION_ID_KEY);
}
function normalizeSessionId(value: unknown) {
if (typeof value !== "string") return null;
const trimmed = value.trim();
return trimmed || null;
}