Skip to main content

lean_ctx/core/context_os/
mod.rs

1use std::path::PathBuf;
2use std::sync::{Arc, OnceLock};
3
4mod shared_sessions;
5pub use shared_sessions::{SharedSessionKey, SharedSessionStore};
6
7mod context_bus;
8pub use context_bus::{ConsistencyLevel, ContextBus, ContextEventKindV1, ContextEventV1};
9
10pub mod redaction;
11pub use redaction::{redact_event_payload, RedactionLevel};
12
13mod metrics;
14pub use metrics::{ContextOsMetrics, MetricsSnapshot};
15
16/// Shared runtime backing Context OS features (shared sessions + event bus).
17///
18/// This is intentionally process-local: it enables multi-client coordination
19/// for HTTP/daemon/team-server deployments (one process handling many clients).
20#[derive(Clone)]
21pub struct ContextOsRuntime {
22    pub shared_sessions: Arc<SharedSessionStore>,
23    pub bus: Arc<ContextBus>,
24    pub metrics: Arc<ContextOsMetrics>,
25}
26
27impl Default for ContextOsRuntime {
28    fn default() -> Self {
29        Self {
30            shared_sessions: Arc::new(SharedSessionStore::new()),
31            bus: Arc::new(ContextBus::new()),
32            metrics: Arc::new(ContextOsMetrics::default()),
33        }
34    }
35}
36
37impl ContextOsRuntime {
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    pub fn data_dir() -> Option<PathBuf> {
43        crate::core::data_dir::lean_ctx_data_dir().ok()
44    }
45}
46
47static RUNTIME: OnceLock<Arc<ContextOsRuntime>> = OnceLock::new();
48
49pub fn runtime() -> Arc<ContextOsRuntime> {
50    RUNTIME
51        .get_or_init(|| Arc::new(ContextOsRuntime::new()))
52        .clone()
53}
54
55/// Convenience: append an event to the global bus with metrics tracking.
56pub fn emit_event(
57    workspace_id: &str,
58    channel_id: &str,
59    kind: &ContextEventKindV1,
60    actor: Option<&str>,
61    payload: serde_json::Value,
62) {
63    let rt = runtime();
64    if rt
65        .bus
66        .append(workspace_id, channel_id, kind, actor, payload)
67        .is_some()
68    {
69        rt.metrics.record_event_appended();
70    }
71}
72
73/// Classify a tool name into a secondary event kind (beyond ToolCallRecorded).
74pub fn secondary_event_kind(tool: &str, action: Option<&str>) -> Option<ContextEventKindV1> {
75    match tool {
76        "ctx_session" => {
77            let a = action.unwrap_or("");
78            if matches!(
79                a,
80                "save"
81                    | "set_task"
82                    | "task"
83                    | "checkpoint"
84                    | "finding"
85                    | "decision"
86                    | "reset"
87                    | "import"
88            ) {
89                Some(ContextEventKindV1::SessionMutated)
90            } else {
91                None
92            }
93        }
94        "ctx_handoff" | "ctx_workflow" | "ctx_share" => Some(ContextEventKindV1::SessionMutated),
95        "ctx_knowledge" | "ctx_knowledge_relations" => {
96            let a = action.unwrap_or("");
97            if matches!(
98                a,
99                "remember" | "relate" | "unrelate" | "feedback" | "remove" | "consolidate"
100            ) {
101                Some(ContextEventKindV1::KnowledgeRemembered)
102            } else {
103                None
104            }
105        }
106        "ctx_artifacts" => {
107            let a = action.unwrap_or("");
108            if matches!(a, "store" | "reindex" | "remove") {
109                Some(ContextEventKindV1::ArtifactStored)
110            } else {
111                None
112            }
113        }
114        "ctx_graph" => {
115            let a = action.unwrap_or("");
116            if matches!(
117                a,
118                "index-build"
119                    | "index-build-full"
120                    | "index-build-background"
121                    | "index-build-full-background"
122            ) {
123                Some(ContextEventKindV1::GraphBuilt)
124            } else {
125                None
126            }
127        }
128        "ctx_proof" | "ctx_verify" => {
129            let a = action.unwrap_or("");
130            if matches!(a, "generate" | "export" | "verify") {
131                Some(ContextEventKindV1::ProofAdded)
132            } else {
133                None
134            }
135        }
136        _ => None,
137    }
138}