package server import ( "context" "crypto/sha256" "encoding/hex" "fmt" "os" "path/filepath" "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 := result.TerminalStatus() summaryContent := result.CompletionSummary() 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 art := result.ArtifactMetadata(); art != nil { if workspaceID != "" { art["workspace_id"] = workspaceID } if sha256Hex != "" { art["sha256"] = sha256Hex } if artifactTitle != "" { art["title"] = artifactTitle } if record.TaskKey != "" { art["task_key"] = record.TaskKey } if docID != "" { art["doc_id"] = docID } meta["artifact"] = art } else 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 { return result.TerminalStatus() } func buildTaskCompletionSummary(result *bizdev.TaskRunResult, terminalStatus string) string { _ = terminalStatus return result.CompletionSummary() } 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, } }