返回 CodeWhale
tool_setup.rs
根目录 / crates / tui / src / core / engine / tool_setup.rs
1 //! Per-turn tool registry setup.
2 //!
3 //! This keeps mode/feature-specific registry construction out of the send path.
4
5 use super::*;
6 use crate::core::authority::shell_policy_for_mode;
7 use crate::tools::AgentToolSurfaceOptions;
8 use crate::worker_profile::ShellPolicy;
9
10 fn should_register_remember_tool(memory_enabled: bool) -> bool {
11 memory_enabled
12 }
13
14 impl Engine {
15 pub(super) fn agent_tool_surface_options(
16 &self,
17 shell_policy: ShellPolicy,
18 ) -> AgentToolSurfaceOptions {
19 let mut options = AgentToolSurfaceOptions::new(shell_policy);
20 options.apply_patch_enabled = self.config.features.enabled(Feature::ApplyPatch);
21 options.web_search_enabled = self.config.features.enabled(Feature::WebSearch);
22 options.memory_tool_enabled = should_register_remember_tool(self.config.memory_enabled);
23 options.vision_config = if self.config.features.enabled(Feature::VisionModel) {
24 self.config.vision_config.clone()
25 } else {
26 None
27 };
28 options.speech_output_dir = self.config.speech_output_dir.clone();
29 options.goal_state = Some(self.config.goal_state.clone());
30 options.verify_tool_enabled = self.config.features.enabled(Feature::Verify);
31 options.user_input_limits = self.config.user_input_limits;
32 options
33 }
34
35 #[cfg(test)]
36 pub(super) fn build_turn_tool_registry_builder(
37 &self,
38 mode: AppMode,
39 todo_list: SharedTodoList,
40 plan_state: SharedPlanState,
41 ) -> ToolRegistryBuilder {
42 self.build_turn_tool_registry_builder_for_route(
43 mode,
44 self.session.allow_shell,
45 self.codewhale_client.clone(),
46 &self.session.model,
47 todo_list,
48 plan_state,
49 )
50 }
51
52 /// Build the registry from the route and authority already resolved for
53 /// this turn. Preview calls this before either is installed on the engine,
54 /// so reading `self.session` here would describe the previous turn's shell
55 /// posture, client, and model.
56 #[allow(clippy::too_many_arguments)]
57 pub(super) fn build_turn_tool_registry_builder_for_route(
58 &self,
59 mode: AppMode,
60 allow_shell: bool,
61 client: Option<CodewhaleClient>,
62 model: &str,
63 todo_list: SharedTodoList,
64 plan_state: SharedPlanState,
65 ) -> ToolRegistryBuilder {
66 let shell_policy = shell_policy_for_mode(mode, allow_shell);
67 if mode != AppMode::Plan {
68 let mut builder = ToolRegistryBuilder::new().with_agent_runtime_surface(
69 client.clone(),
70 model.to_string(),
71 self.agent_tool_surface_options(shell_policy),
72 todo_list,
73 plan_state,
74 );
75 if self.config.features.enabled(Feature::Mcp) {
76 builder = builder.with_registry_mcp_sync_tool();
77 }
78 // `start_mcp_server` belongs to every executable mode. Keep its
79 // handler aligned with the model catalog, which always loads the
80 // tool while MCP is enabled. The former early return registered
81 // it only in Plan mode, so Agent/Full Access advertised a tool
82 // that could never cross the execution boundary.
83 if let Some(ref pool) = self.mcp_pool {
84 builder = builder
85 .with_runtime_mcp_tool(Arc::clone(pool))
86 .with_registry_mcp_start_tool(Arc::clone(pool));
87 }
88 return builder;
89 }
90
91 let mut builder = ToolRegistryBuilder::new()
92 // Modes change authority, not the primitive tool identity.
93 // Plan advertises the same file names as Work; the turn-loop
94 // mode gate below the schema boundary blocks write/edit, and
95 // the ToolContext carries ShellPolicy::None.
96 .with_file_tools()
97 // Foreground-only shell registration: never add terminal/*
98 // lifecycle tools to Plan merely to keep the `bash` identity
99 // stable across modes.
100 .with_foreground_shell_tools()
101 .with_search_tools()
102 .with_git_tools()
103 .with_git_history_tools()
104 .with_diagnostics_tool()
105 .with_read_media_tool()
106 .with_skill_tools()
107 .with_validation_tools()
108 .with_handle_tools()
109 .with_runtime_read_only_task_tools()
110 .with_todo_tool(todo_list)
111 .with_plan_tool(plan_state)
112 .with_goal_tools(self.config.goal_state.clone());
113
114 builder = builder
115 .with_review_tool(client.clone(), model.to_string())
116 .with_user_input_tool(self.config.user_input_limits);
117
118 if self.config.features.enabled(Feature::WebSearch) {
119 builder = builder.with_web_tools();
120 }
121
122 // Register the `remember` tool only when the user has opted in to
123 // user-memory (#489). Without that opt-in the tool would always
124 // fail; surfacing it would just waste catalog slots.
125 if should_register_remember_tool(self.config.memory_enabled) {
126 builder = builder.with_remember_tool();
127 }
128
129 // Register image_analyze tool when vision_model is configured and feature enabled.
130 if self.config.features.enabled(Feature::VisionModel)
131 && let Some(ref vision_config) = self.config.vision_config
132 {
133 builder = builder.with_vision_tools(vision_config.clone(), client.clone());
134 }
135
136 // Register the `notify` tool unconditionally (#1322). Interactive and
137 // headless entry points install the merged notification policy before
138 // tool setup, including method=off, quiet/category, and attention.
139 // The tool returns a truthful suppressed/delivered receipt.
140 builder = builder
141 .with_notify_tool()
142 .with_request_plugin_install_tool();
143
144 // Register the `registry_sync` tool for fetching and caching
145 // MCP Registry server metadata. Rides on `Feature::Mcp` — the same
146 // flag that gates the rest of the MCP system (defaults to enabled;
147 // opt out via `[features]` in config.toml).
148 if self.config.features.enabled(Feature::Mcp) {
149 builder = builder.with_registry_mcp_sync_tool();
150 }
151
152 // Register the start_mcp_server tool so LLM can dynamically start
153 // MCP servers from conversation context. Only when the pool has been
154 // initialized (lazy via ensure_mcp_pool).
155 if let Some(ref pool) = self.mcp_pool {
156 builder = builder
157 .with_runtime_mcp_tool(Arc::clone(pool))
158 .with_registry_mcp_start_tool(Arc::clone(pool));
159 }
160
161 builder
162 }
163 }
164
165 #[cfg(test)]
166 mod tests {
167 use super::should_register_remember_tool;
168
169 #[test]
170 fn remember_tool_registration_requires_memory_opt_in() {
171 assert!(should_register_remember_tool(true));
172 assert!(!should_register_remember_tool(false));
173 }
174 }
175
175 lines RUST