diff --git a/agent/acp/agent.go b/agent/acp/agent.go index 425095c..e36543e 100644 --- a/agent/acp/agent.go +++ b/agent/acp/agent.go @@ -22,6 +22,7 @@ type Agent struct { workDir string command string args []string + staticEnv map[string]string extraEnv []string sessionEnv []string authMethod string // optional, e.g. "cursor_login" for Cursor CLI (see authenticate RPC) @@ -81,6 +82,7 @@ func New(opts map[string]any) (core.Agent, error) { } args := parseStringSlice(opts["args"]) + staticEnv := envMapFromOpts(opts) extra := envPairsFromOpts(opts) authMethod, _ := opts["auth_method"].(string) authMethod = strings.TrimSpace(authMethod) @@ -96,6 +98,7 @@ func New(opts map[string]any) (core.Agent, error) { workDir: workDir, command: cmdStr, args: args, + staticEnv: staticEnv, extraEnv: extra, authMethod: authMethod, displayName: displayName, @@ -103,6 +106,29 @@ func New(opts map[string]any) (core.Agent, error) { }, nil } +func envMapFromOpts(opts map[string]any) map[string]string { + raw, ok := opts["env"] + if !ok || raw == nil { + return nil + } + switch m := raw.(type) { + case map[string]string: + out := make(map[string]string, len(m)) + for k, v := range m { + out[k] = v + } + return out + case map[string]any: + out := make(map[string]string, len(m)) + for k, v := range m { + out[k] = fmt.Sprint(v) + } + return out + default: + return nil + } +} + func envPairsFromOpts(opts map[string]any) []string { raw, ok := opts["env"] if !ok || raw == nil { @@ -163,6 +189,32 @@ func (a *Agent) GetWorkDir() string { return a.workDir } +func (a *Agent) WorkspaceAgentOptions() map[string]any { + a.mu.RLock() + defer a.mu.RUnlock() + + opts := map[string]any{ + "command": a.command, + } + if len(a.args) > 0 { + opts["args"] = append([]string(nil), a.args...) + } + if len(a.staticEnv) > 0 { + env := make(map[string]string, len(a.staticEnv)) + for k, v := range a.staticEnv { + env[k] = v + } + opts["env"] = env + } + if a.authMethod != "" { + opts["auth_method"] = a.authMethod + } + if a.displayName != "" { + opts["display_name"] = a.displayName + } + return opts +} + func (a *Agent) SetSessionEnv(env []string) { a.mu.Lock() a.sessionEnv = env diff --git a/agent/acp/agent_test.go b/agent/acp/agent_test.go index 0597200..245d4cb 100644 --- a/agent/acp/agent_test.go +++ b/agent/acp/agent_test.go @@ -1,6 +1,10 @@ package acp -import "testing" +import ( + "testing" + + "github.com/chenhg5/cc-connect/core" +) func TestNew_DisplayNameDefault(t *testing.T) { a, err := New(map[string]any{"command": "true"}) @@ -26,3 +30,43 @@ func TestNew_DisplayNameCustom(t *testing.T) { t.Fatalf("CLIDisplayName = %q, want Copilot ACP", got) } } + +func TestWorkspaceAgentOptions(t *testing.T) { + a, err := New(map[string]any{ + "command": "true", + "args": []any{"--acp", "--stdio"}, + "env": map[string]any{"FOO": "bar", "COPILOT_VALUE": "a=b"}, + "auth_method": "cursor_login", + "display_name": "Copilot ACP", + }) + if err != nil { + t.Fatal(err) + } + + agent := a.(*Agent) + agent.SetSessionEnv([]string{"SESSION_ONLY=1"}) + + snapshotter, ok := a.(core.WorkspaceAgentOptionSnapshotter) + if !ok { + t.Fatalf("agent does not implement WorkspaceAgentOptionSnapshotter") + } + opts := snapshotter.WorkspaceAgentOptions() + + if got, _ := opts["command"].(string); got != "true" { + t.Fatalf("command = %q, want true", got) + } + gotArgs, _ := opts["args"].([]string) + if len(gotArgs) != 2 || gotArgs[0] != "--acp" || gotArgs[1] != "--stdio" { + t.Fatalf("args = %#v, want [--acp --stdio]", gotArgs) + } + gotEnv, _ := opts["env"].(map[string]string) + if len(gotEnv) != 2 || gotEnv["FOO"] != "bar" || gotEnv["COPILOT_VALUE"] != "a=b" { + t.Fatalf("env = %#v, want config env only", gotEnv) + } + if got, _ := opts["auth_method"].(string); got != "cursor_login" { + t.Fatalf("auth_method = %q, want cursor_login", got) + } + if got, _ := opts["display_name"].(string); got != "Copilot ACP" { + t.Fatalf("display_name = %q, want Copilot ACP", got) + } +}