mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-31 22:37:25 +00:00
feat(hmapdev): 构建期告警「同一 stage 被重复注册」+ qq 合并写法
## 用户指出的关键:告警要给**开发者**看,所以必须在构建期
我第一版把告警放进了工具链生成的 proc_main 模板(插件**运行期**打日志)。
用户直接指出:「本身工具链这个编译器警告就是给开发者看的」——运行期日志
是给运维/用户看的,开发者看不到。已撤销,改为构建期静态检查。
## 缺陷(真实事故,2026-10-18)
插件侧把 stage handler 存在 `map[string]sdk.StageHandler`(stage 名为键)⇒
**同一 stage 注册两次,后者静默覆盖前者**。
QQ 插件当时写成:
RegisterStage(BeforeToolcall, 权限门) ← 被下一条覆盖
RegisterStage(BeforeToolcall, 切状态) ← 实际生效
后果不只是状态不切换 —— **权限门被静默关掉了**(那几版部署里任何人
发消息都能调任何工具)。编译器不报、运行时不报错、内核日志还显示
`2 handlers [qq qq]`(看着完全正常)。只有静态检查能发现。
## 实现
· `stage_dup_check.go`:扫构建目录下的 .go(排除 _test.go 与 z_proc*),
统计每个 stage 常量被 RegisterStage 引用几次,>1 就告警
· 接入 `buildTarget` 开头 ⇒ 所有 target(含 lua)都检查
· **只告警不中断**:存量插件里重复注册可能是本意,直接 fail 会让它们
升 SDK 后无法构建
· `example/qq`:三个重复 stage 合并成单 handler(权限门 + 状态切换)
## 判据(4 条)
· 同 stage 两次 ⇒ 报出来
· 不同 stage 各一次 ⇒ 不误报
· **注释里的示例注册不计入**
· 测试文件 / 工具链生成文件不计入
★ 「注释」这条是这个检查器**第一版自己栽的坑**:直接对全文跑正则,
把插件里「当初写错了、留在注释里当教训」的那几行也统计进去,于是
修好之后照样报「注册了 3 次」(实测)。现在逐行跳 `//` 与跨行 `/* */`。
本项目已多次绊倒在「注释里有、代码里没有」上,这次轮到检查器自己。
## 实测
qq 修好后构建: packaged qq_linux_amd64.hmap(无告警)
故意加一条重复: ⚠️ 警告: stage StageBeforeOutput 注册了 2 次
建议:把多个功能合并进同一个 handler
This commit is contained in:
@ -842,20 +842,28 @@ type 枚举: text(文字)/ voice(语音转文字后发送)/ image(图
|
||||
NoMemory: true,
|
||||
}, p.handleSendLike)
|
||||
|
||||
// 全局权限门:只有 QQ 当前输入需要受此插件约束;Bot 所有者始终完整放行。
|
||||
s.RegisterStage(sdk.StageOnInput, p.onInputAuthContext, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageBeforeToolcall, p.beforeToolcall, sdk.StageScopeGlobal)
|
||||
// 在线状态随阶段切换(默认关闭,见 statusAutoEnabled 的注释)。
|
||||
// ★★ 每个 stage **只能注册一个 handler**(2026-10-18 踩到的坑)。
|
||||
//
|
||||
// ★ 三个钩子都只有一件事:调 p.applyStatus;**永远 return nil**。
|
||||
// 状态是非致命错误 —— 不能因为它阻塞工具调用或改写回复。
|
||||
s.RegisterStage(sdk.StageBeforeToolcall, p.stageStatusWorking, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageBeforeOutput, p.stageStatusThinking, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageAfterOutput, p.stageStatusOnline, sdk.StageScopeGlobal)
|
||||
// 插件侧(工具链生成的 proc_main)把 handler 存在
|
||||
// `stageHandlers map[string]StageHandler`(stage 名为键)⇒
|
||||
// **同一 stage 注册两次,后者静默覆盖前者**。而之前写成:
|
||||
//
|
||||
// s.RegisterStage(StageBeforeToolcall, p.beforeToolcall, Global)
|
||||
// s.RegisterStage(StageBeforeToolcall, p.stageStatusWorking, Global) ← 覆盖了上面
|
||||
// s.RegisterStage(StageAfterOutput, p.stageStatusOnline, Global)
|
||||
// s.RegisterStage(StageAfterOutput, p.afterOutputAuthContext, Global) ← 覆盖了上面
|
||||
//
|
||||
// 后果不只是状态不切换 —— **QQ 的权限门被静默关掉了**(那几版部署里
|
||||
// 任何人发消息都能调任何工具)。hmapdev 现在会在构建期对这类重复告警。
|
||||
//
|
||||
// 现在合并成一个 handler,每个 stage 一次注册。
|
||||
s.RegisterStage(sdk.StageOnInput, p.onInputAuthContext, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageBeforeToolcall, p.stageBeforeToolcall, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageBeforeOutput, p.stageBeforeOutput, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageAfterOutput, p.stageAfterOutput, sdk.StageScopeGlobal)
|
||||
// before_toolcall 的 Response 只用于拒绝当前工具。下一次模型补全到达时必须清掉,
|
||||
// 否则内核会把它误当作“结束整个推理”的最终响应。
|
||||
s.RegisterStage(sdk.StagePostAction, p.clearDeniedResponse, sdk.StageScopeGlobal)
|
||||
s.RegisterStage(sdk.StageAfterOutput, p.afterOutputAuthContext, sdk.StageScopeGlobal)
|
||||
|
||||
// ---- HTTP server for NapCat webhook ----
|
||||
mux := http.NewServeMux()
|
||||
|
||||
Reference in New Issue
Block a user