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