1use std::sync::Arc;
7
8use crate::{PluginOperationInvokeError, SessionError};
9
10use super::LashRuntime;
11use super::state::{
12 RuntimeSessionState, append_session_nodes_to_state_with_clock, normalize_session_graph,
13};
14
15impl LashRuntime {
16 pub fn set_persisted_state(&mut self, state: RuntimeSessionState) -> Result<(), SessionError> {
18 let mut state = state;
19 normalize_session_graph(&mut state);
20 if let Some(session) = self.session.as_ref() {
21 session.invalidate_runtime_caches();
22 if let Some(tool_state) = state.tool_state_snapshot.clone() {
29 let report = session
30 .plugins()
31 .tool_registry()
32 .restore_state(tool_state)
33 .map_err(|err| SessionError::Protocol(err.to_string()))?;
34 if !report.orphaned.is_empty() {
35 tracing::warn!(
36 session_id = %state.session_id,
37 orphaned = ?report.orphaned,
38 "persisted state installed with orphaned tools: no registered \
39 source resolves them; they remain non-members until their source returns"
40 );
41 }
42 }
43 let snapshot = state.plugin_snapshot.clone().unwrap_or_default();
44 session
45 .plugins()
46 .restore(&snapshot)
47 .map_err(|err| SessionError::Protocol(err.to_string()))?;
48 state.plugin_snapshot_revision =
49 Some(session.plugins().snapshot_revision_fingerprint());
50 }
51 self.policy = state.policy.clone();
52 self.protocol_turn_options = state.protocol_turn_options.clone();
53 self.state = state;
54 Ok(())
55 }
56
57 pub async fn append_session_nodes(
58 &mut self,
59 request: crate::AppendSessionNodesRequest,
60 ) -> Result<crate::AppendSessionNodesResult, SessionError> {
61 self.refresh_session_graph_from_store().await?;
62 if let Some(required) = request.requires_ancestor_node_id.as_deref()
63 && !self.state.session_graph.active_path_contains(required)
64 {
65 return Ok(crate::AppendSessionNodesResult::StaleBranch {
66 current_leaf_node_id: self.state.session_graph.leaf_node_id.clone(),
67 });
68 }
69 let node_ids = append_session_nodes_to_state_with_clock(
70 &mut self.state,
71 &request.nodes,
72 self.host.core.clock.as_ref(),
73 );
74 if let Some(session) = self.session.as_mut() {
75 let protocol_session = Arc::clone(session.plugins().protocol_session());
76 let session_id = self.state.session_id.clone();
77 protocol_session
78 .append_session_nodes(
79 crate::plugin::ProtocolSessionContext::new(session, &session_id),
80 &request.nodes,
81 )
82 .await?;
83 }
84 self.stamp_live_plugin_state();
85 if let Some(store) = self
86 .session
87 .as_ref()
88 .and_then(|session| session.history_store())
89 {
90 let graph = crate::store::GraphCommitDelta::Append {
91 nodes: node_ids
92 .iter()
93 .filter_map(|id| self.state.session_graph.find_node(id).cloned())
94 .collect(),
95 leaf_node_id: self.state.session_graph.leaf_node_id.clone(),
96 };
97 let commit = crate::store::RuntimeCommit::persisted_state_with_graph_commit(
98 &self.state,
99 graph,
100 &[],
101 );
102 match super::commit_runtime_state_with_fresh_session_execution_lease(
103 store,
104 commit,
105 &self.runtime_lease_owner,
106 self.host.core.control.lease_timings,
107 Arc::clone(&self.host.core.clock),
108 )
109 .await
110 {
111 Ok(result) => self.state.apply_persisted_commit_result(result),
112 Err(err) => tracing::warn!("failed to persist runtime state: {err}"),
113 }
114 }
115 Ok(crate::AppendSessionNodesResult::Appended {
116 node_ids,
117 leaf_node_id: self
118 .state
119 .session_graph
120 .leaf_node_id
121 .clone()
122 .unwrap_or_default(),
123 })
124 }
125
126 pub async fn apply_protocol_session_extension(
127 &mut self,
128 extension: crate::ProtocolSessionExtensionHandle,
129 ) -> Result<(), SessionError> {
130 let Some(session) = self.session.as_ref() else {
131 return Err(SessionError::Protocol(
132 "runtime session is not available".to_string(),
133 ));
134 };
135 let protocol_session = Arc::clone(session.plugins().protocol_session());
136 protocol_session.apply_session_extension(extension).await
137 }
138
139 pub async fn validate_protocol_turn_extension(
140 &mut self,
141 extension: &crate::ProtocolTurnExtensionHandle,
142 ) -> Result<(), SessionError> {
143 let Some(session) = self.session.as_ref() else {
144 return Err(SessionError::Protocol(
145 "runtime session is not available".to_string(),
146 ));
147 };
148 let protocol_session = Arc::clone(session.plugins().protocol_session());
149 protocol_session.validate_turn_extension(extension).await
150 }
151
152 pub async fn branch_to_node(
153 &mut self,
154 node_id: Option<String>,
155 ) -> Result<crate::SessionSnapshot, SessionError> {
156 let mut state = self.export_state();
157 state.session_graph.branch_to(node_id);
158 let mut persisted_state = RuntimeSessionState::from_snapshot(state);
159 normalize_session_graph(&mut persisted_state);
160
161 let policy = persisted_state.policy.clone();
162 let host = self.host.clone();
163 let services = self.services.clone();
164 let managed_sessions = Arc::clone(&self.managed_sessions);
165 let managed_turns = Arc::clone(&self.managed_turns);
166 let process_sync_needed = Arc::clone(&self.process_sync_needed);
167 let runtime_scope_id = Arc::clone(&self.runtime_scope_id);
168 let runtime_lease_owner = self.runtime_lease_owner.clone();
169 let turn_phase_probe = self.turn_phase_probe.clone();
170
171 let mut rebuilt = Self::from_host_state(policy, host, services, persisted_state).await?;
172 rebuilt.managed_sessions = managed_sessions;
173 rebuilt.managed_turns = managed_turns;
174 rebuilt.process_sync_needed = process_sync_needed;
175 rebuilt.runtime_scope_id = runtime_scope_id;
176 rebuilt.runtime_lease_owner = runtime_lease_owner;
177 rebuilt.turn_phase_probe = turn_phase_probe;
178
179 let exported = rebuilt.export_state();
180 *self = rebuilt;
181 Ok(exported)
182 }
183
184 pub async fn activate_managed_session(&mut self, session_id: &str) -> Result<(), SessionError> {
190 let child = {
191 let mut registry = self.managed_sessions.lock().await;
192 registry.remove(session_id).ok_or_else(|| {
193 SessionError::Protocol(format!("unknown managed session `{session_id}`"))
194 })?
195 };
196 let child = child.try_into_runtime().map_err(|_| {
197 SessionError::Protocol(format!("managed session `{session_id}` is still in use"))
198 })?;
199 *self = child;
200 Ok(())
201 }
202
203 pub async fn snapshot_execution_state(&mut self) -> Result<Option<Vec<u8>>, SessionError> {
205 let Some(session) = self.session.as_mut() else {
206 return Err(SessionError::Protocol(
207 "runtime session not available".to_string(),
208 ));
209 };
210 let code_executor = session
211 .plugins()
212 .code_executor()
213 .ok_or(SessionError::CodeExecutionUnavailable)?;
214 let session_id = self.state.session_id.clone();
215 let blob = code_executor
216 .snapshot_execution_state(crate::plugin::ProtocolSessionContext::new(
217 session,
218 &session_id,
219 ))
220 .await?;
221 self.state.execution_state_snapshot = blob.clone();
222 Ok(blob)
223 }
224
225 pub async fn restore_execution_state(&mut self, snapshot: &[u8]) -> Result<(), SessionError> {
227 let Some(session) = self.session.as_mut() else {
228 return Err(SessionError::Protocol(
229 "runtime session not available".to_string(),
230 ));
231 };
232 let code_executor = session
233 .plugins()
234 .code_executor()
235 .ok_or(SessionError::CodeExecutionUnavailable)?;
236 let session_id = self.state.session_id.clone();
237 code_executor
238 .restore_execution_state(
239 crate::plugin::ProtocolSessionContext::new(session, &session_id),
240 snapshot,
241 )
242 .await?;
243 self.state.execution_state_snapshot = Some(snapshot.to_vec());
244 Ok(())
245 }
246
247 pub async fn list_trigger_registrations(
248 &self,
249 ) -> Result<Vec<crate::TriggerRegistration>, SessionError> {
250 let store = self.host.trigger_store.as_ref().ok_or_else(|| {
251 SessionError::Protocol("trigger store is unavailable in this runtime".to_string())
252 })?;
253 let records = store
254 .list_subscriptions(crate::TriggerSubscriptionFilter::for_session(
255 self.state.session_id.clone(),
256 ))
257 .await
258 .map_err(|err| SessionError::Protocol(err.to_string()))?;
259 Ok(records
260 .iter()
261 .map(crate::TriggerRegistration::from)
262 .collect())
263 }
264
265 pub async fn trigger_registrations_by_source_type(
266 &self,
267 source_type: impl Into<crate::TriggerEventType>,
268 ) -> Result<Vec<crate::TriggerRegistration>, SessionError> {
269 let store = self.host.trigger_store.as_ref().ok_or_else(|| {
270 SessionError::Protocol("trigger store is unavailable in this runtime".to_string())
271 })?;
272 let mut filter =
273 crate::TriggerSubscriptionFilter::for_session(self.state.session_id.clone());
274 filter.source_type = Some(source_type.into().to_string());
275 let records = store
276 .list_subscriptions(filter)
277 .await
278 .map_err(|err| SessionError::Protocol(err.to_string()))?;
279 Ok(records
280 .iter()
281 .map(crate::TriggerRegistration::from)
282 .collect())
283 }
284
285 pub async fn query_plugin(
286 &self,
287 name: &str,
288 args: serde_json::Value,
289 session_id: Option<String>,
290 ) -> Result<(String, serde_json::Value), PluginOperationInvokeError> {
291 let manager = self.runtime_session_services()?;
292 let Some(session) = self.session.as_ref() else {
293 return Err(PluginOperationInvokeError::Unknown(
294 "runtime session not available".to_string(),
295 ));
296 };
297 session
298 .plugins()
299 .query_plugin(
300 name,
301 args,
302 session_id,
303 true,
304 manager.read_service(),
305 manager.process_read_service(),
306 )
307 .await
308 }
309
310 pub async fn run_plugin_command(
311 &mut self,
312 name: &str,
313 args: serde_json::Value,
314 session_id: Option<String>,
315 ) -> Result<crate::PluginCommandReceipt<serde_json::Value>, PluginOperationInvokeError> {
316 let manager = self.runtime_session_services()?;
317 let Some(session) = self.session.as_ref() else {
318 return Err(PluginOperationInvokeError::Unknown(
319 "runtime session not available".to_string(),
320 ));
321 };
322 let (plugin_id, outcome) = session
323 .plugins()
324 .run_plugin_command(
325 name,
326 args,
327 session_id,
328 true,
329 manager.state_service(),
330 manager.lifecycle_service(),
331 manager.graph_service(),
332 manager.process_service(),
333 )
334 .await?;
335 let (events, pending_turn_inputs) = self
336 .apply_plugin_operation_effects(&plugin_id, outcome.events, outcome.directives)
337 .await?;
338 Ok(crate::PluginCommandReceipt {
339 output: outcome.output,
340 events,
341 pending_turn_inputs,
342 })
343 }
344
345 pub async fn run_plugin_task(
346 &mut self,
347 name: &str,
348 args: serde_json::Value,
349 session_id: Option<String>,
350 scoped_effect_controller: crate::ScopedEffectController<'static>,
351 cancellation_token: tokio_util::sync::CancellationToken,
352 ) -> Result<crate::PluginTaskReceipt<serde_json::Value>, PluginOperationInvokeError> {
353 let manager = self.runtime_session_services()?;
354 let Some(session) = self.session.as_ref() else {
355 return Err(PluginOperationInvokeError::Unknown(
356 "runtime session not available".to_string(),
357 ));
358 };
359 let (plugin_id, outcome) = session
360 .plugins()
361 .run_plugin_task(
362 name,
363 args,
364 session_id,
365 true,
366 manager.state_service(),
367 manager.lifecycle_service(),
368 manager.graph_service(),
369 manager.process_service(),
370 scoped_effect_controller,
371 cancellation_token,
372 )
373 .await?;
374 let (events, pending_turn_inputs) = self
375 .apply_plugin_operation_effects(&plugin_id, outcome.events, outcome.directives)
376 .await?;
377 Ok(crate::PluginTaskReceipt {
378 output: outcome.output,
379 events,
380 pending_turn_inputs,
381 })
382 }
383
384 async fn apply_plugin_operation_effects(
385 &mut self,
386 plugin_id: &str,
387 events: Vec<crate::PluginRuntimeEvent>,
388 directives: Vec<crate::PluginRuntimeDirective>,
389 ) -> Result<
390 (
391 Vec<crate::PluginOwned<crate::PluginRuntimeEvent>>,
392 Vec<crate::PendingTurnInput>,
393 ),
394 PluginOperationInvokeError,
395 > {
396 let owned_events = events
397 .into_iter()
398 .map(|event| crate::PluginOwned {
399 plugin_id: plugin_id.to_string(),
400 value: event,
401 })
402 .collect::<Vec<_>>();
403 if !owned_events.is_empty() {
404 let nodes = owned_events
405 .iter()
406 .map(|owned| {
407 crate::plugin_runtime_protocol_event(&owned.plugin_id, owned.value.clone())
408 .map(crate::SessionAppendNode::protocol_event)
409 .map_err(|err| {
410 PluginOperationInvokeError::Failed(format!(
411 "failed to encode plugin runtime event: {err}"
412 ))
413 })
414 })
415 .collect::<Result<Vec<_>, _>>()?;
416 self.append_plugin_runtime_event_nodes(&nodes).await?;
417 }
418 self.stamp_live_plugin_state();
419 self.persist_plugin_operation_state_if_needed().await?;
420
421 let mut pending_turn_inputs = Vec::new();
422 for directive in directives {
423 match directive {
424 crate::PluginRuntimeDirective::QueueTurn { input, source_key } => {
425 let pending = self
426 .enqueue_turn_input(input, crate::TurnInputIngress::NextTurn, source_key)
427 .await
428 .map_err(|err| {
429 PluginOperationInvokeError::Failed(format!(
430 "failed to queue plugin turn request: {err}"
431 ))
432 })?;
433 pending_turn_inputs.push(pending);
434 }
435 }
436 }
437
438 Ok((owned_events, pending_turn_inputs))
439 }
440
441 async fn append_plugin_runtime_event_nodes(
442 &mut self,
443 nodes: &[crate::SessionAppendNode],
444 ) -> Result<(), PluginOperationInvokeError> {
445 let node_ids = append_session_nodes_to_state_with_clock(
446 &mut self.state,
447 nodes,
448 self.host.core.clock.as_ref(),
449 );
450 if let Some(store) = self
451 .session
452 .as_ref()
453 .and_then(|session| session.history_store())
454 {
455 let graph = crate::store::GraphCommitDelta::Append {
456 nodes: node_ids
457 .iter()
458 .filter_map(|id| self.state.session_graph.find_node(id).cloned())
459 .collect(),
460 leaf_node_id: self.state.session_graph.leaf_node_id.clone(),
461 };
462 let commit = crate::store::RuntimeCommit::persisted_state_with_graph_commit(
463 &self.state,
464 graph,
465 &[],
466 );
467 let result = super::commit_runtime_state_with_fresh_session_execution_lease(
468 store,
469 commit,
470 &self.runtime_lease_owner,
471 self.host.core.control.lease_timings,
472 Arc::clone(&self.host.core.clock),
473 )
474 .await
475 .map_err(|err| {
476 PluginOperationInvokeError::Failed(format!(
477 "failed to persist plugin runtime events: {err}"
478 ))
479 })?;
480 self.state.apply_persisted_commit_result(result);
481 }
482 Ok(())
483 }
484
485 async fn persist_plugin_operation_state_if_needed(
486 &mut self,
487 ) -> Result<(), PluginOperationInvokeError> {
488 let Some(store) = self
489 .session
490 .as_ref()
491 .and_then(|session| session.history_store())
492 else {
493 return Ok(());
494 };
495 let commit = crate::store::RuntimeCommit::persisted_state(&self.state, &[]);
496 let result = super::commit_runtime_state_with_fresh_session_execution_lease(
497 store,
498 commit,
499 &self.runtime_lease_owner,
500 self.host.core.control.lease_timings,
501 Arc::clone(&self.host.core.clock),
502 )
503 .await
504 .map_err(|err| {
505 PluginOperationInvokeError::Failed(format!(
506 "failed to persist plugin operation state: {err}"
507 ))
508 })?;
509 self.state.apply_persisted_commit_result(result);
510 Ok(())
511 }
512}