Files
Agent-System/internal/squad/workspace.go
T

874 lines
33 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package squad
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"chat-agent-system/internal/template"
)
// WorkspacePrepareRequest 工作区准备请求
type WorkspacePrepareRequest struct {
WorkDir string
SessionID int64
ProjectID int64
TaskKey string
StageTitle string
ProjectName string
CustomerName string
DeviceType string
IntendedUse string
TargetMarket string
Backend string
TemplateContent string
Materials []MaterialItem
KnowledgeHits []KnowledgeHit
PriorDocs []PriorDocItem
UserContext string
ProjectContext string
AgentContext string
MemoryBankFiles map[string]string
CustomAgents []AgentSpec
WorkflowSteps []StepSpec
InitialFields map[string]string
Metadata map[string]string
}
// Workspace 表示已初始化的工作区句柄
type Workspace struct {
WorkDir string
Meta WorkspaceMetadata
Squad SquadConfig
Workflow WorkflowPlan
}
// WorkspaceEngine 工作区生命周期与沙箱构建引擎
type WorkspaceEngine struct{}
func NewWorkspaceEngine() *WorkspaceEngine {
return &WorkspaceEngine{}
}
// PrepareWorkspace 初始化自包含工作区目录
func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Workspace, error) {
workDir := req.WorkDir
if workDir == "" {
ts := time.Now().UnixNano()
workDir = filepath.Join("data", "dev", "workspaces", "bizdev", fmt.Sprintf("task_%s_%d", req.TaskKey, ts))
}
if err := os.MkdirAll(workDir, 0o755); err != nil {
return nil, fmt.Errorf("create workspace root dir: %w", err)
}
now := time.Now()
wsID := fmt.Sprintf("ws_%s_%d", req.TaskKey, now.UnixNano())
if req.Metadata != nil && req.Metadata["id"] != "" {
wsID = req.Metadata["id"]
}
// 1. 初始化 workspace.json
meta := WorkspaceMetadata{
ID: wsID,
SessionID: req.SessionID,
ProjectID: req.ProjectID,
TaskKey: req.TaskKey,
StageTitle: req.StageTitle,
ProjectName: req.ProjectName,
CustomerName: req.CustomerName,
DeviceType: req.DeviceType,
IntendedUse: req.IntendedUse,
TargetMarket: req.TargetMarket,
Status: "created",
Backend: req.Backend,
WorkDir: workDir,
CreatedAt: now,
UpdatedAt: now,
Metadata: req.Metadata,
}
if err := writeJSON(filepath.Join(workDir, "workspace.json"), meta); err != nil {
return nil, fmt.Errorf("write workspace.json: %w", err)
}
// 2. 初始化自适应小队与工作流规划
var squadCfg SquadConfig
var wf WorkflowPlan
tmplContent := req.TemplateContent
if len(req.WorkflowSteps) > 0 {
agents := req.CustomAgents
if len(agents) == 0 {
agents = DefaultSquadAgents(req.TaskKey)
}
squadCfg = SquadConfig{
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
Version: "1.0.0",
Agents: agents,
}
steps := req.WorkflowSteps
for idx := range steps {
if steps[idx].Status == "" {
steps[idx].Status = "pending"
}
}
wf = WorkflowPlan{
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
TaskKey: req.TaskKey,
Title: req.StageTitle,
Status: "pending",
CurrentStepIndex: 0,
Steps: steps,
}
} else {
planner := NewAdaptivePlanner(nil)
pRes, pErr := planner.Plan(PlannerRequest{
TaskKey: req.TaskKey,
Title: req.StageTitle,
ProjectName: req.ProjectName,
CustomerName: req.CustomerName,
DeviceType: req.DeviceType,
IntendedUse: req.IntendedUse,
TargetMarket: req.TargetMarket,
Materials: req.Materials,
KnowledgeHits: req.KnowledgeHits,
Fields: req.InitialFields,
CustomSteps: req.WorkflowSteps,
})
if pErr == nil && pRes != nil {
squadCfg = pRes.Squad
wf = pRes.Workflow
if strings.TrimSpace(tmplContent) == "" {
tmplContent = pRes.TemplateContent
}
} else {
agents := req.CustomAgents
if len(agents) == 0 {
agents = DefaultSquadAgents(req.TaskKey)
}
squadCfg = SquadConfig{
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
Version: "1.0.0",
Agents: agents,
}
steps := req.WorkflowSteps
if len(steps) == 0 {
steps = DefaultWorkflowSteps(req.TaskKey)
}
wf = WorkflowPlan{
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
Status: "pending",
CurrentStepIndex: 0,
Steps: steps,
}
}
}
if err := writeJSON(filepath.Join(workDir, "squad.json"), squadCfg); err != nil {
return nil, fmt.Errorf("write squad.json: %w", err)
}
if err := writeJSON(filepath.Join(workDir, "workflow.json"), wf); err != nil {
return nil, fmt.Errorf("write workflow.json: %w", err)
}
// 4. 写入自描述模板 template.md
if strings.TrimSpace(tmplContent) == "" {
var err error
tmplContent, err = template.GetDocTemplate(req.TaskKey)
if err != nil {
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", req.StageTitle)
}
}
vars := map[string]string{
"project_name": req.ProjectName,
"product_name": req.ProjectName,
"customer_name": req.CustomerName,
"device_type": req.DeviceType,
"intended_use": req.IntendedUse,
"target_market": req.TargetMarket,
"generated_at": now.Format("2006-01-02 15:04:05"),
"snapshot_id": wsID,
}
for k, v := range req.InitialFields {
vars[k] = v
}
renderedTmpl := template.RenderDoc(tmplContent, vars)
if err := os.WriteFile(filepath.Join(workDir, "template.md"), []byte(renderedTmpl), 0o644); err != nil {
return nil, fmt.Errorf("write template.md: %w", err)
}
// 5. 初始化 blackboard.json
bb := BlackboardState{
Facts: make([]FactItem, 0),
Gaps: make([]GapItem, 0),
Implications: make([]ImplicationItem, 0),
Assumptions: make([]AssumptionItem, 0),
Fields: make(map[string]string),
UpdatedAt: now,
}
if req.CustomerName != "" {
bb.Fields["customer_name"] = req.CustomerName
bb.Facts = append(bb.Facts, FactItem{
ID: "fact_01",
Claim: "客户名称为 " + req.CustomerName,
Source: "用户输入",
Verified: true,
})
}
if req.ProjectName != "" {
bb.Fields["project_name"] = req.ProjectName
}
if req.DeviceType != "" {
bb.Fields["device_type"] = req.DeviceType
}
if req.IntendedUse != "" {
bb.Fields["intended_use"] = req.IntendedUse
}
if req.TargetMarket != "" {
bb.Fields["target_market"] = req.TargetMarket
}
for k, v := range req.InitialFields {
bb.Fields[k] = v
}
if err := writeJSON(filepath.Join(workDir, "blackboard.json"), bb); err != nil {
return nil, fmt.Errorf("write blackboard.json: %w", err)
}
// 6. 构造自描述上下文体系 context/
if err := e.buildContextSystem(workDir, req); err != nil {
return nil, fmt.Errorf("build context system: %w", err)
}
// 7. 创建子工作区 tasks/<sub_dir> 并注入 TASK.md 与 AGENTS.md
if err := e.buildSubWorkspaces(workDir, wf, squadCfg, renderedTmpl); err != nil {
return nil, fmt.Errorf("build sub workspaces: %w", err)
}
// 8. 创建 artifacts/ 与基础日志
_ = os.MkdirAll(filepath.Join(workDir, "artifacts"), 0o755)
logEntry := fmt.Sprintf("[%s] 工作区创建完成: ID=%s, Task=%s\n", now.Format(time.RFC3339), wsID, req.TaskKey)
_ = os.WriteFile(filepath.Join(workDir, "execution.log"), []byte(logEntry), 0o644)
return &Workspace{
WorkDir: workDir,
Meta: meta,
Squad: squadCfg,
Workflow: wf,
}, nil
}
// buildContextSystem 构建具有权威度说明的 context/ 体系
func (e *WorkspaceEngine) buildContextSystem(workDir string, req WorkspacePrepareRequest) error {
ctxDir := filepath.Join(workDir, "context")
_ = os.MkdirAll(filepath.Join(ctxDir, "materials_text"), 0o755)
_ = os.MkdirAll(filepath.Join(ctxDir, "knowledge"), 0o755)
_ = os.MkdirAll(filepath.Join(ctxDir, "prior_docs"), 0o755)
_ = os.MkdirAll(filepath.Join(ctxDir, "memory"), 0o755)
var contextItems []ContextItem
// 1. user.md
userContent := req.UserContext
if strings.TrimSpace(userContent) == "" {
userContent = fmt.Sprintf("# 用户角色与偏好\n\n- 用户角色: 商务与技术负责人\n- 决策风格: 严谨可追溯,注重四段式论据\n- 授权边界: 预算与商业承诺必须经由用户拍板\n")
}
_ = os.WriteFile(filepath.Join(ctxDir, "user.md"), []byte(userContent), 0o644)
contextItems = append(contextItems, ContextItem{
File: "context/user.md",
Description: "操作用户的业务角色、决策授权边界与协同偏好",
AuthorityLevel: "authoritative",
Guidance: "必须尊重用户设定的目标市场与边界,用户未决事项一律列入待确认清单,严禁越权替用户拍板",
})
// 2. project.md
projContent := req.ProjectContext
if strings.TrimSpace(projContent) == "" {
projContent = fmt.Sprintf("# 项目全局基线\n\n- 项目名称: %s\n- 客户名称: %s\n- 产品类型: %s\n- 预期用途: %s\n- 目标市场: %s\n",
req.ProjectName, req.CustomerName, req.DeviceType, req.IntendedUse, req.TargetMarket)
}
_ = os.WriteFile(filepath.Join(ctxDir, "project.md"), []byte(projContent), 0o644)
contextItems = append(contextItems, ContextItem{
File: "context/project.md",
Description: "项目立项章程与全局事实基线",
AuthorityLevel: "authoritative",
Guidance: "本项目核心前提。所有后续分析论证不得推翻已锁定的项目名称与核心预期用途",
})
// 3. agent.md
agentContent := req.AgentContext
if strings.TrimSpace(agentContent) == "" {
agentContent = "# 智能体小队协同通用规范\n\n- 事实闭环: 严禁凭空捏造未提供之参数或法规\n- 引用规范: 引用必须标注出处〔材料:...〕或〔法规:...〕\n- 负向红线: 禁止敷衍写作、禁止'不做展开'、'从略'\n"
}
_ = os.WriteFile(filepath.Join(ctxDir, "agent.md"), []byte(agentContent), 0o644)
contextItems = append(contextItems, ContextItem{
File: "context/agent.md",
Description: "智能体通用协同纪律与引用要求",
AuthorityLevel: "authoritative",
Guidance: "所有小队角色必须严格执行的专业守则与写作禁令",
})
// 4. materials_text/
if len(req.Materials) > 0 {
var allBuilder strings.Builder
for _, m := range req.Materials {
name := filepath.Base(m.Filename)
if name == "" || name == "." {
name = "material.txt"
}
// 自动提取纯文本并注入页码锚点 <!-- page N -->
textContent, err := ExtractTextWithAnchors(name, m.Content)
if err != nil || strings.TrimSpace(textContent) == "" {
textContent = string(m.Content)
}
textBytes := []byte(textContent)
p := filepath.Join(ctxDir, "materials_text", name)
_ = os.WriteFile(p, textBytes, 0o644)
// 若原文件扩展名不是 .txt / .md,同时生成一份 .txt 方便智能体直接阅读
if !strings.HasSuffix(name, ".txt") && !strings.HasSuffix(name, ".md") {
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", name+".txt"), textBytes, 0o644)
}
// Also write to root materials_text/ for backward compatibility
rootMatDir := filepath.Join(workDir, "materials_text")
_ = os.MkdirAll(rootMatDir, 0o755)
_ = os.WriteFile(filepath.Join(rootMatDir, name), textBytes, 0o644)
if !strings.HasSuffix(name, ".txt") && !strings.HasSuffix(name, ".md") {
_ = os.WriteFile(filepath.Join(rootMatDir, name+".txt"), textBytes, 0o644)
}
allBuilder.WriteString(fmt.Sprintf("\n\n=== 文件: %s ===\n\n", name))
allBuilder.WriteString(textContent)
contextItems = append(contextItems, ContextItem{
File: filepath.ToSlash(filepath.Join("context", "materials_text", name)),
Description: fmt.Sprintf("用户上传的输入材料文本: %s", name),
AuthorityLevel: "primary_evidence",
Guidance: "核心原始证据。提取参数需标明出处;材料内部矛盾需在矛盾表中并列呈现",
})
}
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "_all.txt"), []byte(allBuilder.String()), 0o644)
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "README.md"), []byte("# 原始材料清单\n\n已汇总在 `context/materials_text/_all.txt`,亦可单独查阅各文件。\n"), 0o644)
} else {
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "README.md"), []byte("# 原始材料说明\n\n本项目当前未提供独立的附加文本材料,所有事实依据以 `context/project.md`(项目全局基线)、`context/user.md`(用户要求与偏好)以及 `context/knowledge/hits.md`(法规标准知识库)为准。\n"), 0o644)
}
// 5. knowledge/
_ = os.WriteFile(filepath.Join(ctxDir, "knowledge", "WEB_CROSSCHECK.md"), []byte("# 法规检索与交叉核对原则\n\n1. 知识库条目(hits.md)为首要法规事实依据。\n2. 如需查询外部信息,以国家药监局官方分类目录为主。\n"), 0o644)
if len(req.KnowledgeHits) > 0 {
var kbBuilder strings.Builder
kbBuilder.WriteString("# 权威法规与技术标准引用库 (Regulatory Knowledge Hits)\n\n")
kbBuilder.WriteString("以下为针对本项目研发与注册需求命中的权威法规、国家标准(GB)与行业标准(YY)条款。\n")
kbBuilder.WriteString("在输出交付物时,请结合上下文使用,并显式标注【引用标记 (CiteKey)】标明出处。\n\n")
for _, hit := range req.KnowledgeHits {
kbBuilder.WriteString(fmt.Sprintf("### [%s] %s %s\n\n", strings.ToUpper(hit.Category), hit.DocCode, hit.Title))
if hit.HeadingPath != "" {
kbBuilder.WriteString(fmt.Sprintf("- **章节路径**: %s\n", hit.HeadingPath))
}
if hit.CiteKey != "" {
kbBuilder.WriteString(fmt.Sprintf("- **引用标记 (CiteKey)**: `%s`\n", hit.CiteKey))
}
kbBuilder.WriteString(fmt.Sprintf("- **来源**: %s\n\n", hit.Source))
kbBuilder.WriteString(hit.Content + "\n\n---\n\n")
}
_ = os.WriteFile(filepath.Join(ctxDir, "knowledge", "hits.md"), []byte(kbBuilder.String()), 0o644)
contextItems = append(contextItems, ContextItem{
File: "context/knowledge/hits.md",
Description: "法规知识库中命中的医疗器械分类、国家标准与指导原则",
AuthorityLevel: "reference_standard",
Guidance: "合规与技术论据源。引用必须标注标准号与引用标记 (CiteKey);涉及审批结论需标注'需向药监局审评中心确认'",
})
}
// 6. prior_docs/
if len(req.PriorDocs) > 0 {
var indexBuilder strings.Builder
indexBuilder.WriteString("# 前序阶段交付物索引\n\n")
for _, doc := range req.PriorDocs {
filename := doc.Filename
if filename == "" {
filename = doc.DocKey + ".md"
}
p := filepath.Join(ctxDir, "prior_docs", filename)
_ = os.WriteFile(p, []byte(doc.Content), 0o644)
indexBuilder.WriteString(fmt.Sprintf("- [%s] `%s`: %s (阶段: %s)\n", doc.DocKey, filename, doc.Title, doc.Stage))
contextItems = append(contextItems, ContextItem{
File: filepath.ToSlash(filepath.Join("context", "prior_docs", filename)),
Description: fmt.Sprintf("前序已归档文档: %s (%s)", doc.Title, doc.DocKey),
AuthorityLevel: "primary_evidence",
Guidance: "前序阶段已批准的结论基准。本阶段内容应与前序结论保持自洽",
})
}
_ = os.WriteFile(filepath.Join(ctxDir, "prior_docs", "index.md"), []byte(indexBuilder.String()), 0o644)
} else {
_ = os.WriteFile(filepath.Join(ctxDir, "prior_docs", "README.md"), []byte("# 前序交付物说明\n\n本项目当前处于启动阶段,暂无已归档的前序阶段交付物。\n"), 0o644)
}
if req.TemplateContent != "" {
_ = os.WriteFile(filepath.Join(ctxDir, "template.md"), []byte(req.TemplateContent), 0o644)
}
// 7. memory/
if len(req.MemoryBankFiles) > 0 {
for k, v := range req.MemoryBankFiles {
p := filepath.Join(ctxDir, "memory", k)
_ = os.WriteFile(p, []byte(v), 0o644)
}
} else {
// 默认四大核心资产骨架
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "projectbrief.md"), []byte(fmt.Sprintf("# 项目章程\n- 项目: %s\n- 客户: %s\n", req.ProjectName, req.CustomerName)), 0o644)
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "activeContext.md"), []byte(fmt.Sprintf("# 当前焦点\n- 阶段: %s\n- 时间: %s\n", req.TaskKey, time.Now().Format("2006-01-02 15:04"))), 0o644)
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "systemPatterns.md"), []byte("# 规范与模式\n- 遵循四段式结构与严格角色边界\n- 单一真实源原则\n"), 0o644)
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "progress.md"), []byte(fmt.Sprintf("# 进度\n- 当前正在进行: %s\n", req.StageTitle)), 0o644)
}
contextItems = append(contextItems, ContextItem{
File: "context/memory/",
Description: "长期项目记忆银行 (ActiveContext, Progress, ProjectBrief, SystemPatterns)",
AuthorityLevel: "supplementary",
Guidance: "协同上下文背景参考,防止上下文遗忘",
})
// 8. 写入 context/index.json
ctxIndex := ContextIndex{Items: contextItems}
if err := writeJSON(filepath.Join(ctxDir, "index.json"), ctxIndex); err != nil {
return err
}
// 9. 写入 context/README.md 指引
readmeContent := `# 上下文使用指引 (Context Reading Guide)
本目录为当前智能体任务的自包含上下文沙箱。所有智能体专员在执行任务时,必须按照以下权威度等级(Authority Level)采纳信息:
| 权威度级别 | 适用文件类别 | 采信与处理规则 |
|---|---|---|
| **authoritative** | ` + "`user.md`" + `, ` + "`project.md`" + `, ` + "`agent.md`" + ` | **最高权威**。必须绝对遵循;严禁越权替用户拍板,未决项列入待确认清单 |
| **primary_evidence** | ` + "`materials_text/*`" + `, ` + "`prior_docs/*`" + ` | **原始事实证据**。引用必须标明出处条文;若材料间存在矛盾,并列呈现 |
| **reference_standard** | ` + "`knowledge/hits.md`" + ` | **标准与法规依据**。必须指明法规/标准代码;涉及监管裁定标注需窗口核实 |
| **supplementary** | ` + "`memory/*`" + ` | **补充记忆背景**。仅供背景了解,不作为唯一支撑证据 |
---
`
return os.WriteFile(filepath.Join(ctxDir, "README.md"), []byte(readmeContent), 0o644)
}
// buildSubWorkspaces 为 workflow 中的各个子任务构建独立沙箱
func (e *WorkspaceEngine) buildSubWorkspaces(workDir string, wf WorkflowPlan, squadCfg SquadConfig, renderedTmpl string) error {
agentMap := make(map[string]AgentSpec)
for _, a := range squadCfg.Agents {
agentMap[a.Role] = a
}
for _, step := range wf.Steps {
subDir := filepath.Join(workDir, filepath.FromSlash(step.SubWorkspace))
if err := os.MkdirAll(subDir, 0o755); err != nil {
return err
}
// 1. 生成 AGENTS.md
var agentsBuilder strings.Builder
inst, err := template.GetAgentInstruction(step.Role)
if err == nil {
agentsBuilder.WriteString(inst)
} else {
spec := agentMap[step.Role]
agentsBuilder.WriteString(fmt.Sprintf("# 角色: %s (%s)\n\n", spec.Name, spec.Role))
if len(spec.Responsibilities) > 0 {
agentsBuilder.WriteString("## 核心职责\n")
for _, r := range spec.Responsibilities {
agentsBuilder.WriteString(fmt.Sprintf("- %s\n", r))
}
agentsBuilder.WriteString("\n")
}
if len(spec.ForbiddenActions) > 0 {
agentsBuilder.WriteString("## 严禁行为(红线)\n")
for _, f := range spec.ForbiddenActions {
agentsBuilder.WriteString(fmt.Sprintf("- ❌ %s\n", f))
}
agentsBuilder.WriteString("\n")
}
}
_ = os.WriteFile(filepath.Join(subDir, "AGENTS.md"), []byte(agentsBuilder.String()), 0o644)
_ = os.WriteFile(filepath.Join(subDir, "INSTRUCTIONS.md"), []byte(agentsBuilder.String()), 0o644)
// 2. 生成 TASK.md
deliverableName := "output.md"
if step.Mode == "plan" || step.Mode == "replan" {
deliverableName = "output_plan.md"
} else if step.Mode == "review_plan" {
deliverableName = "review_notes.md"
}
var taskBuilder strings.Builder
taskBuilder.WriteString(fmt.Sprintf("# 任务执行指令: %s\n\n", step.Title))
taskBuilder.WriteString(fmt.Sprintf("- **步骤 ID**: `%s`\n", step.ID))
taskBuilder.WriteString(fmt.Sprintf("- **分配角色**: `%s`\n", step.Role))
taskBuilder.WriteString(fmt.Sprintf("- **执行模式**: `%s`\n", step.Mode))
taskBuilder.WriteString(fmt.Sprintf("- **交付产物**: `%s`\n\n", deliverableName))
taskBuilder.WriteString("## 环境与输入导航\n")
taskBuilder.WriteString("请仔细查阅上级目录资源:\n")
taskBuilder.WriteString("- `../../context/index.json` 及 `../../context/README.md`:阅读上下文指引\n")
taskBuilder.WriteString("- `../../template.md`:自描述交付模板(嵌入四段式与格式约束)\n")
taskBuilder.WriteString("- `../../blackboard.json`:前序专员抽取的结构化事实与缺口\n")
taskBuilder.WriteString("- `../../context/project.md`:项目全局立项基线与产品定义\n")
taskBuilder.WriteString("- `../../context/user.md`:用户个性化工作偏好与决策约束\n")
taskBuilder.WriteString("- `../../context/materials_text/README.md`:原始材料全文或基线说明\n")
taskBuilder.WriteString("- `../../context/knowledge/hits.md`:法规与标准命中快照 (若存在)\n\n")
if len(step.Needs) > 0 {
taskBuilder.WriteString("## 前序依赖产出\n")
for _, n := range step.Needs {
taskBuilder.WriteString(fmt.Sprintf("- `../../%s`\n", filepath.ToSlash(n)))
}
taskBuilder.WriteString("\n")
}
if len(step.Accept) > 0 {
taskBuilder.WriteString("## 验收标准(Checklist)\n")
for _, a := range step.Accept {
taskBuilder.WriteString(fmt.Sprintf("- [ ] %s\n", a))
}
taskBuilder.WriteString("\n")
}
taskBuilder.WriteString("## 交付契约与写作纪律\n")
taskBuilder.WriteString(fmt.Sprintf("请在当前目录下生成完整交付物 `%s`:\n", deliverableName))
taskBuilder.WriteString("1. 严格四段式展开(结论句 → 表格/列表 → 解读 → 支撑出处)。\n")
taskBuilder.WriteString("2. 禁止使用'不做展开'、'从略'、'篇幅有限'等省略敷衍套话。\n")
taskBuilder.WriteString("3. 关键事实必须标注出处,未知项列入待确认清单。\n")
if step.Role == "organizer" {
taskBuilder.WriteString("4. 从材料提取的客观字段,请在当前工作目录下输出 `fields.json`。\n")
}
_ = os.WriteFile(filepath.Join(subDir, "TASK.md"), []byte(taskBuilder.String()), 0o644)
// 3. 写入 input_refs.json
inputRefs := map[string]any{
"step_id": step.ID,
"role": step.Role,
"needs": step.Needs,
"target_file": deliverableName,
}
_ = writeJSON(filepath.Join(subDir, "input_refs.json"), inputRefs)
// 4. 创建便捷软链接,使得当前目录下无论是读 template.md、context/ 还是 ../../ 均可访问
_ = os.Symlink(filepath.Join("..", "..", "template.md"), filepath.Join(subDir, "template.md"))
_ = os.Symlink(filepath.Join("..", "..", "context"), filepath.Join(subDir, "context"))
_ = os.Symlink(filepath.Join("..", "..", "blackboard.json"), filepath.Join(subDir, "blackboard.json"))
}
return nil
}
// DefaultSquadAgents 根据任务类型返回默认小队成员编制
func DefaultSquadAgents(taskKey string) []AgentSpec {
switch taskKey {
case "intake":
return []AgentSpec{
{
Role: "organizer",
Name: "材料整理专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"材料事实抽取", "缺口识别", "字段结构化回填"},
ForbiddenActions: []string{"严禁测算价格", "严禁出具法规终结裁定", "严禁撰写正文终稿"},
},
{
Role: "writer",
Name: "文档撰写专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"严格四段式成文", "依据材料出处引用", "模板各章节写满"},
ForbiddenActions: []string{"严禁写不做展开或从略", "严禁随意篡改前序事实"},
},
{
Role: "leader",
Name: "商务对接负责人",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"计划审核", "正文终审", "待确认清单汇总", "对外汇报"},
ForbiddenActions: []string{"严禁代用户做商业拍板", "严禁直接交付未审核初稿"},
},
}
case "research":
return []AgentSpec{
{
Role: "searcher",
Name: "资料检索专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"公开资料检索", "法规标准核对", "出处与日期标注"},
ForbiddenActions: []string{"严禁编造URL与标准号", "未检索到须标明尝试路径"},
},
{
Role: "market",
Name: "市场与竞品分析师",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"竞品同维度对比", "市场规模测算", "机会与风险分析"},
ForbiddenActions: []string{"严禁编造市场份额与价格", "必须采用统一对比维度"},
},
{
Role: "regulatory",
Name: "医疗器械法规专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"器械分类初筛", "注册路径比对", "关键标准提炼"},
ForbiddenActions: []string{"严禁代做行政终局裁定", "必须标注需向窗口确认"},
},
{
Role: "writer",
Name: "文档撰写专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"按计划合成调研报告", "全面四段式展开"},
ForbiddenActions: []string{"严禁写不做展开或从略"},
},
{
Role: "leader",
Name: "商务对接负责人",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"审核撰写计划", "终审报告", "质量把关"},
ForbiddenActions: []string{"严禁代用户做商业拍板"},
},
}
default:
return []AgentSpec{
{
Role: "organizer",
Name: "材料整理专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"事实整理与基线对齐"},
},
{
Role: "writer",
Name: "文档撰写专员",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"正文撰写"},
},
{
Role: "leader",
Name: "商务对接负责人",
Model: "claude-3-7-sonnet",
Tools: []string{"read", "write", "edit"},
BashPermission: "deny",
Responsibilities: []string{"终审与把关"},
},
}
}
}
// DefaultWorkflowSteps 根据任务类型返回最佳实践编排步骤
func DefaultWorkflowSteps(taskKey string) []StepSpec {
switch taskKey {
case "intake":
return []StepSpec{
{
ID: "step_01_organize",
Role: "organizer",
Title: "抽取材料事实并回填字段",
SubWorkspace: "tasks/01_organize",
Mode: "execute",
Accept: []string{
"抽取材料中的客户身份与产品定义",
"回填 fields.json 结构化数据",
"向 blackboard 写入 facts 与 gaps",
},
},
{
ID: "step_02_draft",
Role: "writer",
Title: "撰写用户初始需求收集表初稿",
SubWorkspace: "tasks/02_draft",
Mode: "execute",
Needs: []string{"tasks/01_organize/output.md"},
Accept: []string{
"按 template.md 各章节严格四段式展开",
"正文翔实,禁止写'不做展开'",
"文末集中附录字段快照与待确认清单",
},
},
{
ID: "step_03_review",
Role: "leader",
Title: "审核并定稿交付",
SubWorkspace: "tasks/03_review",
Mode: "execute",
Needs: []string{"tasks/02_draft/output.md"},
Accept: []string{
"核验全文逻辑一致性与出处标注",
"汇编待确认清单并给出下一步建议",
"交付终稿 output.md",
},
},
}
case "research":
return []StepSpec{
{
ID: "step_01_search",
Role: "searcher",
Title: "公开资料与标准检索",
SubWorkspace: "tasks/01_search",
Mode: "execute",
Parallel: true,
Accept: []string{
"检索纪要附带明确出处或未检索到说明",
"对本项目含义逐条阐述",
},
},
{
ID: "step_02_market",
Role: "market",
Title: "市场格局与竞品同维度对比",
SubWorkspace: "tasks/02_market",
Mode: "execute",
Parallel: true,
Accept: []string{
"多维度竞品对比表",
"机会与风险对称分析",
},
},
{
ID: "step_03_regulatory",
Role: "regulatory",
Title: "医疗器械法规与指导原则初筛",
SubWorkspace: "tasks/03_regulatory",
Mode: "execute",
Parallel: true,
Accept: []string{
"分类依据条目与建议类别",
"明确标注需向审评窗口确认",
},
},
{
ID: "step_04_plan",
Role: "writer",
Title: "撰写调研报告段落计划",
SubWorkspace: "tasks/04_plan",
Mode: "plan",
Needs: []string{
"tasks/01_search/output.md",
"tasks/02_market/output.md",
"tasks/03_regulatory/output.md",
},
Accept: []string{"各章节论点与引用规划"},
},
{
ID: "step_05_review_plan",
Role: "leader",
Title: "审核调研报告撰写计划",
SubWorkspace: "tasks/05_review_plan",
Mode: "review_plan",
Needs: []string{"tasks/04_plan/output_plan.md"},
Accept: []string{"出具审核意见 review_notes.md"},
},
{
ID: "step_06_draft",
Role: "writer",
Title: "按计划合成调研报告正文",
SubWorkspace: "tasks/06_draft",
Mode: "execute",
Needs: []string{
"tasks/04_plan/output_plan.md",
"tasks/05_review_plan/review_notes.md",
},
Accept: []string{"四段式完整正文,无省略套话"},
},
{
ID: "step_07_review",
Role: "leader",
Title: "终审并定稿",
SubWorkspace: "tasks/07_review",
Mode: "execute",
Needs: []string{"tasks/06_draft/output.md"},
Accept: []string{"质量把关并通过交付"},
},
}
case "feasibility_reg":
return []StepSpec{
{
ID: "step_01_regulatory",
Role: "regulatory",
Title: "分析法规分类与标准条款",
SubWorkspace: "tasks/01_regulatory",
Mode: "execute",
Accept: []string{
"研判医疗器械管理类别与分类依据",
"结合知识库 hits.md 梳理适用的强制与推荐标准",
"给出型式检验与主要检测项目清单",
},
},
{
ID: "step_02_draft",
Role: "writer",
Title: "撰写注册可行性评估报告初稿",
SubWorkspace: "tasks/02_draft",
Mode: "execute",
Needs: []string{"tasks/01_regulatory/output.md"},
Accept: []string{
"严格按 template.md 结构展开",
"必须显式标注引用标准与条款 CiteKey",
"文末附待确认清单与风险警示",
},
},
{
ID: "step_03_review",
Role: "leader",
Title: "审核定稿交付",
SubWorkspace: "tasks/03_review",
Mode: "execute",
Needs: []string{"tasks/02_draft/output.md"},
Accept: []string{
"核验法规条款引用的准确性与完整性",
"质量门禁达标交付",
},
},
}
default:
return []StepSpec{
{
ID: "step_01_draft",
Role: "writer",
Title: "撰写初稿",
SubWorkspace: "tasks/01_draft",
Mode: "execute",
Accept: []string{"按模板四段式展开"},
},
{
ID: "step_02_review",
Role: "leader",
Title: "审核定稿",
SubWorkspace: "tasks/02_review",
Mode: "execute",
Needs: []string{"tasks/01_draft/output.md"},
Accept: []string{"质量门禁通过"},
},
}
}
}
func writeJSON(path string, val any) error {
b, err := json.MarshalIndent(val, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0o644)
}