返回 DeepSeek-Reasonix
types.go
1 // Package sessioncatalog maintains a disposable SQLite projection of Reasonix
2 // session sidecars. Session JSONL/event/meta files remain authoritative; every
3 // row in this package may be discarded and rebuilt.
4 package sessioncatalog
5
6 import (
7 "context"
8 "path/filepath"
9 "strings"
10 "time"
11
12 "reasonix/internal/agent"
13 "reasonix/internal/config"
14 "reasonix/internal/historywork"
15 )
16
17 const (
18 SchemaVersion = 15
19 repairEngineVersion = 1
20 DefaultLimit = 50
21 MaxLimit = 200
22 )
23
24 type TurnsState string
25
26 const (
27 TurnsUnknown TurnsState = "unknown"
28 TurnsValid TurnsState = "valid"
29 TurnsCorrupt TurnsState = "corrupt"
30 )
31
32 type Health string
33
34 const (
35 HealthOK Health = "ok"
36 HealthMissing Health = "missing"
37 HealthCorrupt Health = "corrupt"
38 HealthDegraded Health = "degraded"
39 )
40
41 type State string
42
43 const (
44 StateOpening State = "opening"
45 StateReady State = "ready"
46 StateDegraded State = "degraded"
47 StateRebuilding State = "rebuilding"
48 StateClosed State = "closed"
49 )
50
51 type Mode string
52
53 const (
54 ModeDisk Mode = "disk"
55 ModeMemory Mode = "memory"
56 )
57
58 type Status struct {
59 State State `json:"state"`
60 Mode Mode `json:"mode"`
61 Path string `json:"path,omitempty"`
62 Revision uint64 `json:"revision"`
63 Indexed int64 `json:"indexed"`
64 Total int64 `json:"total"`
65 RepairPending int64 `json:"repairPending"`
66 RepairActive int64 `json:"repairActive"`
67 RepairDeferred int64 `json:"repairDeferred"`
68 RepairBlocked int64 `json:"repairBlocked"`
69 NextRepairAt int64 `json:"nextRepairAt,omitempty"`
70 RepairErrorKinds map[string]int64 `json:"repairErrorKinds,omitempty"`
71 LastRepairDurationMS int64 `json:"lastRepairDurationMs,omitempty"`
72 PhysicalSessions int64 `json:"physicalSessions"`
73 LogicalSessions int64 `json:"logicalSessions"`
74 RecoveryGroups int64 `json:"recoveryGroups"`
75 RecoveryBranches int64 `json:"recoveryBranches"`
76 RecoveryDiverged int64 `json:"recoveryDiverged"`
77 CleanupEligible int64 `json:"cleanupEligible"`
78 // RepairReason records the last integrity condition that caused a
79 // directory to be scanned instead of trusting its persisted projection.
80 // It is diagnostic-only; the transcript and sidecars remain authoritative.
81 RepairReason string `json:"repairReason,omitempty"`
82 SourceCount int64 `json:"sourceCount"`
83 LastRepairAt int64 `json:"lastRepairAt,omitempty"`
84 LastError string `json:"lastError,omitempty"`
85 QuarantinedPath string `json:"quarantinedPath,omitempty"`
86 }
87
88 type Options struct {
89 Path string
90 InMemory bool
91 DisableRepair bool
92 // MetadataOnly never reads transcripts or repairs content as a side effect
93 // of discovering sessions. Explicit content readers own that work.
94 MetadataOnly bool
95 // DeferredMetadataIntegrity is restricted to advisory metadata catalogs.
96 // The owner must replace the catalog after Invalidated closes.
97 DeferredMetadataIntegrity bool
98 RevisionFloor uint64
99 StartPaused bool // Desktop resumes discovery after the shell and watchers are ready.
100 Maintenance *historywork.Coordinator
101 MissingGrace time.Duration
102 QueueCapacity int
103 Now func() time.Time
104 OnRevision func(uint64, []string, string)
105 // OnDiscovery observes root admission and scan boundaries without source
106 // paths or content. It must return promptly and must not call the catalog.
107 OnDiscovery func(DiscoveryEvent)
108 // repairSession replaces the filesystem repair. Open installs it before
109 // starting repairLoop, so scheduler tests can drive the real wake path
110 // without racing the hook assignment.
111 repairSession func(context.Context, string) (agent.SessionListingRepairResult, error)
112 verifyMetadata func(context.Context) error
113 waitMetadataRetry func(context.Context, time.Duration) error
114 }
115
116 type DirectoryTarget struct {
117 Path string `json:"path"`
118 Scope string `json:"scope"`
119 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
120 mutationSeq uint64
121 }
122
123 type ProjectRecord struct {
124 Scope string `json:"scope"`
125 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
126 Title string `json:"title"`
127 Color string `json:"color,omitempty"`
128 Pinned bool `json:"pinned,omitempty"`
129 SortOrder int `json:"sortOrder,omitempty"`
130 }
131
132 type TopicMetadata struct {
133 Scope string `json:"scope"`
134 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
135 TopicID string `json:"topicId"`
136 Title string `json:"title"`
137 TitleSource string `json:"titleSource,omitempty"`
138 Pinned bool `json:"pinned,omitempty"`
139 SortOrder int `json:"sortOrder,omitempty"`
140 CreatedAt int64 `json:"createdAt,omitempty"`
141 }
142
143 type SessionRecord struct {
144 Path string `json:"path"`
145 pathKey string
146 enqueueSequence uint64
147 metadataUnchanged bool
148 Directory string `json:"directory"`
149 Scope string `json:"scope"`
150 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
151 TopicID string `json:"topicId,omitempty"`
152 TopicTitle string `json:"topicTitle,omitempty"`
153 CustomTitle string `json:"customTitle,omitempty"`
154 CreatedAt int64 `json:"createdAt,omitempty"`
155 LastActivityAt int64 `json:"lastActivityAt,omitempty"`
156 Preview string `json:"preview,omitempty"`
157 Turns int `json:"turns"`
158 TurnsState TurnsState `json:"turnsState"`
159 Recovered bool `json:"recovered,omitempty"`
160 RecoveryReason string `json:"recoveryReason,omitempty"`
161 RecoveryDigest string `json:"recoveryDigest,omitempty"`
162 ParentID string `json:"parentId,omitempty"`
163 RecoveryPreferred bool `json:"recoveryPreferred,omitempty"`
164 // RecoveryCopy is true only when real content is still covered by the parent.
165 RecoveryCopy bool `json:"recoveryCopy,omitempty"`
166 // RecoveryGroupID clusters a lineage of normal + recovery branches that
167 // share content ancestry. Empty for ordinary non-recovery sessions.
168 RecoveryGroupID string `json:"recoveryGroupId,omitempty"`
169 // RecoveryRole is normal | covered_copy | adopted | preferred | diverged.
170 RecoveryRole string `json:"recoveryRole,omitempty"`
171 // RecoveryCanonical marks the unique leaf that covers the group and should
172 // be opened by default. Never moves or rewrites files.
173 RecoveryCanonical bool `json:"recoveryCanonical,omitempty"`
174 // LogicalTopicID is the ordinary-list topic for this physical file. Recovery
175 // copies are re-anchored onto the root conversation's topic in the catalog
176 // only; authoritative sidecars keep their original topic_id.
177 LogicalTopicID string `json:"logicalTopicId,omitempty"`
178 // OrdinaryVisible is true only for the single logical representative that
179 // may appear in the ordinary project tree.
180 OrdinaryVisible bool `json:"ordinaryVisible,omitempty"`
181 // LogFormat is 2 for sessions whose event log is the append-only DAG; the
182 // head fields mirror its selected head and are zero for schema 1.
183 LogFormat int `json:"logFormat,omitempty"`
184 HeadCount int `json:"headCount,omitempty"`
185 SelectedHeadID string `json:"selectedHeadId,omitempty"`
186 heads []HeadRecord
187 ContentFingerprint string `json:"contentFingerprint,omitempty"`
188 MetaFingerprint string `json:"metaFingerprint,omitempty"`
189 Health Health `json:"health"`
190 MissingSince int64 `json:"missingSince,omitempty"`
191 }
192
193 // Recovery role constants for catalog lineage classification.
194 const (
195 RecoveryRoleNormal = "normal"
196 RecoveryRoleCoveredCopy = "covered_copy"
197 RecoveryRoleAdopted = "adopted"
198 RecoveryRolePreferred = "preferred"
199 RecoveryRoleDiverged = "diverged"
200 )
201
202 type TopicKey struct {
203 Scope string `json:"scope"`
204 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
205 TopicID string `json:"topicId"`
206 workspaceKey string
207 }
208
209 type TopicRecord struct {
210 Scope string `json:"scope"`
211 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
212 TopicID string `json:"topicId"`
213 Title string `json:"title"`
214 TitleSource string `json:"titleSource,omitempty"`
215 Pinned bool `json:"pinned,omitempty"`
216 SortOrder int `json:"sortOrder,omitempty"`
217 Turns int `json:"turns"`
218 TurnsState TurnsState `json:"turnsState"`
219 CreatedAt int64 `json:"createdAt,omitempty"`
220 LastActivityAt int64 `json:"lastActivityAt,omitempty"`
221 RecoveryState string `json:"recoveryState,omitempty"`
222 RecoveryBranchCount int `json:"recoveryBranchCount,omitempty"`
223 RecoveryUnresolvedCount int `json:"recoveryUnresolvedCount,omitempty"`
224 RecoveryCleanupEligibleCount int `json:"recoveryCleanupEligibleCount,omitempty"`
225 // RepresentativePath is the automatic open target for this logical topic.
226 RepresentativePath string `json:"representativePath,omitempty"`
227 Health Health `json:"health"`
228 Sessions []SessionRecord `json:"sessions"`
229 }
230
231 type TopicPageRequest struct {
232 Scope string `json:"scope"`
233 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
234 Cursor string `json:"cursor,omitempty"`
235 Limit int `json:"limit,omitempty"`
236 Query string `json:"query,omitempty"`
237 TimeFilter string `json:"timeFilter,omitempty"`
238 SortMode string `json:"sortMode,omitempty"`
239 // IncludeTopicIDsJSON and ExcludeTopicIDsJSON carry a JSON string array into
240 // SQLite's json_each table function. They keep large sidebar groups bounded
241 // to one SQL parameter instead of expanding one placeholder per topic.
242 IncludeTopicIDsJSON string `json:"-"`
243 ExcludeTopicIDsJSON string `json:"-"`
244 ExcludePinned bool `json:"-"`
245 PinnedOnly bool `json:"-"`
246 CursorBinding string `json:"-"`
247 // ManualOrder makes sort_order the primary key within each pinned bucket.
248 // It is intentionally request-scoped: users who have never reordered keep
249 // the activity/created ordering even though metadata rows have a sort value.
250 ManualOrder bool `json:"manualOrder,omitempty"`
251 }
252
253 type TopicPage struct {
254 Items []TopicRecord `json:"items"`
255 NextCursor string `json:"nextCursor,omitempty"`
256 Revision uint64 `json:"revision"`
257 }
258
259 type SessionPageRequest struct {
260 Scope string `json:"scope"`
261 WorkspaceRoot string `json:"workspaceRoot,omitempty"`
262 Directory string `json:"-"`
263 Cursor string `json:"cursor,omitempty"`
264 Limit int `json:"limit,omitempty"`
265 Query string `json:"query,omitempty"`
266 TimeFilter string `json:"timeFilter,omitempty"`
267 }
268
269 type SessionPage struct {
270 Items []SessionRecord `json:"items"`
271 NextCursor string `json:"nextCursor,omitempty"`
272 Revision uint64 `json:"revision"`
273 StaleCursor bool `json:"staleCursor,omitempty"`
274 }
275
276 // DefaultPath is the disposable cache file under CacheDir ("" when unavailable).
277 // v10.sqlite isolates progressive maintenance state from older writers.
278 // Session JSONL/WAL/sidecars remain authoritative and older binaries may keep
279 // using their own disposable cache without cross-writing this one.
280 func DefaultPath() string {
281 cache := strings.TrimSpace(config.CacheDir())
282 if cache == "" {
283 return ""
284 }
285 return filepath.Join(cache, "session-catalog", "v10.sqlite")
286 }
287
287 lines GO