oxios_kernel/tools/
pending_path_access.rs1use parking_lot::Mutex;
11use std::collections::HashMap;
12use tokio::sync::oneshot;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum PathAccessResult {
18 Allowed,
20 Denied,
22}
23
24struct PathAccessEntry {
25 tool_name: String,
27 path: String,
29 #[allow(dead_code)]
31 mode: String,
32 agent_name: String,
34 sender: oneshot::Sender<PathAccessResult>,
35}
36
37#[derive(Default)]
39pub struct PendingPathAccess {
40 inner: Mutex<HashMap<Uuid, PathAccessEntry>>,
41}
42
43impl PendingPathAccess {
44 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn register(
52 &self,
53 tool_name: String,
54 path: String,
55 mode: String,
56 agent_name: String,
57 ) -> (Uuid, oneshot::Receiver<PathAccessResult>) {
58 let id = Uuid::new_v4();
59 let (tx, rx) = oneshot::channel();
60 self.inner.lock().insert(
61 id,
62 PathAccessEntry {
63 tool_name,
64 path,
65 mode,
66 agent_name,
67 sender: tx,
68 },
69 );
70 (id, rx)
71 }
72
73 pub fn resolve(&self, id: Uuid, result: PathAccessResult) -> Option<(String, String, String)> {
76 let entry = self.inner.lock().remove(&id)?;
77 let _ = entry.sender.send(result);
78 Some((entry.tool_name, entry.path, entry.agent_name))
79 }
80
81 pub fn path_context(&self, id: Uuid) -> Option<(String, String)> {
85 let guard = self.inner.lock();
86 let entry = guard.get(&id)?;
87 Some((entry.agent_name.clone(), entry.path.clone()))
88 }
89
90 pub fn cancel_all(&self) {
92 let mut guard = self.inner.lock();
93 for (_, entry) in guard.drain() {
94 let _ = entry.sender.send(PathAccessResult::Denied);
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn register_and_resolve_round_trip() {
105 let registry = PendingPathAccess::new();
106 let (id, rx) = registry.register(
107 "read".to_string(),
108 "/secret/file".to_string(),
109 "read".to_string(),
110 "agent-1".to_string(),
111 );
112 assert_eq!(
113 registry.path_context(id),
114 Some(("agent-1".to_string(), "/secret/file".to_string()))
115 );
116 let resolved = registry.resolve(id, PathAccessResult::Allowed);
117 assert_eq!(
118 resolved,
119 Some((
120 "read".to_string(),
121 "/secret/file".to_string(),
122 "agent-1".to_string()
123 ))
124 );
125 let result = rx.blocking_recv().expect("oneshot did not deliver");
126 assert_eq!(result, PathAccessResult::Allowed);
127 }
128
129 #[test]
130 fn resolve_missing_returns_none() {
131 let registry = PendingPathAccess::new();
132 let missing = Uuid::new_v4();
133 assert!(
134 registry
135 .resolve(missing, PathAccessResult::Denied)
136 .is_none()
137 );
138 }
139}