Skip to main content

j_agent/permission/
queue.rs

1/// 派生 Agent 权限请求队列
2///
3/// 当派生 Agent(SubAgent / Teammate)需要执行需要确认的工具(Write、Edit、Bash 等)
4/// 且未被 .jcli/permissions.yaml 预先允许时,把请求推入此队列并阻塞
5/// 等待主 TUI 用户批准或拒绝。
6///
7/// 设计约束:
8/// - 派生 Agent 线程调用 `request_blocking`,阻塞最长 60 秒
9/// - 主 TUI 循环 poll `pop_pending`,展示对话框,用户 y/n 后调用 `resolve`
10/// - session 取消时调用 `deny_all` 唤醒所有阻塞线程
11use std::collections::VecDeque;
12use std::sync::{Arc, Condvar, Mutex};
13use std::time::Duration;
14
15/// 派生 Agent 权限请求的最大等待超时(秒)
16const AGENT_PERM_TIMEOUT_SECS: u64 = 60;
17
18/// 发起权限请求的 agent 类型
19#[derive(Clone, Debug, PartialEq)]
20pub enum AgentType {
21    /// 主 Agent(拥有 TUI,直接与用户交互;当前不会进入权限队列,但作为默认值预留)
22    Main,
23    /// Teammate agent(name 为 teammate 名称,如 "Backend")
24    Teammate,
25    /// SubAgent(name 为 sub_id,如 "sub_0001")
26    SubAgent,
27}
28
29// NOTE: Cannot derive Debug - contains Condvar which does not implement Debug
30/// 单条待决权限请求(共享给 TUI 和 agent 线程)
31pub struct PendingAgentPerm {
32    /// 发起请求的 agent 类型
33    pub agent_type: AgentType,
34    /// 发起请求的 agent 名称(teammate 名 / sub_id)
35    pub name: String,
36    /// 工具名称("Write"/"Edit"/"Shell")
37    pub tool_name: String,
38    /// 工具自身生成的人读确认提示
39    pub confirm_msg: String,
40    /// 决策通知(None=未决, Some(true)=允许, Some(false)=拒绝)
41    decision: Arc<(Mutex<Option<bool>>, Condvar)>,
42}
43
44impl PendingAgentPerm {
45    pub fn new(
46        agent_type: AgentType,
47        name: String,
48        tool_name: String,
49        confirm_msg: String,
50    ) -> Arc<Self> {
51        Arc::new(Self {
52            agent_type,
53            name,
54            tool_name,
55            confirm_msg,
56            decision: Arc::new((Mutex::new(None), Condvar::new())),
57        })
58    }
59
60    /// 权限请求标题:按 agent 类型区分显示
61    pub fn title(&self) -> String {
62        match &self.agent_type {
63            AgentType::Main => " 权限请求 [Main] ".to_string(),
64            AgentType::Teammate => format!(" 权限请求 [{}] ", self.name),
65            AgentType::SubAgent => format!(" SubAgent 权限请求 [{}] ", self.name),
66        }
67    }
68
69    /// 派生 Agent 线程调用:阻塞等待决策,超时返回 false(拒绝)
70    pub fn wait_for_decision(&self, timeout_secs: u64) -> bool {
71        let (lock, cvar) = &*self.decision;
72        let guard = lock.lock().unwrap_or_else(|e| e.into_inner());
73        let (guard, _timed_out) = cvar
74            .wait_timeout_while(guard, Duration::from_secs(timeout_secs), |d| d.is_none())
75            .unwrap_or_else(|e| e.into_inner());
76        guard.unwrap_or(false)
77    }
78
79    /// TUI 线程调用:设置决策并唤醒等待的 agent 线程
80    pub fn resolve(&self, approved: bool) {
81        let (lock, cvar) = &*self.decision;
82        let mut d = lock.lock().unwrap_or_else(|e| e.into_inner());
83        *d = Some(approved);
84        cvar.notify_one();
85    }
86}
87
88/// 权限请求队列(主 TUI 和所有 agent 线程共享同一个 Arc 实例)
89pub struct PermissionQueue {
90    pub(crate) pending: Mutex<VecDeque<Arc<PendingAgentPerm>>>,
91}
92
93impl Default for PermissionQueue {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl PermissionQueue {
100    pub fn new() -> Self {
101        Self {
102            pending: Mutex::new(VecDeque::new()),
103        }
104    }
105
106    /// 派生 Agent 线程调用:把请求加入队列并阻塞等待(最长 [`AGENT_PERM_TIMEOUT_SECS`] 秒)。
107    /// 返回 true 表示用户批准,false 表示拒绝或超时。
108    pub fn request_blocking(&self, req: Arc<PendingAgentPerm>) -> bool {
109        {
110            let mut q = self.pending.lock().unwrap_or_else(|e| e.into_inner());
111            q.push_back(Arc::clone(&req));
112        }
113        req.wait_for_decision(AGENT_PERM_TIMEOUT_SECS)
114    }
115
116    /// TUI 循环调用:取出下一个待决请求(非阻塞)
117    pub fn pop_pending(&self) -> Option<Arc<PendingAgentPerm>> {
118        self.pending
119            .lock()
120            .unwrap_or_else(|e| e.into_inner())
121            .pop_front()
122    }
123
124    /// session 取消时调用:拒绝所有挂起的请求,唤醒所有等待线程
125    pub fn deny_all(&self) {
126        let mut q = self.pending.lock().unwrap_or_else(|e| e.into_inner());
127        for req in q.drain(..) {
128            req.resolve(false);
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests;