Files
Agent-System/internal/squad/workspace_context_build.go
T
weichen dc29b84cdf feat(ctx): inject cleaned project dialogue into squad and assistant
Add dialoguectx packaging, conflictresolve adjudication with HITL routing,
workspace context/dialogue/ injection (default on), and goose 00071.
2026-09-24 17:04:04 +00:00

252 lines
13 KiB
Go

package squad
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// 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. dialogue/ — project member dialogue (digest + turns + facts)
if err := e.writeDialogueContext(ctxDir, req.DialoguePackage, &contextItems); err != nil {
return err
}
// 9. 写入 context/index.json
ctxIndex := ContextIndex{Items: contextItems}
if err := writeJSON(filepath.Join(ctxDir, "index.json"), ctxIndex); err != nil {
return err
}
// 10. 写入 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`" + ` | **标准与法规依据**。必须指明法规/标准代码;涉及监管裁定标注需窗口核实 |
| **collab_dialogue** | ` + "`dialogue/*`" + ` | **项目成员对话**。采信级低于黑板事实与已批准文档;与高权威来源冲突时须并列呈现矛盾,不得静默采信对话版本 |
| **supplementary** | ` + "`memory/*`" + ` | **补充记忆背景**。仅供背景了解,不作为唯一支撑证据 |
---
`
return os.WriteFile(filepath.Join(ctxDir, "README.md"), []byte(readmeContent), 0o644)
}
func (e *WorkspaceEngine) writeDialogueContext(ctxDir string, pkg *DialoguePackage, items *[]ContextItem) error {
dlgDir := filepath.Join(ctxDir, "dialogue")
if err := os.MkdirAll(dlgDir, 0o755); err != nil {
return err
}
if pkg == nil {
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte("# 项目对话说明\n\n本任务未注入项目成员对话上下文(开关关闭或无可共享对话)。\n"), 0o644)
return nil
}
digest := pkg.DigestMD
if strings.TrimSpace(digest) == "" {
digest = "# 项目对话纪要\n\n暂无可用共享对话纪要。\n"
}
turns := pkg.TurnsMD
if strings.TrimSpace(turns) == "" {
turns = "# 项目成员对话近窗\n\n本窗口内无授权可见的共享对话。\n"
}
facts := pkg.FactsJSON
if len(facts) == 0 {
facts = []byte("[]\n")
}
confirmations := pkg.ConfirmationsMD
if strings.TrimSpace(confirmations) == "" {
confirmations = "# 上下文冲突裁决记录\n\n本轮未检出冲突,或裁决尚未执行。\n"
}
_ = os.WriteFile(filepath.Join(dlgDir, "digest.md"), []byte(digest), 0o644)
_ = os.WriteFile(filepath.Join(dlgDir, "turns.md"), []byte(turns), 0o644)
_ = os.WriteFile(filepath.Join(dlgDir, "facts.json"), facts, 0o644)
_ = os.WriteFile(filepath.Join(dlgDir, "CONFIRMATIONS.md"), []byte(confirmations), 0o644)
_ = os.WriteFile(filepath.Join(dlgDir, "README.md"), []byte("# 项目成员对话上下文\n\n- `digest.md`:按日纪要\n- `turns.md`:近窗带元数据原文\n- `facts.json`:对话沉淀事实候选\n- `CONFIRMATIONS.md`:冲突裁决审计\n\n权威度 `collab_dialogue`:低于黑板与已批准文档。\n"), 0o644)
*items = append(*items, ContextItem{
File: "context/dialogue/",
Description: "项目成员共享对话(纪要/近窗原文/事实候选/冲突裁决)",
AuthorityLevel: "collab_dialogue",
Guidance: "项目成员对话记录。采信级低于黑板事实与已批准文档;与高权威来源冲突时须并列呈现矛盾,不得静默采信对话版本",
})
return nil
}