mirror of
https://github.com/chenhg5/cc-connect.git
synced 2026-07-06 13:40:51 +08:00
Make local workspace init opt-in and tighten media, session close, and workdir concurrency paths uncovered by the post-merge review. Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
package qoder
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/chenhg5/cc-connect/core"
|
|
)
|
|
|
|
func init() {
|
|
core.RegisterAgent("qoder", New)
|
|
}
|
|
|
|
// Agent drives Qoder CLI using `qodercli -p <prompt> -f stream-json`.
|
|
type Agent struct {
|
|
workDir string
|
|
model string
|
|
mode string // "default" | "yolo"
|
|
sessionEnv []string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func New(opts map[string]any) (core.Agent, error) {
|
|
workDir, _ := opts["work_dir"].(string)
|
|
if workDir == "" {
|
|
workDir = "."
|
|
}
|
|
model, _ := opts["model"].(string)
|
|
mode, _ := opts["mode"].(string)
|
|
mode = normalizeMode(mode)
|
|
|
|
if _, err := exec.LookPath("qodercli"); err != nil {
|
|
return nil, fmt.Errorf("qoder: 'qodercli' not found in PATH, install with: curl -fsSL https://qoder.com/install | bash")
|
|
}
|
|
|
|
return &Agent{
|
|
workDir: workDir,
|
|
model: model,
|
|
mode: mode,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeMode(raw string) string {
|
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
|
case "yolo", "bypass", "dangerously-skip-permissions":
|
|
return "yolo"
|
|
default:
|
|
return "default"
|
|
}
|
|
}
|
|
|
|
func (a *Agent) Name() string { return "qoder" }
|
|
func (a *Agent) CLIBinaryName() string { return "qodercli" }
|
|
func (a *Agent) CLIDisplayName() string { return "Qoder" }
|
|
|
|
func (a *Agent) SetWorkDir(dir string) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.workDir = dir
|
|
slog.Info("qoder: work_dir changed", "work_dir", dir)
|
|
}
|
|
|
|
func (a *Agent) GetWorkDir() string {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.workDir
|
|
}
|
|
|
|
func (a *Agent) SetModel(model string) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.model = model
|
|
slog.Info("qoder: model changed", "model", model)
|
|
}
|
|
|
|
func (a *Agent) GetModel() string {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.model
|
|
}
|
|
|
|
func (a *Agent) AvailableModels(_ context.Context) []core.ModelOption {
|
|
return []core.ModelOption{
|
|
{Name: "auto", Desc: "Auto (recommended)"},
|
|
{Name: "ultimate", Desc: "Ultimate (most capable)"},
|
|
{Name: "performance", Desc: "Performance (balanced)"},
|
|
{Name: "efficient", Desc: "Efficient (fast)"},
|
|
{Name: "lite", Desc: "Lite (lightweight)"},
|
|
}
|
|
}
|
|
|
|
func (a *Agent) SetSessionEnv(env []string) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.sessionEnv = env
|
|
}
|
|
|
|
func (a *Agent) StartSession(ctx context.Context, sessionID string) (core.AgentSession, error) {
|
|
a.mu.Lock()
|
|
mode := a.mode
|
|
model := a.model
|
|
workDir := a.workDir
|
|
extraEnv := append([]string{}, a.sessionEnv...)
|
|
a.mu.Unlock()
|
|
|
|
return newQoderSession(ctx, workDir, model, mode, sessionID, extraEnv)
|
|
}
|
|
|
|
func (a *Agent) ListSessions(_ context.Context) ([]core.AgentSessionInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (a *Agent) Stop() error { return nil }
|
|
|
|
// ── ModeSwitcher ─────────────────────────────────────────────
|
|
|
|
func (a *Agent) SetMode(mode string) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
a.mode = normalizeMode(mode)
|
|
slog.Info("qoder: mode changed", "mode", a.mode)
|
|
}
|
|
|
|
func (a *Agent) GetMode() string {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
return a.mode
|
|
}
|
|
|
|
func (a *Agent) PermissionModes() []core.PermissionModeInfo {
|
|
return []core.PermissionModeInfo{
|
|
{Key: "default", Name: "Default", NameZh: "默认", Desc: "Standard permissions", DescZh: "标准权限模式"},
|
|
{Key: "yolo", Name: "YOLO", NameZh: "全自动", Desc: "Skip all permission checks", DescZh: "跳过所有权限检查"},
|
|
}
|
|
}
|
|
|
|
// ── SkillProvider ────────────────────────────────────────────
|
|
|
|
func (a *Agent) SkillDirs() []string {
|
|
workDir := a.GetWorkDir()
|
|
absDir, err := filepath.Abs(workDir)
|
|
if err != nil {
|
|
absDir = workDir
|
|
}
|
|
dirs := []string{filepath.Join(absDir, ".claude", "skills")}
|
|
if home, err := os.UserHomeDir(); err == nil {
|
|
dirs = append(dirs, filepath.Join(home, ".claude", "skills"))
|
|
}
|
|
return dirs
|
|
}
|
|
|
|
// ── ContextCompressor ────────────────────────────────────────
|
|
|
|
func (a *Agent) CompressCommand() string { return "/compact" }
|
|
|
|
// ── MemoryFileProvider ───────────────────────────────────────
|
|
|
|
func (a *Agent) ProjectMemoryFile() string {
|
|
workDir := a.GetWorkDir()
|
|
absDir, err := filepath.Abs(workDir)
|
|
if err != nil {
|
|
absDir = workDir
|
|
}
|
|
return filepath.Join(absDir, "AGENTS.md")
|
|
}
|
|
|
|
func (a *Agent) GlobalMemoryFile() string {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return filepath.Join(homeDir, ".qoder", "AGENTS.md")
|
|
}
|