88 "os"
99 "os/exec"
1010 "runtime"
11- "sync "
11+ "strings "
1212
1313 "github.com/docker/cagent/pkg/tools"
1414)
@@ -25,8 +25,6 @@ type shellHandler struct {
2525 shell string
2626 shellArgsPrefix []string
2727 env []string
28- mu sync.Mutex
29- processes []* os.Process
3028}
3129
3230type RunShellArgs struct {
@@ -40,74 +38,67 @@ func (h *shellHandler) RunShell(ctx context.Context, toolCall tools.ToolCall) (*
4038 return nil , fmt .Errorf ("invalid arguments: %w" , err )
4139 }
4240
43- cmd := exec .CommandContext ( ctx , h .shell , append (h .shellArgsPrefix , params .Cmd )... )
41+ cmd := exec .Command ( h .shell , append (h .shellArgsPrefix , params .Cmd )... )
4442 cmd .Env = h .env
4543 if params .Cwd != "" {
4644 cmd .Dir = params .Cwd
4745 } else {
48- // Use the current working directory; avoid PWD on Windows (may be MSYS-style like /c/...)
4946 if wd , err := os .Getwd (); err == nil {
5047 cmd .Dir = wd
5148 }
5249 }
5350
54- // Set up process group for proper cleanup
55- // On Unix: create new process group so we can kill the entire tree
5651 cmd .SysProcAttr = platformSpecificSysProcAttr ()
5752
58- // Note: On Windows, we would set CreationFlags, but that requires
59- // platform-specific code in a _windows.go file
60-
61- // Capture output using buffers
62- var outBuf , errBuf bytes.Buffer
53+ var outBuf bytes.Buffer
6354 cmd .Stdout = & outBuf
64- cmd .Stderr = & errBuf
55+ cmd .Stderr = & outBuf
6556
66- // Start the command so we can track it
6757 if err := cmd .Start (); err != nil {
6858 return & tools.ToolCallResult {
6959 Output : fmt .Sprintf ("Error starting command: %s" , err ),
7060 }, nil
7161 }
7262
73- // Track the process for cleanup
74- h .mu .Lock ()
75- h .processes = append (h .processes , cmd .Process )
76- h .mu .Unlock ()
77-
78- // Remove from tracking once complete
79- defer func () {
80- h .mu .Lock ()
81- for i , p := range h .processes {
82- if p != nil && p .Pid == cmd .Process .Pid {
83- h .processes = append (h .processes [:i ], h .processes [i + 1 :]... )
84- break
85- }
86- }
87- h .mu .Unlock ()
88- }()
89-
90- // Wait for the command to complete and get the result
91- err := cmd .Wait ()
92-
93- // Combine stdout and stderr
94- output := outBuf .String () + errBuf .String ()
95-
63+ pg , err := createProcessGroup (cmd .Process )
9664 if err != nil {
9765 return & tools.ToolCallResult {
98- Output : fmt .Sprintf ("Error executing command: %s \n Output : %s" , err , output ),
66+ Output : fmt .Sprintf ("Error creating process group : %s" , err ),
9967 }, nil
10068 }
10169
102- if output == "" {
70+ done := make (chan error , 1 )
71+ go func () {
72+ done <- cmd .Wait ()
73+ }()
74+
75+ select {
76+ case <- ctx .Done ():
77+ if cmd .Process != nil {
78+ _ = kill (cmd .Process , pg )
79+ }
10380 return & tools.ToolCallResult {
104- Output : "<no output> " ,
81+ Output : "Command cancelled " ,
10582 }, nil
106- }
83+ case err := <- done :
84+ output := outBuf .String ()
10785
108- return & tools.ToolCallResult {
109- Output : output ,
110- }, nil
86+ if err != nil {
87+ return & tools.ToolCallResult {
88+ Output : fmt .Sprintf ("Error executing command: %s\n Output: %s" , err , output ),
89+ }, nil
90+ }
91+
92+ if strings .TrimSpace (output ) == "" {
93+ return & tools.ToolCallResult {
94+ Output : "<no output>" ,
95+ }, nil
96+ }
97+
98+ return & tools.ToolCallResult {
99+ Output : fmt .Sprintf ("Output: %s" , output ),
100+ }, nil
101+ }
111102}
112103
113104func NewShellTool (env []string ) * ShellTool {
@@ -236,18 +227,5 @@ func (t *ShellTool) Start(context.Context) error {
236227}
237228
238229func (t * ShellTool ) Stop (context.Context ) error {
239- t .handler .mu .Lock ()
240- defer t .handler .mu .Unlock ()
241-
242- // Kill all tracked processes
243- for _ , proc := range t .handler .processes {
244- if proc != nil {
245- _ = kill (proc )
246- }
247- }
248-
249- // Clear the processes list
250- t .handler .processes = nil
251-
252230 return nil
253231}
0 commit comments