返回 DeepSeek-Reasonix
runtimeStateStore.ts
根目录 / desktop / frontend / src / lib / runtimeStateStore.ts
1 import type { ProjectRuntimeTopic } from "./types";
2 import type { PendingInteraction, RecoveryStatus, Todo } from "../generated/desktopContract.generated";
3 import { sameSessionIdentity, type SessionIdentity } from "./sessionIdentity";
4
5 export interface RuntimeState {
6 schemaVersion: number;
7 hostId?: string;
8 sessionId?: string;
9 sessionCodec?: string;
10 projectionEpoch?: string;
11 runtimeEpoch: string;
12 activityRevision: number;
13 revision: number;
14 phase: "idle" | "executing" | "finishing" | "cancelling" | "recovery_required" | "closed";
15 running: boolean;
16 turnId: string;
17 turnStatus: string;
18 turnEventSeq: number;
19 committedEventSeq?: number;
20 durableEventSeq?: number;
21 persistenceStatus?: "ready" | "pending" | "failed" | "uncertain" | "unavailable";
22 persistenceError?: string;
23 headId?: string;
24 pendingPrompt: boolean;
25 pendingInteractions?: PendingInteraction[];
26 todos?: Todo[];
27 todoWritten?: boolean;
28 cancelRequested: boolean;
29 cancellable: boolean;
30 backgroundJobs: number;
31 activity: string;
32 recovery?: RecoveryStatus | null;
33 }
34
35 export function acceptSessionRuntimeSnapshot(current: RuntimeState | undefined, next: RuntimeState, allowProducerBaseline = false): RuntimeState {
36 if (!current) return next;
37 if (current.projectionEpoch !== next.projectionEpoch) return allowProducerBaseline ? next : current;
38 if (next.revision < current.revision) return current;
39 return next.revision === current.revision ? current : next;
40 }
41 export interface RuntimeSession {
42 tabId: string;
43 scope: string;
44 workspaceRoot: string;
45 topicId: string;
46 sessionId?: string;
47 sessionPath: string;
48 sessionGeneration: number;
49 open: boolean;
50 remote: boolean;
51 hostId?: string;
52 freshness: "synced" | "unknown" | "syncing";
53 state: RuntimeState;
54 }
55 export interface RuntimeProjection {
56 epoch: string;
57 revision: number;
58 sessions: RuntimeSession[];
59 topics: ProjectRuntimeTopic[];
60 }
61
62 /** A tab is a reusable surface, not a session identity. Unknown/blank bindings
63 * must not adopt the previous session while navigation metadata catches up. */
64 export function selectRuntimeSession(snapshot: RuntimeProjection | undefined, tabId: string | undefined, identity: SessionIdentity | string | undefined) {
65 const target = typeof identity === "string" ? { sessionPath: identity } : identity;
66 if (!tabId || !target) return undefined;
67 if (typeof identity !== "string" && target.sessionGeneration == null) return undefined;
68 return snapshot?.sessions.find(session => session.open && session.tabId === tabId && sameSessionIdentity(target, {
69 session: target.session?.sessionId && session.sessionId
70 ? { hostId: session.hostId || "local", sessionId: session.sessionId }
71 : undefined,
72 sessionPath: session.sessionPath,
73 sessionGeneration: session.sessionGeneration,
74 }));
75 }
76
77 export function selectRuntime(session?: RuntimeSession, failed = false) {
78 const state = session?.state;
79 const known = state?.schemaVersion === 1;
80 const unknown = Boolean(session && (failed || session.freshness !== "synced"));
81 const finishing = known && state.phase === "finishing";
82 const kind = unknown ? "unknown" : !known ? "legacy" : finishing ? "finishing"
83 : state.phase === "recovery_required" ? "recovery_required"
84 : state.cancelRequested || state.phase === "cancelling" ? "cancelling" : state.pendingPrompt ? "waiting_confirmation"
85 : state.phase === "executing" ? state.activity === "streaming" ? "streaming" : "thinking"
86 : state.backgroundJobs > 0 ? "background_job" : "idle";
87 return { kind, known, unknown, finishing, state,
88 running: known ? state.running : undefined,
89 cancellable: known ? !unknown && !finishing && state.cancellable && !state.cancelRequested : undefined,
90 spinning: !unknown && (kind === "thinking" || kind === "streaming" || kind === "cancelling" || kind === "background_job"),
91 };
92 }
93
94 export function createRuntimeStateStore() {
95 let snapshot: RuntimeProjection | undefined;
96 let failed = false;
97 const listeners = new Set<() => void>();
98 const notify = () => listeners.forEach(listener => listener());
99 return {
100 getSnapshot: () => snapshot,
101 getFailed: () => failed,
102 subscribe(listener: () => void) { listeners.add(listener); return () => { listeners.delete(listener); }; },
103 fail() { if (!failed) { failed = true; notify(); } },
104 commit(next: RuntimeProjection) {
105 if (snapshot === next && !failed) return;
106 snapshot = next;
107 failed = false;
108 notify();
109 },
110 };
111 }
112 export const runtimeStateStore = createRuntimeStateStore();
113
113 lines TYPESCRIPT