oxios_kernel/kernel_handle/
infra_api.rs1use crate::ToolMeta;
4use crate::config::OxiosConfig;
5use crate::cron::{CronJob, CronJobUpdate, CronScheduler};
6use crate::event_bus::{EventBus, KernelEvent};
7use crate::git_layer::{GitLayer, LogEntry};
8use crate::resource_monitor::{ResourceMonitor, ResourceSnapshot};
9use crate::tools::{PendingAskUser, PendingPathAccess, PendingToolApprovals, known_tools};
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13pub struct InfraApi {
15 pub(crate) git_layer: Arc<GitLayer>,
16 pub(crate) cron_scheduler: Arc<CronScheduler>,
17 pub(crate) resource_monitor: Arc<ResourceMonitor>,
18 pub(crate) event_bus: EventBus,
19 pub(crate) config: OxiosConfig,
20 pub(crate) start_time: Instant,
21 pub(crate) orchestrator_config: parking_lot::RwLock<crate::config::OrchestratorConfig>,
23 pub(crate) approval_config: Arc<parking_lot::RwLock<crate::approval::ApprovalConfig>>,
24 pub(crate) pending_tool_approvals: Arc<PendingToolApprovals>,
26 pub(crate) pending_ask_user: Arc<PendingAskUser>,
28 pub(crate) pending_path_access: Arc<PendingPathAccess>,
30}
31
32impl InfraApi {
33 #[allow(clippy::too_many_arguments)]
44 pub fn new(
45 git_layer: Arc<GitLayer>,
46 cron_scheduler: Arc<CronScheduler>,
47 resource_monitor: Arc<ResourceMonitor>,
48 event_bus: EventBus,
49 config: OxiosConfig,
50 start_time: Instant,
51 pending_tool_approvals: Arc<PendingToolApprovals>,
52 pending_ask_user: Arc<PendingAskUser>,
53 approval_config: Arc<parking_lot::RwLock<crate::approval::ApprovalConfig>>,
54 pending_path_access: Arc<PendingPathAccess>,
55 ) -> Self {
56 Self {
57 git_layer,
58 cron_scheduler,
59 resource_monitor,
60 event_bus,
61 config,
62 start_time,
63 orchestrator_config: parking_lot::RwLock::new(
64 crate::config::OrchestratorConfig::default(),
65 ),
66 approval_config,
67 pending_tool_approvals,
68 pending_ask_user,
69 pending_path_access,
70 }
71 }
72 pub fn git(&self) -> &GitLayer {
74 &self.git_layer
75 }
76
77 pub fn approval_config_handle(
78 &self,
79 ) -> Arc<parking_lot::RwLock<crate::approval::ApprovalConfig>> {
80 Arc::clone(&self.approval_config)
81 }
82 pub fn approval_config(&self) -> crate::approval::ApprovalConfig {
83 self.approval_config.read().clone()
84 }
85
86 pub async fn set_approval_config(
87 &self,
88 config: crate::approval::ApprovalConfig,
89 ) -> anyhow::Result<crate::approval::ApprovalConfig> {
90 *self.approval_config.write() = config.clone();
91 Ok(config)
92 }
93
94 pub async fn add_grant(&self, key: String) -> anyhow::Result<crate::approval::ApprovalConfig> {
95 let mut config = self.approval_config();
96 if !config.allow_list.contains(&key) {
97 config.allow_list.push(key);
98 }
99 self.set_approval_config(config.clone()).await
100 }
101
102 pub async fn remove_grant(&self, key: &str) -> anyhow::Result<crate::approval::ApprovalConfig> {
103 let mut config = self.approval_config();
104 config.allow_list.retain(|item| item != key);
105 self.set_approval_config(config.clone()).await
106 }
107
108 pub fn git_log(&self, max: usize) -> anyhow::Result<Vec<LogEntry>> {
110 self.git_layer.log(max)
111 }
112
113 pub fn git_tag(&self, name: &str, message: &str) -> anyhow::Result<()> {
115 self.git_layer.tag(name, message)
116 }
117
118 pub fn git_restore(&self, path: &str, hash: &str) -> anyhow::Result<()> {
120 self.git_layer.restore_file(path, hash)
121 }
122
123 pub fn git_verify(&self) -> anyhow::Result<bool> {
125 self.git_layer.verify()
126 }
127
128 pub fn git_tags(&self) -> anyhow::Result<Vec<String>> {
130 self.git_layer.list_tags()
131 }
132
133 pub fn git_delete_tag(&self, name: &str) -> anyhow::Result<()> {
135 self.git_layer.delete_tag(name)
136 }
137
138 pub async fn add_cron(&self, job: CronJob) -> anyhow::Result<uuid::Uuid> {
140 self.cron_scheduler.add_job(job).await
141 }
142
143 pub fn get_cron(&self, id: uuid::Uuid) -> Option<CronJob> {
145 self.cron_scheduler.get_job(id)
146 }
147
148 pub async fn update_cron(&self, id: uuid::Uuid, update: CronJobUpdate) -> anyhow::Result<()> {
150 self.cron_scheduler.update_job(id, update).await
151 }
152
153 pub async fn remove_cron(&self, id: uuid::Uuid) -> anyhow::Result<()> {
155 self.cron_scheduler.remove_job(id).await
156 }
157
158 pub fn trigger_cron(&self, id: uuid::Uuid) -> anyhow::Result<CronJob> {
160 self.cron_scheduler.trigger_job(id)
161 }
162
163 pub async fn complete_cron(&self, id: uuid::Uuid, success: bool, summary: String) {
165 self.cron_scheduler
166 .mark_job_completed(id, success, summary)
167 .await
168 }
169
170 pub fn list_crons(&self) -> Vec<CronJob> {
172 self.cron_scheduler.list_jobs()
173 }
174
175 pub fn resource_snapshot(&self) -> ResourceSnapshot {
177 self.resource_monitor.snapshot()
178 }
179
180 pub fn resource_history(&self, last_n: usize) -> Vec<ResourceSnapshot> {
182 self.resource_monitor.history(last_n)
183 }
184
185 pub fn is_overloaded(&self) -> bool {
187 self.resource_monitor.is_overloaded()
188 }
189
190 pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<KernelEvent> {
192 self.event_bus.subscribe()
193 }
194
195 pub fn publish(&self, event: KernelEvent) -> anyhow::Result<()> {
197 self.event_bus
198 .publish(event)
199 .map_err(|e| anyhow::anyhow!("broadcast error: {e}"))
200 }
201
202 pub fn config(&self) -> &OxiosConfig {
204 &self.config
205 }
206
207 pub fn resource_monitor(&self) -> &Arc<ResourceMonitor> {
209 &self.resource_monitor
210 }
211
212 pub fn update_orchestrator_config(&self, config: crate::config::OrchestratorConfig) {
214 *self.orchestrator_config.write() = config;
215 }
216
217 pub fn uptime(&self) -> Duration {
219 self.start_time.elapsed()
220 }
221
222 pub fn pending_tool_approvals(&self) -> Arc<PendingToolApprovals> {
224 self.pending_tool_approvals.clone()
225 }
226 pub fn pending_path_access(&self) -> Arc<PendingPathAccess> {
228 self.pending_path_access.clone()
229 }
230
231 pub fn pending_ask_user(&self) -> Arc<PendingAskUser> {
233 Arc::clone(&self.pending_ask_user)
234 }
235
236 pub fn event_bus_clone(&self) -> EventBus {
239 self.event_bus.clone()
240 }
241
242 pub fn list_available_tools(&self) -> &'static [ToolMeta] {
244 known_tools()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::approval::{ApprovalConfig, ApprovalMode};
252 use crate::state_store::StateStore;
253
254 fn minimal_infra(approval_config: Arc<parking_lot::RwLock<ApprovalConfig>>) -> InfraApi {
255 let dir = tempfile::tempdir().unwrap();
256 InfraApi::new(
257 Arc::new(GitLayer::new(dir.path().join("git"), false).unwrap()),
258 Arc::new(CronScheduler::new(
259 Arc::new(StateStore::new(dir.path().join("state")).unwrap()),
260 60,
261 )),
262 Arc::new(ResourceMonitor::new(60, 60)),
263 EventBus::new(64),
264 OxiosConfig::default(),
265 std::time::Instant::now(),
266 Arc::new(PendingToolApprovals::new()),
267 Arc::new(PendingAskUser::new()),
268 approval_config,
269 Arc::new(PendingPathAccess::new()),
270 )
271 }
272
273 #[test]
280 fn new_stores_shared_approval_config_arc() {
281 let shared = Arc::new(parking_lot::RwLock::new(ApprovalConfig::default()));
282 let infra = minimal_infra(Arc::clone(&shared));
283 assert!(
284 Arc::ptr_eq(&infra.approval_config_handle(), &shared),
285 "InfraApi must store the passed approval_config Arc, not a fresh clone"
286 );
287 }
288
289 #[tokio::test]
292 async fn shared_arc_visible_across_instances() {
293 let shared = Arc::new(parking_lot::RwLock::new(ApprovalConfig::default()));
294 let a = minimal_infra(Arc::clone(&shared));
295 let b = minimal_infra(shared);
296
297 let mut cfg = a.approval_config();
299 cfg.mode = ApprovalMode::AutoRun;
300 a.set_approval_config(cfg).await.unwrap();
301
302 assert_eq!(b.approval_config().mode, ApprovalMode::AutoRun);
304 }
305}