Skip to main content
xbot
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage
Edit page

xbot 五大方向重构 — 终审报告

审核机构:门下省
审核日期:2026-03-19
审核范围:6 大代码变更方向,24 个新增/修改文件
审核基准/workspace/xbot/plan/xbot-major-refactor-design.md (v3)


Phase 1:编译与静态检查

检查项结果
go build ./...✅ 全量通过
go vet ./...✅ 全量通过
go test ./...✅ 全量通过

所有包编译、静态分析、测试均无错误。


Phase 2:逐方向深入审核

方向 1:Channel 特化 Prompt 隔离

涉及文件agent/channel_prompt.goagent/channel_prompt_test.goagent/middleware.goagent/agent.gochannel/feishu.gomain.go

✅ 正确性

检查点结论
ChannelPromptProvider 接口定义在 agent 包内✅ 正确,不依赖 channel
ChannelPromptMiddleware 实现 MessageMiddleware 接口✅ 优先级 50,插入在基础 prompt (0) 和 skills/agents (100+) 之间
feishuPromptAdapter 桥接模式(main.go 底部)✅ 正确避免 agent ↔ channel 循环依赖
main.go 已注入 SetChannelPromptProviders✅ line 154,仅在 feishuCh != nil 时注入
FeishuChannel.ChannelSystemParts() 返回 map[string]string✅ 与 SystemParts key-value 模式一致

✅ 完整性

检查点结论
测试覆盖:mock provider + middleware 行为验证✅ 3 个测试用例覆盖匹配/不匹配/空 parts
ChannelSystemParts key 命名规范✅ 使用 "00_feishu_rules" 前缀,符合 BuildSystemPrompt 字典序约定

✅ 循环依赖

依赖路径结论
agentchannel❌ 无直接依赖(通过 feishuPromptAdapter 在 main.go 桥接)
channelagent❌ 无依赖
channelbus✅ channel 只依赖 bus(OK)

裁定:通过


方向 2:LLM Encode 缓存优化

涉及文件llm/types.gollm/anthropic.gollm/anthropic_test.goagent/middleware.go

✅ 正确性

检查点结论
ChatMessage.CacheHint 字段 (llm/types.go:31)"static" / "" 两值语义清晰
Assemble() 设置 sysMsg.CacheHint = "static" (middleware.go:150)✅ system prompt 始终标记为可缓存
buildAnthropicSystem() 处理 CacheHint (anthropic.go)staticcache_control: {"type": "ephemeral"},非 static 不标注
cache_control 格式符合 Anthropic API 规范{"type": "ephemeral"} 为官方标准格式
单条无缓存 system 返回 string(非 array)✅ 向后兼容,不破坏旧 API
多条或含缓存标注时返回 array✅ 符合 Anthropic multi-block system 格式

✅ 完整性

检查点结论
测试覆盖 (anthropic_test.go)✅ 5 个测试:无系统消息 / 单条无缓存 / 静态缓存 / 混合 / 多条动态
OpenAI provider 不处理 CacheHintopenai.go 中无 CacheHint 引用,OpenAI 不支持 prompt caching(正确忽略)

✅ 风险评估

风险点评估
非缓存 system 仍可被 Anthropic KV-cache✅ 无风险,KV-cache 是隐式的
缓存断开后首次请求额外延迟✅ Anthropic 文档已说明,设计预期内
CacheHint 字段序列化到 sessionjson:"cache_hint,omitempty" — 为空时不序列化,不污染持久化数据

裁定:通过


方向 3:LLM 回复与进度报告增强

涉及文件agent/progress.goagent/progress_test.goagent/engine.goagent/reply.goagent/reply_test.go

3a. 结构化进度事件系统

✅ 正确性
检查点结论
ProgressEvent / StructuredProgress / ToolProgress / ToolStatus 类型定义✅ 结构合理,常量定义清晰(PhaseThinking/PhaseToolExec
RunConfig.ProgressEventHandler 字段 (engine.go:77)✅ 回调签名 func(event *ProgressEvent)
Run()structuredProgress 初始化与生命周期管理✅ 仅在 ProgressEventHandler != nil 时创建
工具执行时状态流转 ToolPending → ToolRunning → ToolDone/ToolError✅ 每个工具独立跟踪,含耗时记录
ProgressNotifierProgressEventHandler 并存✅ 旧回调保持不变,新回调是增强(非替代)
✅ 完整性
检查点结论
测试覆盖 (progress_test.go)✅ 验证事件创建、字段赋值、零值安全
迭代计数更新✅ 每轮循环更新 structuredProgress.Iteration
思考内容记录structuredProgress.ThinkingContent = cleanContent
完成工具归档CompletedTools = append(CompletedTools, ActiveTools...) 后清空
⚠️ 注意事项
说明
ProgressEventHandler 尚未被上层消费当前 main.go 未注入 ProgressEventHandler,飞书卡片进度渲染需后续接入

3b. ExtractFinalReply

✅ 正确性
检查点结论
短内容(< 500 字符)原样返回
长内容取最后一段
最后一段过短(< 50 字符)时合并倒数两段
✅ 完整性
检查点结论
测试覆盖 (reply_test.go)✅ 3 个用例:短内容 / 正常长内容 / 末段过短
⚠️ 问题
严重度说明
死代码⚠️ 中ExtractFinalReply 仅在测试中被调用,无任何生产代码引用。函数定义合理但尚未集成

裁定:通过 ✅(附建议:尽快集成 ExtractFinalReply 或标记为 planned)


方向 4:全局 Skill/Agent Registry

涉及文件agent/registry.goagent/command_builtin.gostorage/sqlite/registry.gostorage/sqlite/registry_test.gostorage/sqlite/db.go

✅ 正确性

检查点结论
RegistryManager 结构体与 CRUD 方法✅ Publish / Unpublish / Browse / Install / Uninstall / MyEntries 完整
SharedSkillRegistry 存储层 (storage/sqlite/registry.go)✅ 标准 SQLite CRUD,SQL 注入安全(参数化查询)
type 约束 CHECK(type IN ('skill', 'agent'))✅ 数据库层防护
sharing 约束 CHECK(sharing IN ('private', 'public'))
v12 → v13 migration (db.go)CREATE TABLE IF NOT EXISTS 幂等,含索引
新建数据库 schema 直接包含 v13 表createSchema() 中已包含 shared_registryuser_settings

✅ 完整性

检查点结论
测试覆盖 (registry_test.go)✅ CRUD + 边界(重复发布、未找到、类型过滤)
内置命令注册 (command_builtin.go)/publish/unpublish/browse/install/uninstall/my 均已注册
nil 保护 (command_builtin.go:265)a.registryManager == nil 检查后返回提示

⚠️ 问题

严重度说明
RegistryManager 未注入🔴 高Agent.registryManager 字段已声明(agent.go:254)但无公开 settermain.go 未注入/publish 等命令运行时会因 nil 而静默失败
缺少 setter 方法🔴 高Agent 没有 SetRegistryManager(*RegistryManager) 方法,外部无法注入
Unpublish 权限校验⚠️ 低仅检查 entry.Author == senderID,未校验 entry.Type 与请求类型匹配(但 data layer 已有 type 过滤)

裁定:驳回修改 ⚠️

修改意见

  1. agent/agent.go 添加 SetRegistryManager(rm *RegistryManager) 方法
  2. main.go 初始化 RegistryManager 并注入到 Agent
  3. 注入时机:在 agentLoop := agent.New(...) 之后、Run() 之前

方向 5:Channel Settings Capability

涉及文件channel/capability.gochannel/capability_test.goagent/settings.goagent/settings_test.gostorage/sqlite/user_settings.goagent/command_builtin.gochannel/feishu.go

✅ 正确性

检查点结论
SettingsCapability 接口 (channel/capability.go)SettingsSchema() + HandleSettingSubmit() + BuildSettingsUI() + BuildProgressUI()
UIBuilder 接口✅ 与 SettingsCapability 组合使用
SettingDefinition 结构体✅ key / label / type / options / required / default 字段完整
SettingsService (agent/settings.go)✅ 封装 UserSettingsService,增加 channel 层 UI 能力查询
UserSettingsService (storage/sqlite/user_settings.go)✅ 标准 CRUD,UNIQUE(channel, sender_id, key)
FeishuChannel 实现 SettingsCapability✅ 4 个方法均已实现
内置命令 /set/menu (command_builtin.go)✅ 已注册,nil 保护完备
user_settings 表 v13 migration✅ 幂等,含索引

⚠️ 问题

严重度说明
SettingsService 未注入🔴 高Agent.settingsSvc 字段已声明(agent.go:257)但无公开 settermain.go 未注入/set/menu 命令运行时因 nil 静默失败
agent/settings.go import “xbot/channel”⚠️ 中虽然 channel 不反向依赖 agent(无循环),但 agent 包直接依赖 channel 包打破了既有的分层架构。目前通过 import 只用了 SettingsCapability 接口和 UIBuilder 接口。建议:将这两个接口提取到独立包(如 channel/typeschannel/capability 已在此包中,但 agent 仍直接引用 channel 包)

⚠️ 循环依赖检查

依赖路径状态
agentchannel⚠️ 新增(settings.go
channelagent❌ 无
结论当前无循环依赖,但 agent → channel 是架构上的方向反转

裁定:驳回修改 ⚠️

修改意见

  1. agent/agent.go 添加 SetSettingsService(svc *SettingsService) 方法
  2. main.go 初始化 SettingsService 并注入到 Agent
  3. (架构建议)考虑将 SettingsCapability / UIBuilder 接口从 channel 包提取为独立接口包,避免 agent → channel 的直接依赖

方向 6:LLM 层类型增强

涉及文件llm/types.gollm/anthropic.gollm/anthropic_test.gollm/openai.go

已在方向 2 中一并审核。补充:

检查点结论
ChatMessage 新增 CacheHint 字段json:"cache_hint,omitempty" — 空值不序列化,向后兼容
TokenUsage.Add() 方法✅ 值类型语义正确
ToolCallDelta 新增 IDName 字段✅ 支持流式工具调用增量
StreamEventType 枚举扩展✅ 含 EventReasoningContent 支持 DeepSeek 推理模型

裁定:通过


全局问题汇总

🔴 必须修复(阻塞性)

#问题影响范围修复方案
1RegistryManager 无 setter、main.go 未注入/publish/unpublish/browse/install/uninstall/my 6 个命令全部静默失败添加 setter + main.go 注入
2SettingsService 无 setter、main.go 未注入/set/menu 2 个命令全部静默失败添加 setter + main.go 注入

⚠️ 建议改进(非阻塞)

#问题建议
3ExtractFinalReply 为死代码(无生产调用点)尽快集成到 reply 发送路径,或添加 // Planned: ... 注释说明意图
4ProgressEventHandler 未被 main.go 消费飞书卡片进度渲染的接入需排入后续迭代
5agent/settings.go 直接 import channel考虑提取接口到独立包,维持分层清晰
6Unpublish 缺少 type 匹配校验建议在 Service 层增加校验(低优先级)

✅ 确认无问题的架构要点

结论
循环依赖✅ 无循环(agent ↔ channel 通过 main.go 桥接,settings.go 的新依赖是单向的)
Go 接口惯用法✅ 小接口(1-3 方法)、显式接口满足、nil 安全
向后兼容CacheHint omitempty、schema migration 幂等、旧 ProgressNotifier 保持不变
测试覆盖✅ 每个新增模块均有对应测试文件,覆盖正常路径和边界条件
并发安全MessagePipeline 读写锁、Agent 字段初始化在 Run() 前

终审裁定

总评:⚠️ 驳回修改(附 2 项必须修复)

方向 1-3、6 实现质量优秀,代码风格一致,架构设计合理。但方向 4 和 5 存在同一类系统性遗漏:新增的服务字段(registryManagersettingsSvc)在 Agent struct 中声明、在 command_builtin.go 中消费,但缺少公开 setter 方法且 main.go 未注入初始化代码。这导致 8 个新增命令(/publish/unpublish/browse/install/uninstall/my/set/menu)在运行时全部因 nil check 而静默返回提示信息,功能完全不生效。

修复清单(预计工作量:~30 行代码)

  1. agent/agent.go — 添加两个 setter:

    func (a *Agent) SetRegistryManager(rm *RegistryManager) { a.registryManager = rm }
    func (a *Agent) SetSettingsService(svc *SettingsService) { a.settingsSvc = svc }
    
  2. main.go — 在 agentLoop := agent.New(...) 之后添加初始化:

    // 注入 RegistryManager
    sharedDB, _ := sqlite.Open(dbPath)  // 复用已有连接
    agentLoop.SetRegistryManager(agent.NewRegistryManager(sharedDB, xbotDir))
    
    // 注入 SettingsService
    agentLoop.SetSettingsService(agent.NewSettingsService(
        sqlite.NewUserSettingsService(sharedDB),
        disp,  // channel.Dispatcher 用于查询 SettingsCapability
    ))
    

修复完成后可免审提交(门下省确认修改范围可控)。


门下省 · 审核完毕