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        rt.metrics.record_event_broadcast();
71        rt.metrics.record_workspace_active(workspace_id);
72    }
73}
74
75/// Classify a tool name into a secondary event kind (beyond ToolCallRecorded).
76pub fn secondary_event_kind(tool: &str, action: Option<&str>) -> Option<ContextEventKindV1> {
77    match tool {
78        "ctx_session" => {
79            let a = action.unwrap_or("");
80            if matches!(
81                a,
82                "save"
83                    | "set_task"
84                    | "task"
85                    | "checkpoint"
86                    | "finding"
87                    | "decision"
88                    | "reset"
89                    | "import"
90                    | "export"
91            ) {
92                Some(ContextEventKindV1::SessionMutated)
93            } else {
94                None
95            }
96        }
97        "ctx_handoff" | "ctx_workflow" | "ctx_share" => Some(ContextEventKindV1::SessionMutated),
98        "ctx_knowledge" | "ctx_knowledge_relations" => {
99            let a = action.unwrap_or("");
100            if matches!(
101                a,
102                "remember"
103                    | "relate"
104                    | "unrelate"
105                    | "feedback"
106                    | "remove"
107                    | "consolidate"
108                    | "import"
109            ) {
110                Some(ContextEventKindV1::KnowledgeRemembered)
111            } else {
112                None
113            }
114        }
115        "ctx_artifacts" => {
116            let a = action.unwrap_or("");
117            if matches!(a, "reindex" | "remove") {
118                Some(ContextEventKindV1::ArtifactStored)
119            } else {
120                None
121            }
122        }
123        "ctx_graph" => {
124            let a = action.unwrap_or("");
125            if matches!(
126                a,
127                "index-build"
128                    | "index-build-full"
129                    | "index-build-background"
130                    | "index-build-full-background"
131            ) {
132                Some(ContextEventKindV1::GraphBuilt)
133            } else {
134                None
135            }
136        }
137        "ctx_proof" | "ctx_verify" => {
138            let a = action.unwrap_or("");
139            if matches!(a, "generate" | "export" | "verify") {
140                Some(ContextEventKindV1::ProofAdded)
141            } else {
142                None
143            }
144        }
145        _ => None,
146    }
147}