mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 01:18:08 +00:00
新增 internal/plugins/skillmgr(native skill 全生命周期 owner): - skill_list/info/load/unload/enable/disable/create/export/install - skill_create 两步式:先生成骨架模板,LLM 补全后传 content 覆盖写入 (plugin.ValidateSKILLContent 校验)并自动加载生效 - .skm 分发包(tar.gz):packSkill/unpackSkill 含 TarSlip 防护 (拒绝绝对路径/../逃逸、强制单根目录、校验包内 SKILL.md) - skills 目录扫描:纯 SKILL.md/skill.json 条目归本插件; sidecar(main.js/main.py)/OC plugin(openclaw.plugin.json) 留给兼容层 clawhubadapter 职责分离(OpenClaw 兼容层不再持有 native skill): - 删除 p.skills 字段与 default 分支 LoadSKILL 逻辑 - 发现纯 SKILL 条目改为发布 events.EventSkillDetected 移交事件, 由 skillmgr 订阅注册;启动时序 c<s 下全扫兜底,事件用于热新增 - claw_list/plugin_info 不再输出 SKILL 段,统一走 skill_list 方案B prompt 注入: - agentCore 新增 SkillIndexProvider 接口 + SetSkillIndexProvider - buildSystemPrompt 注入【可用技能】轻量索引(名称+版本+描述), LLM 匹配场景时主动 skill_info 拉全文按文档执行 - main.go 在插件加载后将 skillmgr 实例接线到 agent 内核小修: - extractDescription 跳过 YAML frontmatter 块(此前所有带 frontmatter 的 SKILL.md 描述都被误判为 '---') - extractField 剥离 YAML 成对引号(version: "1.0" 不再带尾引号) - plugin.ValidateSKILLContent 导出供生成侧校验
49 lines
1.6 KiB
Go
49 lines
1.6 KiB
Go
package plugin
|
||
|
||
import "testing"
|
||
|
||
func TestExtractDescriptionSkipsFrontmatter(t *testing.T) {
|
||
// 带 frontmatter:描述取正文首行
|
||
c := "---\nname: demo\nversion: 1.0.0\n---\n\n# Demo\n\n这是一句描述。\n"
|
||
if d := extractDescription(c); d != "这是一句描述。" {
|
||
t.Fatalf("got %q, want 这是一句描述。", d)
|
||
}
|
||
// 无 frontmatter:兼容旧格式
|
||
c2 := "# Demo\n\n老格式描述\n"
|
||
if d := extractDescription(c2); d != "老格式描述" {
|
||
t.Fatalf("got %q, want 老格式描述", d)
|
||
}
|
||
// frontmatter 闭合后紧跟标题仍不误判
|
||
c3 := "---\nname: x\n---\n## 步骤\n\n正文描述\n"
|
||
if d := extractDescription(c3); d != "正文描述" {
|
||
t.Fatalf("got %q, want 正文描述", d)
|
||
}
|
||
}
|
||
|
||
func TestValidateSKILLContent(t *testing.T) {
|
||
good := "---\nname: ok\n---\n\n# OK\n\n描述\n"
|
||
if err := ValidateSKILLContent(good); err != nil {
|
||
t.Fatalf("good content rejected: %v", err)
|
||
}
|
||
if err := ValidateSKILLContent(""); err == nil {
|
||
t.Fatal("empty content should be rejected")
|
||
}
|
||
noDesc := "---\nname: x\n---\n\n## 步骤\n"
|
||
if err := ValidateSKILLContent(noDesc); err == nil {
|
||
t.Fatal("content without description should be rejected")
|
||
}
|
||
}
|
||
|
||
func TestExtractFieldStripsQuotes(t *testing.T) {
|
||
c := "---\nname: demo\nversion: \"1.0\"\nauthor: 'tester'\n---\n"
|
||
if v := extractField(c, "version"); v != "1.0" {
|
||
t.Fatalf("version = %q, want 1.0", v)
|
||
}
|
||
if v := extractField(c, "author"); v != "tester" {
|
||
t.Fatalf("author = %q, want tester", v)
|
||
}
|
||
if v := extractField(c, "name"); v != "demo" {
|
||
t.Fatalf("name = %q, want demo", v)
|
||
}
|
||
}
|