From 6152854fa0c4e232c4deb7e537981e1bd0fdebcf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 09:59:22 +0800 Subject: [PATCH] feat(cursor): add sandbox option to disable restricted tool mode Cursor Agent CLI defaults to sandbox mode in headless environments, limiting available tools to read-only operations (ls, etc.). Add a configurable `sandbox` option ("enabled"/"disabled"/"") that maps to the `--sandbox` CLI flag, allowing users to unlock full tool access (shell, edit, write) when running via cc-connect. Config example: [projects.agent.options] sandbox = "disabled" Made-with: Cursor --- agent/cursor/cursor.go | 17 ++++++++++++++++- agent/cursor/session.go | 8 +++++++- config.example.toml | 10 ++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/agent/cursor/cursor.go b/agent/cursor/cursor.go index f89971f..2ddca17 100644 --- a/agent/cursor/cursor.go +++ b/agent/cursor/cursor.go @@ -34,6 +34,7 @@ type Agent struct { model string mode string cmd string // CLI binary name, default "agent" + sandbox string // "enabled", "disabled", or "" (let CLI decide) providers []core.ProviderConfig activeIdx int sessionEnv []string @@ -52,6 +53,7 @@ func New(opts map[string]any) (core.Agent, error) { if cmd == "" { cmd = "agent" } + sandbox := normalizeSandbox(opts["sandbox"]) if _, err := exec.LookPath(cmd); err != nil { return nil, fmt.Errorf("cursor: %q CLI not found in PATH, install with: npm i -g @anthropic-ai/cursor-agent (or from Cursor IDE settings)", cmd) @@ -62,6 +64,7 @@ func New(opts map[string]any) (core.Agent, error) { model: model, mode: mode, cmd: cmd, + sandbox: sandbox, activeIdx: -1, }, nil } @@ -79,6 +82,18 @@ func normalizeMode(raw string) string { } } +func normalizeSandbox(v any) string { + s, _ := v.(string) + switch strings.ToLower(strings.TrimSpace(s)) { + case "enabled", "on", "true", "1": + return "enabled" + case "disabled", "off", "false", "0": + return "disabled" + default: + return "" + } +} + func (a *Agent) Name() string { return "cursor" } func (a *Agent) CLIBinaryName() string { return "agent" } func (a *Agent) CLIDisplayName() string { return "Cursor Agent" } @@ -202,7 +217,7 @@ func (a *Agent) StartSession(ctx context.Context, sessionID string) (core.AgentS } a.mu.Unlock() - return newCursorSession(ctx, cmd, a.workDir, model, mode, sessionID, extraEnv) + return newCursorSession(ctx, cmd, a.workDir, model, mode, a.sandbox, sessionID, extraEnv) } // ListSessions reads sessions from ~/.cursor/chats//. diff --git a/agent/cursor/session.go b/agent/cursor/session.go index c30fbf9..3fd691c 100644 --- a/agent/cursor/session.go +++ b/agent/cursor/session.go @@ -26,6 +26,7 @@ type cursorSession struct { workDir string model string mode string + sandbox string // "enabled", "disabled", or "" extraEnv []string events chan core.Event chatID atomic.Value // stores string — Cursor chat/session ID @@ -37,7 +38,7 @@ type cursorSession struct { thinkingBuf strings.Builder // accumulate thinking deltas } -func newCursorSession(ctx context.Context, cmd, workDir, model, mode, resumeID string, extraEnv []string) (*cursorSession, error) { +func newCursorSession(ctx context.Context, cmd, workDir, model, mode, sandbox, resumeID string, extraEnv []string) (*cursorSession, error) { sessionCtx, cancel := context.WithCancel(ctx) cs := &cursorSession{ @@ -45,6 +46,7 @@ func newCursorSession(ctx context.Context, cmd, workDir, model, mode, resumeID s workDir: workDir, model: model, mode: mode, + sandbox: sandbox, extraEnv: extraEnv, events: make(chan core.Event, 64), ctx: sessionCtx, @@ -89,6 +91,10 @@ func (cs *cursorSession) Send(prompt string, images []core.ImageAttachment, file args = append(args, "--mode", "ask") } + if cs.sandbox != "" { + args = append(args, "--sandbox", cs.sandbox) + } + if isResume { args = append(args, "--resume", chatID) } diff --git a/config.example.toml b/config.example.toml index fee3003..24c3285 100644 --- a/config.example.toml +++ b/config.example.toml @@ -911,8 +911,9 @@ app_secret = "your-feishu-app-secret" # # [projects.agent.options] # work_dir = "/path/to/project" -# mode = "force" # "default" | "force" (yolo) | "plan" | "ask" -# cmd = "agent" # CLI binary name (default: "agent") / CLI 二进制名称(默认 "agent") +# mode = "force" # "default" | "force" (yolo) | "plan" | "ask" +# cmd = "agent" # CLI binary name (default: "agent") / CLI 二进制名称(默认 "agent") +# sandbox = "disabled" # "enabled" | "disabled" | "" (let CLI decide) # # Mode options / 模式说明: # - "default": Trust workspace, ask before tool use / 信任工作区,工具调用前询问 @@ -920,6 +921,11 @@ app_secret = "your-feishu-app-secret" # - "plan": Read-only analysis, no edits / 只读分析,不做修改 # - "ask": Q&A style, read-only / 问答风格,只读 # +# Sandbox options / 沙箱模式说明: +# - "disabled": Full tool access (shell, edit, write, etc.) / 完整工具访问(shell、edit、write 等) +# - "enabled": Restricted tools (read-only, ls, etc.) / 受限工具(只读、ls 等) +# - "": Let Cursor CLI decide (may default to sandbox in headless) / 由 CLI 决定(headless 可能默认沙箱) +# # Optional: specify a model / 可选:指定模型 # model = "claude-sonnet-4-20250514" #