Remove coordinator/bizdev/storage data/dev fallbacks and register agent→workspace so default agent targets use workspace.DeliverableKey. Remaining U5 path and G3 task_runs clearance stay open.
116 lines
2.5 KiB
Go
116 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/config"
|
|
"chat-agent-system/internal/workspace"
|
|
)
|
|
|
|
type CursorCLIBackend struct {
|
|
cfg *config.Config
|
|
}
|
|
|
|
func (c *CursorCLIBackend) Name() string { return "cursor-cli" }
|
|
|
|
func (c *CursorCLIBackend) Execute(ctx context.Context, req Request) (*Result, error) {
|
|
if len(req.Targets) == 0 {
|
|
req.Targets = []string{workspace.DeliverableKey}
|
|
}
|
|
|
|
workDir := req.WorkDir
|
|
if !filepath.IsAbs(workDir) {
|
|
wd, _ := os.Getwd()
|
|
workDir = filepath.Join(wd, workDir)
|
|
}
|
|
|
|
payload, _ := json.Marshal(map[string]any{
|
|
"workspace": workDir,
|
|
"api_key": c.cfg.CursorAPIKey,
|
|
"model": c.cfg.CursorModel,
|
|
"prompt": req.Prompt,
|
|
"instructions": req.Instructions,
|
|
"targets": req.Targets,
|
|
})
|
|
|
|
bridgeBin := c.cfg.CursorBridgeBin
|
|
if !filepath.IsAbs(bridgeBin) {
|
|
wd, _ := os.Getwd()
|
|
bridgeBin = filepath.Join(wd, bridgeBin)
|
|
}
|
|
|
|
timeout := req.Timeout
|
|
if timeout <= 0 {
|
|
timeout = c.cfg.AgentTimeout
|
|
}
|
|
runCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(runCtx, bridgeBin)
|
|
cmd.Dir = workDir
|
|
cmd.Stdin = bytes.NewReader(payload)
|
|
cmd.Env = append(os.Environ(),
|
|
"CURSOR_API_KEY="+c.cfg.CursorAPIKey,
|
|
"CURSOR_MODEL="+c.cfg.CursorModel,
|
|
)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return &Result{
|
|
Success: false,
|
|
ErrorReason: fmt.Sprintf("cursor execution error: %v, stderr: %s", err, stderr.String()),
|
|
}, nil
|
|
}
|
|
|
|
type bridgeResponse struct {
|
|
OK bool `json:"ok"`
|
|
FallbackReason string `json:"fallback_reason"`
|
|
Stderr string `json:"stderr"`
|
|
}
|
|
var bResp bridgeResponse
|
|
lines := strings.Split(strings.TrimSpace(stdout.String()), "\n")
|
|
if len(lines) > 0 {
|
|
_ = json.Unmarshal([]byte(lines[len(lines)-1]), &bResp)
|
|
}
|
|
|
|
if !bResp.OK {
|
|
errMsg := bResp.FallbackReason
|
|
if bResp.Stderr != "" {
|
|
errMsg += " (" + bResp.Stderr + ")"
|
|
}
|
|
if errMsg == "" {
|
|
errMsg = "cursor bridge reported task failed"
|
|
}
|
|
return &Result{
|
|
Success: false,
|
|
ErrorReason: errMsg,
|
|
Logs: stdout.String(),
|
|
}, nil
|
|
}
|
|
|
|
patches := make(map[string][]byte)
|
|
for _, t := range req.Targets {
|
|
p := filepath.Join(workDir, t)
|
|
if b, err := os.ReadFile(p); err == nil {
|
|
patches[t] = b
|
|
}
|
|
}
|
|
|
|
return &Result{
|
|
Success: true,
|
|
Patches: patches,
|
|
Logs: stdout.String(),
|
|
}, nil
|
|
}
|