返回 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 maintenance?: {
34 operationId: string;
35 kind: string;
36 activity: "running" | "cancelling" | "finalizing" | "recovery_required" | string;
37 status?: string;
38 operationRevision?: number;
39 runtimeEpoch?: string;
40 errorCode?: string;
41 detail?: string;
42 applied?: boolean;
43 inputTokens?: number;
44 resultTokens?: number;
45 messages?: number;
46 };
47 }
48
49 export function acceptSessionRuntimeSnapshot(current: RuntimeState | undefined, next: RuntimeState, allowProducerBaseline = false): RuntimeState {
50 if (!current) return next;
51 if (current.projectionEpoch !== next.projectionEpoch) return allowProducerBaseline ? next : current;
52 if (next.revision < current.revision) return current;
53 return next.revision === current.revision ? current : next;
54 }
55 export interface RuntimeSession {
56 tabId: string;
57 scope: string;
58 workspaceRoot: string;
59 topicId: string;
60 sessionId?: string;
61 sessionPath: string;
62 sessionGeneration: number;
63 open: boolean;
64 remote: boolean;
65 hostId?: string;
66 freshness: "synced" | "unknown" | "syncing";
67 state: RuntimeState;
68 }
69 export interface RuntimeProjection {
70 epoch: string;
71 revision: number;
72 sessions: RuntimeSession[];
73 topics: ProjectRuntimeTopic[];
74 }
75
76 /** A tab is a reusable surface, not a session identity. Unknown/blank bindings
77 * must not adopt the previous session while navigation metadata catches up. */
78 export function selectRuntimeSession(snapshot: RuntimeProjection | undefined, tabId: string | undefined, identity: SessionIdentity | string | undefined) {
79 const target = typeof identity === "string" ? { sessionPath: identity } : identity;
80 if (!tabId || !target) return undefined;
81 if (typeof identity !== "string" && target.sessionGeneration == null) return undefined;
82 return snapshot?.sessions.find(session => session.open && session.tabId === tabId && sameSessionIdentity(target, {
83 session: target.session?.sessionId && session.sessionId
84 ? { hostId: session.hostId || "local", sessionId: session.sessionId }
85 : undefined,
86 sessionPath: session.sessionPath,
87 sessionGeneration: session.sessionGeneration,
88 }));
89 }
90
91 export function selectRuntime(session?: RuntimeSession, failed = false) {
92 const state = session?.state;
93 const known = state?.schemaVersion === 1;
94 const unknown = Boolean(session && (failed || session.freshness !== "synced"));
95 const finishing = known && state.phase === "finishing";
96 const kind = unknown ? "unknown" : !known ? "legacy" : state.maintenance?.activity === "finalizing" ? "maintenance_finalizing"
97 : state.maintenance?.activity === "cancelling" ? "maintenance_cancelling"
98 : state.maintenance?.activity === "recovery_required" ? "recovery_required"
99 : state.maintenance ? "maintenance_running" : finishing ? "finishing"
100 : state.phase === "recovery_required" ? "recovery_required"
101 : state.cancelRequested || state.phase === "cancelling" ? "cancelling" : state.pendingPrompt ? "waiting_confirmation"
102 : state.phase === "executing" ? state.activity === "streaming" ? "streaming" : "thinking"
103 : state.backgroundJobs > 0 ? "background_job" : "idle";
104 return { kind, known, unknown, finishing, state,
105 running: known ? state.running : undefined,
106 cancellable: known ? !unknown && !finishing && state.cancellable && !state.cancelRequested : undefined,
107 spinning: !unknown && (kind === "thinking" || kind === "streaming" || kind === "cancelling" || kind === "maintenance_running" || kind === "maintenance_cancelling" || kind === "maintenance_finalizing" || kind === "background_job"),
108 };
109 }
110
111 export function createRuntimeStateStore() {
112 let snapshot: RuntimeProjection | undefined;
113 let failed = false;
114 const listeners = new Set<() => void>();
115 const notify = () => listeners.forEach(listener => listener());
116 return {
117 getSnapshot: () => snapshot,
118 getFailed: () => failed,
119 subscribe(listener: () => void) { listeners.add(listener); return () => { listeners.delete(listener); }; },
120 fail() { if (!failed) { failed = true; notify(); } },
121 commit(next: RuntimeProjection) {
122 if (snapshot === next && !failed) return;
123 snapshot = next;
124 failed = false;
125 notify();
126 },
127 };
128 }
129 export const runtimeStateStore = createRuntimeStateStore();
130
130 lines TYPESCRIPT