@@ -122,12 +122,20 @@ impl HookExecutor {
122122 }
123123
124124 if let Err ( err) = & result {
125+ let hook_desc = if let Some ( tool_name) = & hook. 1 . tool_name {
126+ format ! ( "tool:{}" , tool_name)
127+ } else if let Some ( command) = & hook. 1 . command {
128+ command. clone ( )
129+ } else {
130+ "unknown hook" . to_string ( )
131+ } ;
132+
125133 queue ! (
126134 output,
127135 style:: SetForegroundColor ( style:: Color :: Red ) ,
128136 style:: Print ( "✗ " ) ,
129137 style:: SetForegroundColor ( style:: Color :: Blue ) ,
130- style:: Print ( & hook . 1 . command ) ,
138+ style:: Print ( & hook_desc ) ,
131139 style:: ResetColor ,
132140 style:: Print ( " failed after " ) ,
133141 style:: SetForegroundColor ( style:: Color :: Yellow ) ,
@@ -189,8 +197,94 @@ impl HookExecutor {
189197 ) -> ( ( HookTrigger , Hook ) , Result < String > , Duration ) {
190198 let start_time = Instant :: now ( ) ;
191199
192- let command = & hook. 1 . command ;
200+ let result = if let Some ( tool_name) = & hook. 1 . tool_name {
201+ // Execute tool
202+ self . execute_tool ( tool_name, & hook. 1 . tool_args , prompt) . await
203+ } else if let Some ( command) = & hook. 1 . command {
204+ // Execute shell command
205+ self . execute_command ( command, & hook. 1 , prompt) . await
206+ } else {
207+ Err ( eyre ! ( "Hook must have either command or tool_name" ) )
208+ } ;
209+
210+ ( hook, result, start_time. elapsed ( ) )
211+ }
212+
213+ async fn execute_tool (
214+ & self ,
215+ tool_name : & str ,
216+ tool_args : & Option < serde_json:: Value > ,
217+ prompt : Option < & str > ,
218+ ) -> Result < String > {
219+ // Replace ${USER_PROMPT} in tool_args if prompt is provided
220+ let mut substituted_args = tool_args. clone ( ) ;
221+ if let ( Some ( args) , Some ( user_prompt) ) = ( & mut substituted_args, prompt) {
222+ if let Some ( obj) = args. as_object_mut ( ) {
223+ for ( _, value) in obj {
224+ if let Some ( s) = value. as_str ( ) {
225+ * value = serde_json:: Value :: String ( s. replace ( "${USER_PROMPT}" , user_prompt) ) ;
226+ }
227+ }
228+ }
229+ }
230+ use crate :: cli:: chat:: tools:: Tool ;
231+ use crate :: os:: Os ;
232+ use std:: collections:: HashMap ;
233+
234+ // Create OS instance for tool execution
235+ let os = Os :: new ( ) . await ?;
236+
237+ // Parse tool arguments
238+ let default_args = serde_json:: Value :: Object ( serde_json:: Map :: new ( ) ) ;
239+ let args = substituted_args. as_ref ( ) . unwrap_or ( & default_args) ;
240+
241+ // Create tool instance based on name and arguments
242+ let tool = match tool_name {
243+ "context" => {
244+ let context_tool: crate :: cli:: chat:: tools:: context:: Context =
245+ serde_json:: from_value ( args. clone ( ) ) ?;
246+ Tool :: Context ( context_tool)
247+ } ,
248+ "fs_read" => {
249+ let fs_read_tool: crate :: cli:: chat:: tools:: fs_read:: FsRead =
250+ serde_json:: from_value ( args. clone ( ) ) ?;
251+ Tool :: FsRead ( fs_read_tool)
252+ } ,
253+ "fs_write" => {
254+ let fs_write_tool: crate :: cli:: chat:: tools:: fs_write:: FsWrite =
255+ serde_json:: from_value ( args. clone ( ) ) ?;
256+ Tool :: FsWrite ( fs_write_tool)
257+ } ,
258+ "execute_bash" | "execute_cmd" => {
259+ let execute_tool: crate :: cli:: chat:: tools:: execute:: ExecuteCommand =
260+ serde_json:: from_value ( args. clone ( ) ) ?;
261+ Tool :: ExecuteCommand ( execute_tool)
262+ } ,
263+ "use_aws" => {
264+ let aws_tool: crate :: cli:: chat:: tools:: use_aws:: UseAws =
265+ serde_json:: from_value ( args. clone ( ) ) ?;
266+ Tool :: UseAws ( aws_tool)
267+ } ,
268+ _ => return Err ( eyre ! ( "Unsupported tool: {}" , tool_name) ) ,
269+ } ;
270+
271+ // Execute the tool
272+ let mut output = Vec :: new ( ) ;
273+ let mut line_tracker = HashMap :: new ( ) ;
274+ let invoke_result = tool. invoke ( & os, & mut output, & mut line_tracker, None ) . await ?;
275+
276+ let result = invoke_result. as_str ( ) . to_string ( ) ;
277+
278+ // Return the tool result as string
279+ Ok ( result)
280+ }
193281
282+ async fn execute_command (
283+ & self ,
284+ command : & str ,
285+ hook : & Hook ,
286+ prompt : Option < & str > ,
287+ ) -> Result < String > {
194288 #[ cfg( unix) ]
195289 let mut cmd = tokio:: process:: Command :: new ( "bash" ) ;
196290 #[ cfg( unix) ]
@@ -211,7 +305,7 @@ impl HookExecutor {
211305 . stdout ( Stdio :: piped ( ) )
212306 . stderr ( Stdio :: piped ( ) ) ;
213307
214- let timeout = Duration :: from_millis ( hook. 1 . timeout_ms ) ;
308+ let timeout = Duration :: from_millis ( hook. timeout_ms ) ;
215309
216310 // Set USER_PROMPT environment variable if provided
217311 if let Some ( prompt) = prompt {
@@ -223,14 +317,14 @@ impl HookExecutor {
223317 let command_future = cmd. output ( ) ;
224318
225319 // Run with timeout
226- let result = match tokio:: time:: timeout ( timeout, command_future) . await {
320+ match tokio:: time:: timeout ( timeout, command_future) . await {
227321 Ok ( Ok ( result) ) => {
228322 if result. status . success ( ) {
229323 let stdout = result. stdout . to_str_lossy ( ) ;
230324 let stdout = format ! (
231325 "{}{}" ,
232- truncate_safe( & stdout, hook. 1 . max_output_size) ,
233- if stdout. len( ) > hook. 1 . max_output_size {
326+ truncate_safe( & stdout, hook. max_output_size) ,
327+ if stdout. len( ) > hook. max_output_size {
234328 " ... truncated"
235329 } else {
236330 ""
@@ -243,9 +337,7 @@ impl HookExecutor {
243337 } ,
244338 Ok ( Err ( err) ) => Err ( eyre ! ( "failed to execute command: {}" , err) ) ,
245339 Err ( _) => Err ( eyre ! ( "command timed out after {} ms" , timeout. as_millis( ) ) ) ,
246- } ;
247-
248- ( hook, result, start_time. elapsed ( ) )
340+ }
249341 }
250342
251343 /// Will return a cached hook's output if it exists and isn't expired.
@@ -303,7 +395,14 @@ impl HooksArgs {
303395 true => writeln ! ( & mut out, "<none>" ) ?,
304396 false => {
305397 for hook in hooks {
306- writeln ! ( & mut out, " - {}" , hook. command) ?;
398+ let hook_desc = if let Some ( tool_name) = & hook. tool_name {
399+ format ! ( "tool: {}" , tool_name)
400+ } else if let Some ( command) = & hook. command {
401+ command. clone ( )
402+ } else {
403+ "unknown hook" . to_string ( )
404+ } ;
405+ writeln ! ( & mut out, " - {}" , hook_desc) ?;
307406 }
308407 } ,
309408 }
0 commit comments