Restore active run state on session replay
This commit is contained in:
@@ -45,6 +45,13 @@ type ClawStoredSession = {
|
||||
messages?: ClawStoredMessage[];
|
||||
};
|
||||
|
||||
export type ClawRunStatus = {
|
||||
run_id?: string;
|
||||
session_id?: string;
|
||||
status?: "idle" | "queued" | "running" | "completed" | "failed" | "cancelled";
|
||||
current_stage?: string;
|
||||
};
|
||||
|
||||
type ClawStoredToolCall = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -222,9 +229,16 @@ const ClawSessionList: FC = () => {
|
||||
if (!response.ok) return;
|
||||
const payload =
|
||||
(await response.json()) as ClawStoredSession;
|
||||
const runStatus = await fetchLatestRunStatus(
|
||||
session.session_id,
|
||||
);
|
||||
replaySession(
|
||||
session.session_id,
|
||||
toReplayRepository(payload, session.session_id),
|
||||
toReplayRepository(
|
||||
payload,
|
||||
session.session_id,
|
||||
runStatus,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
setLoadingSessionId(null);
|
||||
@@ -323,6 +337,18 @@ function dedupeSessions(payload: unknown[]) {
|
||||
return [...byId.values()].sort((a, b) => b.modified_at - a.modified_at);
|
||||
}
|
||||
|
||||
export async function fetchLatestRunStatus(sessionId: string) {
|
||||
try {
|
||||
const url = new URL("/api/claw/runs/latest", window.location.origin);
|
||||
url.searchParams.set("session_id", sessionId);
|
||||
const response = await fetch(url, { cache: "no-store" });
|
||||
if (!response.ok) return null;
|
||||
return (await response.json()) as ClawRunStatus;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isClawSession(value: unknown): value is ClawSession {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const item = value as Partial<ClawSession>;
|
||||
@@ -334,6 +360,7 @@ function isClawSession(value: unknown): value is ClawSession {
|
||||
export function toReplayRepository(
|
||||
session: ClawStoredSession,
|
||||
fallbackSessionId: string,
|
||||
runStatus?: ClawRunStatus | null,
|
||||
): ExportedMessageRepository {
|
||||
const messages: ExportedMessageRepository["messages"] = [];
|
||||
const toolResults = collectToolResults(session.messages ?? []);
|
||||
@@ -393,12 +420,49 @@ export function toReplayRepository(
|
||||
messages.push({ message: threadMessage, parentId: previousId });
|
||||
previousId = id;
|
||||
}
|
||||
if (isActiveRunStatus(runStatus)) {
|
||||
const id = `${fallbackSessionId}-run-${runStatus.run_id ?? "active"}`;
|
||||
const text =
|
||||
runStatus.status === "queued"
|
||||
? "当前会话已有任务在执行,本轮正在排队。"
|
||||
: runStatus.current_stage || "后端正在执行这个会话。";
|
||||
const parts = [{ type: "reasoning", text }] as UIMessage["parts"];
|
||||
const uiMessage: UIMessage = {
|
||||
id,
|
||||
role: "assistant",
|
||||
parts,
|
||||
metadata: { sessionId: session.session_id ?? fallbackSessionId },
|
||||
} as UIMessage;
|
||||
const threadMessage = {
|
||||
id,
|
||||
role: "assistant",
|
||||
createdAt: new Date(),
|
||||
content: toThreadMessageContent(parts),
|
||||
status: { type: "running" },
|
||||
metadata: {
|
||||
unstable_state: null,
|
||||
unstable_annotations: [],
|
||||
unstable_data: [],
|
||||
steps: [],
|
||||
custom: { sessionId: session.session_id ?? fallbackSessionId },
|
||||
},
|
||||
} as ThreadMessage;
|
||||
bindExternalStoreMessage(threadMessage, uiMessage);
|
||||
messages.push({ message: threadMessage, parentId: previousId });
|
||||
previousId = id;
|
||||
}
|
||||
return {
|
||||
headId: previousId,
|
||||
messages,
|
||||
};
|
||||
}
|
||||
|
||||
function isActiveRunStatus(
|
||||
runStatus?: ClawRunStatus | null,
|
||||
): runStatus is ClawRunStatus {
|
||||
return runStatus?.status === "queued" || runStatus?.status === "running";
|
||||
}
|
||||
|
||||
function findLastAssistantContentIndex(messages: readonly ClawStoredMessage[]) {
|
||||
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
||||
const message = messages[index];
|
||||
|
||||
Reference in New Issue
Block a user