Skip to content

Commit 35f4f5f

Browse files
committed
more self-review fixes
1 parent 1c3c9aa commit 35f4f5f

3 files changed

Lines changed: 42 additions & 15 deletions

File tree

cmd/server/server.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,20 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
114114
var agentIO st.AgentIO
115115
var transport = "pty"
116116
var process *termexec.Process
117-
var acpWait func() error
117+
var acpResult *httpapi.SetupACPResult
118118

119119
if printOpenAPI {
120120
agentIO = nil
121121
} else if experimentalACP {
122-
acpResult, err := httpapi.SetupACP(ctx, httpapi.SetupACPConfig{
122+
var err error
123+
acpResult, err = httpapi.SetupACP(ctx, httpapi.SetupACPConfig{
123124
Program: agent,
124125
ProgramArgs: argsToPass[1:],
125126
})
126127
if err != nil {
127128
return xerrors.Errorf("failed to setup ACP: %w", err)
128129
}
129130
acpIO := acpResult.AgentIO
130-
acpWait = acpResult.Wait
131131
agentIO = acpIO
132132
transport = "acp"
133133
} else {
@@ -181,10 +181,11 @@ func runServer(ctx context.Context, logger *slog.Logger, argsToPass []string) er
181181
}()
182182
}
183183
// Wait for process exit in ACP mode
184-
if acpWait != nil {
184+
if acpResult != nil {
185185
go func() {
186186
defer close(processExitCh)
187-
if err := acpWait(); err != nil {
187+
defer close(acpResult.Done) // Signal cleanup goroutine to exit
188+
if err := acpResult.Wait(); err != nil {
188189
processExitCh <- xerrors.Errorf("ACP process exited: %w", err)
189190
}
190191
if err := srv.Stop(ctx); err != nil {

lib/httpapi/setup.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ type SetupACPConfig struct {
6969
// SetupACPResult contains the result of setting up an ACP process.
7070
type SetupACPResult struct {
7171
AgentIO *acpio.ACPAgentIO
72-
Wait func() error // Calls cmd.Wait() and returns exit error
72+
Wait func() error // Calls cmd.Wait() and returns exit error
73+
Done chan struct{} // Close this when Wait() returns to clean up goroutine
7374
}
7475

7576
func SetupACP(ctx context.Context, config SetupACPConfig) (*SetupACPResult, error) {
@@ -93,23 +94,35 @@ func SetupACP(ctx context.Context, config SetupACPConfig) (*SetupACPResult, erro
9394
return nil, fmt.Errorf("failed to start process: %w", err)
9495
}
9596

96-
agentIO, err := acpio.NewWithPipes(ctx, stdin, stdout, logger)
97+
agentIO, err := acpio.NewWithPipes(ctx, stdin, stdout, logger, os.Getwd)
9798
if err != nil {
9899
_ = cmd.Process.Kill()
99100
return nil, fmt.Errorf("failed to initialize ACP connection: %w", err)
100101
}
101102

103+
done := make(chan struct{})
102104
go func() {
103-
<-ctx.Done()
104-
logger.Info("Context done, closing ACP agent")
105-
_ = stdin.Close()
106-
_ = stdout.Close()
107-
_ = cmd.Process.Kill()
105+
select {
106+
case <-ctx.Done():
107+
logger.Info("Context done, closing ACP agent")
108+
_ = stdin.Close()
109+
_ = stdout.Close()
110+
// Try graceful shutdown first
111+
_ = cmd.Process.Signal(syscall.SIGTERM)
112+
// Force kill after timeout
113+
time.AfterFunc(5*time.Second, func() {
114+
_ = cmd.Process.Kill()
115+
})
116+
case <-done:
117+
// Process exited normally, nothing to clean up
118+
return
119+
}
108120
}()
109121

110122
return &SetupACPResult{
111123
AgentIO: agentIO,
112124
Wait: cmd.Wait,
125+
Done: done,
113126
}, nil
114127
}
115128

x/acpio/acpio.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log/slog"
88
"strings"
99
"sync"
10+
"time"
1011

1112
acp "github.com/coder/acp-go-sdk"
1213
st "github.com/coder/agentapi/lib/screentracker"
@@ -15,6 +16,9 @@ import (
1516
// Compile-time assertion that ACPAgentIO implements st.AgentIO
1617
var _ st.AgentIO = (*ACPAgentIO)(nil)
1718

19+
// DefaultPromptTimeout is the maximum time to wait for an agent response.
20+
const DefaultPromptTimeout = 5 * time.Minute
21+
1822
// ACPAgentIO implements screentracker.AgentIO using the ACP protocol
1923
type ACPAgentIO struct {
2024
ctx context.Context
@@ -131,7 +135,7 @@ func (a *ACPAgentIO) SetOnChunk(fn func(chunk string)) {
131135
}
132136

133137
// NewWithPipes creates an ACPAgentIO connected via the provided pipes
134-
func NewWithPipes(ctx context.Context, toAgent io.Writer, fromAgent io.Reader, logger *slog.Logger) (*ACPAgentIO, error) {
138+
func NewWithPipes(ctx context.Context, toAgent io.Writer, fromAgent io.Reader, logger *slog.Logger, getwd func() (string, error)) (*ACPAgentIO, error) {
135139
if logger == nil {
136140
logger = slog.Default()
137141
}
@@ -155,8 +159,13 @@ func NewWithPipes(ctx context.Context, toAgent io.Writer, fromAgent io.Reader, l
155159
logger.Debug("ACP initialized", "protocolVersion", initResp.ProtocolVersion)
156160

157161
// Create a session
162+
cwd, err := getwd()
163+
if err != nil {
164+
logger.Error("Failed to get working directory", "error", err)
165+
return nil, err
166+
}
158167
sessResp, err := conn.NewSession(ctx, acp.NewSessionRequest{
159-
Cwd: "/tmp",
168+
Cwd: cwd,
160169
McpServers: []acp.McpServer{},
161170
})
162171
if err != nil {
@@ -199,7 +208,11 @@ func (a *ACPAgentIO) Write(data []byte) (int, error) {
199208
"textLen", len(text),
200209
"rawDataLen", len(data))
201210

202-
resp, err := a.conn.Prompt(a.ctx, acp.PromptRequest{
211+
// Use a timeout to prevent hanging indefinitely
212+
promptCtx, cancel := context.WithTimeout(a.ctx, DefaultPromptTimeout)
213+
defer cancel()
214+
215+
resp, err := a.conn.Prompt(promptCtx, acp.PromptRequest{
203216
SessionId: a.sessionID,
204217
Prompt: []acp.ContentBlock{acp.TextBlock(text)},
205218
})

0 commit comments

Comments
 (0)