package main import ( "bufio" "context" "flag" "fmt" "os" "path/filepath" "strings" "time" "chat-agent-system/internal/bizdev" "chat-agent-system/internal/chat" "chat-agent-system/internal/config" "chat-agent-system/internal/coordinator" "chat-agent-system/internal/intent" "chat-agent-system/internal/squad" ) func main() { stageFlag := flag.String("stage", "", "执行成熟度阶段 (可选: V0.1_triage, V0.5_feasibility, V1.0_quoting)") taskKey := flag.String("task", "intake", "任务标识 (intake, research, cdo_quote 等)") backend := flag.String("backend", "", "智能体后端 (留空则读取 .env 配置,可选: stub, pi-agent, cursor-cli)") project := flag.String("project", "医疗器械研发演示项目", "项目名称") customer := flag.String("customer", "北京水木东方", "客户名称") device := flag.String("device", "医疗器械", "设备类型") use := flag.String("use", "临床诊疗", "预期用途") market := flag.String("market", "国内", "目标市场") materials := flag.String("materials", "", "输入补充材料内容") materialsDir := flag.String("materials-dir", "", "输入材料文件夹路径 (批量载入目录下所有文件)") envPath := flag.String("env", ".env", "环境配置文件路径") inspectDir := flag.String("inspect", "", "审查并查看指定工作区状态目录") autoRepair := flag.Bool("auto-repair", true, "启用质量门禁自动闭环修正") listTasks := flag.Bool("list-tasks", false, "列出所有受支持的商务对接任务清单") listStages := flag.Bool("list-stages", false, "列出所有受支持的商务成熟度阶段清单") addStep := flag.String("add-step", "", "动态向工作区追加任务步骤 (格式: role:title,配合 -inspect)") // 对话协同与意图识别相关命令行参数 chatMode := flag.Bool("chat", false, "启动与项目参谋的自然语言交互式对话 (REPL)") chatQuery := flag.String("query", "", "直接在命令行传入单次提问/指令并输出参谋诊断与调度结果 (配合 -chat)") workDirFlag := flag.String("work-dir", "", "关联现有工作区目录 (如 sandbox/intake_xxx)") userName := flag.String("user", "张工", "用户姓名") userRole := flag.String("role", "engineer", "用户角色") flag.Parse() cfg, err := config.Load(*envPath) if err != nil { fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err) os.Exit(1) } if *backend != "" { cfg.AgentBackend = *backend } svc := bizdev.NewService(cfg) // 0. 列出支持的阶段 if *listStages { fmt.Println("=== 商务对接成熟度螺旋收敛阶段清单 ===") for _, st := range svc.ListStages() { fmt.Printf("- %-18s: %s\n", st.Stage, st.Title) fmt.Printf(" 说明: %s\n", st.Description) fmt.Printf(" 包含任务: %v\n", st.Tasks) } return } // 1. 列出支持的任务 if *listTasks { fmt.Println("=== 商务对接小队支持的独立任务清单 ===") for _, t := range svc.ListTasks() { fmt.Printf("- %-18s: %s (交付: %s)\n", t.Key, t.Title, t.ArtifactTitle) if t.Description != "" { fmt.Printf(" 说明: %s\n", t.Description) } } return } // 2. 审查已有工作区 if *inspectDir != "" { handleInspect(context.Background(), svc, *inspectDir, *addStep, *backend) return } // 3. 交互式对话参谋模式 if *chatMode || *chatQuery != "" { handleChatMode( context.Background(), svc, *chatQuery, *workDirFlag, *project, *customer, *device, *use, *market, *userName, *userRole, ) return } // 4. 执行阶段或常规任务 activeBackend := cfg.AgentBackend if *backend != "" { activeBackend = *backend cfg.AgentBackend = *backend } var materialsList []bizdev.MaterialInput if *materialsDir != "" { entries, err := os.ReadDir(*materialsDir) if err != nil { fmt.Fprintf(os.Stderr, "读取材料目录失败 %s: %v\n", *materialsDir, err) os.Exit(1) } for _, entry := range entries { if entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { continue } fPath := filepath.Join(*materialsDir, entry.Name()) content, err := os.ReadFile(fPath) if err != nil { fmt.Fprintf(os.Stderr, "读取材料文件失败 %s: %v\n", fPath, err) continue } materialsList = append(materialsList, bizdev.MaterialInput{ Filename: entry.Name(), Content: content, }) fmt.Printf("已载入输入材料: %s (%d 字节)\n", entry.Name(), len(content)) } } if *materials != "" { materialsList = append(materialsList, bizdev.MaterialInput{ Filename: "user_input.md", Content: []byte(*materials), }) } // 如果指定了 -stage,执行阶段编排 if *stageFlag != "" { handleStageRun(context.Background(), svc, *stageFlag, *project, *customer, *device, *use, *market, activeBackend, *autoRepair, materialsList) return } res, err := svc.ExecuteTask(context.Background(), bizdev.TaskRunRequest{ TaskKey: *taskKey, ProjectName: *project, CustomerName: *customer, DeviceType: *device, IntendedUse: *use, TargetMarket: *market, Materials: materialsList, Backend: activeBackend, AutoRepair: *autoRepair, }) if err != nil { fmt.Fprintf(os.Stderr, "任务执行失败: %v\n", err) os.Exit(1) } if !res.Success { fmt.Fprintf(os.Stderr, "警告: 后端执行未完全成功 [%s]: %s\n", res.TaskKey, res.ErrorReason) } fmt.Printf("=== 任务执行成功 [%s] ===\n", res.TaskKey) fmt.Printf("耗时: %v\n", res.Duration) fmt.Printf("工作区沙箱: %s\n", res.WorkDir) fmt.Printf("交付文档产物: %s\n", filepath.Join(res.WorkDir, "output.md")) if _, err := os.Stat(filepath.Join(res.WorkDir, "execution.log")); err == nil { fmt.Printf("执行审计日志: %s\n", filepath.Join(res.WorkDir, "execution.log")) } fmt.Printf("使用的后端: %s\n", res.BackendUsed) // 打印质量门禁与指纹报告 if res.QualityReport != nil { fmt.Printf("质量门禁评分: %.1f / 100 (达标: %v)\n", res.QualityReport.Score, res.QualityReport.Passed) if len(res.QualityReport.Violations) > 0 { fmt.Printf("门禁检测项:\n") for _, v := range res.QualityReport.Violations { fmt.Printf(" - %s\n", v) } } } if res.Manifest != nil { fmt.Printf("审计指纹清单: %s (追踪文件数: %d)\n", filepath.Join(res.WorkDir, "manifest.json"), len(res.Manifest.Files)) } if len(res.ExtractedData) > 0 { fmt.Printf("黑板抽取事实字段:\n") for k, v := range res.ExtractedData { fmt.Printf(" - %s: %s\n", k, v) } } if len(res.Attachments) > 0 { fmt.Printf("附加产物:\n") for _, a := range res.Attachments { p := filepath.Join(res.WorkDir, a.Filename) fmt.Printf(" - %s (%d bytes) -> %s\n", a.Filename, len(a.Data), p) } } // 打印 B 轨业务参谋锦囊 if res.AdvisorBrief != nil { printAdvisorBrief(res.AdvisorBrief) } } func handleStageRun( ctx context.Context, svc bizdev.BizdevService, stageName string, project, customer, device, use, market, backend string, autoRepair bool, materials []bizdev.MaterialInput, ) { fmt.Printf("\n>>> 启动商务对接成熟度阶段: [%s] <<<\n", stageName) stageRes, err := svc.ExecuteStage(ctx, bizdev.StageRunRequest{ Stage: bizdev.MaturityStage(stageName), ProjectName: project, CustomerName: customer, DeviceType: device, IntendedUse: use, TargetMarket: market, Backend: backend, AutoRepair: autoRepair, Materials: materials, }) if err != nil { fmt.Fprintf(os.Stderr, "阶段执行失败: %v\n", err) os.Exit(1) } fmt.Printf("\n=== 阶段执行成功: %s ===\n", stageRes.StageTitle) fmt.Printf("总耗时: %v\n", stageRes.Duration) fmt.Printf("阶段工作区根目录: %s\n", stageRes.WorkDir) fmt.Printf("包含交付任务数: %d\n", len(stageRes.SubArtifacts)) for idx, sub := range stageRes.SubArtifacts { fmt.Printf(" [%d] %-18s: %s -> %s\n", idx+1, sub.TaskKey, sub.ArtifactTitle, filepath.Join(sub.WorkDir, "output.md")) } // 优先呈现 B 轨:贴身业务参谋锦囊 if stageRes.AdvisorBrief != nil { printAdvisorBrief(stageRes.AdvisorBrief) } } func printAdvisorBrief(brief *bizdev.AdvisorBriefing) { fmt.Println("\n==================================================================") fmt.Println(" 💡【B轨:贴身业务参谋决策锦囊】 ") fmt.Println("==================================================================") fmt.Printf("【成熟度诊断】 等级: %s | 完备度评分: %d / 100\n", brief.MaturityLevel, brief.MaturityScore) fmt.Printf("【进展研判】 %s\n", brief.MaturityHeadline) if len(brief.BaselineAssumptions) > 0 { fmt.Println("\n【基准预设假设(替代填空,为您先行兜底)】:") for _, a := range brief.BaselineAssumptions { fmt.Printf(" • %s\n", a) } } if len(brief.PreemptiveRisks) > 0 { fmt.Println("\n【预见性风险雷达(提前预警后续法规/商务卡点)】:") for _, r := range brief.PreemptiveRisks { fmt.Printf(" ⚠️ %s\n", r) } } if len(brief.NextBestActions) > 0 { fmt.Println("\n【建议下一步行动(提供选项供您掌舵决策)】:") for idx, act := range brief.NextBestActions { fmt.Printf(" [%d] %s\n", idx+1, act) } } if brief.CommunicationKit != "" { fmt.Println("\n【对外赋能:建议沟通话术(可直接复制使用)】:") fmt.Println(brief.CommunicationKit) } fmt.Println("==================================================================") } func handleInspect(ctx context.Context, svc bizdev.BizdevService, workDir string, addStepFlag string, backend string) { state, err := svc.InspectWorkspace(ctx, workDir) if err != nil { fmt.Fprintf(os.Stderr, "查看工作区失败: %v\n", err) os.Exit(1) } fmt.Printf("=== 工作区状态审查 [%s] ===\n", workDir) fmt.Printf("项目: %s | 客户: %s | 阶段: %s | 状态: %s\n", state.Metadata.ProjectName, state.Metadata.CustomerName, state.Metadata.TaskKey, state.Metadata.Status) fmt.Printf("\n小队角色编制 (%d 位):\n", len(state.Squad.Agents)) for _, a := range state.Squad.Agents { fmt.Printf(" - %-12s: %s (模型: %s, 工具: %v)\n", a.Role, a.Name, a.Model, a.Tools) } fmt.Printf("\n工作流步骤清单 (%d 步):\n", len(state.Workflow.Steps)) for _, s := range state.Workflow.Steps { fmt.Printf(" - [%s] %-10s (%-8s) : %s -> %s\n", s.Status, s.ID, s.Role, s.Title, s.SubWorkspace) } if len(state.Blackboard.Fields) > 0 { fmt.Printf("\n黑板抽取字段:\n") for k, v := range state.Blackboard.Fields { fmt.Printf(" - %s: %s\n", k, v) } } // 若用户指定了动态追加步骤 if addStepFlag != "" { parts := strings.SplitN(addStepFlag, ":", 2) if len(parts) == 2 { role := strings.TrimSpace(parts[0]) title := strings.TrimSpace(parts[1]) fmt.Printf("\n动态追加步骤: [%s] %s\n", role, title) modifyReq := squad.ModifyWorkflowRequest{ WorkDir: workDir, Backend: backend, AddSteps: []squad.StepSpec{ { ID: fmt.Sprintf("step_dyn_%d", len(state.Workflow.Steps)+1), Role: role, Title: title, SubWorkspace: fmt.Sprintf("tasks/dyn_%s", role), Mode: "execute", }, }, } runRes, err := svc.ResumeOrModifyWorkspace(ctx, modifyReq) if err != nil { fmt.Fprintf(os.Stderr, "执行动态追加步骤失败: %v\n", err) } else { fmt.Printf("动态步骤执行成功! 耗时: %v\n", runRes.Duration) } } } } func handleChatMode( ctx context.Context, svc bizdev.BizdevService, chatQuery string, workDir string, project string, customer string, device string, use string, market string, userName string, userRole string, ) { u := coordinator.UserInfo{ ID: "u_local", Name: userName, Role: userRole, ProjectRole: "composer", } assembler := coordinator.NewContextAssembler() var userCtx *coordinator.UserProjectContext var err error if workDir != "" { userCtx, err = assembler.AssembleFromWorkspace(workDir, u) if err != nil { fmt.Fprintf(os.Stderr, "从工作区装载上下文失败 %s: %v,将初始化新上下文\n", workDir, err) } } if userCtx == nil { userCtx = &coordinator.UserProjectContext{ SnapshotID: fmt.Sprintf("ctx_%d", time.Now().UnixNano()), AssembledAt: time.Now(), User: u, Project: coordinator.ProjectInfo{ Name: project, CustomerName: customer, DeviceType: device, IntendedUse: use, TargetMarket: market, CurrentWorkflow: "intake", CurrentStage: bizdev.StageV01_Triage, }, BlackboardFacts: make(map[string]string), AvailableActions: coordinator.ComputeAvailableActions(bizdev.StageV01_Triage, u.ProjectRole), } if market != "" { userCtx.BlackboardFacts["target_market"] = market } if device != "" { userCtx.BlackboardFacts["device_type"] = device } } rec := intent.NewRecognizer() engine := coordinator.NewCoordinatorEngine(svc) session := chat.NewSession("cli_chat_sess", userCtx, rec, engine) // 单次提问模式 (支持脚本与自动化测试) if chatQuery != "" { resp, err := session.SendMessage(ctx, chatQuery) if err != nil { fmt.Fprintf(os.Stderr, "对话处理失败: %v\n", err) os.Exit(1) } top := resp.IntentResult.TopIntent fmt.Printf("[意图识别: %s (置信度: %.0f%%) | 消解分支: %s] - %s\n", top.Intent, top.Confidence*100, resp.Branch, top.Reasoning) fmt.Printf("\n%s\n", resp.ReplyText) if resp.AdvisorBrief != nil && (top.Intent == intent.IntentAdvanceStage || top.Intent == intent.IntentQueryProgress) { printAdvisorBrief(resp.AdvisorBrief) } return } // 交互式 REPL 模式 fmt.Println("==================================================================") fmt.Println(" 🤖 医疗器械商务与研发智能参谋(L1/L2 协作对话终端) ") fmt.Println("==================================================================") fmt.Printf("项目名称: %s | 客户单位: %s\n", userCtx.Project.Name, userCtx.Project.CustomerName) fmt.Printf("当前阶段: %s | 用户身份: %s (%s)\n", userCtx.Project.CurrentStage, userCtx.User.Name, userCtx.User.Role) fmt.Printf("可用动作: %v\n", userCtx.AvailableActions) fmt.Println("------------------------------------------------------------------") fmt.Println("💡 参谋互动小贴士(三分支柔性消解):") fmt.Println(" • 极速兑现(分支 1):当参谋给出下一步建议时,回复“好”、“行”或“1”直接推进") fmt.Println(" • 复合补丁(分支 1):可以肯定并同时附带微调,如:“好,但目标市场改成欧洲CE”") fmt.Println(" • 暂缓撤销(分支 1):如果不想推进,直接说:“先别推,等等”") fmt.Println(" • 突发抢占(分支 2):随时发起新问题,如:“查一下探头功耗要怎么设计”") fmt.Println(" • 模糊分忧(分支 3):拿不定主意时,可以说:“这个先放放,你觉得呢”") fmt.Println(" • 输入 'exit' 或 'quit' 退出") fmt.Println("==================================================================") scanner := bufio.NewScanner(os.Stdin) for { fmt.Print("\n[您] > ") if !scanner.Scan() { break } input := strings.TrimSpace(scanner.Text()) if input == "" { continue } if input == "exit" || input == "quit" { fmt.Println("已退出参谋对话。") break } resp, err := session.SendMessage(ctx, input) if err != nil { fmt.Fprintf(os.Stderr, "处理异常: %v\n", err) continue } top := resp.IntentResult.TopIntent fmt.Printf("\n[智能参谋] (消解分支: %s | 核心意图: %s | 置信度: %.0f%%)\n", resp.Branch, top.Intent, top.Confidence*100) fmt.Println(resp.ReplyText) if resp.AdvisorBrief != nil && (top.Intent == intent.IntentAdvanceStage || top.Intent == intent.IntentQueryProgress) { printAdvisorBrief(resp.AdvisorBrief) } } }