返回 DeepSeek-TUI-2026
approval.rs
根目录 / crates / tui / src / core / engine / approval.rs
1 //! Approval + user-input handshake for the agent loop.
2 //!
3 //! Extracted from `core/engine.rs` (P1.3). The agent loop blocks on these
4 //! two futures whenever a tool requires explicit approval (`await_tool_approval`)
5 //! or whenever a tool requests live user input (`await_user_input`). Channels
6 //! and engine state stay private to the parent module.
7
8 use crate::core::events::Event;
9 use crate::tools::spec::ToolError;
10 use crate::tools::user_input::{UserInputRequest, UserInputResponse};
11
12 use super::Engine;
13
14 #[derive(Debug, Clone)]
15 pub(super) enum ApprovalDecision {
16 Approved {
17 id: String,
18 },
19 Denied {
20 id: String,
21 },
22 /// Retry a tool with an elevated sandbox policy.
23 RetryWithPolicy {
24 id: String,
25 policy: crate::sandbox::SandboxPolicy,
26 },
27 }
28
29 #[derive(Debug, Clone)]
30 pub(super) enum UserInputDecision {
31 Submitted {
32 id: String,
33 response: UserInputResponse,
34 },
35 Cancelled {
36 id: String,
37 },
38 }
39
40 /// Result of awaiting tool approval from the user.
41 #[derive(Debug)]
42 pub(super) enum ApprovalResult {
43 /// User approved the tool execution.
44 Approved,
45 /// User denied the tool execution.
46 Denied,
47 /// User requested retry with an elevated sandbox policy.
48 RetryWithPolicy(crate::sandbox::SandboxPolicy),
49 }
50
51 impl Engine {
52 pub(super) async fn await_tool_approval(
53 &mut self,
54 tool_id: &str,
55 ) -> Result<ApprovalResult, ToolError> {
56 loop {
57 tokio::select! {
58 _ = self.cancel_token.cancelled() => {
59 return Err(ToolError::execution_failed(
60 "Request cancelled while awaiting approval".to_string(),
61 ));
62 }
63 decision = self.rx_approval.recv() => {
64 let Some(decision) = decision else {
65 return Err(ToolError::execution_failed(
66 "Approval channel closed".to_string(),
67 ));
68 };
69 match decision {
70 ApprovalDecision::Approved { id } if id == tool_id => {
71 return Ok(ApprovalResult::Approved);
72 }
73 ApprovalDecision::Denied { id } if id == tool_id => {
74 return Ok(ApprovalResult::Denied);
75 }
76 ApprovalDecision::RetryWithPolicy { id, policy } if id == tool_id => {
77 return Ok(ApprovalResult::RetryWithPolicy(policy));
78 }
79 _ => continue,
80 }
81 }
82 }
83 }
84 }
85
86 pub(super) async fn await_user_input(
87 &mut self,
88 tool_id: &str,
89 request: UserInputRequest,
90 ) -> Result<UserInputResponse, ToolError> {
91 let _ = self
92 .tx_event
93 .send(Event::UserInputRequired {
94 id: tool_id.to_string(),
95 request,
96 })
97 .await;
98
99 loop {
100 tokio::select! {
101 _ = self.cancel_token.cancelled() => {
102 return Err(ToolError::execution_failed(
103 "Request cancelled while awaiting user input".to_string(),
104 ));
105 }
106 decision = self.rx_user_input.recv() => {
107 let Some(decision) = decision else {
108 return Err(ToolError::execution_failed(
109 "User input channel closed".to_string(),
110 ));
111 };
112 match decision {
113 UserInputDecision::Submitted { id, response } if id == tool_id => {
114 return Ok(response);
115 }
116 UserInputDecision::Cancelled { id } if id == tool_id => {
117 return Err(ToolError::execution_failed(
118 "User input cancelled".to_string(),
119 ));
120 }
121 _ => continue,
122 }
123 }
124 }
125 }
126 }
127 }
128
128 lines RUST