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, } }