oxios_kernel/kernel_handle/
mod.rs1pub mod a2a_api;
4pub mod agent_api;
5pub mod calendar_api;
6pub mod email_api;
7pub mod engine_api;
8pub mod exec_api;
9pub mod extension_api;
10pub mod infra_api;
11pub mod knowledge_lens;
12pub mod marketplace_api;
13pub mod mcp_api;
14pub mod memory_api;
15pub mod mount_api;
16pub mod persona_api;
17pub mod project_api;
18pub mod security_api;
19pub mod state_api;
20
21pub use a2a_api::A2aApi;
22pub use agent_api::AgentApi;
23pub use calendar_api::CalendarApi;
24pub use email_api::EmailApi;
25pub use engine_api::{
26 EngineApi, EngineConfigResponse, FallbackEvent, InputModality, ModelInfo, ProviderCategory,
27 ProviderInfo, RoutingConfigSnapshot, RoutingStats, RoutingStatsSnapshot, RoutingUpdate,
28 ValidateKeyResult,
29};
30pub use exec_api::ExecApi;
31pub use exec_api::SharedExecConfig;
32pub use extension_api::ExtensionApi;
33pub use infra_api::InfraApi;
34pub use knowledge_lens::{
35 CopilotResponse, KnowledgeContext, KnowledgeLens, KnowledgeNote, MemoryNote,
36};
37pub use marketplace_api::MarketplaceApi;
38pub use mcp_api::McpApi;
39pub use memory_api::MemoryApi;
40pub use mount_api::{MountApi, MountInfo};
41pub use persona_api::PersonaApi;
42pub use project_api::{ProjectApi, ProjectInfo};
43pub use security_api::SecurityApi;
44pub use state_api::StateApi;
45
46use crate::git_layer::CommitInfo;
47use crate::readiness::ReadinessGate;
48use serde::Serialize;
49use std::sync::Arc;
50
51pub struct KernelHandle {
67 pub state: StateApi,
69 pub agents: AgentApi,
71 pub security: SecurityApi,
73 pub persona: PersonaApi,
75 pub extensions: ExtensionApi,
77 pub mcp: McpApi,
79 pub infra: InfraApi,
81 pub projects: Option<ProjectApi>,
83 pub mounts: Option<MountApi>,
85 pub exec: ExecApi,
87 pub a2a: A2aApi,
89 pub engine: EngineApi,
91 pub knowledge: Arc<oxios_markdown::KnowledgeBase>,
93 pub knowledge_lens: Arc<KnowledgeLens>,
95 pub marketplace_api: MarketplaceApi,
97 pub calendar: Option<CalendarApi>,
99 pub email: Option<EmailApi>,
101 pub readiness: Arc<ReadinessGate>,
103}
104
105impl KernelHandle {
106 #[allow(clippy::too_many_arguments)]
111 pub fn new(
112 state: StateApi,
113 agents: AgentApi,
114 security: SecurityApi,
115 persona: PersonaApi,
116 extensions: ExtensionApi,
117 mcp: McpApi,
118 infra: InfraApi,
119 projects: Option<ProjectApi>,
120 exec: ExecApi,
121 a2a: A2aApi,
122 engine: EngineApi,
123 knowledge: Arc<oxios_markdown::KnowledgeBase>,
124 knowledge_lens: Arc<KnowledgeLens>,
125 marketplace_api: MarketplaceApi,
126 calendar: Option<CalendarApi>,
127 email: Option<EmailApi>,
128 ) -> Self {
129 Self {
130 state,
131 agents,
132 security,
133 persona,
134 extensions,
135 mcp,
136 infra,
137 projects,
138 mounts: None,
139 exec,
140 a2a,
141 engine,
142 knowledge,
143 knowledge_lens,
144 marketplace_api,
145 calendar,
146 email,
147 readiness: Arc::new(ReadinessGate::new(0)),
151 }
152 }
153
154 pub fn with_mounts(mut self, mounts: MountApi) -> Self {
160 self.mounts = Some(mounts);
161 self
162 }
163
164 pub fn set_mounts(&mut self, mounts: MountApi) {
166 self.mounts = Some(mounts);
167 }
168
169 pub async fn save_and_commit<T: Serialize>(
181 &self,
182 category: &str,
183 name: &str,
184 data: &T,
185 ) -> anyhow::Result<()> {
186 self.state.save(category, name, data).await?;
187 let git = self.infra.git();
188 if git.is_enabled() {
189 let rel_path = format!("{category}/{name}.json");
190 if let Err(e) = git.commit_file(&rel_path, &format!("save {category}/{name}")) {
191 tracing::warn!(
192 error = %e, rel_path = %rel_path,
193 "save_and_commit: git commit failed (data was still saved)"
194 );
195 }
196 }
197 Ok(())
198 }
199
200 pub async fn save_markdown_and_commit(
204 &self,
205 category: &str,
206 name: &str,
207 content: &str,
208 ) -> anyhow::Result<()> {
209 self.state.save_markdown(category, name, content).await?;
210 let git = self.infra.git();
211 if git.is_enabled() {
212 let rel_path = format!("{category}/{name}.md");
213 if let Err(e) = git.commit_file(&rel_path, &format!("save {category}/{name}")) {
214 tracing::warn!(
215 error = %e, rel_path = %rel_path,
216 "save_markdown_and_commit: git commit failed (data was still saved)"
217 );
218 }
219 }
220 Ok(())
221 }
222
223 pub async fn delete_and_commit(&self, category: &str, name: &str) -> anyhow::Result<bool> {
227 let deleted = self.state.delete(category, name).await?;
228 if deleted {
229 let git = self.infra.git();
230 if git.is_enabled() {
231 let rel_path = format!("{category}/{name}.json");
232 if let Err(e) = git.remove_file(&rel_path, &format!("delete {category}/{name}")) {
233 tracing::warn!(
234 error = %e, rel_path = %rel_path,
235 "delete_and_commit: git remove failed (file was still deleted)"
236 );
237 }
238 }
239 }
240 Ok(deleted)
241 }
242
243 pub fn commit_all(&self, message: &str) -> anyhow::Result<Option<CommitInfo>> {
245 self.state.commit_all(self.infra.git(), message)
246 }
247
248 pub fn flush_audit(&self) -> anyhow::Result<()> {
250 self.security.flush(self.infra.git())
251 }
252
253 pub async fn schedule(
261 &self,
262 cron_expr: &str,
263 task: &str,
264 persona: Option<&str>,
265 ) -> anyhow::Result<String> {
266 if let Some(p) = persona
267 && !p.is_empty()
268 && p != "default"
269 {
270 tracing::warn!(
271 persona = p,
272 "schedule: persona argument is not yet honored by the cron executor; job will run with the default persona"
273 );
274 }
275 let job = crate::cron::CronJob::new(
276 format!("job_{}", uuid::Uuid::new_v4()),
277 cron_expr.to_string(),
278 task.to_string(),
279 );
280 let job_id = self.infra.add_cron(job).await?;
281 Ok(job_id.to_string())
282 }
283
284 pub async fn unschedule(&self, job_id: &str) -> anyhow::Result<bool> {
292 let uuid =
293 uuid::Uuid::parse_str(job_id).map_err(|e| anyhow::anyhow!("invalid job id: {e}"))?;
294 match self.infra.remove_cron(uuid).await {
295 Ok(()) => Ok(true),
296 Err(e) => {
297 let msg = format!("{e}");
298 if msg.to_lowercase().contains("not found") {
299 Ok(false)
301 } else {
302 Err(anyhow::anyhow!("failed to remove cron job {job_id}: {e}"))
303 }
304 }
305 }
306 }
307 pub fn list_schedules(&self) -> Vec<crate::cron::CronJob> {
308 self.infra.list_crons()
309 }
310
311 pub async fn load_json<T: serde::de::DeserializeOwned>(
313 &self,
314 category: &str,
315 name: &str,
316 ) -> anyhow::Result<Option<T>> {
317 self.state.load(category, name).await
318 }
319
320 pub fn start_time(&self) -> std::time::Instant {
322 self.infra.start_time
323 }
324
325 pub fn marketplace_api(&self) -> &MarketplaceApi {
327 &self.marketplace_api
328 }
329
330 pub fn memory(&self) -> MemoryApi {
337 let mm = self.agents.memory_manager().clone();
338 let hnsw = self.agents.hnsw_index.clone();
339 MemoryApi::new(mm, hnsw)
340 }
341}