55 "encoding/json"
66 "fmt"
77 "io/fs"
8+ "log/slog"
89 "os"
10+ "os/exec"
911 "path/filepath"
1012 "regexp"
1113 "strings"
@@ -14,9 +16,16 @@ import (
1416 "github.com/docker/cagent/pkg/tools"
1517)
1618
19+ // PostEditConfig represents a post-edit command configuration
20+ type PostEditConfig struct {
21+ Path string // File path pattern (glob-style)
22+ Cmd string // Command to execute (with $path placeholder)
23+ }
24+
1725type FilesystemTool struct {
1826 allowedDirectories []string
1927 allowedTools []string
28+ postEditCommands []PostEditConfig
2029}
2130
2231type FileSystemOpt func (* FilesystemTool )
@@ -27,6 +36,12 @@ func WithAllowedTools(allowedTools []string) FileSystemOpt {
2736 }
2837}
2938
39+ func WithPostEditCommands (postEditCommands []PostEditConfig ) FileSystemOpt {
40+ return func (t * FilesystemTool ) {
41+ t .postEditCommands = postEditCommands
42+ }
43+ }
44+
3045func NewFilesystemTool (allowedDirectories []string , opts ... FileSystemOpt ) * FilesystemTool {
3146 t := & FilesystemTool {
3247 allowedDirectories : allowedDirectories ,
@@ -439,6 +454,34 @@ func (t *FilesystemTool) Tools(context.Context) ([]tools.Tool, error) {
439454 return allowedTools , nil
440455}
441456
457+ // executePostEditCommands executes any matching post-edit commands for the given file path
458+ func (t * FilesystemTool ) executePostEditCommands (ctx context.Context , filePath string ) error {
459+ if len (t .postEditCommands ) == 0 {
460+ return nil
461+ }
462+
463+ for _ , postEdit := range t .postEditCommands {
464+ matched , err := filepath .Match (postEdit .Path , filepath .Base (filePath ))
465+ if err != nil {
466+ slog .WarnContext (ctx , "Invalid post-edit pattern" , "pattern" , postEdit .Path , "error" , err )
467+ continue
468+ }
469+ if ! matched {
470+ continue
471+ }
472+
473+ cmd := exec .CommandContext (ctx , "/bin/sh" , "-c" , postEdit .Cmd )
474+ cmd .Env = cmd .Environ ()
475+ cmd .Env = append (cmd .Env , "path=" + filePath )
476+
477+ if err := cmd .Run (); err != nil {
478+ return fmt .Errorf ("post-edit command failed for %s: %w" , filePath , err )
479+ }
480+
481+ }
482+ return nil
483+ }
484+
442485// Security helper to check if path is allowed
443486func (t * FilesystemTool ) isPathAllowed (path string ) error {
444487 absPath , err := filepath .Abs (path )
@@ -554,7 +597,7 @@ func (t *FilesystemTool) buildDirectoryTree(path string, maxDepth *int, currentD
554597 return node , nil
555598}
556599
557- func (t * FilesystemTool ) handleEditFile (_ context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
600+ func (t * FilesystemTool ) handleEditFile (ctx context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
558601 var args struct {
559602 Path string `json:"path"`
560603 Edits []struct {
@@ -596,6 +639,11 @@ func (t *FilesystemTool) handleEditFile(_ context.Context, toolCall tools.ToolCa
596639 return & tools.ToolCallResult {Output : fmt .Sprintf ("Error writing file: %s" , err )}, nil
597640 }
598641
642+ // Execute post-edit commands
643+ if err := t .executePostEditCommands (ctx , args .Path ); err != nil {
644+ return & tools.ToolCallResult {Output : fmt .Sprintf ("File edited successfully but post-edit command failed: %s" , err )}, nil
645+ }
646+
599647 return & tools.ToolCallResult {Output : fmt .Sprintf ("File edited successfully. Changes:\n %s" , strings .Join (changes , "\n " ))}, nil
600648}
601649
@@ -1010,7 +1058,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, toolCall to
10101058 return & tools.ToolCallResult {Output : strings .Join (results , "\n " )}, nil
10111059}
10121060
1013- func (t * FilesystemTool ) handleWriteFile (_ context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
1061+ func (t * FilesystemTool ) handleWriteFile (ctx context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
10141062 var args struct {
10151063 Path string `json:"path"`
10161064 Content string `json:"content"`
@@ -1027,6 +1075,11 @@ func (t *FilesystemTool) handleWriteFile(_ context.Context, toolCall tools.ToolC
10271075 return & tools.ToolCallResult {Output : fmt .Sprintf ("Error writing file: %s" , err )}, nil
10281076 }
10291077
1078+ // Execute post-edit commands
1079+ if err := t .executePostEditCommands (ctx , args .Path ); err != nil {
1080+ return & tools.ToolCallResult {Output : fmt .Sprintf ("File written successfully but post-edit command failed: %s" , err )}, nil
1081+ }
1082+
10301083 return & tools.ToolCallResult {Output : fmt .Sprintf ("File written successfully: %s (%d bytes)" , args .Path , len (args .Content ))}, nil
10311084}
10321085
0 commit comments