返回 DeepSeek-Reasonix
useRemoteRunningWatchdog.ts
根目录 / desktop / frontend / src / lib / useRemoteRunningWatchdog.ts
1 // The remote running-state watchdog: while the pill claims a turn is running,
2 // poll the serve's /status and feed it through the shared backend_status
3 // reducer. This is the remote twin of the local tab's reconcile loop — a lost
4 // turn_done frame (dropped SSE, slow-consumer drop, half-dead tunnel) then
5 // clears within one tick instead of spinning forever.
6
7 import { useEffect, type RefObject } from "react";
8
9 const REMOTE_RUNNING_RECONCILE_MS = 30_000;
10
11 /** The connection effect's /status reader, absent while no connection owns the tab. */
12 export interface RemoteStatusRefreshHandle {
13 tabId: string;
14 run(): Promise<void>;
15 }
16
17 /**
18 * Polls /status while the shown tab claims a running turn and the runtime
19 * projection holds no liveness of its own, which is the only case the feed
20 * cannot settle by itself.
21 */
22 export function useRemoteRunningWatchdog(input: {
23 tabId: string | undefined;
24 /** The shown tab adopted a session and hydrated it. */
25 ready: boolean;
26 /** The runtime projection knows the turn, so its own liveness already settles the pill. */
27 runtimeKnown: boolean;
28 running: boolean;
29 refreshStatusRef: RefObject<RemoteStatusRefreshHandle | null>;
30 }): void {
31 const { tabId, ready, runtimeKnown, running, refreshStatusRef } = input;
32 useEffect(() => {
33 if (!ready || runtimeKnown || !tabId || !running) return;
34 const reconcile = () => {
35 const current = refreshStatusRef.current;
36 if (!current || current.tabId !== tabId) return;
37 void current.run().catch(() => {
38 // Transient; the next tick retries.
39 });
40 };
41 const timer = window.setInterval(reconcile, REMOTE_RUNNING_RECONCILE_MS);
42 return () => {
43 window.clearInterval(timer);
44 };
45 }, [ready, refreshStatusRef, running, runtimeKnown, tabId]);
46 }
47
47 lines TYPESCRIPT