lash-core 0.1.0-alpha.1

Sans-IO turn machine and runtime kernel for the lash agent runtime.
Documentation
use std::sync::Arc;

use arc_swap::ArcSwap;
use tokio::sync::Mutex;

use super::{BackgroundTaskHost, BackgroundTaskRecord, LashRuntime};

#[derive(Clone)]
pub struct RuntimeObservation {
    pub session_id: Arc<str>,
    pub policy: crate::SessionPolicy,
    pub read_view: crate::SessionReadView,
    pub persisted_state: super::PersistedSessionState,
    pub usage_report: super::SessionUsageReport,
    pub tool_state: Option<crate::ToolState>,
    pub tool_catalog: Arc<Vec<serde_json::Value>>,
    pub runtime_scope_id: Arc<str>,
    pub background_task_host: Option<Arc<dyn BackgroundTaskHost>>,
}

impl RuntimeObservation {
    fn from_runtime(runtime: &LashRuntime) -> Self {
        Self {
            session_id: Arc::from(runtime.session_id()),
            policy: runtime.read_view().policy().clone(),
            read_view: runtime.read_view(),
            persisted_state: runtime.export_persisted_state(),
            usage_report: runtime.usage_report(),
            tool_state: runtime.tool_state().ok(),
            tool_catalog: runtime.active_tool_catalog_shared(),
            runtime_scope_id: Arc::clone(&runtime.runtime_scope_id),
            background_task_host: runtime.host.background_task_host.clone(),
        }
    }

    pub fn session_id(&self) -> &str {
        &self.session_id
    }

    pub fn background_scope_key(&self) -> String {
        format!("{}:{}", self.runtime_scope_id, self.session_id)
    }

    pub async fn list_background_tasks(&self) -> Vec<BackgroundTaskRecord> {
        let Some(executor) = self.background_task_host.as_ref() else {
            return Vec::new();
        };
        executor.list_managed(&self.background_scope_key()).await
    }
}

#[derive(Clone)]
pub struct RuntimeHandle {
    pub(in crate::runtime) runtime: Arc<Mutex<LashRuntime>>,
    observation: Arc<ArcSwap<RuntimeObservation>>,
}

impl RuntimeHandle {
    pub fn new(runtime: LashRuntime) -> Self {
        let observation = RuntimeObservation::from_runtime(&runtime);
        Self {
            runtime: Arc::new(Mutex::new(runtime)),
            observation: Arc::new(ArcSwap::from_pointee(observation)),
        }
    }

    pub fn writer(&self) -> Arc<Mutex<LashRuntime>> {
        Arc::clone(&self.runtime)
    }

    pub fn observe(&self) -> Arc<RuntimeObservation> {
        self.observation.load_full()
    }

    pub fn publish_from(&self, runtime: &LashRuntime) {
        self.observation
            .store(Arc::new(RuntimeObservation::from_runtime(runtime)));
    }

    pub fn try_into_runtime(self) -> Result<LashRuntime, Self> {
        match Arc::try_unwrap(self.runtime) {
            Ok(mutex) => Ok(mutex.into_inner()),
            Err(runtime) => Err(Self {
                runtime,
                observation: self.observation,
            }),
        }
    }
}