202 lines
7.2 KiB
Go
202 lines
7.2 KiB
Go
package coordinator
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"chat-agent-system/internal/contracts/vocabulary"
|
||
"chat-agent-system/internal/intent"
|
||
)
|
||
|
||
// handleDocGovReopenRequest 须 approved;卡片含原因 → PendingAction reopen_request
|
||
func (e *CoordinatorEngine) handleDocGovReopenRequest(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
doc, gate := e.resolveTargetDocGate(ctx, cand, userCtx)
|
||
if gate != nil {
|
||
return gate, nil
|
||
}
|
||
reason := strings.TrimSpace(cand.FilledSlots[intent.SlotReopenReason])
|
||
if reason == "" {
|
||
return &CoordinationResult{
|
||
Status: StatusClarificationRequired,
|
||
Intent: cand.Intent,
|
||
Branch: cand.Branch,
|
||
ClarificationPrompt: "请补充重开原因后再申请。",
|
||
NaturalReply: "已定位文档,但缺少重开原因。请说明为何需要再修订。",
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
state := doc.State
|
||
if state == "" && userCtx != nil && userCtx.ToSnapshot() != nil {
|
||
state = vocabulary.DocumentState(userCtx.ToSnapshot().DocLifecycleState)
|
||
}
|
||
if state != "" && state != vocabulary.StateApproved {
|
||
return &CoordinationResult{
|
||
Status: StatusRejected,
|
||
Intent: cand.Intent,
|
||
Branch: cand.Branch,
|
||
RejectReason: "REOPEN_STATE_DENIED",
|
||
NaturalReply: fmt.Sprintf("当前文档处于「%s」阶段,仅已批准(approved)文档可申请重开。", state),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
role := effectiveDocRole(userCtx)
|
||
title := displayDocTitle(doc)
|
||
card, exp := buildConfirmCard(
|
||
"申请受控文档重开确认",
|
||
doc.DocID, title, "", reason,
|
||
"确认后将提交重开申请;批准人优先审批(创建人兜底),服务端二次校验职责权限。",
|
||
"confirm_reopen_request", "确认申请重开",
|
||
map[string]string{
|
||
"role": role,
|
||
"reopen_reason": reason,
|
||
"task_key": doc.TaskKey,
|
||
},
|
||
)
|
||
userCtx.applyCard(card)
|
||
userCtx.applyPending(exp)
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: fmt.Sprintf("已为《%s》起草重开申请确认单。原因:%s。请确认或回复「确认」。", title, reason),
|
||
InteractiveCard: card, NextExpectation: exp, UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
// handleDocGovReopenApprove 列 pending 重开申请 → 卡片 → PendingAction
|
||
func (e *CoordinatorEngine) handleDocGovReopenApprove(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
projectID := int64(0)
|
||
if userCtx != nil {
|
||
projectID = parseDecimalID(userCtx.Project.ID)
|
||
}
|
||
var items []ReopenListItem
|
||
if e != nil && e.reopenLister != nil {
|
||
var err error
|
||
items, err = e.reopenLister.ListPendingReopens(ctx, projectID)
|
||
if err != nil {
|
||
return &CoordinationResult{
|
||
Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch,
|
||
RejectReason: "REOPEN_LIST_FAILED", NaturalReply: fmt.Sprintf("无法列出待批重开申请:%v", err),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
}
|
||
reqID := int64(0)
|
||
if cand.FilledSlots != nil {
|
||
if raw := strings.TrimSpace(cand.FilledSlots["request_id"]); raw != "" {
|
||
reqID, _ = strconv.ParseInt(raw, 10, 64)
|
||
}
|
||
}
|
||
if reqID == 0 && len(items) == 1 {
|
||
reqID = items[0].RequestID
|
||
}
|
||
if reqID == 0 {
|
||
if len(items) == 0 {
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: "当前没有待批准的重开申请。", UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
var b strings.Builder
|
||
b.WriteString("请指定要批准的重开申请编号(回复 request_id=N):\n")
|
||
for _, it := range items {
|
||
b.WriteString(fmt.Sprintf("- #%d 文档任务=%v 原因=%s\n", it.RequestID, it.DocTasks, it.Reason))
|
||
}
|
||
return &CoordinationResult{
|
||
Status: StatusClarificationRequired, Intent: cand.Intent, Branch: cand.Branch,
|
||
ClarificationPrompt: b.String(), NaturalReply: b.String(), UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
reason := ""
|
||
docID := ""
|
||
for _, it := range items {
|
||
if it.RequestID == reqID {
|
||
reason = it.Reason
|
||
if len(it.DocTasks) > 0 {
|
||
docID = it.DocTasks[0]
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if docID == "" {
|
||
doc, gate := e.resolveTargetDocGate(ctx, cand, userCtx)
|
||
if gate != nil && doc == nil {
|
||
// still allow approve by request id alone
|
||
docID = fmt.Sprintf("reopen#%d", reqID)
|
||
} else if doc != nil {
|
||
docID = doc.DocID
|
||
} else {
|
||
docID = fmt.Sprintf("reopen#%d", reqID)
|
||
}
|
||
}
|
||
title := docID
|
||
proposed := fmt.Sprintf("批准重开申请 #%d", reqID)
|
||
if reason != "" {
|
||
proposed += ":" + reason
|
||
}
|
||
card, exp := buildConfirmCard(
|
||
"批准受控文档重开确认",
|
||
docID, title, "", proposed,
|
||
"确认后将解锁文档至编制态;服务端按批准人优先/创建人兜底二次校验。",
|
||
"confirm_reopen_approve", "确认批准重开",
|
||
map[string]string{"request_id": strconv.FormatInt(reqID, 10)},
|
||
)
|
||
userCtx.applyCard(card)
|
||
userCtx.applyPending(exp)
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: fmt.Sprintf("已起草批准重开确认单(申请 #%d)。请确认或回复「确认」。", reqID),
|
||
InteractiveCard: card, NextExpectation: exp, UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
// handleQueryPendingSupplements 只读直答积压补充
|
||
func (e *CoordinatorEngine) handleQueryPendingSupplements(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
projectID := int64(0)
|
||
if userCtx != nil {
|
||
projectID = parseDecimalID(userCtx.Project.ID)
|
||
}
|
||
docID := ""
|
||
if cand != nil && cand.FilledSlots != nil {
|
||
docID = strings.TrimSpace(cand.FilledSlots[intent.SlotTargetDoc])
|
||
}
|
||
if e == nil || e.pendingSupps == nil {
|
||
return e.applyNarrative(ctx, userCtx, "handleQueryPendingSupplements", "", "", &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: "积压补充查询尚未接到当前环境。", UpdatedContext: userCtx,
|
||
}), nil
|
||
}
|
||
rows, err := e.pendingSupps.ListPendingSupplements(ctx, projectID, docID)
|
||
if err != nil {
|
||
return &CoordinationResult{
|
||
Status: StatusRejected, Intent: cand.Intent, Branch: cand.Branch,
|
||
RejectReason: "PENDING_SUPPLEMENT_QUERY_FAILED",
|
||
NaturalReply: fmt.Sprintf("查询积压补充失败:%v", err), UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
if len(rows) == 0 {
|
||
return e.applyNarrative(ctx, userCtx, "handleQueryPendingSupplements", "", "", &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: "当前没有未消化的锁定期间积压补充。", UpdatedContext: userCtx,
|
||
}), nil
|
||
}
|
||
var b strings.Builder
|
||
b.WriteString(fmt.Sprintf("共有 %d 条待消化积压补充:\n", len(rows)))
|
||
for i, r := range rows {
|
||
title := r.DocTitle
|
||
if title == "" {
|
||
title = r.DocID
|
||
}
|
||
b.WriteString(fmt.Sprintf("%d) 《%s》来源:%s", i+1, title, r.SourceSummary))
|
||
if r.ImpactReason != "" {
|
||
b.WriteString(";影响:" + r.ImpactReason)
|
||
}
|
||
b.WriteString("\n")
|
||
}
|
||
return e.applyNarrative(ctx, userCtx, "handleQueryPendingSupplements", "", "", &CoordinationResult{
|
||
Status: StatusAccepted, Intent: cand.Intent, Branch: cand.Branch,
|
||
NaturalReply: b.String(), UpdatedContext: userCtx,
|
||
}), nil
|
||
}
|