package bizdev import ( "fmt" "strings" "chat-agent-system/internal/squad" ) // Terminal task statuses projected to chat / workflow_runs. const ( TaskStatusCompleted = "completed" TaskStatusFailed = "failed" TaskStatusQualityFailed = "quality_failed" TaskStatusPendingAuthorConfirmation = "pending_author_confirmation" ) // AwaitsAgentConfirmation reports high/critical revision candidate awaiting author decision. // Distinct from DocGov change-proposal intercept (PendingProposal). func (r *TaskRunResult) AwaitsAgentConfirmation() bool { return r != nil && r.PendingAgentCandidateID > 0 } // IsProposalIntercept reports claimed-document DocGov change-proposal interception. func (r *TaskRunResult) IsProposalIntercept() bool { return r != nil && r.PendingProposal != nil && r.RequiresAuthorApproval } // TerminalStatus maps TaskRunResult to a conversation/workflow terminal status. // Pending agent confirmation is never "completed" until commit+projection. // Quality failure stays quality_failed even when Success was cleared to block Materialize. func (r *TaskRunResult) TerminalStatus() string { if r == nil { return TaskStatusFailed } if r.QualityReport != nil && !r.QualityReport.Passed { return TaskStatusQualityFailed } if !r.Success { return TaskStatusFailed } if r.AwaitsAgentConfirmation() { return TaskStatusPendingAuthorConfirmation } return TaskStatusCompleted } // CompletionSummary builds the assistant-visible completion text for sync and async finalizers. func (r *TaskRunResult) CompletionSummary() string { if r == nil { return "任务执行失败。" } title := r.ArtifactTitle if title == "" && r.TaskKey != "" { title = squad.TaskArtifactTitle(r.TaskKey) } if title == "" { title = "交付文档" } status := r.TerminalStatus() var b strings.Builder switch status { case TaskStatusPendingAuthorConfirmation: fmt.Fprintf(&b, "《%s》文档候选已生成,待编制人确认。\n\n", title) b.WriteString("当前状态:**待编制人确认**") if r.PendingRiskLevel != "" { fmt.Fprintf(&b, "(风险:%s)", r.PendingRiskLevel) } b.WriteString("\n正式正文仍为账本当前 head;候选正文仅保留在不可变快照中。请打开文档「待确认报告」确认提交或拒绝后,正式正文才会更新。") if r.PendingConfirmationNote != "" { fmt.Fprintf(&b, "\n%s", r.PendingConfirmationNote) } fmt.Fprintf(&b, "\n候选 ID:%d", r.PendingAgentCandidateID) if r.PendingChangeReportID > 0 { fmt.Fprintf(&b, " · 变更报告:%d", r.PendingChangeReportID) } case TaskStatusQualityFailed: fmt.Fprintf(&b, "《%s》已生成。\n\n", title) b.WriteString("当前状态:**质量检查未通过,待整改**") if r.QualityReport != nil { fmt.Fprintf(&b, "\n质量评分:%.0f/100", r.QualityReport.Score) if len(r.QualityReport.Violations) > 0 { fmt.Fprintf(&b, "\n主要问题:%s", strings.Join(r.QualityReport.Violations, ";")) } } case TaskStatusCompleted: fmt.Fprintf(&b, "《%s》已生成。\n\n", title) b.WriteString("当前状态:**已完成**") if r.QualityReport != nil && r.QualityReport.Passed { fmt.Fprintf(&b, "\n质量评分:%.0f/100", r.QualityReport.Score) } if r.DocRecord == nil && controlledDeliverableRequiresProject(r.TaskKey) { b.WriteString("\n交付物已生成于工作区,尚未写入受控文档账本。") } default: fmt.Fprintf(&b, "《%s》未成功落盘。\n\n", title) if r.ErrorReason != "" { fmt.Fprintf(&b, "执行未成功:%s", r.ErrorReason) } else { b.WriteString("任务执行失败。") } } if r.DocRecord != nil && r.DocRecord.DocID != "" { fmt.Fprintf(&b, "\n\n📄 受控文档:`%s`", r.DocRecord.DocID) } return b.String() } // ArtifactMetadata returns chat metadata fields for a clickable document entry. func (r *TaskRunResult) ArtifactMetadata() map[string]any { if r == nil { return nil } docID := "" if r.DocRecord != nil { docID = r.DocRecord.DocID } if docID == "" { return nil } title := r.ArtifactTitle if title == "" && r.TaskKey != "" { title = squad.TaskArtifactTitle(r.TaskKey) } out := map[string]any{ "doc_id": docID, "title": title, "task_key": r.TaskKey, } if r.WorkDir != "" { out["workspace_id"] = r.WorkDir } if r.DocRecord != nil && r.DocRecord.ContentSHA256 != "" { out["sha256"] = r.DocRecord.ContentSHA256 } if r.QualityReport != nil { out["quality_passed"] = r.QualityReport.Passed out["quality_score"] = r.QualityReport.Score if len(r.QualityReport.Violations) > 0 { out["quality_violations"] = r.QualityReport.Violations } } if r.AwaitsAgentConfirmation() { out["pending_agent_candidate_id"] = r.PendingAgentCandidateID if r.PendingChangeReportID > 0 { out["pending_change_report_id"] = r.PendingChangeReportID } if r.PendingRiskLevel != "" { out["pending_risk_level"] = r.PendingRiskLevel } out["awaiting_author_confirmation"] = true } return out }