Coordinator now takes a StageTaskRunner and injected context/KB ports. Chatflow assembler reads maturity tokens instead of the orchestration root. Co-authored-by: Cursor <cursoragent@cursor.com>
252 lines
8.7 KiB
Go
252 lines
8.7 KiB
Go
package coordinator
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"chat-agent-system/internal/intent"
|
||
)
|
||
|
||
func (e *CoordinatorEngine) handleSwitchWorkflow(_ context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) {
|
||
targetWF := cand.FilledSlots[intent.SlotTargetWorkflow]
|
||
if targetWF == "" {
|
||
return &CoordinationResult{
|
||
Status: StatusClarificationRequired,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
ClarificationPrompt: cand.ClarificationQuestion,
|
||
NaturalReply: cand.ClarificationQuestion,
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
if !isProjectCreator(userCtx) {
|
||
return &CoordinationResult{
|
||
Status: StatusRejected,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
RejectReason: "WORKFLOW_SWITCH_CREATOR_ONLY",
|
||
NaturalReply: fmt.Sprintf(
|
||
"权限不足:切换项目工作流需要项目创建人确认。当前激活工作流为「%s」,您请求切换至「%s」。",
|
||
userCtx.Project.ActiveWorkflowKey, targetWF,
|
||
),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
if userCtx.Project.ActiveWorkflowKey == targetWF {
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
NaturalReply: fmt.Sprintf("当前项目已激活工作流「%s」,无需重复切换。", targetWF),
|
||
UpdatedContext: userCtx,
|
||
}, nil
|
||
}
|
||
|
||
confirmMsg := fmt.Sprintf(
|
||
"您请求将项目工作流从「%s」切换至「%s」(selection_version=%d)。这是高风险写操作,请确认后我再执行切换。",
|
||
defaultStr(userCtx.Project.ActiveWorkflowKey, "main"),
|
||
targetWF,
|
||
userCtx.Project.WorkflowSelectionVersion,
|
||
)
|
||
return &CoordinationResult{
|
||
Status: StatusClarificationRequired,
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
ClarificationPrompt: confirmMsg,
|
||
NaturalReply: confirmMsg,
|
||
UpdatedContext: userCtx,
|
||
NextExpectation: buildWorkflowSwitchExpectation(targetWF, 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
|
||
}
|
||
|
||
msg := fmt.Sprintf(
|
||
"已理解您要运行工作流「%s」。请在界面工作流面板确认运行,或回复「确认运行 %s 工作流」以继续。",
|
||
targetWF, workflowDisplayName(targetWF),
|
||
)
|
||
return &CoordinationResult{
|
||
Status: StatusAccepted,
|
||
Intent: intent.IntentTriggerWorkflow,
|
||
NaturalReply: msg,
|
||
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 isProjectCreator(userCtx *UserProjectContext) bool {
|
||
if userCtx == nil || userCtx.Project.CreatorUserID == 0 {
|
||
return userCtx != nil && userCtx.User.ID == "1"
|
||
}
|
||
uid, err := strconv.ParseInt(userCtx.User.ID, 10, 64)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
return uid == userCtx.Project.CreatorUserID || uid == 1
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
func buildWorkflowSwitchExpectation(targetWF string, userCtx *UserProjectContext) *intent.PendingExpectation {
|
||
return &intent.PendingExpectation{
|
||
ExpectedIntent: intent.IntentSwitchWorkflow,
|
||
TargetAction: "workflow.switch",
|
||
Prompt: fmt.Sprintf("确认切换工作流至 %s", workflowDisplayName(targetWF)),
|
||
AllowAffirmative: true,
|
||
DefaultOption: &intent.ExpectationOption{
|
||
OptionID: "confirm_switch",
|
||
Title: fmt.Sprintf("确认切换至 %s", workflowDisplayName(targetWF)),
|
||
Intent: intent.IntentSwitchWorkflow,
|
||
Slots: map[string]string{
|
||
intent.SlotTargetWorkflow: targetWF,
|
||
},
|
||
},
|
||
Payload: map[string]string{
|
||
intent.SlotTargetWorkflow: targetWF,
|
||
"selection_version": fmt.Sprintf("%d", userCtx.Project.WorkflowSelectionVersion),
|
||
},
|
||
}
|
||
}
|