fix(all): resolve whole-branch review findings on Excel labels, persona forwarding, and error handling

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WeiCHEN
2026-09-13 02:50:18 +00:00
committed by weichen
co-authored by Cursor
parent 6e4c8e319b
commit 260e1a6b8a
11 changed files with 105 additions and 21 deletions
+4
View File
@@ -41,6 +41,10 @@ func main() {
os.Exit(1)
}
if !res.Success {
fmt.Fprintf(os.Stderr, "警告: 后端执行未完全成功 [%s]: %s\n", res.TaskKey, res.ErrorReason)
}
fmt.Printf("=== 任务执行成功 [%s] ===\n", res.TaskKey)
fmt.Printf("耗时: %v\n", res.Duration)
fmt.Printf("工作区: %s\n", res.WorkDir)
+2 -1
View File
@@ -2,12 +2,13 @@ module chat-agent-system
go 1.27.1
require github.com/xuri/excelize/v2 v2.11.0
require (
github.com/richardlehane/mscfb v1.0.7 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/tiendc/go-deepcopy v1.7.2 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/excelize/v2 v2.11.0 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
+10
View File
@@ -1,7 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/richardlehane/mscfb v1.0.7 h1:oeoiM0WE79vHwE8RpIYYvIAc8ajTH2mb6UZm55/+EB0=
github.com/richardlehane/mscfb v1.0.7/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
@@ -12,7 +18,11 @@ github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBL
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1
View File
@@ -28,6 +28,7 @@ func (c *CursorCLIBackend) Execute(ctx context.Context, req Request) (*Result, e
"api_key": c.cfg.CursorAPIKey,
"model": c.cfg.CursorModel,
"prompt": req.Prompt,
"instructions": req.Instructions,
"targets": req.Targets,
})
+2
View File
@@ -28,6 +28,7 @@ func (p *PiAgentBackend) Execute(ctx context.Context, req Request) (*Result, err
"model": p.cfg.PiAgentModel,
"provider": p.cfg.PiAgentProvider,
"prompt": req.Prompt,
"instructions": req.Instructions,
"targets": req.Targets,
})
@@ -51,6 +52,7 @@ func (p *PiAgentBackend) Execute(ctx context.Context, req Request) (*Result, err
"PI_AGENT_BIN="+p.cfg.PiAgentBin,
"PI_AGENT_MODEL="+p.cfg.PiAgentModel,
"PI_AGENT_PROVIDER="+p.cfg.PiAgentProvider,
"PI_AGENT_SYSTEM_PROMPT_FILE=AGENTS.md",
)
var stdout, stderr bytes.Buffer
+25 -9
View File
@@ -69,6 +69,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
return nil, err
}
snapshotTs := time.Now().UnixNano()
vars := map[string]string{
"project_name": req.ProjectName,
"product_name": req.ProjectName,
@@ -77,6 +78,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
"intended_use": req.IntendedUse,
"target_market": req.TargetMarket,
"generated_at": time.Now().Format("2006-01-02 15:04:05"),
"snapshot_id": fmt.Sprintf("snap_%s_%d", req.TaskKey, snapshotTs),
}
for k, v := range req.Fields {
vars[k] = v
@@ -86,6 +88,9 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
inst, _ := template.GetAgentInstruction("writer")
_ = os.WriteFile(filepath.Join(workDir, "AGENTS.md"), []byte(inst), 0o644)
// Some backends look for a different instructions filename; write both
// unconditionally so persona/role guidance reaches whichever bridge runs.
_ = os.WriteFile(filepath.Join(workDir, "INSTRUCTIONS.md"), []byte(inst), 0o644)
taskPrompt := fmt.Sprintf("# 任务执行: %s\n\n请按 template.md 的指导撰写正文并交付到 output.md。\n", req.TaskKey)
_ = os.WriteFile(filepath.Join(workDir, "TASK.md"), []byte(taskPrompt), 0o644)
@@ -111,6 +116,17 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
return nil, fmt.Errorf("backend execute: %w", err)
}
res := &TaskRunResult{
Success: true,
}
if backendRes != nil && !backendRes.Success {
res.Success = false
res.ErrorReason = backendRes.ErrorReason
if backendName != "stub" {
return nil, fmt.Errorf("backend execution failed: %s", backendRes.ErrorReason)
}
}
markdownBody := renderedTmpl
if backendRes != nil && len(backendRes.Patches["output.md"]) > 0 && backendName != "stub" {
markdownBody = string(backendRes.Patches["output.md"])
@@ -147,13 +163,13 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
})
}
return &TaskRunResult{
TaskKey: req.TaskKey,
ArtifactTitle: taskArtifactTitle(req.TaskKey),
MarkdownBody: markdownBody,
Attachments: attachments,
WorkDir: workDir,
BackendUsed: backendName,
Duration: time.Since(start),
}, nil
res.TaskKey = req.TaskKey
res.ArtifactTitle = taskArtifactTitle(req.TaskKey)
res.MarkdownBody = markdownBody
res.Attachments = attachments
res.WorkDir = workDir
res.BackendUsed = backendName
res.Duration = time.Since(start)
return res, nil
}
+2
View File
@@ -46,6 +46,8 @@ type TaskRunResult struct {
WorkDir string `json:"work_dir"`
BackendUsed string `json:"backend_used"`
Duration time.Duration `json:"duration"`
Success bool `json:"success"`
ErrorReason string `json:"error_reason"`
}
type BizdevService interface {
+13
View File
@@ -80,6 +80,15 @@ func CalculateAndFill(in Input, templateXlsx string, outPath string) (*Summary,
_ = f.SetCellValue(sheet, "B4", in.ProductName)
_ = f.SetCellValue(sheet, "B5", totalPrice)
_ = f.SetCellValue(sheet, "A7", "报价代码")
_ = f.SetCellValue(sheet, "B7", "服务阶段与内容")
_ = f.SetCellValue(sheet, "D7", "人年(PM)")
_ = f.SetCellValue(sheet, "E7", "基础成本(元)")
_ = f.SetCellValue(sheet, "F7", "缓冲金(20%)")
_ = f.SetCellValue(sheet, "G7", "毛利(40%)")
_ = f.SetCellValue(sheet, "H7", "税费(7%)")
_ = f.SetCellValue(sheet, "J7", "综合报价(元)")
writeSummaryLine(f, sheet, 8, summary.Lines[0])
writeSummaryLine(f, sheet, 9, summary.Lines[1])
writeSummaryLine(f, sheet, 10, summary.Lines[2])
@@ -93,6 +102,8 @@ func CalculateAndFill(in Input, templateXlsx string, outPath string) (*Summary,
totalTax += line.Tax
}
writeSummaryLine(f, sheet, 18, SummaryLine{
Code: "总计",
Name: "全阶段综合报价",
PersonYear: totalPersonYear,
Cost: totalCost,
Buffer: totalBuffer,
@@ -113,6 +124,8 @@ func CalculateAndFill(in Input, templateXlsx string, outPath string) (*Summary,
}
func writeSummaryLine(f *excelize.File, sheet string, row int, line SummaryLine) {
_ = f.SetCellValue(sheet, fmt.Sprintf("A%d", row), line.Code)
_ = f.SetCellValue(sheet, fmt.Sprintf("B%d", row), line.Name)
_ = f.SetCellValue(sheet, fmt.Sprintf("D%d", row), line.PersonYear)
_ = f.SetCellValue(sheet, fmt.Sprintf("E%d", row), line.Cost)
_ = f.SetCellValue(sheet, fmt.Sprintf("F%d", row), line.Buffer)
+24
View File
@@ -6,6 +6,7 @@ import (
"testing"
"chat-agent-system/internal/quote"
"github.com/xuri/excelize/v2"
)
func TestCDOQuoteGeneration(t *testing.T) {
@@ -39,4 +40,27 @@ func TestCDOQuoteGeneration(t *testing.T) {
if len(summary.Notes) != 7 {
t.Errorf("expected 7 statutory notes, got %d", len(summary.Notes))
}
xf, err := excelize.OpenFile(outXlsx)
if err != nil {
t.Fatalf("failed to reopen output xlsx: %v", err)
}
defer xf.Close()
sheet := "报价模型"
assertCell := func(cell, want string) {
got, err := xf.GetCellValue(sheet, cell)
if err != nil {
t.Fatalf("failed to read cell %s: %v", cell, err)
}
if got != want {
t.Errorf("cell %s: expected %q, got %q", cell, want, got)
}
}
assertCell("A7", "报价代码")
assertCell("A8", "ID2")
assertCell("B8", "产品规划")
assertCell("A18", "总计")
assertCell("B18", "全阶段综合报价")
}
+7
View File
@@ -1,14 +1,21 @@
package template
import (
"regexp"
"strings"
)
var unresolvedPlaceholderPattern = regexp.MustCompile(`\{\{[a-zA-Z0-9_]+\}\}`)
func RenderDoc(tmpl string, vars map[string]string) string {
res := tmpl
for k, v := range vars {
placeholder := "{{" + k + "}}"
res = strings.ReplaceAll(res, placeholder, v)
}
// Any variable that wasn't provided leaves a raw {{placeholder}} in the
// output. Replace those with a clear "待确认" marker instead of leaking
// template syntax into delivered documents.
res = unresolvedPlaceholderPattern.ReplaceAllString(res, "待确认")
return res
}
+5 -1
View File
@@ -8,6 +8,7 @@
* "api_key": "...",
* "model": "auto",
* "prompt": "...",
* "instructions": "...",
* "targets": ["docs/RD-UN.md"]
* }
*
@@ -81,6 +82,7 @@ async function main() {
const apiKey = sanitizeApiKey(req.api_key || process.env.CURSOR_API_KEY || "");
const model = (req.model || process.env.CURSOR_MODEL || "auto").trim();
const prompt = req.prompt || "";
const instructions = req.instructions || "";
const targets = Array.isArray(req.targets) ? req.targets : [];
if (!workspace || !existsSync(workspace)) {
@@ -106,10 +108,12 @@ async function main() {
return;
}
const fullPrompt = instructions ? `${instructions}\n\n---\n\n${prompt}` : prompt;
let agentId = "";
let runId = "";
try {
const result = await Agent.prompt(prompt, {
const result = await Agent.prompt(fullPrompt, {
apiKey,
model: { id: model },
local: {