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 e.applyNarrative(context.Background(), userCtx, "handleSwitchWorkflow", "", "", &CoordinationResult{ Status: StatusRejected, Intent: intent.IntentSwitchWorkflow, RejectReason: "WORKFLOW_SWITCH_UI_ONLY", NaturalReply: msg, UpdatedContext: userCtx, }), nil } if userCtx.Project.ActiveWorkflowKey == targetWF { return e.applyNarrative(context.Background(), userCtx, "handleSwitchWorkflow", "", "", &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 e.applyNarrative(context.Background(), userCtx, "handleSwitchWorkflow", "", "", &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 e.applyNarrative(context.Background(), userCtx, "handleQueryWorkflowStatus", "", "", &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 e.applyNarrative(ctx, userCtx, "handleQueryDocStatus", "", "", &CoordinationResult{ Status: StatusAccepted, Intent: intent.IntentQueryDocStatus, NaturalReply: reply, UpdatedContext: userCtx, }), nil } func (e *CoordinatorEngine) handleHelpCapabilities(ctx 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 e.applyNarrative(ctx, userCtx, "handleHelpCapabilities", "", "", &CoordinationResult{ Status: StatusAccepted, Intent: intent.IntentHelpCapabilities, NaturalReply: sb.String(), UpdatedContext: userCtx, }), nil } func (e *CoordinatorEngine) handleUnsupportedAction(ctx context.Context, cand *intent.CandidateIntent, userCtx *UserProjectContext) (*CoordinationResult, error) { raw := "" if cand != nil { raw = cand.Reasoning } return e.applyNarrative(ctx, userCtx, "handleUnsupportedAction", raw, "", &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 } }