mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-17 08:52:15 +00:00
5e2fa395c7
* 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>
72 lines
1.9 KiB
Go
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,
|
|
})
|
|
}
|