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:
committed by
weichen
co-authored by
Cursor
parent
6e4c8e319b
commit
260e1a6b8a
@@ -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,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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", "全阶段综合报价")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user