lean_ctx/core/context_os/
mod.rs1use 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#[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
55pub 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
75pub 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}