Skip to main content

starweaver_cli/local_store/
session_store.rs

1//! CLI adapter over the shared `SQLite` session store.
2
3use 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/// Session store bound to a resolved CLI database path.
19#[derive(Clone, Debug)]
20pub struct LocalSessionStore {
21    store: SqliteSessionStore,
22}
23
24impl LocalSessionStore {
25    /// Open a CLI session-store adapter before it is used by async runtime code.
26    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}