Files
Agent-System/internal/server/task_finalize.go
T
WeiCHENandCursor 6e489733ec feat(agent): unify async task FinalizeTaskRun projection to chat and runs
Extend taskrun executors to return full TaskRunResult, finalize assistant
placeholder content with artifact metadata, and map quality_failed terminal
state instead of metadata-only updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 16:46:49 +08:00

186 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/squad"
"chat-agent-system/internal/taskrun"
)
// FinalizeTaskRunProjection 将任务执行结果投影到 workflow_runs 与聊天助手消息(供回填/管理命令使用)。
func (s *Server) FinalizeTaskRunProjection(ctx context.Context, record *taskrun.RunRecord, result *bizdev.TaskRunResult) (string, error) {
return s.finalizeTaskRun(ctx, record, result)
}
// finalizeTaskRun 将任务执行结果投影到 workflow_runs 与聊天助手消息。
func (s *Server) finalizeTaskRun(ctx context.Context, record *taskrun.RunRecord, result *bizdev.TaskRunResult) (string, error) {
if record == nil || result == nil {
return "failed", fmt.Errorf("finalize: missing record or result")
}
workspaceID := result.WorkDir
if workspaceID == "" {
workspaceID = record.WorkspaceID
}
terminalStatus := resolveTerminalStatus(result)
summaryContent := buildTaskCompletionSummary(result, terminalStatus)
docID := ""
artifactTitle := result.ArtifactTitle
sha256Hex := ""
if result.DocRecord != nil {
docID = result.DocRecord.DocID
if result.DocRecord.ContentSHA256 != "" {
sha256Hex = result.DocRecord.ContentSHA256
}
}
if docID == "" && record.ProjectID > 0 && record.TaskKey != "" {
docID = fmt.Sprintf("doc_proj%d_%s", record.ProjectID, record.TaskKey)
}
if artifactTitle == "" && record.TaskKey != "" {
artifactTitle = squad.TaskArtifactTitle(record.TaskKey)
}
if sha256Hex == "" && workspaceID != "" {
if computed, err := hashOutputFile(workspaceID); err == nil {
sha256Hex = computed
}
}
meta := map[string]any{
"run_id": record.RunID,
"workspace_id": workspaceID,
"task_status": terminalStatus,
"task_key": record.TaskKey,
}
if record.ProjectID > 0 {
meta["project_id"] = record.ProjectID
}
if docID != "" {
artifact := map[string]any{
"doc_id": docID,
"title": artifactTitle,
"task_key": record.TaskKey,
"workspace_id": workspaceID,
}
if sha256Hex != "" {
artifact["sha256"] = sha256Hex
}
if result.QualityReport != nil {
artifact["quality_passed"] = result.QualityReport.Passed
artifact["quality_score"] = result.QualityReport.Score
if len(result.QualityReport.Violations) > 0 {
artifact["quality_violations"] = result.QualityReport.Violations
}
}
meta["artifact"] = artifact
}
if s.pwSvc != nil && record.ProjectID > 0 && record.RunID != "" {
if err := s.pwSvc.UpdateWorkflowRun(ctx, record.RunID, terminalStatus, docID, artifactTitle, ""); err != nil {
return "finalizing_failed", fmt.Errorf("update workflow run: %w", err)
}
}
if s.svc != nil && s.svc.ContextManager() != nil && record.AssistantMsgID > 0 {
if err := s.svc.ContextManager().FinalizeAssistantTaskMessage(ctx, record.AssistantMsgID, summaryContent, meta, terminalStatus); err != nil {
return "finalizing_failed", fmt.Errorf("finalize assistant message: %w", err)
}
}
return terminalStatus, nil
}
func resolveTerminalStatus(result *bizdev.TaskRunResult) string {
if result == nil || !result.Success {
return "failed"
}
if result.QualityReport != nil && !result.QualityReport.Passed {
return "quality_failed"
}
return "completed"
}
func buildTaskCompletionSummary(result *bizdev.TaskRunResult, terminalStatus string) string {
title := result.ArtifactTitle
if title == "" && result.TaskKey != "" {
title = squad.TaskArtifactTitle(result.TaskKey)
}
if title == "" {
title = "交付文档"
}
var b strings.Builder
fmt.Fprintf(&b, "《%s》已生成。\n\n", title)
switch terminalStatus {
case "quality_failed":
b.WriteString("当前状态:**质量检查未通过,待整改**")
if result.QualityReport != nil {
fmt.Fprintf(&b, "\n质量评分:%.0f/100", result.QualityReport.Score)
if len(result.QualityReport.Violations) > 0 {
fmt.Fprintf(&b, "\n主要问题:%s", strings.Join(result.QualityReport.Violations, ";"))
}
}
case "completed":
b.WriteString("当前状态:**已完成**")
if result.QualityReport != nil && result.QualityReport.Passed {
fmt.Fprintf(&b, "\n质量评分:%.0f/100", result.QualityReport.Score)
}
default:
if result.ErrorReason != "" {
fmt.Fprintf(&b, "执行未成功:%s", result.ErrorReason)
} else {
b.WriteString("任务执行失败。")
}
}
if result.DocRecord != nil && result.DocRecord.DocID != "" {
fmt.Fprintf(&b, "\n\n📄 受控文档:`%s`", result.DocRecord.DocID)
}
return b.String()
}
func hashOutputFile(workDir string) (string, error) {
path := filepath.Join(workDir, "output.md")
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
if len(data) == 0 {
return "", fmt.Errorf("output.md is empty")
}
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:]), nil
}
func squadResultToTaskRun(res *squad.SquadRunResult, taskKey string) *bizdev.TaskRunResult {
if res == nil {
return nil
}
return &bizdev.TaskRunResult{
TaskKey: res.TaskKey,
ArtifactTitle: res.ArtifactTitle,
MarkdownBody: res.MarkdownBody,
ExtractedData: res.ExtractedData,
Attachments: res.Attachments,
WorkDir: res.WorkDir,
BackendUsed: res.BackendUsed,
Duration: res.Duration,
Success: res.Success,
ErrorReason: res.ErrorReason,
QualityReport: res.QualityReport,
Manifest: res.Manifest,
AdvisorBrief: res.AdvisorBrief,
}
}