package coordinator import ( "chat-agent-system/internal/contracts/vocabulary" "context" "fmt" "strings" "time" "chat-agent-system/internal/intent" ) // handleDocGovReject 处理受控文档审核/批准驳回与二次确认交互卡片挂载(角色硬门禁) func (e *CoordinatorEngine) handleDocGovReject(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { docName := cand.FilledSlots[intent.SlotTargetDoc] if docName == "" { if userCtx.InteractiveCard != nil && userCtx.InteractiveCard.TargetDocID != "" { docName = userCtx.InteractiveCard.TargetDocID } else { docName = "当前受控文档" } } rawReason := cand.FilledSlots[intent.SlotRejectReason] section := cand.FilledSlots[intent.SlotTargetSection] if section == "" { section = extractSectionClues(rawReason) } role := effectiveDocRole(userCtx) isFinal := cand.Intent == intent.IntentDocGovFinalReject // 与批准对称的硬门禁 if isFinal { if !isApproverAuthorized(userCtx.User) && role != "approver" { return &CoordinationResult{ Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch, RejectReason: "UNAUTHORIZED_REJECT_ROLE", NaturalReply: fmt.Sprintf("权限不足:终审驳回需批准人角色。当前您的身份为「%s」。若需了解驳回原因,可直接询问。", roleLabelCN(role)), UpdatedContext: userCtx, }, nil } } else { if !isReviewerAuthorized(userCtx.User) && role != "reviewer" { return &CoordinationResult{ Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch, RejectReason: "UNAUTHORIZED_REJECT_ROLE", NaturalReply: fmt.Sprintf("权限不足:审核驳回需审核人角色。当前您的身份为「%s」。若需了解驳回原因,可直接询问「被驳回的原因是什么」。", roleLabelCN(role)), UpdatedContext: userCtx, }, nil } } // 规范化策略角色(授权通过后按意图选审核/批准确认卡) policyRole := role if isFinal { policyRole = "approver" } else if policyRole != "reviewer" && isReviewerAuthorized(userCtx.User) { policyRole = "reviewer" } lifecycle := "reviewing" if isFinal { lifecycle = "approving" } resolved := ResolveRejectCard(policyRole, lifecycle, docName, docName, section, rawReason, RejectUtteranceExecute) if resolved.Kind == "deny_text" || resolved.Card == nil { return &CoordinationResult{ Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch, RejectReason: resolved.DenyReason, NaturalReply: resolved.Reply, UpdatedContext: userCtx, }, nil } userCtx.applyCard(resolved.Card) userCtx.applyPending(resolved.Expectation) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: resolved.Reply, InteractiveCard: resolved.Card, NextExpectation: resolved.Expectation, UpdatedContext: userCtx, }, nil } // isReviewerAuthorized 校验审核人或主导工程师权限 func isReviewerAuthorized(u UserInfo) bool { r := strings.ToLower(strings.TrimSpace(u.Role)) pr := strings.ToLower(strings.TrimSpace(u.ProjectRole)) return r == "reviewer" || pr == "reviewer" || r == "lead_engineer" || pr == "lead_engineer" || strings.Contains(pr, "审核") || strings.Contains(r, "审核") || strings.Contains(pr, "主导") || strings.Contains(r, "主导") } // isApproverAuthorized 校验批准人或项目经理权限 func isApproverAuthorized(u UserInfo) bool { r := strings.ToLower(strings.TrimSpace(u.Role)) pr := strings.ToLower(strings.TrimSpace(u.ProjectRole)) return r == "approver" || pr == "approver" || r == "project_manager" || pr == "project_manager" || strings.Contains(pr, "批准") || strings.Contains(r, "批准") || strings.Contains(pr, "总监") || strings.Contains(r, "总监") || r == "director" } // handleDocGovApprove 处理受控文档审核/批准通过与终审批准二次确认 func (e *CoordinatorEngine) handleDocGovApprove(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { docName := cand.FilledSlots[intent.SlotTargetDoc] if docName == "" { if userCtx.InteractiveCard != nil && userCtx.InteractiveCard.TargetDocID != "" { docName = userCtx.InteractiveCard.TargetDocID } else { docName = "当前受控文档" } } currentRole := userCtx.User.ProjectRole if currentRole == "" { currentRole = userCtx.User.Role } if currentRole == "" { currentRole = "未知角色" } isFinal := cand.Intent == intent.IntentDocGovFinalApprove if isFinal { // 校验终审批准权限: approver 或 project_manager if !isApproverAuthorized(userCtx.User) { return &CoordinationResult{ Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch, RejectReason: "UNAUTHORIZED_APPROVAL_ROLE", NaturalReply: fmt.Sprintf("权限不足:受控文档终审批准需要批准人 (approver) 或项目经理 (project_manager) 权限。当前您的身份为「%s」,无权执行终审批准操作。", currentRole), UpdatedContext: userCtx, }, nil } cardID := fmt.Sprintf("card_final_appr_%d", time.Now().UnixNano()) cardPayload := &intent.InteractiveCardPayload{ CardID: cardID, CardType: "CONFIRMATION", Title: "受控文档终审批准生效确认", TargetDocID: docName, TargetDocTitle: fmt.Sprintf("《%s》", docName), ProposedContent: "正式终审批准受控文档,签署电子签名并受控归档生效", ImpactRisk: "终审批准后文档正文将永久锁定为基线版本,后续非受控修改将被严格拦截,任何变更必须走变更审批流程", CurrentStatus: "active", TTLTurns: 3, CreatedAt: time.Now(), Actions: []intent.CardAction{ { ActionID: "confirm_final_approve", Label: "确认终审批准生效", Style: "primary", IsAffirmativeDefault: true, Payload: map[string]string{ "target_doc": docName, "target_action": "confirm_final_approve", }, }, { ActionID: "cancel", Label: "取消", Style: "subtle", Payload: map[string]string{ "action": "cancel", }, }, }, } nextExp := &intent.PendingExpectation{ ExpectationID: "exp_" + cardPayload.CardID, ExpectedIntent: intent.IntentDocGovProposalDecide, TargetAction: "confirm_final_approve", Prompt: "请确认是否执行终审批准操作", AffirmativeWord: "确认", DeclineWord: "取消", ExpiresAt: time.Now().Add(5 * time.Minute), Payload: cardPayload.Actions[0].Payload, AllowAffirmative: true, CreatedAt: time.Now(), DefaultOption: &intent.ExpectationOption{ OptionID: "confirm_final_approve", Title: "确认终审批准生效", Intent: intent.IntentDocGovProposalDecide, Slots: cardPayload.Actions[0].Payload, }, } userCtx.applyCard(cardPayload) userCtx.applyPending(nextExp) reply := fmt.Sprintf("已为您起草针对《%s》的终审批准确认单。终审批准将使文档正式归档并永久锁定,请审阅下方卡片并在确认无误后点击确认或回复‘确认’。", docName) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: reply, InteractiveCard: cardPayload, NextExpectation: nextExp, UpdatedContext: userCtx, }, nil } // 校验审核通过权限: reviewer 或 lead_engineer if !isReviewerAuthorized(userCtx.User) { return &CoordinationResult{ Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch, RejectReason: "UNAUTHORIZED_REVIEW_ROLE", NaturalReply: fmt.Sprintf("权限不足:受控文档审核通过需要审核人 (reviewer) 或主导工程师 (lead_engineer) 权限。当前您的身份为「%s」,无权执行审核通过操作。", currentRole), UpdatedContext: userCtx, }, nil } reply := fmt.Sprintf("正在处理您对《%s》的审核通过请求…", docName) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: reply, UpdatedContext: userCtx, PendingAction: &PendingAction{ Kind: "docgov.review_approve", DocID: docName, Comment: cand.FilledSlots[intent.SlotRejectReason], }, }, nil } // handleDocGovClaim 处理受控文档认领 func (e *CoordinatorEngine) handleDocGovClaim(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { docName := cand.FilledSlots[intent.SlotTargetDoc] if docName == "" { docName = "当前受控文档" } // 记录在用户的已认领清单中 found := false for _, d := range userCtx.UserDocs.Claimed { if d == docName { found = true break } } if !found { userCtx.UserDocs.Claimed = append(userCtx.UserDocs.Claimed, docName) } reply := fmt.Sprintf("您已成功认领《%s》。系统已锁定您作为该文档的法定责任人,您可以开始在线就地修订正文或提交审核。", docName) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: reply, UpdatedContext: userCtx, }, nil } // handleDocGovSubmitReview 处理受控文档提交审核 func (e *CoordinatorEngine) handleDocGovSubmitReview(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { docName := cand.FilledSlots[intent.SlotTargetDoc] if docName == "" { docName = "当前受控文档" } reply := fmt.Sprintf("《%s》已成功提交审核!文档正文已进入受控锁定状态,并已通知审核责任人进行合规审查与批注。", docName) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: reply, UpdatedContext: userCtx, }, nil } // handleDocGovProposalDecide 处理智能体修改建议与提案裁决,以及驳回/终审批准确认执行 func (e *CoordinatorEngine) handleDocGovProposalDecide(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { targetAction := cand.FilledSlots["target_action"] if targetAction == "" { targetAction = cand.FilledSlots["action"] } if targetAction == "" && cand.MatchedOption != nil { targetAction = cand.MatchedOption.OptionID } docName := cand.FilledSlots[intent.SlotTargetDoc] if docName == "" && userCtx.InteractiveCard != nil && userCtx.InteractiveCard.TargetDocID != "" { docName = userCtx.InteractiveCard.TargetDocID } if docName == "" { docName = "当前受控文档" } if targetAction == "confirm_reject" { reason := cand.FilledSlots[intent.SlotRejectReason] if reason == "" && userCtx.InteractiveCard != nil { reason = userCtx.InteractiveCard.ProposedContent } if userCtx.InteractiveCard != nil { userCtx.dismissCard("resolved") } userCtx.applyPending(nil) rollback := vocabulary.StateDrafting if userCtx.InteractiveCard != nil && strings.Contains(userCtx.InteractiveCard.ImpactRisk, "reviewing") { rollback = vocabulary.StateDrafting } return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: fmt.Sprintf("正在处理您对《%s》的驳回确认…", docName), UpdatedContext: userCtx, ClearExpectation: true, PendingAction: &PendingAction{ Kind: "docgov.reject", DocID: docName, Reason: reason, Rollback: rollback, }, }, nil } if targetAction == "confirm_final_approve" { if userCtx.InteractiveCard != nil { userCtx.dismissCard("resolved") } userCtx.applyPending(nil) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: fmt.Sprintf("正在处理您对《%s》的终审批准…", docName), UpdatedContext: userCtx, ClearExpectation: true, PendingAction: &PendingAction{ Kind: "docgov.approver_approve", DocID: docName, }, }, nil } if targetAction == "cancel" { if userCtx.InteractiveCard != nil { userCtx.dismissCard("cancelled") } userCtx.applyPending(nil) return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: fmt.Sprintf("已取消针对《%s》的操作,文档状态保持不变。", docName), UpdatedContext: userCtx, ClearExpectation: true, }, nil } action := cand.FilledSlots[intent.SlotProposalAction] var reply string if action == "reject" { reply = "已拒绝该修改变更提案。文档正文保持原样,提案记录已归档。" } else { reply = "已采纳该修改变更提案,补充内容与参数已合并入受控文档。" } return &CoordinationResult{ Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch, NaturalReply: reply, UpdatedContext: userCtx, }, nil }