返回 DeepSeek-Reasonix
sessionReadActivity.ts
根目录 / desktop / frontend / src / lib / sessionReadActivity.ts
1 import type { ProjectNode } from "./types";
2 import { projectTreeReadActivityKey, topicReadRevision, type ProjectTreeReadActivity } from "./projectTreeTopic";
3
4 export type ReadRecord = {
5 metric: "result" | "time";
6 value: number;
7 revision: number;
8 imported?: boolean;
9 verifiedVersion?: string;
10 repairVersion?: number;
11 readFloor?: number;
12 needsBaseline?: boolean;
13 };
14 export type ReadStore = { version: 3; baselineAt: number; records: Record<string, ReadRecord> };
15 export type BaselineObservation = { complete: boolean; resultSequence: number; eventVersion: string };
16
17 export function readActivityValues(store: ReadStore): ProjectTreeReadActivity {
18 return Object.fromEntries(Object.entries(store.records).map(([key, record]) => [key, record.needsBaseline ? Number.MAX_SAFE_INTEGER : record.value]));
19 }
20
21 export function markSessionRead(store: ReadStore, node: ProjectNode, now = Date.now()): ReadStore {
22 const key = projectTreeReadActivityKey(node);
23 if (!key) return store;
24 const metric = node.session ? "result" : "time";
25 const value = node.session ? topicReadRevision(node) : Math.max(topicReadRevision(node), now);
26 const previous = store.records[key];
27 if (value <= 0 || (previous?.metric === metric && previous.value >= value && !previous.needsBaseline && (!previous.imported || (previous.readFloor ?? 0) >= value))) return store;
28 return { ...store, records: { ...store.records, [key]: { metric,
29 value: previous?.metric === metric ? Math.max(previous.value, value) : value,
30 revision: (previous?.revision ?? 0) + 1, repairVersion: previous?.repairVersion,
31 imported: previous?.imported, readFloor: previous?.imported ? Math.max(previous.readFloor ?? 0,value) : undefined } } };
32 }
33
34 export function repairReadBaseline(store: ReadStore, key: string, captured: ReadRecord, observation: BaselineObservation): ReadStore {
35 const current = store.records[key];
36 if (current?.needsBaseline && current.value === captured.value && current.revision === captured.revision
37 && observation.complete && observation.resultSequence > 0 && observation.eventVersion) {
38 return { ...store, records: { ...store.records, [key]: { ...current, metric: "result", value: observation.resultSequence,
39 revision: current.revision + 1, needsBaseline: false, verifiedVersion: observation.eventVersion } } };
40 }
41 if (!current || !current.imported || current.metric !== "result" || current.repairVersion
42 || current.value !== captured.value || current.revision !== captured.revision
43 || !observation.complete || observation.resultSequence <= 0 || observation.resultSequence < (current.readFloor ?? 0) || !observation.eventVersion) return store;
44 return { ...store, records: { ...store.records, [key]: { ...current,
45 value: Math.min(current.value, observation.resultSequence), imported: false,
46 revision: current.revision + 1, repairVersion: 1, verifiedVersion: observation.eventVersion } } };
47 }
48
49 /** Normal writes merge monotonically. A one-time repair is accepted only for
50 * the exact imported record revision it verified, never over a user's read. */
51 export function mergeReadStores(current: ReadStore, incoming: ReadStore): ReadStore {
52 let records = current.records;
53 for (const [key, next] of Object.entries(incoming.records)) {
54 const old = records[key];
55 if (old && !old.needsBaseline && next.needsBaseline) continue;
56 if (old?.repairVersion && next.imported) {
57 if (next.metric === old.metric && (next.readFloor ?? 0) > old.value) {
58 if (records === current.records) records = { ...records };
59 records[key] = { ...old, value: next.readFloor!, readFloor: next.readFloor, revision: Math.max(old.revision, next.revision) + 1 };
60 }
61 continue;
62 }
63 if (old?.metric !== undefined && old.metric !== next.metric) continue;
64 const repair = old?.imported && !old.repairVersion && next.repairVersion === 1
65 && next.verifiedVersion && next.revision === old.revision + 1;
66 if (!old || repair || next.value > old.value
67 || next.value === old.value && next.revision > old.revision) {
68 if (records === current.records) records = { ...records };
69 records[key] = next;
70 }
71 }
72 return records === current.records ? current : { ...current, records };
73 }
74
75 export function seedSessionReads(store: ReadStore, nodes: readonly ProjectNode[]): ReadStore {
76 let next = store;
77 const visit = (node: ProjectNode) => {
78 const key = projectTreeReadActivityKey(node);
79 if (key && node.session && node.turnsState === "ready" && !next.records[key]) {
80 // Source time values must never become canonical result sequences.
81 const needsBaseline = node.identityAliases?.some(alias => next.records[alias]?.metric === "time") ?? false;
82 next = { ...next, records: { ...next.records, [key]: { metric: "result", value: needsBaseline ? 0 : topicReadRevision(node), revision: 1, needsBaseline } } };
83 }
84 node.children?.forEach(visit);
85 };
86 nodes.forEach(visit);
87 return next;
88 }
89
89 lines TYPESCRIPT