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>
186 lines
5.4 KiB
Go
186 lines
5.4 KiB
Go
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,
|
||
}
|
||
}
|