返回 DeepSeek-Reasonix
useStaleTurnWatchdog.ts
根目录 / desktop / frontend / src / lib / useStaleTurnWatchdog.ts
1 import { useEffect, type RefObject } from "react";
2
3 const STALE_TURN_RECONCILE_MS = 30_000;
4
5 export type StaleTurnWatchdogState = {
6 running: boolean;
7 turnStartAt: number;
8 };
9
10 export function shouldReconcileStaleTurn(
11 state: StaleTurnWatchdogState | undefined,
12 lastTurnActivityAt: number,
13 now = Date.now(),
14 timeoutMs = STALE_TURN_RECONCILE_MS,
15 ): boolean {
16 const lastEvidenceAt = lastTurnActivityAt > 0 ? lastTurnActivityAt : state?.turnStartAt ?? 0;
17 if (!state?.running || lastEvidenceAt <= 0) return false;
18 return Math.max(0, now - lastEvidenceAt) >= timeoutMs;
19 }
20
21 export function useStaleTurnWatchdog<T extends StaleTurnWatchdogState>({
22 tabId,
23 visibleState,
24 activeTabIdRef,
25 statesRef,
26 lastTurnActivityAtByTab,
27 reconcile,
28 }: {
29 tabId?: string;
30 visibleState: StaleTurnWatchdogState;
31 activeTabIdRef: RefObject<string | undefined>;
32 statesRef: RefObject<Map<string, T>>;
33 lastTurnActivityAtByTab: RefObject<Map<string, number>>;
34 reconcile: (tabId: string) => Promise<void>;
35 }) {
36 useEffect(() => {
37 if (!tabId) return;
38 let cancelled = false;
39 let timer: number | undefined;
40
41 const schedule = (delay?: number) => {
42 if (cancelled || activeTabIdRef.current !== tabId) return;
43 const current = statesRef.current.get(tabId);
44 const lastActivityAt = lastTurnActivityAtByTab.current.get(tabId) ?? 0;
45 const lastEvidenceAt = lastActivityAt > 0 ? lastActivityAt : current?.turnStartAt ?? 0;
46 if (!current?.running || lastEvidenceAt <= 0) return;
47 const nextDelay = delay ?? Math.max(0, STALE_TURN_RECONCILE_MS - Math.max(0, Date.now() - lastEvidenceAt));
48 timer = window.setTimeout(() => { void tick(); }, nextDelay);
49 };
50
51 const tick = async () => {
52 if (cancelled || activeTabIdRef.current !== tabId) return;
53 const current = statesRef.current.get(tabId);
54 const lastActivityAt = lastTurnActivityAtByTab.current.get(tabId) ?? 0;
55 if (!shouldReconcileStaleTurn(current, lastActivityAt)) {
56 schedule();
57 return;
58 }
59 await reconcile(tabId);
60 if (!cancelled && activeTabIdRef.current === tabId && statesRef.current.get(tabId)?.running) {
61 schedule(STALE_TURN_RECONCILE_MS);
62 }
63 };
64
65 schedule();
66 return () => {
67 cancelled = true;
68 if (timer !== undefined) window.clearTimeout(timer);
69 };
70 }, [activeTabIdRef, lastTurnActivityAtByTab, reconcile, statesRef, tabId, visibleState.running, visibleState.turnStartAt]);
71 }
72
72 lines TYPESCRIPT