use std::collections::VecDeque;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
#[derive(Clone, Debug, PartialEq)]
pub enum AgentType {
Main,
Teammate,
SubAgent,
}
pub struct PendingAgentPerm {
pub agent_type: AgentType,
pub name: String,
pub tool_name: String,
pub confirm_msg: String,
decision: Arc<(Mutex<Option<bool>>, Condvar)>,
}
impl PendingAgentPerm {
pub fn new(
agent_type: AgentType,
name: String,
tool_name: String,
confirm_msg: String,
) -> Arc<Self> {
Arc::new(Self {
agent_type,
name,
tool_name,
confirm_msg,
decision: Arc::new((Mutex::new(None), Condvar::new())),
})
}
pub fn title(&self) -> String {
match &self.agent_type {
AgentType::Main => " 权限请求 [Main] ".to_string(),
AgentType::Teammate => format!(" 权限请求 [{}] ", self.name),
AgentType::SubAgent => format!(" SubAgent 权限请求 [{}] ", self.name),
}
}
pub fn wait_for_decision(&self, timeout_secs: u64) -> bool {
let (lock, cvar) = &*self.decision;
let guard = lock.lock().unwrap_or_else(|e| e.into_inner());
let (guard, _timed_out) = cvar
.wait_timeout_while(guard, Duration::from_secs(timeout_secs), |d| d.is_none())
.unwrap_or_else(|e| e.into_inner());
guard.unwrap_or(false)
}
pub fn resolve(&self, approved: bool) {
let (lock, cvar) = &*self.decision;
let mut d = lock.lock().unwrap_or_else(|e| e.into_inner());
*d = Some(approved);
cvar.notify_one();
}
}
pub struct PermissionQueue {
pending: Mutex<VecDeque<Arc<PendingAgentPerm>>>,
}
impl Default for PermissionQueue {
fn default() -> Self {
Self::new()
}
}
impl PermissionQueue {
pub fn new() -> Self {
Self {
pending: Mutex::new(VecDeque::new()),
}
}
pub fn request_blocking(&self, req: Arc<PendingAgentPerm>) -> bool {
{
let mut q = self.pending.lock().unwrap_or_else(|e| e.into_inner());
q.push_back(Arc::clone(&req));
}
req.wait_for_decision(60)
}
pub fn pop_pending(&self) -> Option<Arc<PendingAgentPerm>> {
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
.pop_front()
}
pub fn deny_all(&self) {
let mut q = self.pending.lock().unwrap_or_else(|e| e.into_inner());
for req in q.drain(..) {
req.resolve(false);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn agent_type_title_format() {
let main_perm = PendingAgentPerm::new(
AgentType::Main,
"Main".into(),
"Bash".into(),
"run script".into(),
);
assert_eq!(main_perm.title(), " 权限请求 [Main] ");
let teammate_perm = PendingAgentPerm::new(
AgentType::Teammate,
"Backend".into(),
"Write".into(),
"write file".into(),
);
assert_eq!(teammate_perm.title(), " 权限请求 [Backend] ");
let sub_perm = PendingAgentPerm::new(
AgentType::SubAgent,
"sub_0001".into(),
"Edit".into(),
"edit file".into(),
);
assert_eq!(sub_perm.title(), " SubAgent 权限请求 [sub_0001] ");
}
#[test]
fn pending_perm_resolve_approved() {
let perm = PendingAgentPerm::new(
AgentType::Teammate,
"Backend".into(),
"Bash".into(),
"run tests".into(),
);
let perm_clone = Arc::clone(&perm);
let handle = thread::spawn(move || perm_clone.wait_for_decision(5));
perm.resolve(true);
let result = handle.join().expect("线程不应 panic");
assert!(result, "resolve(true) 后 wait_for_decision 应返回 true");
}
#[test]
fn pending_perm_resolve_denied() {
let perm = PendingAgentPerm::new(
AgentType::SubAgent,
"sub_0001".into(),
"Write".into(),
"write file".into(),
);
let perm_clone = Arc::clone(&perm);
let handle = thread::spawn(move || perm_clone.wait_for_decision(5));
perm.resolve(false);
let result = handle.join().expect("线程不应 panic");
assert!(!result, "resolve(false) 后 wait_for_decision 应返回 false");
}
#[test]
fn pending_perm_timeout_returns_false() {
let perm = PendingAgentPerm::new(
AgentType::Teammate,
"Frontend".into(),
"Edit".into(),
"edit file".into(),
);
let result = perm.wait_for_decision(1);
assert!(!result, "超时未 resolve 时应返回 false");
}
#[test]
fn pending_perm_agent_type_equality() {
assert_eq!(AgentType::Main, AgentType::Main);
assert_eq!(AgentType::Teammate, AgentType::Teammate);
assert_eq!(AgentType::SubAgent, AgentType::SubAgent);
assert_ne!(AgentType::Main, AgentType::Teammate);
assert_ne!(AgentType::Teammate, AgentType::SubAgent);
}
#[test]
fn pending_perm_fields_preserved() {
let perm = PendingAgentPerm::new(
AgentType::Teammate,
"Backend".into(),
"Bash".into(),
"run script".into(),
);
assert_eq!(perm.agent_type, AgentType::Teammate);
assert_eq!(perm.name, "Backend");
assert_eq!(perm.tool_name, "Bash");
assert_eq!(perm.confirm_msg, "run script");
}
#[test]
fn queue_empty_pop_returns_none() {
let queue = PermissionQueue::new();
assert!(queue.pop_pending().is_none(), "空队列 pop 应返回 None");
}
#[test]
fn queue_request_and_pop_fifo() {
let queue = PermissionQueue::new();
let req1 = PendingAgentPerm::new(
AgentType::Teammate,
"Backend".into(),
"Bash".into(),
"first".into(),
);
let req2 = PendingAgentPerm::new(
AgentType::SubAgent,
"sub_0001".into(),
"Write".into(),
"second".into(),
);
let mut q = queue.pending.lock().unwrap_or_else(|e| e.into_inner());
q.push_back(Arc::clone(&req1));
q.push_back(Arc::clone(&req2));
drop(q);
let popped1 = queue.pop_pending();
assert!(popped1.is_some(), "第一次 pop 应有结果");
assert_eq!(popped1.unwrap().confirm_msg, "first");
let popped2 = queue.pop_pending();
assert!(popped2.is_some(), "第二次 pop 应有结果");
assert_eq!(popped2.unwrap().confirm_msg, "second");
assert!(queue.pop_pending().is_none(), "第三次 pop 应为 None");
}
#[test]
fn queue_deny_all_resolves_false() {
let queue = PermissionQueue::new();
let req1 = PendingAgentPerm::new(
AgentType::Teammate,
"Frontend".into(),
"Edit".into(),
"edit css".into(),
);
let req2 = PendingAgentPerm::new(
AgentType::Teammate,
"Backend".into(),
"Bash".into(),
"run build".into(),
);
{
let mut q = queue.pending.lock().unwrap_or_else(|e| e.into_inner());
q.push_back(Arc::clone(&req1));
q.push_back(Arc::clone(&req2));
}
queue.deny_all();
thread::sleep(Duration::from_millis(50));
assert!(!req1.wait_for_decision(0), "deny_all 后 req1 应被拒绝");
assert!(!req2.wait_for_decision(0), "deny_all 后 req2 应被拒绝");
assert!(queue.pop_pending().is_none(), "deny_all 后队列应为空");
}
#[test]
fn queue_default_is_new() {
let queue = PermissionQueue::default();
assert!(queue.pop_pending().is_none(), "default 队列应为空");
}
}