1use async_trait::async_trait;
4use starweaver_context::ResumableState;
5use starweaver_core::{RunId, SessionId};
6use starweaver_runtime::{AgentCheckpoint, AgentStreamRecord};
7use starweaver_session::{
8 AcquireRunAdmission, ApprovalRecord, CompactRunTrace, CompactSessionTrace, DeferredToolRecord,
9 DurableControlReceipt, EnvironmentStateRef, HitlResumeClaim, ManagedRunTarget,
10 PendingStreamPublication, RunAdmissionLease, RunAdmissionReceipt, RunEvidenceCommit, RunRecord,
11 RunStatus, SessionContinuationFence, SessionFilter, SessionRecord, SessionStatus, SessionStore,
12 SessionStoreResult, StreamCursorRef, StreamPublicationTarget, UpdateManagedSession,
13};
14use starweaver_storage::SqliteSessionStore;
15
16use crate::config::CliConfig;
17
18#[derive(Clone, Debug)]
20pub struct LocalSessionStore {
21 store: SqliteSessionStore,
22}
23
24impl LocalSessionStore {
25 pub fn new(config: CliConfig) -> SessionStoreResult<Self> {
27 crate::config::ensure_config_dirs(&config)
28 .map_err(|error| starweaver_session::SessionStoreError::Failed(error.to_string()))?;
29 let database_path = config.database_path;
30 Ok(Self {
31 store: SqliteSessionStore::open(&database_path)?,
32 })
33 }
34}
35
36#[async_trait]
37impl SessionStore for LocalSessionStore {
38 async fn commit_run_evidence(
39 &self,
40 commit: RunEvidenceCommit,
41 ) -> SessionStoreResult<RunRecord> {
42 self.store.commit_run_evidence(commit).await
43 }
44
45 async fn commit_checkpoint(
46 &self,
47 session_id: &SessionId,
48 checkpoint: AgentCheckpoint,
49 ) -> SessionStoreResult<()> {
50 self.store.commit_checkpoint(session_id, checkpoint).await
51 }
52
53 async fn claim_hitl_resume(&self, claim: HitlResumeClaim) -> SessionStoreResult<()> {
54 self.store.claim_hitl_resume(claim).await
55 }
56
57 async fn mark_hitl_resume_started(
58 &self,
59 session_id: &SessionId,
60 run_id: &RunId,
61 claim_id: &str,
62 ) -> SessionStoreResult<()> {
63 self.store
64 .mark_hitl_resume_started(session_id, run_id, claim_id)
65 .await
66 }
67
68 async fn release_hitl_resume_claim(
69 &self,
70 session_id: &SessionId,
71 run_id: &RunId,
72 claim_id: &str,
73 ) -> SessionStoreResult<()> {
74 self.store
75 .release_hitl_resume_claim(session_id, run_id, claim_id)
76 .await
77 }
78
79 async fn pending_stream_publications(
80 &self,
81 session_id: &SessionId,
82 ) -> SessionStoreResult<Vec<PendingStreamPublication>> {
83 self.store.pending_stream_publications(session_id).await
84 }
85
86 async fn acknowledge_stream_publication(
87 &self,
88 publication_id: &str,
89 target: StreamPublicationTarget,
90 ) -> SessionStoreResult<()> {
91 self.store
92 .acknowledge_stream_publication(publication_id, target)
93 .await
94 }
95
96 async fn create_session_idempotent(
97 &self,
98 session: SessionRecord,
99 idempotency_key: &str,
100 command_fingerprint: &str,
101 ) -> SessionStoreResult<SessionRecord> {
102 self.store
103 .create_session_idempotent(session, idempotency_key, command_fingerprint)
104 .await
105 }
106
107 async fn update_managed_session(
108 &self,
109 command: UpdateManagedSession,
110 command_fingerprint: &str,
111 ) -> SessionStoreResult<SessionRecord> {
112 self.store
113 .update_managed_session(command, command_fingerprint)
114 .await
115 }
116
117 async fn acquire_session_deletion_fence(
118 &self,
119 session_id: &SessionId,
120 expected_revision: u64,
121 fence_id: &str,
122 requested_by: &str,
123 idempotency_key: &str,
124 command_fingerprint: &str,
125 ) -> SessionStoreResult<SessionRecord> {
126 self.store
127 .acquire_session_deletion_fence(
128 session_id,
129 expected_revision,
130 fence_id,
131 requested_by,
132 idempotency_key,
133 command_fingerprint,
134 )
135 .await
136 }
137
138 async fn tombstone_session(
139 &self,
140 session_id: &SessionId,
141 fence_id: &str,
142 ) -> SessionStoreResult<SessionRecord> {
143 self.store.tombstone_session(session_id, fence_id).await
144 }
145
146 async fn session_continuation_fence(
147 &self,
148 namespace_id: &str,
149 session_id: &SessionId,
150 ) -> SessionStoreResult<SessionContinuationFence> {
151 self.store
152 .session_continuation_fence(namespace_id, session_id)
153 .await
154 }
155
156 async fn acquire_run_admission(
157 &self,
158 request: AcquireRunAdmission,
159 ) -> SessionStoreResult<RunAdmissionReceipt> {
160 self.store.acquire_run_admission(request).await
161 }
162
163 async fn heartbeat_run_admission(
164 &self,
165 lease: &RunAdmissionLease,
166 lease_expires_at: chrono::DateTime<chrono::Utc>,
167 ) -> SessionStoreResult<RunAdmissionLease> {
168 self.store
169 .heartbeat_run_admission(lease, lease_expires_at)
170 .await
171 }
172
173 async fn release_run_admission(&self, lease: &RunAdmissionLease) -> SessionStoreResult<()> {
174 self.store.release_run_admission(lease).await
175 }
176
177 async fn load_run_admission(
178 &self,
179 target: &ManagedRunTarget,
180 ) -> SessionStoreResult<Option<RunAdmissionLease>> {
181 self.store.load_run_admission(target).await
182 }
183
184 async fn reconcile_expired_run_admissions(
185 &self,
186 namespace_id: &str,
187 now: chrono::DateTime<chrono::Utc>,
188 ) -> SessionStoreResult<Vec<ManagedRunTarget>> {
189 self.store
190 .reconcile_expired_run_admissions(namespace_id, now)
191 .await
192 }
193
194 async fn load_control_receipt(
195 &self,
196 target: &ManagedRunTarget,
197 idempotency_key: &str,
198 ) -> SessionStoreResult<Option<DurableControlReceipt>> {
199 self.store
200 .load_control_receipt(target, idempotency_key)
201 .await
202 }
203
204 async fn reserve_control_receipt(
205 &self,
206 receipt: DurableControlReceipt,
207 ) -> SessionStoreResult<DurableControlReceipt> {
208 self.store.reserve_control_receipt(receipt).await
209 }
210
211 async fn update_control_receipt_state(
212 &self,
213 receipt_id: &str,
214 state: &str,
215 ) -> SessionStoreResult<DurableControlReceipt> {
216 self.store
217 .update_control_receipt_state(receipt_id, state)
218 .await
219 }
220
221 async fn drain_background_subagent_operations(&self) -> SessionStoreResult<()> {
222 self.store.drain_background_subagent_operations().await
223 }
224
225 async fn save_session(&self, session: SessionRecord) -> SessionStoreResult<()> {
226 self.store.save_session(session).await
227 }
228
229 async fn load_session(&self, session_id: &SessionId) -> SessionStoreResult<SessionRecord> {
230 self.store.load_session(session_id).await
231 }
232
233 async fn list_sessions(&self, filter: SessionFilter) -> SessionStoreResult<Vec<SessionRecord>> {
234 self.store.list_sessions(filter).await
235 }
236
237 async fn update_session_status(
238 &self,
239 session_id: &SessionId,
240 status: SessionStatus,
241 ) -> SessionStoreResult<()> {
242 self.store.update_session_status(session_id, status).await
243 }
244
245 async fn save_context_state(
246 &self,
247 session_id: &SessionId,
248 state: ResumableState,
249 ) -> SessionStoreResult<()> {
250 self.store.save_context_state(session_id, state).await
251 }
252
253 async fn save_environment_state(
254 &self,
255 session_id: &SessionId,
256 environment_state: EnvironmentStateRef,
257 ) -> SessionStoreResult<()> {
258 self.store
259 .save_environment_state(session_id, environment_state)
260 .await
261 }
262
263 async fn append_run(&self, run: RunRecord) -> SessionStoreResult<()> {
264 self.store.append_run(run).await
265 }
266
267 async fn load_run(
268 &self,
269 session_id: &SessionId,
270 run_id: &RunId,
271 ) -> SessionStoreResult<RunRecord> {
272 self.store.load_run(session_id, run_id).await
273 }
274
275 async fn list_runs(&self, session_id: &SessionId) -> SessionStoreResult<Vec<RunRecord>> {
276 self.store.list_runs(session_id).await
277 }
278
279 async fn update_run_status(
280 &self,
281 session_id: &SessionId,
282 run_id: &RunId,
283 status: RunStatus,
284 output_preview: Option<String>,
285 ) -> SessionStoreResult<()> {
286 self.store
287 .update_run_status(session_id, run_id, status, output_preview)
288 .await
289 }
290
291 async fn append_checkpoint(
292 &self,
293 session_id: &SessionId,
294 checkpoint: AgentCheckpoint,
295 ) -> SessionStoreResult<()> {
296 self.store.append_checkpoint(session_id, checkpoint).await
297 }
298
299 async fn load_checkpoints(
300 &self,
301 session_id: &SessionId,
302 run_id: &RunId,
303 ) -> SessionStoreResult<Vec<AgentCheckpoint>> {
304 self.store.load_checkpoints(session_id, run_id).await
305 }
306
307 async fn append_stream_records(
308 &self,
309 session_id: &SessionId,
310 run_id: &RunId,
311 records: Vec<AgentStreamRecord>,
312 ) -> SessionStoreResult<()> {
313 self.store
314 .append_stream_records(session_id, run_id, records)
315 .await
316 }
317
318 async fn replay_stream_records(
319 &self,
320 session_id: &SessionId,
321 run_id: &RunId,
322 ) -> SessionStoreResult<Vec<AgentStreamRecord>> {
323 self.store.replay_stream_records(session_id, run_id).await
324 }
325
326 async fn replay_stream_records_after(
327 &self,
328 session_id: &SessionId,
329 run_id: &RunId,
330 after_sequence: Option<usize>,
331 ) -> SessionStoreResult<Vec<AgentStreamRecord>> {
332 self.store
333 .replay_stream_records_after(session_id, run_id, after_sequence)
334 .await
335 }
336
337 async fn save_stream_cursor(
338 &self,
339 session_id: &SessionId,
340 run_id: &RunId,
341 cursor: StreamCursorRef,
342 ) -> SessionStoreResult<()> {
343 self.store
344 .save_stream_cursor(session_id, run_id, cursor)
345 .await
346 }
347
348 async fn append_approval(&self, approval: ApprovalRecord) -> SessionStoreResult<()> {
349 self.store.append_approval(approval).await
350 }
351
352 async fn load_approvals(
353 &self,
354 session_id: &SessionId,
355 run_id: &RunId,
356 ) -> SessionStoreResult<Vec<ApprovalRecord>> {
357 self.store.load_approvals(session_id, run_id).await
358 }
359
360 async fn append_deferred_tool(&self, record: DeferredToolRecord) -> SessionStoreResult<()> {
361 self.store.append_deferred_tool(record).await
362 }
363
364 async fn load_deferred_tools(
365 &self,
366 session_id: &SessionId,
367 run_id: &RunId,
368 ) -> SessionStoreResult<Vec<DeferredToolRecord>> {
369 self.store.load_deferred_tools(session_id, run_id).await
370 }
371
372 async fn compact_run_trace(
373 &self,
374 session_id: &SessionId,
375 run_id: &RunId,
376 ) -> SessionStoreResult<CompactRunTrace> {
377 self.store.compact_run_trace(session_id, run_id).await
378 }
379
380 async fn compact_session_trace(
381 &self,
382 session_id: &SessionId,
383 ) -> SessionStoreResult<CompactSessionTrace> {
384 self.store.compact_session_trace(session_id).await
385 }
386}