theway-daemon 0.1.25

theway daemon — the single agent-runtime kernel (bin `thewayd`): harness assembly, local/sandbox tool policy, triggers/cron/session/DAG runtime, skills, MCP/LSP wiring, serving the gRPC/HTTP/MCP transports from theway-transport. Terminal UI lives in the theway-tui crate.
Documentation
use std::collections::BTreeSet;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use theway_contract::extension::{
    ExtensionAuditEvent, ExtensionAuditOperation, ExtensionAuditOutcome, ExtensionPermission,
};

#[derive(Clone)]
pub struct ExtensionAuditLog {
    path: PathBuf,
    events: Arc<parking_lot::Mutex<Vec<ExtensionAuditEvent>>>,
    writer: Arc<parking_lot::Mutex<()>>,
}

impl ExtensionAuditLog {
    pub fn for_base(base: &Path) -> Self {
        Self {
            path: base.join("extensions").join("audit.jsonl"),
            events: Arc::new(parking_lot::Mutex::new(Vec::new())),
            writer: Arc::new(parking_lot::Mutex::new(())),
        }
    }

    pub fn events(&self) -> Vec<ExtensionAuditEvent> {
        self.events.lock().clone()
    }

    pub(super) fn record(
        &self,
        extension_id: impl Into<String>,
        session_id: Option<String>,
        operation: ExtensionAuditOperation,
        outcome: ExtensionAuditOutcome,
        capability: Option<ExtensionPermission>,
        target: Option<&str>,
        redacted_fields: impl IntoIterator<Item = String>,
    ) {
        let target =
            target.map(|value| crate::bug_report::redact(value).chars().take(160).collect());
        let event = ExtensionAuditEvent {
            timestamp: chrono::Utc::now().to_rfc3339(),
            extension_id: extension_id.into(),
            session_id,
            operation,
            outcome,
            capability,
            target,
            redacted_fields: redacted_fields.into_iter().collect::<BTreeSet<_>>(),
        };
        self.events.lock().push(event.clone());
        let _writer = self.writer.lock();
        let Some(parent) = self.path.parent() else {
            return;
        };
        if std::fs::create_dir_all(parent).is_err() {
            return;
        }
        let Ok(mut file) = std::fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(&self.path)
        else {
            return;
        };
        if let Ok(serialized) = serde_json::to_string(&event) {
            let _ = writeln!(file, "{serialized}");
        }
    }
}

#[cfg(test)]
mod coverage_gap {
    use super::*;

    #[test]
    fn record_handles_missing_parent_and_unwritable_paths() {
        // Path without a parent hits the `path.parent()` None branch.
        let log = ExtensionAuditLog {
            path: PathBuf::from("audit.jsonl"),
            events: Arc::new(parking_lot::Mutex::new(Vec::new())),
            writer: Arc::new(parking_lot::Mutex::new(())),
        };
        log.record(
            "ext",
            Some("sess".into()),
            ExtensionAuditOperation::TrustChanged,
            ExtensionAuditOutcome::Allowed,
            None,
            None,
            std::iter::empty(),
        );

        // A file where the audit parent directory should be prevents both
        // create_dir_all and OpenOptions from succeeding.
        let base = tempfile::tempdir().unwrap();
        std::fs::write(base.path().join("extensions"), "not a directory").unwrap();
        let log = ExtensionAuditLog::for_base(base.path());
        log.record(
            "ext",
            Some("sess".into()),
            ExtensionAuditOperation::TrustChanged,
            ExtensionAuditOutcome::Allowed,
            None,
            None,
            std::iter::empty(),
        );

        // Parent directory exists but the audit path is a directory, so open
        // fails while create_dir_all succeeds.
        let base = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(base.path().join("extensions/audit.jsonl")).unwrap();
        let log = ExtensionAuditLog::for_base(base.path());
        log.record(
            "ext",
            Some("sess".into()),
            ExtensionAuditOperation::TrustChanged,
            ExtensionAuditOutcome::Allowed,
            None,
            None,
            std::iter::empty(),
        );
    }
}