Files
goclaw/internal/providers/acp/session.go
T
Goon 5e2fa395c7 feat(providers): add ACP provider for external coding agents (#190)
* feat(providers): add ACP provider for orchestrating external coding agents (#189)

Implement native Go ACP (Agent Client Protocol) client as a new Provider.
Enables GoClaw to orchestrate any ACP-compatible agent (Claude Code, Codex
CLI, Gemini CLI) as a subprocess via JSON-RPC 2.0 over stdio.

- Add bidirectional JSON-RPC 2.0 transport over stdio pipes
- Add subprocess process pool with idle TTL reaping and crash recovery
- Add ACP session lifecycle (initialize, session/new, session/prompt)
- Add tool bridge for agent-initiated fs/terminal/permission requests
- Add workspace sandboxing, shell deny patterns, and env var filtering
- Wire config-based and DB-based provider registration paths
- Export DefaultDenyPatterns from tools package for reuse

* feat(providers): add changelog entry for ACP provider integration

* fix(tools): prevent workspace traversal bypass via /tmp/ fallback in resolveMediaPath

Reject paths containing ".." in the isInTempDir fallback to prevent
workspace escape where traversal path still resolves inside /tmp/.

* fix(tools): block workspace-sibling paths in resolveMediaPath /tmp/ fallback

When workspace is inside /tmp/, traversal paths like workspace/../X
resolve to /tmp/ siblings that pass isInTempDir. Reject paths inside
the workspace parent directory to prevent this escape.

* feat(providers): add ACP provider web UI and live reload via pubsub

Web UI for creating/editing ACP providers with dedicated form fields
(binary, args, idle TTL, permission mode, work directory). ACP providers
now update immediately without gateway restart via cache invalidation
pubsub pattern.

Frontend:
- New ACPSection form component with i18n (en/vi/zh)
- Provider form dialog integration with ACP state management
- ACP type badge on providers list page
- Settings field added to provider TypeScript types

Backend:
- ACP models handler (claude/codex/gemini) without API key requirement
- Binary path validation + LookPath verification in verify handler
- Provider CRUD emits cache.invalidate events via msgBus
- Subscriber in gateway_managed.go re-registers ACP providers from DB
- ACP core improvements from code review (helpers, jsonrpc, process,
  terminal, tool_bridge)

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-14 16:16:08 +07:00

72 lines
1.9 KiB
Go

package acp
import (
"context"
"fmt"
"time"
)
// Initialize sends the ACP initialize request to establish capabilities.
func (p *ACPProcess) Initialize(ctx context.Context) error {
req := InitializeRequest{
ClientInfo: ClientInfo{Name: "goclaw", Version: "1.0"},
Capabilities: ClientCaps{
Fs: &FsCaps{ReadTextFile: true, WriteTextFile: true},
Terminal: &TerminalCaps{Enabled: true},
},
}
var resp InitializeResponse
if err := p.conn.Call(ctx, "initialize", req, &resp); err != nil {
return fmt.Errorf("acp initialize: %w", err)
}
p.agentCaps = resp.Capabilities
return nil
}
// NewSession creates a new ACP session on this process.
func (p *ACPProcess) NewSession(ctx context.Context) error {
var resp NewSessionResponse
if err := p.conn.Call(ctx, "session/new", NewSessionRequest{}, &resp); err != nil {
return fmt.Errorf("acp session/new: %w", err)
}
p.sessionID = resp.SessionID
return nil
}
// Prompt sends user content and blocks until the agent responds.
// onUpdate is called for each session/update notification (streaming).
func (p *ACPProcess) Prompt(ctx context.Context, content []ContentBlock, onUpdate func(SessionUpdate)) (*PromptResponse, error) {
p.inUse.Add(1)
defer p.inUse.Add(-1)
p.mu.Lock()
p.lastActive = time.Now()
p.mu.Unlock()
// Install the update callback for this prompt
p.setUpdateFn(onUpdate)
defer p.setUpdateFn(nil)
req := PromptRequest{
SessionID: p.sessionID,
Content: content,
}
var resp PromptResponse
if err := p.conn.Call(ctx, "session/prompt", req, &resp); err != nil {
return nil, fmt.Errorf("acp session/prompt: %w", err)
}
p.mu.Lock()
p.lastActive = time.Now()
p.mu.Unlock()
return &resp, nil
}
// Cancel sends a session/cancel notification for cooperative cancellation.
func (p *ACPProcess) Cancel() error {
return p.conn.Notify("session/cancel", CancelNotification{
SessionID: p.sessionID,
})
}