Skip to main content

ai_agent/
agent.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/utils/model/agent.ts
2use crate::env::EnvConfig;
3use crate::error::AgentError;
4use crate::query_engine::{QueryEngine, QueryEngineConfig};
5use crate::stream::{CancelGuard, EventBroadcasters, EventSubscriber};
6use crate::tools::ask::AskUserQuestionTool;
7use crate::tools::bash::BashTool;
8use crate::tools::brief::BriefTool;
9use crate::tools::synthetic_output::SyntheticOutputTool;
10use crate::tools::config::ConfigTool;
11use crate::tools::cron::{CronCreateTool, CronDeleteTool, CronListTool};
12use crate::tools::edit::FileEditTool;
13use crate::tools::glob::GlobTool;
14use crate::tools::grep::GrepTool;
15use crate::tools::lsp::LSPTool;
16use crate::tools::mcp_resource_reader::ReadMcpResourceTool;
17use crate::tools::mcp_resources::ListMcpResourcesTool;
18use crate::tools::mcp_tool::McpTool;
19use crate::tools::mcp_auth::McpAuthTool;
20use crate::tools::monitor::MonitorTool;
21use crate::tools::notebook_edit::NotebookEditTool;
22use crate::tools::plan::{EnterPlanModeTool, ExitPlanModeTool};
23use crate::tools::read::FileReadTool as ReadTool;
24use crate::tools::remote_trigger::RemoteTriggerTool;
25use crate::tools::search::ToolSearchTool;
26use crate::tools::send_user_file::SendUserFileTool;
27use crate::tools::skill::SkillTool;
28use crate::tools::skill::register_skills_from_dir;
29use crate::skills::loader::load_all_skills;
30use crate::utils::hooks::register_hooks_from_skills;
31use crate::tools::sleep_tool::SleepTool;
32use crate::tools::powershell::powershell_tool::PowerShellTool;
33use crate::tools::task_output::TaskOutputTool;
34use crate::tools::tasks::{TaskCreateTool, TaskGetTool, TaskListTool, TaskUpdateTool};
35use crate::tools::team::{SendMessageTool, TeamCreateTool, TeamDeleteTool};
36use crate::tools::todo::TodoWriteTool;
37use crate::tools::web_browser::WebBrowserTool;
38use crate::tools::web_fetch::WebFetchTool;
39use crate::tools::web_search::WebSearchTool;
40use crate::tools::worktree::{EnterWorktreeTool, ExitWorktreeTool};
41use crate::tools::write::FileWriteTool as WriteTool;
42use crate::permission::{PermissionResult, PermissionAllowDecision, PermissionDenyDecision, PermissionDecisionReason};
43use crate::types::AgentEvent;
44use crate::types::*;
45use crate::types::ToolRender;
46use std::sync::Arc;
47use tokio::sync::mpsc;
48
49// Implement ToolRender trait for each tool type, delegating to the existing methods
50impl ToolRender for BashTool {
51    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
52        <BashTool>::user_facing_name(self, input)
53    }
54    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
55        <BashTool>::get_tool_use_summary(self, input)
56    }
57    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
58        <BashTool>::render_tool_result_message(self, content)
59    }
60}
61impl ToolRender for ReadTool {
62    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
63        <ReadTool>::user_facing_name(self, input)
64    }
65    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
66        <ReadTool>::get_tool_use_summary(self, input)
67    }
68    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
69        <ReadTool>::render_tool_result_message(self, content)
70    }
71}
72impl ToolRender for WriteTool {
73    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
74        <WriteTool>::user_facing_name(self, input)
75    }
76    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
77        <WriteTool>::get_tool_use_summary(self, input)
78    }
79    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
80        <WriteTool>::render_tool_result_message(self, content)
81    }
82}
83impl ToolRender for GlobTool {
84    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
85        <GlobTool>::user_facing_name(self, input)
86    }
87    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
88        <GlobTool>::get_tool_use_summary(self, input)
89    }
90    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
91        <GlobTool>::render_tool_result_message(self, content)
92    }
93}
94impl ToolRender for GrepTool {
95    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
96        <GrepTool>::user_facing_name(self, input)
97    }
98    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
99        <GrepTool>::get_tool_use_summary(self, input)
100    }
101    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
102        <GrepTool>::render_tool_result_message(self, content)
103    }
104}
105impl ToolRender for FileEditTool {
106    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
107        <FileEditTool>::user_facing_name(self, input)
108    }
109    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
110        <FileEditTool>::get_tool_use_summary(self, input)
111    }
112    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
113        <FileEditTool>::render_tool_result_message(self, content)
114    }
115}
116impl ToolRender for SkillTool {
117    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
118        <SkillTool>::user_facing_name(self, input)
119    }
120    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
121        <SkillTool>::get_tool_use_summary(self, input)
122    }
123    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
124        <SkillTool>::render_tool_result_message(self, content)
125    }
126}
127impl ToolRender for MonitorTool {
128    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
129        <MonitorTool>::user_facing_name(self, input)
130    }
131    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
132        <MonitorTool>::get_tool_use_summary(self, input)
133    }
134    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
135        <MonitorTool>::render_tool_result_message(self, content)
136    }
137}
138impl ToolRender for SendUserFileTool {
139    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
140        <SendUserFileTool>::user_facing_name(self, input)
141    }
142    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
143        <SendUserFileTool>::get_tool_use_summary(self, input)
144    }
145    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
146        <SendUserFileTool>::render_tool_result_message(self, content)
147    }
148}
149impl ToolRender for WebBrowserTool {
150    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
151        <WebBrowserTool>::user_facing_name(self, input)
152    }
153    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
154        <WebBrowserTool>::get_tool_use_summary(self, input)
155    }
156    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
157        <WebBrowserTool>::render_tool_result_message(self, content)
158    }
159}
160impl ToolRender for WebFetchTool {
161    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
162        <WebFetchTool>::user_facing_name(self, input)
163    }
164    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
165        <WebFetchTool>::get_tool_use_summary(self, input)
166    }
167    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
168        <WebFetchTool>::render_tool_result_message(self, content)
169    }
170}
171impl ToolRender for WebSearchTool {
172    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
173        <WebSearchTool>::user_facing_name(self, input)
174    }
175    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
176        <WebSearchTool>::get_tool_use_summary(self, input)
177    }
178    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
179        <WebSearchTool>::render_tool_result_message(self, content)
180    }
181}
182impl ToolRender for NotebookEditTool {
183    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
184        <NotebookEditTool>::user_facing_name(self, input)
185    }
186    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
187        <NotebookEditTool>::get_tool_use_summary(self, input)
188    }
189    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
190        <NotebookEditTool>::render_tool_result_message(self, content)
191    }
192}
193impl ToolRender for TaskCreateTool {
194    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
195        <TaskCreateTool>::user_facing_name(self, input)
196    }
197    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
198        <TaskCreateTool>::get_tool_use_summary(self, input)
199    }
200    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
201        <TaskCreateTool>::render_tool_result_message(self, content)
202    }
203}
204impl ToolRender for TaskListTool {
205    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
206        <TaskListTool>::user_facing_name(self, input)
207    }
208    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
209        <TaskListTool>::get_tool_use_summary(self, input)
210    }
211    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
212        <TaskListTool>::render_tool_result_message(self, content)
213    }
214}
215impl ToolRender for TaskUpdateTool {
216    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
217        <TaskUpdateTool>::user_facing_name(self, input)
218    }
219    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
220        <TaskUpdateTool>::get_tool_use_summary(self, input)
221    }
222    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
223        <TaskUpdateTool>::render_tool_result_message(self, content)
224    }
225}
226impl ToolRender for TaskGetTool {
227    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
228        <TaskGetTool>::user_facing_name(self, input)
229    }
230    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
231        <TaskGetTool>::get_tool_use_summary(self, input)
232    }
233    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
234        <TaskGetTool>::render_tool_result_message(self, content)
235    }
236}
237impl ToolRender for TaskOutputTool {
238    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
239        <TaskOutputTool>::user_facing_name(self, input)
240    }
241    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
242        <TaskOutputTool>::get_tool_use_summary(self, input)
243    }
244    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
245        <TaskOutputTool>::render_tool_result_message(self, content)
246    }
247}
248impl ToolRender for TodoWriteTool {
249    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
250        <TodoWriteTool>::user_facing_name(self, input)
251    }
252    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
253        <TodoWriteTool>::get_tool_use_summary(self, input)
254    }
255    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
256        <TodoWriteTool>::render_tool_result_message(self, content)
257    }
258}
259impl ToolRender for CronCreateTool {
260    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
261        <CronCreateTool>::user_facing_name(self, input)
262    }
263    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
264        <CronCreateTool>::get_tool_use_summary(self, input)
265    }
266    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
267        <CronCreateTool>::render_tool_result_message(self, content)
268    }
269}
270impl ToolRender for CronDeleteTool {
271    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
272        <CronDeleteTool>::user_facing_name(self, input)
273    }
274    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
275        <CronDeleteTool>::get_tool_use_summary(self, input)
276    }
277    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
278        <CronDeleteTool>::render_tool_result_message(self, content)
279    }
280}
281impl ToolRender for CronListTool {
282    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
283        <CronListTool>::user_facing_name(self, input)
284    }
285    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
286        <CronListTool>::get_tool_use_summary(self, input)
287    }
288    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
289        <CronListTool>::render_tool_result_message(self, content)
290    }
291}
292impl ToolRender for ConfigTool {
293    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
294        <ConfigTool>::user_facing_name(self, input)
295    }
296    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
297        <ConfigTool>::get_tool_use_summary(self, input)
298    }
299    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
300        <ConfigTool>::render_tool_result_message(self, content)
301    }
302}
303impl ToolRender for EnterWorktreeTool {
304    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
305        <EnterWorktreeTool>::user_facing_name(self, input)
306    }
307    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
308        <EnterWorktreeTool>::get_tool_use_summary(self, input)
309    }
310    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
311        <EnterWorktreeTool>::render_tool_result_message(self, content)
312    }
313}
314impl ToolRender for ExitWorktreeTool {
315    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
316        <ExitWorktreeTool>::user_facing_name(self, input)
317    }
318    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
319        <ExitWorktreeTool>::get_tool_use_summary(self, input)
320    }
321    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
322        <ExitWorktreeTool>::render_tool_result_message(self, content)
323    }
324}
325impl ToolRender for EnterPlanModeTool {
326    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
327        <EnterPlanModeTool>::user_facing_name(self, input)
328    }
329    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
330        <EnterPlanModeTool>::get_tool_use_summary(self, input)
331    }
332    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
333        <EnterPlanModeTool>::render_tool_result_message(self, content)
334    }
335}
336impl ToolRender for ExitPlanModeTool {
337    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
338        <ExitPlanModeTool>::user_facing_name(self, input)
339    }
340    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
341        <ExitPlanModeTool>::get_tool_use_summary(self, input)
342    }
343    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
344        <ExitPlanModeTool>::render_tool_result_message(self, content)
345    }
346}
347impl ToolRender for AskUserQuestionTool {
348    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
349        <AskUserQuestionTool>::user_facing_name(self, input)
350    }
351    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
352        <AskUserQuestionTool>::get_tool_use_summary(self, input)
353    }
354    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
355        <AskUserQuestionTool>::render_tool_result_message(self, content)
356    }
357}
358impl ToolRender for ToolSearchTool {
359    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
360        <ToolSearchTool>::user_facing_name(self, input)
361    }
362    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
363        <ToolSearchTool>::get_tool_use_summary(self, input)
364    }
365    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
366        <ToolSearchTool>::render_tool_result_message(self, content)
367    }
368}
369impl ToolRender for TeamCreateTool {
370    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
371        <TeamCreateTool>::user_facing_name(self, input)
372    }
373    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
374        <TeamCreateTool>::get_tool_use_summary(self, input)
375    }
376    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
377        <TeamCreateTool>::render_tool_result_message(self, content)
378    }
379}
380impl ToolRender for TeamDeleteTool {
381    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
382        <TeamDeleteTool>::user_facing_name(self, input)
383    }
384    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
385        <TeamDeleteTool>::get_tool_use_summary(self, input)
386    }
387    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
388        <TeamDeleteTool>::render_tool_result_message(self, content)
389    }
390}
391impl ToolRender for SendMessageTool {
392    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
393        <SendMessageTool>::user_facing_name(self, input)
394    }
395    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
396        <SendMessageTool>::get_tool_use_summary(self, input)
397    }
398    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
399        <SendMessageTool>::render_tool_result_message(self, content)
400    }
401}
402impl ToolRender for SleepTool {
403    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
404        <SleepTool>::user_facing_name(self, input)
405    }
406    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
407        <SleepTool>::get_tool_use_summary(self, input)
408    }
409    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
410        <SleepTool>::render_tool_result_message(self, content)
411    }
412}
413impl ToolRender for PowerShellTool {
414    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
415        <PowerShellTool>::user_facing_name(self, input)
416    }
417    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
418        <PowerShellTool>::get_tool_use_summary(self, input)
419    }
420    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
421        <PowerShellTool>::render_tool_result_message(self, content)
422    }
423}
424impl ToolRender for LSPTool {
425    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
426        <LSPTool>::user_facing_name(self, input)
427    }
428    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
429        <LSPTool>::get_tool_use_summary(self, input)
430    }
431    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
432        <LSPTool>::render_tool_result_message(self, content)
433    }
434}
435impl ToolRender for RemoteTriggerTool {
436    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
437        <RemoteTriggerTool>::user_facing_name(self, input)
438    }
439    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
440        <RemoteTriggerTool>::get_tool_use_summary(self, input)
441    }
442    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
443        <RemoteTriggerTool>::render_tool_result_message(self, content)
444    }
445}
446impl ToolRender for ListMcpResourcesTool {
447    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
448        <ListMcpResourcesTool>::user_facing_name(self, input)
449    }
450    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
451        <ListMcpResourcesTool>::get_tool_use_summary(self, input)
452    }
453    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
454        <ListMcpResourcesTool>::render_tool_result_message(self, content)
455    }
456}
457impl ToolRender for ReadMcpResourceTool {
458    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
459        <ReadMcpResourceTool>::user_facing_name(self, input)
460    }
461    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
462        <ReadMcpResourceTool>::get_tool_use_summary(self, input)
463    }
464    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
465        <ReadMcpResourceTool>::render_tool_result_message(self, content)
466    }
467}
468impl ToolRender for McpTool {
469    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
470        <McpTool>::user_facing_name(self, input)
471    }
472    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
473        <McpTool>::get_tool_use_summary(self, input)
474    }
475    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
476        <McpTool>::render_tool_result_message(self, content)
477    }
478}
479impl ToolRender for McpAuthTool {
480    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
481        <McpAuthTool>::user_facing_name(self, input)
482    }
483    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
484        <McpAuthTool>::get_tool_use_summary(self, input)
485    }
486    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
487        <McpAuthTool>::render_tool_result_message(self, content)
488    }
489}
490impl ToolRender for BriefTool {
491    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
492        <BriefTool>::user_facing_name(self, input)
493    }
494    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
495        <BriefTool>::get_tool_use_summary(self, input)
496    }
497    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
498        <BriefTool>::render_tool_result_message(self, content)
499    }
500}
501impl ToolRender for SyntheticOutputTool {
502    fn user_facing_name(&self, input: Option<&serde_json::Value>) -> String {
503        <SyntheticOutputTool>::user_facing_name(self, input)
504    }
505    fn get_tool_use_summary(&self, input: Option<&serde_json::Value>) -> Option<String> {
506        <SyntheticOutputTool>::get_tool_use_summary(self, input)
507    }
508    fn render_tool_result_message(&self, content: &serde_json::Value) -> Option<String> {
509        <SyntheticOutputTool>::render_tool_result_message(self, content)
510    }
511}
512
513/// Construct ToolRenderFns from any ToolRender implementor.
514/// The tool instance is wrapped in Arc so the render closures can share ownership.
515fn make_render_fns<T: ToolRender + 'static>(tool: T) -> crate::query_engine::ToolRenderFns {
516    let tool = Arc::new(tool);
517    let t2 = Arc::clone(&tool);
518    let t3 = Arc::clone(&tool);
519    crate::query_engine::ToolRenderFns {
520        user_facing_name: Arc::new(move |input| tool.user_facing_name(input)),
521        get_tool_use_summary: Some(Arc::new(move |input| t2.get_tool_use_summary(input))),
522        get_activity_description: None,
523        render_tool_result_message: Some(Arc::new(
524            move |content, _progress, _options| t3.render_tool_result_message(content),
525        )),
526    }
527}
528
529/// Register all built-in tool executors
530pub(crate) fn register_all_tool_executors(engine: &mut QueryEngine) {
531    type BoxFuture<T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send>>;
532
533    // Bash tool - clone tool and ctx into async block
534    let bash_executor = move |input: serde_json::Value,
535                              ctx: &ToolContext|
536          -> BoxFuture<Result<ToolResult, AgentError>> {
537        let tool_clone = BashTool::new();
538        let cwd = ctx.cwd.clone();
539        let abort_signal = ctx.abort_signal.clone();
540        Box::pin(async move {
541            let ctx2 = ToolContext {
542                cwd,
543                abort_signal: abort_signal.clone(),
544            };
545            tool_clone.execute(input, &ctx2).await
546        })
547    };
548    let bash_tool = BashTool::new();
549    let bash_rf = self::make_render_fns(bash_tool);
550    engine.register_tool_with_render("Bash".to_string(), bash_executor, bash_rf);
551
552    // Read tool - register backfill function (expand file_path for observers)
553    engine.register_tool_backfill(
554        "Read".to_string(),
555        |input: &mut serde_json::Value| {
556            if let Some(fp) = input.get("file_path").and_then(|v| v.as_str()) {
557                let expanded = crate::utils::path::expand_path(fp);
558                if let Some(obj) = input.as_object_mut() {
559                    obj.insert("file_path".to_string(), serde_json::json!(expanded));
560                }
561            }
562        },
563    );
564
565    // FileRead tool
566    let read_executor = move |input: serde_json::Value,
567                              ctx: &ToolContext|
568          -> BoxFuture<Result<ToolResult, AgentError>> {
569        let tool_clone = ReadTool::new();
570        let cwd = ctx.cwd.clone();
571        let abort_signal = ctx.abort_signal.clone();
572        Box::pin(async move {
573            let ctx2 = ToolContext {
574                cwd,
575                abort_signal: abort_signal.clone(),
576            };
577            tool_clone.execute(input, &ctx2).await
578        })
579    };
580    let read_tool = ReadTool::new();
581    let read_rf = self::make_render_fns(read_tool);
582    engine.register_tool_with_render("Read".to_string(), read_executor, read_rf);
583
584    // FileWrite tool
585    let write_executor = move |input: serde_json::Value,
586                               ctx: &ToolContext|
587          -> BoxFuture<Result<ToolResult, AgentError>> {
588        let tool_clone = WriteTool::new();
589        let cwd = ctx.cwd.clone();
590        let abort_signal = ctx.abort_signal.clone();
591        Box::pin(async move {
592            let ctx2 = ToolContext {
593                cwd,
594                abort_signal: abort_signal.clone(),
595            };
596            tool_clone.execute(input, &ctx2).await
597        })
598    };
599    let write_tool = WriteTool::new();
600    let write_rf = self::make_render_fns(write_tool);
601    engine.register_tool_with_render("Write".to_string(), write_executor, write_rf);
602    // Write tool - register backfill function (expand file_path for observers)
603    engine.register_tool_backfill(
604        "Write".to_string(),
605        |input: &mut serde_json::Value| {
606            if let Some(fp) = input.get("file_path").and_then(|v| v.as_str()) {
607                let expanded = crate::utils::path::expand_path(fp);
608                if let Some(obj) = input.as_object_mut() {
609                    obj.insert("file_path".to_string(), serde_json::json!(expanded));
610                }
611            }
612        },
613    );
614
615    // Glob tool
616    let glob_executor = move |input: serde_json::Value,
617                              ctx: &ToolContext|
618          -> BoxFuture<Result<ToolResult, AgentError>> {
619        let tool_clone = GlobTool::new();
620        let cwd = ctx.cwd.clone();
621        let abort_signal = ctx.abort_signal.clone();
622        Box::pin(async move {
623            let ctx2 = ToolContext {
624                cwd,
625                abort_signal: abort_signal.clone(),
626            };
627            tool_clone.execute(input, &ctx2).await
628        })
629    };
630    let glob_tool = GlobTool::new();
631    let glob_rf = self::make_render_fns(glob_tool);
632    engine.register_tool_with_render("Glob".to_string(), glob_executor, glob_rf);
633
634    // Grep tool
635    let grep_executor = move |input: serde_json::Value,
636                              ctx: &ToolContext|
637          -> BoxFuture<Result<ToolResult, AgentError>> {
638        let tool_clone = GrepTool::new();
639        let cwd = ctx.cwd.clone();
640        let abort_signal = ctx.abort_signal.clone();
641        Box::pin(async move {
642            let ctx2 = ToolContext {
643                cwd,
644                abort_signal: abort_signal.clone(),
645            };
646            tool_clone.execute(input, &ctx2).await
647        })
648    };
649    let grep_tool = GrepTool::new();
650    let grep_rf = self::make_render_fns(grep_tool);
651    engine.register_tool_with_render("Grep".to_string(), grep_executor, grep_rf);
652
653    // FileEdit tool - with rendering metadata for TUI display
654    let edit_executor = move |input: serde_json::Value,
655                              ctx: &ToolContext|
656          -> BoxFuture<Result<ToolResult, AgentError>> {
657        let tool_clone = FileEditTool::new();
658        let cwd = ctx.cwd.clone();
659        let abort_signal = ctx.abort_signal.clone();
660        Box::pin(async move {
661            let ctx2 = ToolContext {
662                cwd,
663                abort_signal: abort_signal.clone(),
664            };
665            tool_clone.execute(input, &ctx2).await
666        })
667    };
668    let edit_rf = make_render_fns(FileEditTool::new());
669    engine.register_tool_with_render("FileEdit".to_string(), edit_executor, edit_rf);
670    // FileEdit tool - register backfill function (expand file_path for observers)
671    engine.register_tool_backfill(
672        "FileEdit".to_string(),
673        |input: &mut serde_json::Value| {
674            if let Some(fp) = input.get("file_path").and_then(|v| v.as_str()) {
675                let expanded = crate::utils::path::expand_path(fp);
676                if let Some(obj) = input.as_object_mut() {
677                    obj.insert("file_path".to_string(), serde_json::json!(expanded));
678                }
679            }
680        },
681    );
682
683    // Skill tool - register skills from examples/skills directory
684    use std::path::Path;
685    register_skills_from_dir(Path::new("examples/skills"));
686
687    let skill_executor = move |input: serde_json::Value,
688                               ctx: &ToolContext|
689          -> BoxFuture<Result<ToolResult, AgentError>> {
690        let tool_clone = SkillTool::new();
691        let cwd = ctx.cwd.clone();
692        let abort_signal = ctx.abort_signal.clone();
693        Box::pin(async move {
694            let ctx2 = ToolContext {
695                cwd,
696                abort_signal: abort_signal.clone(),
697            };
698            tool_clone.execute(input, &ctx2).await
699        })
700    };
701    let skill_tool = SkillTool::new();
702    let skill_rf = self::make_render_fns(skill_tool);
703    engine.register_tool_with_render("Skill".to_string(), skill_executor, skill_rf);
704
705    // Monitor tool
706    let monitor_executor = move |input: serde_json::Value,
707                                 ctx: &ToolContext|
708          -> BoxFuture<Result<ToolResult, AgentError>> {
709        let tool_clone = MonitorTool::new();
710        let cwd = ctx.cwd.clone();
711        let abort_signal = ctx.abort_signal.clone();
712        Box::pin(async move {
713            let ctx2 = ToolContext {
714                cwd,
715                abort_signal: abort_signal.clone(),
716            };
717            tool_clone.execute(input, &ctx2).await
718        })
719    };
720    let monitor_tool = MonitorTool::new();
721    let monitor_rf = self::make_render_fns(monitor_tool);
722    engine.register_tool_with_render("Monitor".to_string(), monitor_executor, monitor_rf);
723
724    // SendUserFile tool
725    let send_user_file_executor = move |input: serde_json::Value,
726                                        ctx: &ToolContext|
727          -> BoxFuture<Result<ToolResult, AgentError>> {
728        let tool_clone = SendUserFileTool::new();
729        let cwd = ctx.cwd.clone();
730        let abort_signal = ctx.abort_signal.clone();
731        Box::pin(async move {
732            let ctx2 = ToolContext {
733                cwd,
734                abort_signal: abort_signal.clone(),
735            };
736            tool_clone.execute(input, &ctx2).await
737        })
738    };
739    let send_user_file_tool = SendUserFileTool::new();
740    let send_user_file_rf = self::make_render_fns(send_user_file_tool);
741    engine.register_tool_with_render("send_user_file".to_string(), send_user_file_executor, send_user_file_rf);
742
743    // WebBrowser tool
744    let web_browser_executor = move |input: serde_json::Value,
745                                     ctx: &ToolContext|
746          -> BoxFuture<Result<ToolResult, AgentError>> {
747        let tool_clone = WebBrowserTool::new();
748        let cwd = ctx.cwd.clone();
749        let abort_signal = ctx.abort_signal.clone();
750        Box::pin(async move {
751            let ctx2 = ToolContext {
752                cwd,
753                abort_signal: abort_signal.clone(),
754            };
755            tool_clone.execute(input, &ctx2).await
756        })
757    };
758    let web_browser_tool = WebBrowserTool::new();
759    let web_browser_rf = self::make_render_fns(web_browser_tool);
760    engine.register_tool_with_render("WebBrowser".to_string(), web_browser_executor, web_browser_rf);
761
762    // WebFetch tool
763    let web_fetch_executor = move |input: serde_json::Value,
764                                   ctx: &ToolContext|
765          -> BoxFuture<Result<ToolResult, AgentError>> {
766        let tool_clone = WebFetchTool::new();
767        let cwd = ctx.cwd.clone();
768        let abort_signal = ctx.abort_signal.clone();
769        Box::pin(async move {
770            let ctx2 = ToolContext {
771                cwd,
772                abort_signal: abort_signal.clone(),
773            };
774            tool_clone.execute(input, &ctx2).await
775        })
776    };
777    let web_fetch_tool = WebFetchTool::new();
778    let web_fetch_rf = self::make_render_fns(web_fetch_tool);
779    engine.register_tool_with_render("WebFetch".to_string(), web_fetch_executor, web_fetch_rf);
780
781    // WebSearch tool
782    let web_search_executor = move |input: serde_json::Value,
783                                    ctx: &ToolContext|
784          -> BoxFuture<Result<ToolResult, AgentError>> {
785        let tool_clone = WebSearchTool::new();
786        let cwd = ctx.cwd.clone();
787        let abort_signal = ctx.abort_signal.clone();
788        Box::pin(async move {
789            let ctx2 = ToolContext {
790                cwd,
791                abort_signal: abort_signal.clone(),
792            };
793            tool_clone.execute(input, &ctx2).await
794        })
795    };
796    let web_search_tool = WebSearchTool::new();
797    let web_search_rf = self::make_render_fns(web_search_tool);
798    engine.register_tool_with_render("WebSearch".to_string(), web_search_executor, web_search_rf);
799
800    // NotebookEdit tool
801    let notebook_edit_executor = move |input: serde_json::Value,
802                                       ctx: &ToolContext|
803          -> BoxFuture<Result<ToolResult, AgentError>> {
804        let tool_clone = NotebookEditTool::new();
805        let cwd = ctx.cwd.clone();
806        let abort_signal = ctx.abort_signal.clone();
807        Box::pin(async move {
808            let ctx2 = ToolContext {
809                cwd,
810                abort_signal: abort_signal.clone(),
811            };
812            tool_clone.execute(input, &ctx2).await
813        })
814    };
815    let notebook_edit_tool = NotebookEditTool::new();
816    let notebook_edit_rf = self::make_render_fns(notebook_edit_tool);
817    engine.register_tool_with_render("NotebookEdit".to_string(), notebook_edit_executor, notebook_edit_rf);
818
819    // TaskCreate tool
820    let task_create_executor = move |input: serde_json::Value,
821                                     ctx: &ToolContext|
822          -> BoxFuture<Result<ToolResult, AgentError>> {
823        let tool_clone = TaskCreateTool::new();
824        let cwd = ctx.cwd.clone();
825        let abort_signal = ctx.abort_signal.clone();
826        Box::pin(async move {
827            let ctx2 = ToolContext {
828                cwd,
829                abort_signal: abort_signal.clone(),
830            };
831            tool_clone.execute(input, &ctx2).await
832        })
833    };
834    let task_create_tool = TaskCreateTool::new();
835    let task_create_rf = self::make_render_fns(task_create_tool);
836    engine.register_tool_with_render("TaskCreate".to_string(), task_create_executor, task_create_rf);
837
838    // TaskList tool
839    let task_list_executor = move |input: serde_json::Value,
840                                   ctx: &ToolContext|
841          -> BoxFuture<Result<ToolResult, AgentError>> {
842        let tool_clone = TaskListTool::new();
843        let cwd = ctx.cwd.clone();
844        let abort_signal = ctx.abort_signal.clone();
845        Box::pin(async move {
846            let ctx2 = ToolContext {
847                cwd,
848                abort_signal: abort_signal.clone(),
849            };
850            tool_clone.execute(input, &ctx2).await
851        })
852    };
853    let task_list_tool = TaskListTool::new();
854    let task_list_rf = self::make_render_fns(task_list_tool);
855    engine.register_tool_with_render("TaskList".to_string(), task_list_executor, task_list_rf);
856
857    // TaskUpdate tool
858    let task_update_executor = move |input: serde_json::Value,
859                                     ctx: &ToolContext|
860          -> BoxFuture<Result<ToolResult, AgentError>> {
861        let tool_clone = TaskUpdateTool::new();
862        let cwd = ctx.cwd.clone();
863        let abort_signal = ctx.abort_signal.clone();
864        Box::pin(async move {
865            let ctx2 = ToolContext {
866                cwd,
867                abort_signal: abort_signal.clone(),
868            };
869            tool_clone.execute(input, &ctx2).await
870        })
871    };
872    let task_update_tool = TaskUpdateTool::new();
873    let task_update_rf = self::make_render_fns(task_update_tool);
874    engine.register_tool_with_render("TaskUpdate".to_string(), task_update_executor, task_update_rf);
875
876    // TaskGet tool
877    let task_get_executor = move |input: serde_json::Value,
878                                  ctx: &ToolContext|
879          -> BoxFuture<Result<ToolResult, AgentError>> {
880        let tool_clone = TaskGetTool::new();
881        let cwd = ctx.cwd.clone();
882        let abort_signal = ctx.abort_signal.clone();
883        Box::pin(async move {
884            let ctx2 = ToolContext {
885                cwd,
886                abort_signal: abort_signal.clone(),
887            };
888            tool_clone.execute(input, &ctx2).await
889        })
890    };
891    let task_get_tool = TaskGetTool::new();
892    let task_get_rf = self::make_render_fns(task_get_tool);
893    engine.register_tool_with_render("TaskGet".to_string(), task_get_executor, task_get_rf);
894
895    // TaskOutput tool
896    let task_output_executor = move |input: serde_json::Value,
897                                     ctx: &ToolContext|
898          -> BoxFuture<Result<ToolResult, AgentError>> {
899        let tool_clone = TaskOutputTool::new();
900        let cwd = ctx.cwd.clone();
901        let abort_signal = ctx.abort_signal.clone();
902        Box::pin(async move {
903            let ctx2 = ToolContext {
904                cwd,
905                abort_signal: abort_signal.clone(),
906            };
907            tool_clone.execute(input, &ctx2).await
908        })
909    };
910    let task_output_tool = TaskOutputTool::new();
911    let task_output_rf = self::make_render_fns(task_output_tool);
912    engine.register_tool_with_render("TaskOutput".to_string(), task_output_executor, task_output_rf);
913
914    // TodoWrite tool
915    let todo_write_executor = move |input: serde_json::Value,
916                                    ctx: &ToolContext|
917          -> BoxFuture<Result<ToolResult, AgentError>> {
918        let tool_clone = TodoWriteTool::new();
919        let cwd = ctx.cwd.clone();
920        let abort_signal = ctx.abort_signal.clone();
921        Box::pin(async move {
922            let ctx2 = ToolContext {
923                cwd,
924                abort_signal: abort_signal.clone(),
925            };
926            tool_clone.execute(input, &ctx2).await
927        })
928    };
929    let todo_write_tool = TodoWriteTool::new();
930    let todo_write_rf = self::make_render_fns(todo_write_tool);
931    engine.register_tool_with_render("TodoWrite".to_string(), todo_write_executor, todo_write_rf);
932
933    // CronCreate tool
934    let cron_create_executor = move |input: serde_json::Value,
935                                     ctx: &ToolContext|
936          -> BoxFuture<Result<ToolResult, AgentError>> {
937        let tool_clone = CronCreateTool::new();
938        let cwd = ctx.cwd.clone();
939        let abort_signal = ctx.abort_signal.clone();
940        Box::pin(async move {
941            let ctx2 = ToolContext {
942                cwd,
943                abort_signal: abort_signal.clone(),
944            };
945            tool_clone.execute(input, &ctx2).await
946        })
947    };
948    let cron_create_tool = CronCreateTool::new();
949    let cron_create_rf = self::make_render_fns(cron_create_tool);
950    engine.register_tool_with_render("CronCreate".to_string(), cron_create_executor, cron_create_rf);
951
952    // CronDelete tool
953    let cron_delete_executor = move |input: serde_json::Value,
954                                     ctx: &ToolContext|
955          -> BoxFuture<Result<ToolResult, AgentError>> {
956        let tool_clone = CronDeleteTool::new();
957        let cwd = ctx.cwd.clone();
958        let abort_signal = ctx.abort_signal.clone();
959        Box::pin(async move {
960            let ctx2 = ToolContext {
961                cwd,
962                abort_signal: abort_signal.clone(),
963            };
964            tool_clone.execute(input, &ctx2).await
965        })
966    };
967    let cron_delete_tool = CronDeleteTool::new();
968    let cron_delete_rf = self::make_render_fns(cron_delete_tool);
969    engine.register_tool_with_render("CronDelete".to_string(), cron_delete_executor, cron_delete_rf);
970
971    // CronList tool
972    let cron_list_executor = move |input: serde_json::Value,
973                                   ctx: &ToolContext|
974          -> BoxFuture<Result<ToolResult, AgentError>> {
975        let tool_clone = CronListTool::new();
976        let cwd = ctx.cwd.clone();
977        let abort_signal = ctx.abort_signal.clone();
978        Box::pin(async move {
979            let ctx2 = ToolContext {
980                cwd,
981                abort_signal: abort_signal.clone(),
982            };
983            tool_clone.execute(input, &ctx2).await
984        })
985    };
986    let cron_list_tool = CronListTool::new();
987    let cron_list_rf = self::make_render_fns(cron_list_tool);
988    engine.register_tool_with_render("CronList".to_string(), cron_list_executor, cron_list_rf);
989
990    // Config tool
991    let config_executor = move |input: serde_json::Value,
992                                ctx: &ToolContext|
993          -> BoxFuture<Result<ToolResult, AgentError>> {
994        let tool_clone = ConfigTool::new();
995        let cwd = ctx.cwd.clone();
996        let abort_signal = ctx.abort_signal.clone();
997        Box::pin(async move {
998            let ctx2 = ToolContext {
999                cwd,
1000                abort_signal: abort_signal.clone(),
1001            };
1002            tool_clone.execute(input, &ctx2).await
1003        })
1004    };
1005    let config_tool = ConfigTool::new();
1006    let config_rf = self::make_render_fns(config_tool);
1007    engine.register_tool_with_render("Config".to_string(), config_executor, config_rf);
1008
1009    // EnterWorktree tool
1010    let enter_worktree_executor = move |input: serde_json::Value,
1011                                        ctx: &ToolContext|
1012          -> BoxFuture<Result<ToolResult, AgentError>> {
1013        let tool_clone = EnterWorktreeTool::new();
1014        let cwd = ctx.cwd.clone();
1015        let abort_signal = ctx.abort_signal.clone();
1016        Box::pin(async move {
1017            let ctx2 = ToolContext {
1018                cwd,
1019                abort_signal: abort_signal.clone(),
1020            };
1021            tool_clone.execute(input, &ctx2).await
1022        })
1023    };
1024    let enter_worktree_tool = EnterWorktreeTool::new();
1025    let enter_worktree_rf = self::make_render_fns(enter_worktree_tool);
1026    engine.register_tool_with_render("EnterWorktree".to_string(), enter_worktree_executor, enter_worktree_rf);
1027
1028    // ExitWorktree tool
1029    let exit_worktree_executor = move |input: serde_json::Value,
1030                                       ctx: &ToolContext|
1031          -> BoxFuture<Result<ToolResult, AgentError>> {
1032        let tool_clone = ExitWorktreeTool::new();
1033        let cwd = ctx.cwd.clone();
1034        let abort_signal = ctx.abort_signal.clone();
1035        Box::pin(async move {
1036            let ctx2 = ToolContext {
1037                cwd,
1038                abort_signal: abort_signal.clone(),
1039            };
1040            tool_clone.execute(input, &ctx2).await
1041        })
1042    };
1043    let exit_worktree_tool = ExitWorktreeTool::new();
1044    let exit_worktree_rf = self::make_render_fns(exit_worktree_tool);
1045    engine.register_tool_with_render("ExitWorktree".to_string(), exit_worktree_executor, exit_worktree_rf);
1046
1047    // EnterPlanMode tool
1048    let enter_plan_mode_executor = move |input: serde_json::Value,
1049                                         ctx: &ToolContext|
1050          -> BoxFuture<Result<ToolResult, AgentError>> {
1051        let tool_clone = EnterPlanModeTool::new();
1052        let cwd = ctx.cwd.clone();
1053        let abort_signal = ctx.abort_signal.clone();
1054        Box::pin(async move {
1055            let ctx2 = ToolContext {
1056                cwd,
1057                abort_signal: abort_signal.clone(),
1058            };
1059            tool_clone.execute(input, &ctx2).await
1060        })
1061    };
1062    let enter_plan_mode_tool = EnterPlanModeTool::new();
1063    let enter_plan_mode_rf = self::make_render_fns(enter_plan_mode_tool);
1064    engine.register_tool_with_render("EnterPlanMode".to_string(), enter_plan_mode_executor, enter_plan_mode_rf);
1065
1066    // ExitPlanMode tool
1067    let exit_plan_mode_executor = move |input: serde_json::Value,
1068                                        ctx: &ToolContext|
1069          -> BoxFuture<Result<ToolResult, AgentError>> {
1070        let tool_clone = ExitPlanModeTool::new();
1071        let cwd = ctx.cwd.clone();
1072        let abort_signal = ctx.abort_signal.clone();
1073        Box::pin(async move {
1074            let ctx2 = ToolContext {
1075                cwd,
1076                abort_signal: abort_signal.clone(),
1077            };
1078            tool_clone.execute(input, &ctx2).await
1079        })
1080    };
1081    let exit_plan_mode_tool = ExitPlanModeTool::new();
1082    let exit_plan_mode_rf = self::make_render_fns(exit_plan_mode_tool);
1083    engine.register_tool_with_render("ExitPlanMode".to_string(), exit_plan_mode_executor, exit_plan_mode_rf);
1084
1085    // AskUserQuestion tool
1086    let ask_user_question_executor = move |input: serde_json::Value,
1087                                           ctx: &ToolContext|
1088          -> BoxFuture<Result<ToolResult, AgentError>> {
1089        let tool_clone = AskUserQuestionTool::new();
1090        let cwd = ctx.cwd.clone();
1091        let abort_signal = ctx.abort_signal.clone();
1092        Box::pin(async move {
1093            let ctx2 = ToolContext {
1094                cwd,
1095                abort_signal: abort_signal.clone(),
1096            };
1097            tool_clone.execute(input, &ctx2).await
1098        })
1099    };
1100    let ask_user_question_tool = AskUserQuestionTool::new();
1101    let ask_user_question_rf = self::make_render_fns(ask_user_question_tool);
1102    engine.register_tool_with_render("AskUserQuestion".to_string(), ask_user_question_executor, ask_user_question_rf);
1103
1104    // ToolSearch tool
1105    let tool_search_executor = move |input: serde_json::Value,
1106                                     ctx: &ToolContext|
1107          -> BoxFuture<Result<ToolResult, AgentError>> {
1108        let tool_clone = ToolSearchTool::new();
1109        let cwd = ctx.cwd.clone();
1110        let abort_signal = ctx.abort_signal.clone();
1111        Box::pin(async move {
1112            let ctx2 = ToolContext {
1113                cwd,
1114                abort_signal: abort_signal.clone(),
1115            };
1116            tool_clone.execute(input, &ctx2).await
1117        })
1118    };
1119    let tool_search_tool = ToolSearchTool::new();
1120    let tool_search_rf = self::make_render_fns(tool_search_tool);
1121    engine.register_tool_with_render("ToolSearch".to_string(), tool_search_executor, tool_search_rf);
1122
1123    // TeamCreate tool
1124    let team_create_executor = move |input: serde_json::Value,
1125                                     ctx: &ToolContext|
1126          -> BoxFuture<Result<ToolResult, AgentError>> {
1127        let tool_clone = TeamCreateTool::new();
1128        let cwd = ctx.cwd.clone();
1129        let abort_signal = ctx.abort_signal.clone();
1130        Box::pin(async move {
1131            let ctx2 = ToolContext {
1132                cwd,
1133                abort_signal: abort_signal.clone(),
1134            };
1135            tool_clone.execute(input, &ctx2).await
1136        })
1137    };
1138    let team_create_tool = TeamCreateTool::new();
1139    let team_create_rf = self::make_render_fns(team_create_tool);
1140    engine.register_tool_with_render("TeamCreate".to_string(), team_create_executor, team_create_rf);
1141
1142    // TeamDelete tool
1143    let team_delete_executor = move |input: serde_json::Value,
1144                                     ctx: &ToolContext|
1145          -> BoxFuture<Result<ToolResult, AgentError>> {
1146        let tool_clone = TeamDeleteTool::new();
1147        let cwd = ctx.cwd.clone();
1148        let abort_signal = ctx.abort_signal.clone();
1149        Box::pin(async move {
1150            let ctx2 = ToolContext {
1151                cwd,
1152                abort_signal: abort_signal.clone(),
1153            };
1154            tool_clone.execute(input, &ctx2).await
1155        })
1156    };
1157    let team_delete_tool = TeamDeleteTool::new();
1158    let team_delete_rf = self::make_render_fns(team_delete_tool);
1159    engine.register_tool_with_render("TeamDelete".to_string(), team_delete_executor, team_delete_rf);
1160
1161    // SendMessage tool
1162    let send_message_executor = move |input: serde_json::Value,
1163                                      ctx: &ToolContext|
1164          -> BoxFuture<Result<ToolResult, AgentError>> {
1165        let tool_clone = SendMessageTool::new();
1166        let cwd = ctx.cwd.clone();
1167        let abort_signal = ctx.abort_signal.clone();
1168        Box::pin(async move {
1169            let ctx2 = ToolContext {
1170                cwd,
1171                abort_signal: abort_signal.clone(),
1172            };
1173            tool_clone.execute(input, &ctx2).await
1174        })
1175    };
1176    let send_message_tool = SendMessageTool::new();
1177    let send_message_rf = self::make_render_fns(send_message_tool);
1178    engine.register_tool_with_render("SendMessage".to_string(), send_message_executor, send_message_rf);
1179
1180    // Sleep tool - wait for a duration without holding a shell process
1181    let sleep_executor = move |input: serde_json::Value,
1182                               ctx: &ToolContext|
1183          -> BoxFuture<Result<ToolResult, AgentError>> {
1184        let tool_clone = SleepTool::new();
1185        let cwd = ctx.cwd.clone();
1186        let abort_signal = ctx.abort_signal.clone();
1187        Box::pin(async move {
1188            let ctx2 = ToolContext {
1189                cwd,
1190                abort_signal: abort_signal.clone(),
1191            };
1192            tool_clone.execute(input, &ctx2).await
1193        })
1194    };
1195    let sleep_tool = SleepTool::new();
1196    let sleep_rf = self::make_render_fns(sleep_tool);
1197    engine.register_tool_with_render("Sleep".to_string(), sleep_executor, sleep_rf);
1198
1199    // PowerShell tool - execute PowerShell commands
1200    let powershell_executor = move |input: serde_json::Value,
1201                                    ctx: &ToolContext|
1202          -> BoxFuture<Result<ToolResult, AgentError>> {
1203        let tool_clone = PowerShellTool::new();
1204        let cwd = ctx.cwd.clone();
1205        let abort_signal = ctx.abort_signal.clone();
1206        Box::pin(async move {
1207            let ctx2 = ToolContext {
1208                cwd,
1209                abort_signal: abort_signal.clone(),
1210            };
1211            tool_clone.execute(input, &ctx2).await
1212        })
1213    };
1214    let powershell_tool = PowerShellTool::new();
1215    let powershell_rf = self::make_render_fns(powershell_tool);
1216    engine.register_tool_with_render("PowerShell".to_string(), powershell_executor, powershell_rf);
1217
1218    // LSP tool - code intelligence via Language Server Protocol
1219    let lsp_executor = move |input: serde_json::Value,
1220                             ctx: &ToolContext|
1221          -> BoxFuture<Result<ToolResult, AgentError>> {
1222        let tool_clone = LSPTool::new();
1223        let cwd = ctx.cwd.clone();
1224        let abort_signal = ctx.abort_signal.clone();
1225        Box::pin(async move {
1226            let ctx2 = ToolContext {
1227                cwd,
1228                abort_signal: abort_signal.clone(),
1229            };
1230            tool_clone.execute(input, &ctx2).await
1231        })
1232    };
1233    let lsp_tool = LSPTool::new();
1234    let lsp_rf = self::make_render_fns(lsp_tool);
1235    engine.register_tool_with_render("LSP".to_string(), lsp_executor, lsp_rf);
1236
1237    // RemoteTrigger tool - manage remote agent triggers
1238    let remote_trigger_executor = move |input: serde_json::Value,
1239                                        ctx: &ToolContext|
1240          -> BoxFuture<Result<ToolResult, AgentError>> {
1241        let tool_clone = RemoteTriggerTool::new();
1242        let cwd = ctx.cwd.clone();
1243        let abort_signal = ctx.abort_signal.clone();
1244        Box::pin(async move {
1245            let ctx2 = ToolContext {
1246                cwd,
1247                abort_signal: abort_signal.clone(),
1248            };
1249            tool_clone.execute(input, &ctx2).await
1250        })
1251    };
1252    let remote_trigger_tool = RemoteTriggerTool::new();
1253    let remote_trigger_rf = self::make_render_fns(remote_trigger_tool);
1254    engine.register_tool_with_render("RemoteTrigger".to_string(), remote_trigger_executor, remote_trigger_rf);
1255
1256    // ListMcpResourcesTool - list MCP server resources
1257    let list_mcp_resources_executor = move |input: serde_json::Value,
1258                                            ctx: &ToolContext|
1259          -> BoxFuture<Result<ToolResult, AgentError>> {
1260        let tool_clone = ListMcpResourcesTool::new();
1261        let cwd = ctx.cwd.clone();
1262        let abort_signal = ctx.abort_signal.clone();
1263        Box::pin(async move {
1264            let ctx2 = ToolContext {
1265                cwd,
1266                abort_signal: abort_signal.clone(),
1267            };
1268            tool_clone.execute(input, &ctx2).await
1269        })
1270    };
1271    let list_mcp_resources_tool = ListMcpResourcesTool::new();
1272    let list_mcp_resources_rf = self::make_render_fns(list_mcp_resources_tool);
1273    engine.register_tool_with_render(
1274        "ListMcpResourcesTool".to_string(),
1275        list_mcp_resources_executor,
1276        list_mcp_resources_rf,
1277    );
1278
1279    // ReadMcpResourceTool - read MCP resources
1280    let read_mcp_resource_executor = move |input: serde_json::Value,
1281                                           ctx: &ToolContext|
1282          -> BoxFuture<Result<ToolResult, AgentError>> {
1283        let tool_clone = ReadMcpResourceTool::new();
1284        let cwd = ctx.cwd.clone();
1285        let abort_signal = ctx.abort_signal.clone();
1286        Box::pin(async move {
1287            let ctx2 = ToolContext {
1288                cwd,
1289                abort_signal: abort_signal.clone(),
1290            };
1291            tool_clone.execute(input, &ctx2).await
1292        })
1293    };
1294    let read_mcp_resource_tool = ReadMcpResourceTool::new();
1295    let read_mcp_resource_rf = self::make_render_fns(read_mcp_resource_tool);
1296    engine.register_tool_with_render(
1297        "ReadMcpResourceTool".to_string(),
1298        read_mcp_resource_executor,
1299        read_mcp_resource_rf,
1300    );
1301
1302    // BriefTool (SendUserMessage) — primary visible output channel
1303    let brief_executor = move |input: serde_json::Value,
1304                               ctx: &ToolContext|
1305          -> BoxFuture<Result<ToolResult, AgentError>> {
1306        let tool_clone = BriefTool::new();
1307        let cwd = ctx.cwd.clone();
1308        let abort_signal = ctx.abort_signal.clone();
1309        Box::pin(async move {
1310            let ctx2 = ToolContext {
1311                cwd,
1312                abort_signal: abort_signal.clone(),
1313            };
1314            tool_clone.execute(input, &ctx2).await
1315        })
1316    };
1317    let brief_tool = BriefTool::new();
1318    let brief_rf = self::make_render_fns(brief_tool);
1319    engine.register_tool_with_render("SendUserMessage".to_string(), brief_executor, brief_rf);
1320
1321    // SyntheticOutputTool (StructuredOutput) — structured output enforcement
1322    let synthetic_output_executor =
1323        move |input: serde_json::Value, ctx: &ToolContext|
1324          -> BoxFuture<Result<ToolResult, AgentError>> {
1325            let tool_clone = SyntheticOutputTool::new();
1326            let cwd = ctx.cwd.clone();
1327            let abort_signal = ctx.abort_signal.clone();
1328            Box::pin(async move {
1329                let ctx2 = ToolContext {
1330                    cwd,
1331                    abort_signal: abort_signal.clone(),
1332                };
1333                tool_clone.execute(input, &ctx2).await
1334            })
1335        };
1336    let synthetic_output_tool = SyntheticOutputTool::new();
1337    let synthetic_output_rf = self::make_render_fns(synthetic_output_tool);
1338    engine.register_tool_with_render("StructuredOutput".to_string(), synthetic_output_executor, synthetic_output_rf);
1339
1340    // MCPTool — generic MCP tool execution dispatcher
1341    let mcp_tool_executor =
1342        move |input: serde_json::Value, ctx: &ToolContext|
1343          -> BoxFuture<Result<ToolResult, AgentError>> {
1344            let tool_clone = McpTool::new();
1345            let cwd = ctx.cwd.clone();
1346            let abort_signal = ctx.abort_signal.clone();
1347            Box::pin(async move {
1348                let ctx2 = ToolContext {
1349                    cwd,
1350                    abort_signal: abort_signal.clone(),
1351                };
1352                tool_clone.execute(input, &ctx2).await
1353            })
1354        };
1355    let mcp_tool = McpTool::new();
1356    let mcp_tool_rf = self::make_render_fns(mcp_tool);
1357    engine.register_tool_with_render("MCPTool".to_string(), mcp_tool_executor, mcp_tool_rf);
1358
1359    // McpAuthTool — authenticate MCP server via OAuth
1360    let mcp_auth_executor =
1361        move |input: serde_json::Value, ctx: &ToolContext|
1362          -> BoxFuture<Result<ToolResult, AgentError>> {
1363            let tool_clone = McpAuthTool::new();
1364            let cwd = ctx.cwd.clone();
1365            let abort_signal = ctx.abort_signal.clone();
1366            Box::pin(async move {
1367                let ctx2 = ToolContext {
1368                    cwd,
1369                    abort_signal: abort_signal.clone(),
1370                };
1371                tool_clone.execute(input, &ctx2).await
1372            })
1373        };
1374    let mcp_auth_tool = McpAuthTool::new();
1375    let mcp_auth_rf = self::make_render_fns(mcp_auth_tool);
1376    engine.register_tool_with_render("McpAuth".to_string(), mcp_auth_executor, mcp_auth_rf);
1377}
1378
1379/// Subscriber info for fan-out event delivery
1380///
1381/// Thread-safe, Clone-able agent handle for tokio async usage.
1382/// All internal state is held behind a single `Arc<Mutex<>>` — cloning Agent
1383/// just increments the reference count. All public methods take `&self`.
1384///
1385/// # Sharing across tasks
1386///
1387/// ```rust,ignore
1388/// let agent = Agent::new("claude-sonnet-4-6");
1389///
1390/// // Clone into another task
1391/// let agent2 = agent.clone();
1392/// let handle = tokio::spawn(async move {
1393///     agent2.query("do work").await
1394/// });
1395///
1396/// // Subscribe from the original
1397/// let (mut sub, _guard) = agent.subscribe();
1398/// ```
1399#[derive(Clone)]
1400pub struct Agent {
1401    pub(crate) inner: std::sync::Arc<parking_lot::Mutex<AgentInner>>,
1402}
1403
1404#[cfg(test)]
1405impl Agent {
1406    /// Test-only accessor for the inner agent state.
1407    pub(crate) fn inner_for_test(&self) -> &std::sync::Arc<parking_lot::Mutex<AgentInner>> {
1408        &self.inner
1409    }
1410}
1411
1412pub(crate) struct AgentInner {
1413    model: String,
1414    api_key: Option<String>,
1415    base_url: Option<String>,
1416    cwd: String,
1417    system_prompt: Option<String>,
1418    max_turns: u32,
1419    max_budget_usd: Option<f64>,
1420    max_tokens: u32,
1421    fallback_model: Option<String>,
1422    pub(crate) thinking: Option<ThinkingConfig>,
1423    mcp_servers: Option<std::collections::HashMap<String, crate::mcp::McpServerConfig>>,
1424    tool_pool: Vec<ToolDefinition>,
1425    pub(crate) allowed_tools: Vec<String>,
1426    pub(crate) disallowed_tools: Vec<String>,
1427    #[cfg(test)]
1428    pub on_event: Option<std::sync::Arc<dyn Fn(AgentEvent) + Send + Sync>>,
1429    #[cfg(not(test))]
1430    pub(crate) on_event: Option<std::sync::Arc<dyn Fn(AgentEvent) + Send + Sync>>,
1431    session_id: String,
1432    abort_controller: std::sync::Arc<crate::utils::AbortController>,
1433    /// Persisted QueryEngine for multi-turn reuse (matches TypeScript pattern).
1434    /// Shared via Arc<parking_lot::RwLock<QueryEngine>> so spawned tasks from
1435    /// query() can access the same conversation state (messages, usage, turns).
1436    /// Write lock held for the duration of query(); read locks for get_messages().
1437    /// `None` until first query — lazily initialized.
1438    engine: Option<Arc<parking_lot::RwLock<QueryEngine>>>,
1439    /// Event broadcast channels for subscribe() callers
1440    broadcasters: EventBroadcasters,
1441}
1442
1443impl Agent {
1444    /// Create a new agent with the given model name.
1445    ///
1446    /// The model defaults to the `AI_MODEL` environment variable, then
1447    /// `"claude-sonnet-4-6"`. All other config (API key, base URL, max turns,
1448    /// thinking) also defaults from environment.
1449    ///
1450    /// Chain builder methods to customize:
1451    /// ```ignore
1452    /// let agent = Agent::new("claude-sonnet-4-6")
1453    ///     .max_turns(20)
1454    ///     .system_prompt("You are a code reviewer.")
1455    ///     .thinking(ThinkingConfig::Enabled { budget_tokens: 4096 });
1456    /// ```
1457    ///
1458    /// Returns a [`Clone`]able handle — all internal state uses interior
1459    /// mutability, so [`Agent::query`] takes `&self` and the agent can be
1460    /// shared across async tasks via `Arc<Agent>`.
1461    pub fn new(model: &str) -> Self {
1462        let env_config = EnvConfig::load();
1463        let model = env_config.model.unwrap_or_else(|| model.to_string());
1464        let api_key = env_config.auth_token.clone();
1465        let base_url = env_config.base_url.clone();
1466        let cwd = std::env::current_dir()
1467            .map(|p| p.to_string_lossy().to_string())
1468            .unwrap_or_else(|_| ".".to_string());
1469        let default_max_tokens =
1470            crate::utils::context::get_max_output_tokens_for_model(&model) as u32;
1471
1472        Self {
1473            inner: std::sync::Arc::new(parking_lot::Mutex::new(AgentInner {
1474                model,
1475                api_key,
1476                base_url,
1477                cwd,
1478                system_prompt: None,
1479                max_turns: 10,
1480                max_budget_usd: None,
1481                max_tokens: default_max_tokens,
1482                fallback_model: None,
1483                thinking: None,
1484                mcp_servers: None,
1485                tool_pool: vec![],
1486                allowed_tools: vec![],
1487                disallowed_tools: vec![],
1488                on_event: None,
1489                session_id: uuid::Uuid::new_v4().to_string(),
1490                abort_controller: std::sync::Arc::new(
1491                    crate::utils::create_abort_controller_default(),
1492                ),
1493                engine: None,
1494                broadcasters: EventBroadcasters::new(),
1495            })),
1496        }
1497    }
1498
1499    /// Configure the model name.
1500    ///
1501    /// Triggers engine recreation if the engine is already initialized.
1502    pub fn model(mut self, model: &str) -> Self {
1503        self.inner.lock().model = model.to_string();
1504        self
1505    }
1506
1507    /// Set the API key.
1508    ///
1509    /// Triggers engine recreation if the engine is already initialized.
1510    pub fn api_key(mut self, api_key: &str) -> Self {
1511        self.inner.lock().api_key = Some(api_key.to_string());
1512        self
1513    }
1514
1515    /// Set a custom base URL for the API.
1516    ///
1517    /// Triggers engine recreation if the engine is already initialized.
1518    pub fn base_url(mut self, base_url: &str) -> Self {
1519        self.inner.lock().base_url = Some(base_url.to_string());
1520        self
1521    }
1522
1523    /// Set the working directory.
1524    ///
1525    /// Triggers engine recreation if the engine is already initialized.
1526    pub fn cwd(mut self, cwd: &str) -> Self {
1527        self.inner.lock().cwd = cwd.to_string();
1528        self
1529    }
1530
1531    /// Set a custom system prompt.
1532    pub fn system_prompt(mut self, prompt: &str) -> Self {
1533        self.inner.lock().system_prompt = Some(prompt.to_string());
1534        self
1535    }
1536
1537    /// Set the maximum number of turns.
1538    ///
1539    /// Triggers engine recreation if the engine is already initialized.
1540    pub fn max_turns(mut self, max_turns: u32) -> Self {
1541        self.inner.lock().max_turns = max_turns;
1542        self
1543    }
1544
1545    /// Set the maximum budget in USD.
1546    pub fn max_budget_usd(mut self, budget: f64) -> Self {
1547        self.inner.lock().max_budget_usd = Some(budget);
1548        self
1549    }
1550
1551    /// Set the maximum tokens for a single response.
1552    pub fn max_tokens(mut self, max_tokens: u32) -> Self {
1553        self.inner.lock().max_tokens = max_tokens;
1554        self
1555    }
1556
1557    /// Set the fallback model.
1558    pub fn fallback_model(mut self, model: &str) -> Self {
1559        self.inner.lock().fallback_model = Some(model.to_string());
1560        self
1561    }
1562
1563    /// Set thinking configuration for extended thinking.
1564    pub fn thinking(mut self, thinking: ThinkingConfig) -> Self {
1565        self.inner.lock().thinking = Some(thinking);
1566        self
1567    }
1568
1569    /// Set tool definitions.
1570    pub fn tools(mut self, tools: Vec<ToolDefinition>) -> Self {
1571        self.inner.lock().tool_pool = tools;
1572        self
1573    }
1574
1575    /// Only allow specific tools by name.
1576    pub fn allowed_tools(mut self, tools: Vec<String>) -> Self {
1577        self.inner.lock().allowed_tools = tools;
1578        self
1579    }
1580
1581    /// Explicitly disallow specific tools by name.
1582    pub fn disallowed_tools(mut self, tools: Vec<String>) -> Self {
1583        self.inner.lock().disallowed_tools = tools;
1584        self
1585    }
1586
1587    /// Set MCP server configurations.
1588    pub fn mcp_servers(
1589        mut self,
1590        servers: std::collections::HashMap<String, crate::mcp::McpServerConfig>,
1591    ) -> Self {
1592        self.inner.lock().mcp_servers = Some(servers);
1593        self
1594    }
1595
1596    /// Set an event callback for streaming agent events.
1597    ///
1598    /// Can be called at any time (before or after `query()`). Takes `self`
1599    /// so it can be updated between queries for dynamic event handling.
1600    ///
1601    /// For fully async event handling, prefer [`Agent::subscribe()`] with
1602    /// the [`EventSubscriber`] stream instead.
1603    pub fn on_event<F>(mut self, callback: F) -> Self
1604    where
1605        F: Fn(AgentEvent) + Send + Sync + 'static,
1606    {
1607        self.inner.lock().on_event = Some(std::sync::Arc::new(callback));
1608        self
1609    }
1610
1611    /// Uses interior mutability — takes `&self` so the agent can be shared across tasks.
1612    /// Lazily creates the QueryEngine on first query.
1613    fn init_engine(&self) {
1614        let mut inner = self.inner.lock();
1615        if inner.engine.is_none() {
1616            let cwd = inner.cwd.clone();
1617            let allowed_tools = inner.allowed_tools.clone();
1618            let disallowed_tools = inner.disallowed_tools.clone();
1619            let tool_pool = inner.tool_pool.clone();
1620            let can_use_tool: Option<
1621                std::sync::Arc<dyn Fn(ToolDefinition, serde_json::Value) -> PermissionResult + Send + Sync>,
1622            > = if !allowed_tools.is_empty() || !disallowed_tools.is_empty() {
1623                Some(std::sync::Arc::new(
1624                    move |tool_def: ToolDefinition, _input: serde_json::Value| {
1625                        if !allowed_tools.is_empty() && !allowed_tools.contains(&tool_def.name) {
1626                            return PermissionResult::Deny(PermissionDenyDecision::new(
1627                                &format!("Tool '{}' is not in the allowed tools list", tool_def.name),
1628                                PermissionDecisionReason::Other { reason: "allowed tools filter".to_string() },
1629                            ));
1630                        }
1631                        if disallowed_tools.contains(&tool_def.name) {
1632                            return PermissionResult::Deny(PermissionDenyDecision::new(
1633                                &format!("Tool '{}' is in the disallowed tools list", tool_def.name),
1634                                PermissionDecisionReason::Other { reason: "disallowed tools filter".to_string() },
1635                            ));
1636                        }
1637                        PermissionResult::Allow(PermissionAllowDecision::default())
1638                    },
1639                ))
1640            } else {
1641                None
1642            };
1643            let config = QueryEngineConfig {
1644                cwd: cwd.clone(),
1645                model: inner.model.clone(),
1646                api_key: inner.api_key.clone(),
1647                base_url: inner.base_url.clone(),
1648                tools: tool_pool,
1649                system_prompt: None,
1650                max_turns: inner.max_turns,
1651                max_budget_usd: inner.max_budget_usd,
1652                max_tokens: inner.max_tokens,
1653                fallback_model: inner.fallback_model.clone(),
1654                user_context: std::collections::HashMap::new(),
1655                system_context: std::collections::HashMap::new(),
1656                can_use_tool,
1657                on_event: inner.on_event.clone(),
1658                thinking: inner.thinking.clone(),
1659                abort_controller: Some(inner.abort_controller.clone()),
1660                token_budget: None,
1661                agent_id: None,
1662                session_state: None,
1663                loaded_nested_memory_paths: std::collections::HashSet::new(),
1664                task_budget: None,
1665                orphaned_permission: None,
1666            };
1667            let mut engine = QueryEngine::new(config);
1668            register_all_tool_executors(&mut engine);
1669
1670            // Register hooks from loaded skills
1671            let session_id = inner.session_id.clone();
1672            if let Ok(skills) = load_all_skills(&cwd) {
1673                let _set_app_state = Arc::new(|_: &dyn Fn(&mut serde_json::Value)| {})
1674                    as Arc<dyn Fn(&dyn Fn(&mut serde_json::Value)) + Send + Sync>;
1675                register_hooks_from_skills(_set_app_state, &session_id, &skills);
1676            }
1677
1678            inner.engine = Some(Arc::new(parking_lot::RwLock::new(engine)));
1679        }
1680    }
1681
1682    /// One-shot query — creates an agent, sends the prompt, and returns the text.
1683    ///
1684    /// Use this for single-turn interactions where you don't need conversation history
1685    /// or multi-turn reuse. For persistent agents, use `Agent::new()` + `.query()`.
1686    ///
1687    /// # Example
1688    ///
1689    /// ```rust,ignore
1690    /// let answer = Agent::prompt("claude-sonnet-4-6", "Explain quantum computing")
1691    ///     .await?;
1692    /// println!("{answer}");
1693    /// ```
1694    pub async fn prompt(model: &str, prompt: &str) -> Result<String, AgentError> {
1695        let agent = Self::new(model);
1696        let result = agent.query(prompt).await?;
1697        Ok(result.text)
1698    }
1699
1700    /// Get the configured model name.
1701    pub fn get_model(&self) -> String {
1702        self.inner.lock().model.clone()
1703    }
1704
1705    /// Get the session ID.
1706    pub fn get_session_id(&self) -> String {
1707        self.inner.lock().session_id.clone()
1708    }
1709
1710    /// Get all messages in the conversation history.
1711    /// Delegates to the persisted QueryEngine which owns the message state
1712    /// (matches TypeScript: engine.mutableMessages).
1713    pub fn get_messages(&self) -> Vec<Message> {
1714        let engine_opt = {
1715            let inner = self.inner.lock();
1716            inner.engine.clone()
1717        };
1718        if let Some(engine) = engine_opt {
1719            let eng = engine.read();
1720            eng.get_messages()
1721        } else {
1722            Vec::new()
1723        }
1724    }
1725
1726    /// Get all tools available to the agent
1727    pub fn get_tools(&self) -> Vec<ToolDefinition> {
1728        self.inner.lock().tool_pool.clone()
1729    }
1730
1731    /// Set system prompt for the agent (interior mutability).
1732    pub fn set_system_prompt(&self, prompt: &str) {
1733        self.inner.lock().system_prompt = Some(prompt.to_string());
1734    }
1735
1736    /// Set the working directory for the agent (interior mutability).
1737    pub fn set_cwd(&self, cwd: &str) {
1738        self.inner.lock().cwd = cwd.to_string();
1739    }
1740
1741
1742    /// Set thinking configuration for the agent (interior mutability).
1743    pub fn set_thinking(&self, thinking: Option<ThinkingConfig>) {
1744        self.inner.lock().thinking = thinking;
1745    }
1746
1747    /// Set the model name at runtime (interior mutability).
1748    ///
1749    /// Changes the model for subsequent `query()` calls, matching TypeScript's
1750    /// `QueryEngine.setModel()`.
1751    pub fn set_model(&self, model: &str) {
1752        self.inner.lock().model = model.to_string();
1753    }
1754
1755    /// Execute a tool directly.
1756    pub(crate) async fn execute_tool(
1757        &self,
1758        name: &str,
1759        input: serde_json::Value,
1760    ) -> Result<ToolResult, AgentError> {
1761        // Clone all needed data before dropping the lock
1762        let (cwd, model, api_key, base_url, abort_controller, allowed_tools, disallowed_tools, on_event, thinking) = {
1763            let inner = self.inner.lock();
1764            (
1765                inner.cwd.clone(),
1766                inner.model.clone(),
1767                inner.api_key.clone(),
1768                inner.base_url.clone(),
1769                inner.abort_controller.clone(),
1770                inner.allowed_tools.clone(),
1771                inner.disallowed_tools.clone(),
1772                inner.on_event.clone(),
1773                inner.thinking.clone(),
1774            )
1775        };
1776
1777        let mut engine = QueryEngine::new(QueryEngineConfig {
1778            cwd: cwd.clone(),
1779            model: model.clone(),
1780            api_key: api_key.clone(),
1781            base_url: base_url.clone(),
1782            tools: vec![],
1783            system_prompt: None,
1784            max_turns: 10,
1785            max_budget_usd: None,
1786            max_tokens: crate::utils::context::get_max_output_tokens_for_model(&model) as u32,
1787            fallback_model: None,
1788            user_context: std::collections::HashMap::new(),
1789            system_context: std::collections::HashMap::new(),
1790            can_use_tool: None,
1791            on_event: None,
1792            thinking: None,
1793            abort_controller: Some(abort_controller.clone()),
1794            token_budget: None,
1795            agent_id: None,
1796            session_state: None,
1797            loaded_nested_memory_paths: std::collections::HashSet::new(),
1798            task_budget: None,
1799            orphaned_permission: None,
1800        });
1801
1802        // Snapshot parent context fields to thread into subagent closures
1803        let parent_can_use_tool: Option<
1804            std::sync::Arc<dyn Fn(ToolDefinition, serde_json::Value) -> PermissionResult + Send + Sync>,
1805        > = {
1806            if !allowed_tools.is_empty() || !disallowed_tools.is_empty() {
1807                Some(std::sync::Arc::new(
1808                    move |tool_def: ToolDefinition, _input: serde_json::Value| {
1809                        if !allowed_tools.is_empty() && !allowed_tools.contains(&tool_def.name) {
1810                            return PermissionResult::Deny(PermissionDenyDecision::new(
1811                                &format!("Tool '{}' is not in the allowed tools list", tool_def.name),
1812                                PermissionDecisionReason::Other { reason: "allowed tools filter".to_string() },
1813                            ));
1814                        }
1815                        if disallowed_tools.contains(&tool_def.name) {
1816                            return PermissionResult::Deny(PermissionDenyDecision::new(
1817                                &format!("Tool '{}' is in the disallowed tools list", tool_def.name),
1818                                PermissionDecisionReason::Other { reason: "disallowed tools filter".to_string() },
1819                            ));
1820                        }
1821                        PermissionResult::Allow(PermissionAllowDecision::default())
1822                    },
1823                ))
1824            } else {
1825                None
1826            }
1827        };
1828        let parent_on_event = on_event;
1829        let parent_thinking = thinking;
1830
1831        // Register all tool executors (including Bash, Read, Write, etc.)
1832        register_all_tool_executors(&mut engine);
1833
1834        // Register Agent tool using the AgentTool struct
1835        {
1836            use crate::tools::agent::{AgentTool, AgentToolConfig, create_agent_tool_executor};
1837            let agent_tool = Arc::new(AgentTool::new(AgentToolConfig {
1838                cwd: cwd.clone(),
1839                api_key: api_key.clone(),
1840                base_url: base_url.clone(),
1841                model: model.clone(),
1842                tool_pool: crate::tools::get_all_base_tools(),
1843                abort_controller: abort_controller.clone(),
1844                can_use_tool: parent_can_use_tool,
1845                on_event: parent_on_event,
1846                thinking: parent_thinking,
1847                parent_messages: Vec::new(),
1848                parent_user_context: std::collections::HashMap::new(),
1849                parent_system_context: std::collections::HashMap::new(),
1850                parent_session_id: None,
1851            }));
1852            engine.register_tool(
1853                "Agent".to_string(),
1854                create_agent_tool_executor(agent_tool),
1855            );
1856        }
1857        let tool_call_id = uuid::Uuid::new_v4().to_string();
1858        engine.execute_tool(name, input, tool_call_id).await
1859    }
1860
1861    /// Build the system prompt by combining AI.md, memory mechanics,
1862    /// base system prompt, and custom system prompt.
1863    fn build_system_prompt(&self, cwd: &std::path::Path) -> Option<String> {
1864        use crate::ai_md::load_ai_md;
1865        use crate::memdir::load_memory_prompt_sync;
1866        use crate::prompts::build_system_prompt as base_build_system_prompt;
1867
1868        let inner = &*self.inner.lock();
1869        let ai_md_prompt = load_ai_md(cwd).ok().flatten();
1870        let memory_mechanics_prompt =
1871            if inner.system_prompt.is_some() && crate::memdir::has_auto_mem_path_override() {
1872                load_memory_prompt_sync()
1873            } else {
1874                None
1875            };
1876        let base_system_prompt = base_build_system_prompt();
1877
1878        let system_prompt = match (
1879            &ai_md_prompt,
1880            &memory_mechanics_prompt,
1881            &inner.system_prompt,
1882        ) {
1883            (Some(ai_md), Some(mem), Some(custom)) => Some(format!(
1884                "{}\n\n{}\n\n{}\n\n{}",
1885                ai_md, mem, base_system_prompt, custom
1886            )),
1887            (Some(ai_md), Some(mem), None) => {
1888                Some(format!("{}\n\n{}\n\n{}", ai_md, mem, base_system_prompt))
1889            }
1890            (Some(ai_md), None, Some(custom)) => {
1891                Some(format!("{}\n\n{}\n\n{}", ai_md, base_system_prompt, custom))
1892            }
1893            (Some(ai_md), None, None) => Some(format!("{}\n\n{}", ai_md, base_system_prompt)),
1894            (None, Some(mem), Some(custom)) => {
1895                Some(format!("{}\n\n{}\n\n{}", mem, base_system_prompt, custom))
1896            }
1897            (None, Some(mem), None) => Some(format!("{}\n\n{}", mem, base_system_prompt)),
1898            (None, None, Some(custom)) => Some(format!("{}\n\n{}", base_system_prompt, custom)),
1899            (None, None, None) => Some(base_system_prompt),
1900        };
1901        system_prompt
1902    }
1903
1904    /// Select the tools to use: all base tools if tool_pool is empty, otherwise the tool pool.
1905    /// Tools are sorted by name for prompt cache stability (matches TypeScript assembleToolPool).
1906    /// Applies deny rules to filter out disallowed MCP tools.
1907    fn select_tools(&self) -> Vec<ToolDefinition> {
1908        let inner = &*self.inner.lock();
1909        let tools = if inner.tool_pool.is_empty() {
1910            crate::tools::get_all_base_tools()
1911        } else {
1912            inner.tool_pool.clone()
1913        };
1914        let disallowed_tools = inner.disallowed_tools.clone();
1915
1916        // Sort by name for prompt cache stability (built-in tools are already sorted)
1917        let mut sorted = tools;
1918        sorted.sort_by(|a, b| a.name.cmp(&b.name));
1919        // Deduplicate by name (first occurrence wins)
1920        let mut seen = std::collections::HashSet::new();
1921        sorted.retain(|t| seen.insert(t.name.clone()));
1922
1923        // Apply deny rules (MCP server-prefix, wildcard, exact match)
1924        if !disallowed_tools.is_empty() {
1925            sorted = crate::tools::filter_tools_by_deny_rules(&sorted, &disallowed_tools);
1926        }
1927        sorted
1928    }
1929
1930    /// Main query method - handles the full agent loop including tool use,
1931    /// streaming responses, and multi-turn interaction with the LLM.
1932    ///
1933    /// Reuses a persisted QueryEngine across calls so that conversation history,
1934    /// usage tracking, and tool state accumulate naturally (matches TypeScript pattern).
1935    ///
1936    /// Takes `&self` (interior mutability) — the agent can be shared across tasks.
1937    pub async fn query(&self, prompt: &str) -> Result<QueryResult, AgentError> {
1938        self.init_engine();
1939
1940        // Clone all data from AgentInner before any .await (MutexGuard is not Send).
1941        // Single lock acquisition.
1942        let (cwd, on_event, thinking, abort_controller, engine, broadcasters) = {
1943            let inner = self.inner.lock();
1944            (
1945                inner.cwd.clone(),
1946                inner.on_event.clone(),
1947                inner.thinking.clone(),
1948                inner.abort_controller.clone(),
1949                Arc::clone(inner.engine.as_ref().unwrap()),
1950                inner.broadcasters.clone(),
1951            )
1952        };
1953        let cwd_path = std::path::Path::new(&cwd);
1954
1955        let system_prompt = self.build_system_prompt(&cwd_path);
1956        let tools = self.select_tools();
1957
1958        let start = std::time::Instant::now();
1959        let query_result: Result<
1960            (String, ExitReason, String, crate::types::TokenUsage, u32),
1961            AgentError,
1962        > = {
1963            let mut eng = engine.write();
1964
1965            // Update per-query config
1966            eng.config.system_prompt = system_prompt;
1967            eng.config.tools = tools;
1968            // Wrap on_event to also broadcast to channel subscribers
1969            let on_event = {
1970                let cb = on_event;
1971                Some(Arc::new(move |event: crate::types::AgentEvent| {
1972                    if let Some(ref f) = cb {
1973                        f(event.clone());
1974                    }
1975                    broadcasters.broadcast(&event);
1976                }) as std::sync::Arc<dyn Fn(crate::types::AgentEvent) + Send + Sync>)
1977            };
1978            eng.config.on_event = on_event;
1979            eng.config.thinking = thinking;
1980
1981            // Snapshot engine config values needed by the subagent closure
1982            let engine_tools = eng.config.tools.clone();
1983            let engine_model = eng.config.model.clone();
1984            let engine_api_key = eng.config.api_key.clone();
1985            let engine_base_url = eng.config.base_url.clone();
1986            let engine_cwd = eng.config.cwd.clone();
1987            let subagent_abort = abort_controller.clone();
1988            // Additional values for fork subagent path
1989            let engine_messages = eng.messages.clone();
1990            let engine_user_context = eng.config.user_context.clone();
1991            let engine_system_context = eng.config.system_context.clone();
1992            let engine_thinking = eng.config.thinking.clone();
1993            let engine_can_use_tool = eng.config.can_use_tool.clone();
1994            let engine_on_event = eng.config.on_event.clone();
1995
1996            // Register Agent tool using the AgentTool struct (with fork support)
1997            {
1998                use crate::tools::agent::{AgentTool, AgentToolConfig, create_agent_tool_executor};
1999                let agent_tool = Arc::new(AgentTool::new(AgentToolConfig {
2000                    cwd: engine_cwd,
2001                    api_key: engine_api_key,
2002                    base_url: engine_base_url,
2003                    model: engine_model,
2004                    tool_pool: engine_tools,
2005                    abort_controller: subagent_abort,
2006                    can_use_tool: engine_can_use_tool,
2007                    on_event: engine_on_event,
2008                    thinking: engine_thinking,
2009                    parent_messages: engine_messages,
2010                    parent_user_context: engine_user_context,
2011                    parent_system_context: engine_system_context,
2012                    parent_session_id: None,
2013                }));
2014                eng.register_tool(
2015                    "Agent".to_string(),
2016                    create_agent_tool_executor(agent_tool),
2017                );
2018            }
2019
2020            // Run the query — on error, broadcast Done with ModelError and preserve state
2021            match eng.submit_message(prompt).await {
2022                Ok(r) => Ok((
2023                    r.0,
2024                    r.1,
2025                    eng.config.model.clone(),
2026                    eng.get_usage(),
2027                    eng.get_turn_count(),
2028                )),
2029                Err(e) => {
2030                    let duration_ms = start.elapsed().as_millis() as u64;
2031                    // Do NOT reset messages — preserve state for user replay (matches TypeScript)
2032                    // Only reset counters
2033                    eng.reset_counters();
2034
2035                    // Detect image errors (ImageSizeError / ImageResizeError patterns)
2036                    // Matches TypeScript: error instanceof ImageSizeError || ImageResizeError
2037                    let is_image_error = crate::services::api::errors::is_media_size_error(&e.to_string())
2038                        && e.to_string().to_lowercase().contains("image");
2039
2040                    if is_image_error {
2041                        // Write image error as API error message
2042                        eng.messages.push(crate::types::Message {
2043                            role: crate::types::MessageRole::Assistant,
2044                            content: e.to_string(),
2045                            is_api_error_message: Some(true),
2046                            error_details: Some(e.to_string()),
2047                            ..Default::default()
2048                        });
2049                        // Flush session storage
2050                        let _ = crate::utils::session_storage::flush_session_storage();
2051                        // Broadcast Done with ImageError exit reason
2052                        if let Some(ref cb) = eng.config.on_event {
2053                            cb(AgentEvent::Done {
2054                                result: QueryResult {
2055                                    text: String::new(),
2056                                    exit_reason: ExitReason::ImageError {
2057                                        error: e.to_string(),
2058                                    },
2059                                    usage: Default::default(),
2060                                    num_turns: 0,
2061                                    duration_ms,
2062                                },
2063                            });
2064                        }
2065                    } else {
2066                        // Write error as API error message in conversation (matches TypeScript)
2067                        let api_err = crate::services::api::errors::error_to_api_message(&e.to_string(), None);
2068                        eng.messages.push(crate::types::Message {
2069                            role: crate::types::MessageRole::Assistant,
2070                            content: api_err.content.clone().unwrap_or_default(),
2071                            is_api_error_message: Some(true),
2072                            error_details: api_err.error_details.clone(),
2073                            ..Default::default()
2074                        });
2075                        // Flush session storage before error result (matches TypeScript flushSessionStorage)
2076                        let _ = crate::utils::session_storage::flush_session_storage();
2077                        // Broadcast Done event so the TUI unblocks
2078                        if let Some(ref cb) = eng.config.on_event {
2079                            cb(AgentEvent::Done {
2080                                result: QueryResult {
2081                                    text: String::new(),
2082                                    exit_reason: ExitReason::ModelError {
2083                                        error: e.to_string(),
2084                                    },
2085                                    usage: Default::default(),
2086                                    num_turns: 0,
2087                                    duration_ms,
2088                                },
2089                            });
2090                        }
2091                    }
2092                    Err(e)
2093                }
2094            }
2095        }; // Lock released here
2096
2097        let (response_text, exit_reason, current_model, usage, turns) = query_result?;
2098
2099        // Track model in case it changed (for recreation detection)
2100        if current_model != self.get_model() {
2101            self.inner.lock().model = current_model;
2102        }
2103
2104        Ok(QueryResult {
2105            text: response_text,
2106            usage: TokenUsage {
2107                input_tokens: usage.input_tokens,
2108                output_tokens: usage.output_tokens,
2109                cache_creation_input_tokens: usage.cache_creation_input_tokens,
2110                cache_read_input_tokens: usage.cache_read_input_tokens,
2111                iterations: usage.iterations,
2112            },
2113            num_turns: turns,
2114            duration_ms: start.elapsed().as_millis() as u64,
2115            exit_reason,
2116        })
2117    }
2118
2119    /// Reset the agent's conversation history, keeping configuration intact.
2120    ///
2121    /// Clears all messages, usage tracking, and turn count. This starts a fresh
2122    /// conversation while preserving model, API key, tools, and other settings.
2123    pub fn reset(&self) {
2124        let engine_opt = {
2125            let inner = self.inner.lock();
2126            inner.engine.clone()
2127        };
2128        if let Some(engine) = engine_opt {
2129            let mut eng = engine.write();
2130            eng.reset();
2131        }
2132    }
2133
2134    /// Subscribe to agent events for the current and subsequent queries.
2135    ///
2136    /// Returns an [`EventSubscriber`] (implements [`futures_util::Stream`]) and a
2137    /// [`CancelGuard`]. Events flow to the subscriber until the guard is dropped.
2138    ///
2139    /// Takes `&self` (not `&mut self`) — you can subscribe from a shared reference,
2140    /// enabling decoupled TUI architectures where the subscriber is owned by a
2141    /// separate task from the agent.
2142    ///
2143    /// # Example
2144    ///
2145    /// ```rust,ignore
2146    /// let (mut sub, _guard) = agent.subscribe();
2147    /// tokio::pin!(sub);
2148    ///
2149    /// // Run query asynchronously
2150    /// tokio::spawn(async move {
2151    ///     agent.query("hello").await;
2152    /// });
2153    ///
2154    /// // Consume events
2155    /// while let Some(ev) = sub.next().await {
2156    ///     // render in TUI
2157    /// }
2158    /// ```
2159    pub fn subscribe(&self) -> (EventSubscriber, CancelGuard) {
2160        let inner = self.inner.lock();
2161        inner.broadcasters.subscribe()
2162    }
2163
2164    /// Interrupt the agent loop. This aborts the current `query()` call,
2165    /// cancelling any in-flight API requests and tool execution.
2166    ///
2167    /// # Example
2168    ///
2169    /// ```rust,ignore
2170    /// let agent = Agent::new("claude-sonnet-4-6");
2171    ///
2172    /// tokio::spawn(async move {
2173    ///     agent.query("Do a lot of work").await.unwrap();
2174    /// });
2175    ///
2176    /// tokio::time::sleep(Duration::from_secs(5)).await;
2177    /// agent.interrupt(); // Cancel the running prompt
2178    /// ```
2179    pub fn interrupt(&self) {
2180        let inner = self.inner.lock();
2181        inner.abort_controller.abort(None);
2182    }
2183
2184    /// Generate a short session recap summarizing the conversation so far.
2185    ///
2186    /// Produces a 1-3 sentence summary suitable for a "while you were away"
2187    /// card or CLI status display. Uses the small/fast model (Haiku) with
2188    /// the last 30 messages and optional session memory context.
2189    ///
2190    /// Returns `AwaySummaryResult` which indicates whether the LLM produced
2191    /// a summary, was aborted, or returned empty (no conversation history).
2192    ///
2193    /// # Example
2194    /// ```ignore
2195    /// let agent = Agent::new("claude-sonnet-4-6")
2196    ///     .api_key("sk-ant-...");
2197    /// agent.query("build a REST API").await.ok();
2198    /// // ... user steps away ...
2199    /// let recap = agent.recap().await;
2200    /// if let Some(summary) = recap.summary {
2201    ///     println!("※ {}", summary);
2202    /// }
2203    /// ```
2204    pub async fn recap(
2205        &self,
2206    ) -> crate::services::away_summary::AwaySummaryResult {
2207        let engine_opt = {
2208            let inner = self.inner.lock();
2209            inner.engine.clone()
2210        };
2211        let messages = if let Some(engine) = engine_opt.as_ref() {
2212            let eng = engine.read();
2213            eng.get_messages()
2214        } else {
2215            Vec::new()
2216        };
2217        let (api_key, abort_ctrl) = {
2218            let inner = self.inner.lock();
2219            (inner.api_key.clone(), inner.abort_controller.clone())
2220        };
2221
2222        let api_key = match api_key {
2223            Some(k) if !k.is_empty() => k,
2224            _ => std::env::var("AI_AUTH_TOKEN")
2225                .or_else(|_| std::env::var("AI_API_KEY"))
2226                .unwrap_or_default(),
2227        };
2228
2229        crate::services::away_summary::generate_away_summary(
2230            &messages,
2231            &api_key,
2232            abort_ctrl.signal().abort_flag(),
2233        )
2234        .await
2235    }
2236}
2237
2238/// Build system prompt for subagent based on agent type
2239pub(crate) fn build_agent_system_prompt(
2240    agent_description: &str,
2241    agent_type: Option<&str>,
2242) -> String {
2243    let base_prompt = "You are an agent that helps users with software engineering tasks. Use the tools available to you to assist the user.\n\nComplete the task fully—don't gold-plate, but don't leave it half-done. When you complete the task, respond with a concise report covering what was done and any key findings.";
2244
2245    match agent_type {
2246        Some("Explore") => {
2247            format!(
2248                "{}\n\nYou are an Explore agent. Your goal is to explore and understand the codebase thoroughly. Use search and read tools to investigate. Report your findings in detail.",
2249                base_prompt
2250            )
2251        }
2252        Some("Plan") => {
2253            format!(
2254                "{}\n\nYou are a Plan agent. Your goal is to plan and analyze tasks before execution. Break down complex tasks into steps. Provide a detailed plan.",
2255                base_prompt
2256            )
2257        }
2258        Some("Review") => {
2259            format!(
2260                "{}\n\nYou are a Review agent. Your goal is to review code and provide constructive feedback. Be thorough and focus on best practices.",
2261                base_prompt
2262            )
2263        }
2264        _ => {
2265            // General purpose agent
2266            format!("{}\n\nTask description: {}", base_prompt, agent_description)
2267        }
2268    }
2269}