Files
homeagent-sdk/tools/hmapdev/lua_skill_packaging_test.go
JianFeeeee 74e2c654ed feat(sdk): 22 个示例插件配齐 skill + 三态启动声明 + 打包期校验
## 用户的三条要求

1. 「所有插件都默认自启会占启动时间,还在 llms 上下文塞大量工具」
   ⇒ 纯工具型改为按需加载,靠 skill 让 agent 知道要先拉起。
2. 「会向 agent 注入输入的部分插件不能这样做」
   ⇒ 9 个必须常驻,其余 13 个按需。
3. 「sdk 是不是没有暴露插件声明自身行为的方案」
   ⇒ 对:auto_start 只在 internal/plugin/manifest.go 有,SDK 零命中。

## 定级依据(代码证据,不是猜描述)

注入 / 钩子 ⇒ 必须常驻(9 个):

	qq         HTTP 监听 NapCat 推送,消息到达要立刻注入
	rss        轮询订阅源,新文章主动推送
	calendar   30s ticker 检查提醒
	memo       5min ticker 检查待办到期
	a2a        TCP 监听外部 Agent 请求
	acp        TCP 监听 ACP 客户端
	browser    ticker + 长命令完成回调
	sanitizer  注册 stage 钩子(无工具,要每轮生效)
	luademo    注册 stage 钩子做示范

纯工具型 ⇒ 按需(13 个):
	weather ocr music bili ai_image files editdoc deepsearch
	plugindev recoverydiag vanblog vikunja commondev

★ commondev 值得单独说:它虽然有 6 处 Inject 调用,但**注入源是自己的
sidecar 完成回投**,而 sidecar 只在 commondev_agent_up 时才拉起
(Start() 里不启 sidecar,通道也在 agent_up 时才注册)。所以没有 agent
调用它时,它不需要做任何事 —— 可以按需。44 个工具 ≈726 tokens 省下来可观。

## 改动

 · 22 个插件各配一个 skill,写明:何时用、先 plugin_start、
   工具清单、**用 plugin_stop 而不是 plugin_disable**
 · 13 个 plg.json 加 `auto_start: false`
 · `sdk/autostart.go`:把三态语义与"按需插件必须有 skill"的理由写进公开 SDK
 · hmapdev 打包期校验:`auto_start=false` 但无 skill/ 目录 ⇒ 直接 exit 1
   (理由:缺 skill 的按需插件**等价于没装**,比没装更糟 —— 占着磁盘让人以为可用)
 · 修 Lua 打包路径:它**完全不走** rawfile/skill 收集,luademo 的 skill 永远打不进去
 · 修 build-examples.sh:原来 `[ ! -x ]` 判"二进制在不在",改了 hmapdev 源码后
   重新打包**仍用旧工具链**(实测:luademo 的 skill 一直是 0)
 · 补 mocksdk.ToolDef.Timeout(上一轮加 ToolDef.Timeout 时漏了镜像)

## 判据

 · TestHasSkillDirRequiresRealSkillMd(空目录/.gitkeep/无 SKILL.md 都不算)
 · TestBuildRejectsOnDemandWithoutSkill(打包期必须真拦住 + exit 1)
 · TestLuaPackagingCollectsSkillAndRawfiles(**验代码行**,不验注释 —— 第一版
   只查符号存在,把 createHmapWithRaw 换回 createHmap 判据照样通过)

## 踩过的坑

 · Lua 分支的结束边界原是 `// Resolve build config`,但那个注释在文件里
   **出现多次**(Go 路径里也有),用它截取会把 Go 分支一起包进来 ⇒
   变异测不出。改成截到 Lua 分支自己的 `return` + `}`。
2026-10-18 13:21:47 +08:00

85 lines
3.1 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 main
// Lua 插件的打包路径也必须收集 skill。
//
// ## 缺陷(2026-10-18 实测)
//
// `buildTarget` 里 Go 路径会调 collectRawFiles + collectSkillFiles,
// 而 **Lua 路径是另一段代码**,直接 `createHmap(out, pkgFiles)` ——
// 完全不走 rawfile/skill 收集。
//
// 实测:给 luademo 加了 skill/luademo-tools/SKILL.md 后,
// 打出的 `linux_amd64_lua_demo_lua.hmap` 里 skill 条目数为 **0**。
//
// 这对 Lua 插件伤害更大:Go 插件至少有 RegisterTool 的 Description
// 作为文档位,而 Lua 插件向 agent 说明自己的**唯一**途径就是 skill。
// 缺了它,一个功能完整的 Lua 插件对模型来说是隐形的。
//
// ## 判据
//
// 断言 Lua 分支里出现 collectRawFiles + collectSkillFiles,
// 并且最终走 createHmapWithRaw(而不是只包 pkgFiles 的 createHmap)。
import (
"os"
"strings"
"testing"
)
// ★★★ 判据:Lua 打包分支必须收集 rawfile 与 skill。
func TestLuaPackagingCollectsSkillAndRawfiles(t *testing.T) {
b, err := os.ReadFile("cmd_build.go")
if err != nil {
t.Fatal(err)
}
src := string(b)
// 定位 Lua 分支:`if target == "lua" {` 之后到 `// Resolve build config`
i := strings.Index(src, `if target == "lua" {`)
if i < 0 {
t.Fatal("找不到 Lua 打包分支")
}
// ★ 结束边界必须是 Lua 分支自己的 `return` + `}`,不能用
// "// Resolve build config" —— 实测那个注释在文件里**出现多次**
// (Go 路径里也有),用它截取会把 Go 分支一起包进来,
// 于是"Lua 分支没传 skills"这种变异测不出来。
endMark := "\t\tfmt.Printf(\" packaged %s\\n\", hmapName)"
j := strings.Index(src[i:], "\t\treturn\n\t}")
if j < 0 {
t.Fatal("找不到 Lua 分支的结束位置")
}
// 更稳妥:截到该分支的第一个 `\n\t}\n\n\t// Resolve` 之前。
lua := src[i : i+j]
_ = endMark
// ★★ 只查符号存在是不够的(实测:把 createHmapWithRaw 换回 createHmap,
// 判据依然通过 —— 因为那行 `createHmapWithRaw` 的字符串还在注释里)。
// 必须断言:收集到的 raws/skills 确实**传给了** createHmapWithRaw。
for _, want := range []struct{ sym, why string }{
{"collectRawFiles", "Lua 插件带不上 rawfile(随包资源全部丢失)"},
{"collectSkillFiles", "Lua 插件带不上 skill(它对模型来说是隐形的)"},
} {
if !strings.Contains(lua, want.sym) {
t.Fatalf("★ Lua 打包分支缺少 %s —— %s", want.sym, want.why)
}
}
// 必须是**代码行**在调用 createHmapWithRaw,且参数里带上了 skills。
// 逐行查(跳过注释),避免"注释里有、代码里没有"。
callOK := false
for _, ln := range strings.Split(lua, "\n") {
code := strings.TrimSpace(ln)
if strings.HasPrefix(code, "//") {
continue
}
if strings.Contains(code, "createHmapWithRaw(") && strings.Contains(code, "skills") {
callOK = true
break
}
}
if !callOK {
t.Fatal("★ Lua 打包没有把 skill 传给 createHmapWithRaw —— " +
"收集到了却没写进包,Lua 插件对模型来说仍是隐形的")
}
}