claim/submit 经 DocResolver+确认卡后 PendingAction 真写 docgov;proposal 默认分支改澄清;workflow.switch 诚实拒绝(UI专属);workflow.run 确认后走 CreateWorkflowRun(ActionServices.Workflow 由合入时接线)。
220 lines
8.1 KiB
Go
220 lines
8.1 KiB
Go
package coordinator
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"chat-agent-system/internal/intent"
|
||
)
|
||
|
||
func (e *CoordinatorEngine) handleSwitchWorkflow(_ context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
targetWF := cand.FilledSlots[intent.SlotTargetWorkflow]
|
||
active := defaultStr(userCtx.Project.ActiveWorkflowKey, "main")
|
||
if targetWF == "" {
|
||
msg := fmt.Sprintf(
|
||
"切换项目激活工作流属于治理结构操作,仅支持在项目设置界面完成。当前激活工作流为「%s」。如需了解状态,可询问「当前工作流是什么」。",
|
||
workflowDisplayName(active),
|
||
)
|
||
return &CoordinationResult{
|
||
Status: StatusRejected,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
RejectReason: "WORKFLOW_SWITCH_UI_ONLY",
|
||
NaturalReply: msg,
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
if userCtx.Project.ActiveWorkflowKey == targetWF {
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
NaturalReply: fmt.Sprintf("当前项目已激活工作流「%s」,无需切换。激活工作流的变更请在项目设置界面操作。", workflowDisplayName(targetWF)),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
msg := fmt.Sprintf(
|
||
"无法通过对话切换工作流:从「%s」切换至「%s」属于项目治理结构操作,请在项目设置 → 工作流面板完成。对话仅支持查询工作流状态与(在授权下)确认运行已激活的工作流。",
|
||
workflowDisplayName(active),
|
||
workflowDisplayName(targetWF),
|
||
)
|
||
return &CoordinationResult{
|
||
Status: StatusRejected,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
RejectReason: "WORKFLOW_SWITCH_UI_ONLY",
|
||
NaturalReply: msg,
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func (e *CoordinatorEngine) handleTriggerWorkflow(_ context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
targetWF := cand.FilledSlots[intent.SlotTargetWorkflow]
|
||
if targetWF == "" {
|
||
targetWF = defaultStr(userCtx.Project.ActiveWorkflowKey, "main")
|
||
}
|
||
capKey := "TRIGGER_WORKFLOW:" + targetWF
|
||
if !hasCapability(userCtx, capKey) && !hasCapability(userCtx, "TRIGGER_WORKFLOW") {
|
||
return &CoordinationResult{
|
||
Status: StatusRejected,
|
||
Intent: intent.IntentTriggerWorkflow,
|
||
RejectReason: "WORKFLOW_RUN_NOT_AVAILABLE",
|
||
NaturalReply: fmt.Sprintf(
|
||
"当前上下文未开放工作流「%s」的运行能力(缺少 %s)。请先确认项目阶段或在界面授权后再试。",
|
||
targetWF, capKey,
|
||
),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
card, exp := buildConfirmCard(
|
||
"工作流运行确认",
|
||
"", workflowDisplayName(targetWF), "",
|
||
fmt.Sprintf("确认运行工作流「%s」", workflowDisplayName(targetWF)),
|
||
"确认后将创建一次真实的工作流运行记录;取消则不做任何变更。",
|
||
"confirm_workflow_run", "确认运行",
|
||
map[string]string{intent.SlotTargetWorkflow: targetWF, "workflow_key": targetWF},
|
||
)
|
||
// TargetDocID unused for workflow; keep card title meaningful
|
||
card.TargetDocTitle = workflowDisplayName(targetWF)
|
||
userCtx.applyCard(card)
|
||
userCtx.applyPending(exp)
|
||
msg := fmt.Sprintf("已为工作流「%s」起草运行确认单。确认后将创建运行实例;取消则不会启动。", workflowDisplayName(targetWF))
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentTriggerWorkflow,
|
||
NaturalReply: msg,
|
||
InteractiveCard: card,
|
||
NextExpectation: exp,
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func (e *CoordinatorEngine) handleQueryWorkflowStatus(_ context.Context, _ *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
active := defaultStr(userCtx.Project.ActiveWorkflowKey, userCtx.Project.CurrentWorkflow)
|
||
if active == "" {
|
||
active = "main"
|
||
}
|
||
var sb strings.Builder
|
||
sb.WriteString(fmt.Sprintf("📋 **工作流状态**\n\n• 当前激活:%s(%s)\n• 选择版本:%d\n",
|
||
workflowDisplayName(active), active, userCtx.Project.WorkflowSelectionVersion))
|
||
if len(userCtx.AvailableActions) > 0 {
|
||
sb.WriteString("\n• 当前可用能力:\n")
|
||
for _, a := range userCtx.AvailableActions {
|
||
if strings.Contains(a, "WORKFLOW") || strings.Contains(a, "TRIGGER_TASK") {
|
||
sb.WriteString(fmt.Sprintf(" - %s\n", a))
|
||
}
|
||
}
|
||
}
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentQueryWorkflowStatus,
|
||
NaturalReply: sb.String(),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func (e *CoordinatorEngine) handleQueryDocStatus(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
if cand.FilledSlots[intent.SlotQueryTopic] == "doc_summary" {
|
||
return e.handleDocumentSummary(ctx, cand, userCtx)
|
||
}
|
||
docID := cand.FilledSlots[intent.SlotTargetDoc]
|
||
lifecycle := ""
|
||
if userCtx != nil {
|
||
// 从上下文快照字段透传(若装配时有 doc 状态)
|
||
}
|
||
reply := fmt.Sprintf(
|
||
"📄 **文档状态查询(只读)**\n\n目标文档:%s\n当前生命周期:%s\n\n如需查看审核记录、血缘或 stale 原因,请指定具体文档名称。",
|
||
defaultStr(docID, "当前激活文档"),
|
||
defaultStr(lifecycle, "请在工作台文档面板查看详情"),
|
||
)
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentQueryDocStatus,
|
||
NaturalReply: reply,
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func (e *CoordinatorEngine) handleHelpCapabilities(_ context.Context, _ *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
var sb strings.Builder
|
||
sb.WriteString("🧭 **我能帮您做什么**\n\n")
|
||
sb.WriteString("当前阶段您可以通过对话尝试:\n")
|
||
if len(userCtx.AvailableActions) == 0 {
|
||
sb.WriteString("• 查询项目进展与风险\n• 补充项目事实\n• 查询法规与权限\n")
|
||
} else {
|
||
for _, a := range userCtx.AvailableActions {
|
||
sb.WriteString(fmt.Sprintf("• %s\n", formatCapabilityLabel(a)))
|
||
}
|
||
}
|
||
sb.WriteString("\n高风险操作(文档审批、阶段推进、运行工作流)需要明确确认后才会执行;切换激活工作流请使用项目设置界面。")
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentHelpCapabilities,
|
||
NaturalReply: sb.String(),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func (e *CoordinatorEngine) handleUnsupportedAction(_ context.Context, _ *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentUnsupportedAction,
|
||
NaturalReply: "我理解您的诉求,但该操作目前仅支持通过界面按钮完成(如下载、共享、批量导出、直接修改受控正文等),暂不支持自然语言自动执行。",
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
func hasCapability(userCtx *UserProjectContext, key string) bool {
|
||
if userCtx == nil {
|
||
return false
|
||
}
|
||
for _, a := range userCtx.AvailableActions {
|
||
if a == key {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func defaultStr(v, fallback string) string {
|
||
if v == "" {
|
||
return fallback
|
||
}
|
||
return v
|
||
}
|
||
|
||
func workflowDisplayName(key string) string {
|
||
switch key {
|
||
case "product_manager":
|
||
return "智能产品经理"
|
||
case "test_manager":
|
||
return "智能测试经理"
|
||
case "main":
|
||
return "主工作流"
|
||
default:
|
||
return key
|
||
}
|
||
}
|
||
|
||
func formatCapabilityLabel(cap string) string {
|
||
switch {
|
||
case cap == "QUERY_PROGRESS":
|
||
return "查询项目进展、风险与沟通话术"
|
||
case cap == "SUPPLEMENT_INFO":
|
||
return "补充或修正项目事实"
|
||
case cap == "VIEW_DOC":
|
||
return "查看受控文档"
|
||
case cap == "SWITCH_WORKFLOW":
|
||
return "切换项目工作流(仅界面;对话不代执行)"
|
||
case strings.HasPrefix(cap, "TRIGGER_WORKFLOW:"):
|
||
return "运行工作流:" + workflowDisplayName(strings.TrimPrefix(cap, "TRIGGER_WORKFLOW:"))
|
||
case strings.HasPrefix(cap, "TRIGGER_TASK:"):
|
||
return "生成交付物:" + strings.TrimPrefix(cap, "TRIGGER_TASK:")
|
||
case strings.HasPrefix(cap, "ADVANCE_STAGE:"):
|
||
return "推进阶段:" + strings.TrimPrefix(cap, "ADVANCE_STAGE:")
|
||
default:
|
||
return cap
|
||
}
|
||
}
|