mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat(skillmgr): 原生技能管理器插件 + OpenClaw 兼容层职责分离
新增 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 导出供生成侧校验
This commit is contained in:
@ -120,12 +120,44 @@ func (p *SKILLPlugin) SetEnabled(v bool) { p.mu.Lock(); defer p.mu.Unlock()
|
||||
func (p *SKILLPlugin) Tools() []ToolDef { return p.toolDefs }
|
||||
func (p *SKILLPlugin) IOConfig() *IOConfig { return p.ioConfig }
|
||||
func (p *SKILLPlugin) RawContent() string { return p.rawContent }
|
||||
func (p *SKILLPlugin) SourceDir() string { return p.sourceDir }
|
||||
|
||||
// ValidateSKILLContent 校验 SKILL.md 内容是否可被 LoadSKILL 正确解析:
|
||||
// 必须含非空正文(description 来源),且提取出的工具定义名称合法。
|
||||
// 供 skillmgr 的 skill_create 在落盘前校验生成结果。
|
||||
func ValidateSKILLContent(content string) error {
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return fmt.Errorf("SKILL content is empty")
|
||||
}
|
||||
if extractDescription(content) == "" {
|
||||
return fmt.Errorf("SKILL content has no description (first non-empty non-heading line required)")
|
||||
}
|
||||
for _, td := range extractToolDefs(content) {
|
||||
if strings.TrimSpace(td.Name) == "" || strings.ContainsAny(td.Name, " \t\n/") {
|
||||
return fmt.Errorf("invalid tool name in SKILL content: %q", td.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractDescription(content string) string {
|
||||
for _, line := range splitLines(content) {
|
||||
line = trimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "#") {
|
||||
return line
|
||||
inFrontmatter := false
|
||||
for i, line := range splitLines(content) {
|
||||
trimmed := trimSpace(line)
|
||||
// 跳过 YAML frontmatter 块(首行 --- 至闭合 ---),
|
||||
// 否则分隔符会被误认为描述(所有带 frontmatter 的 SKILL.md 描述都变成 "---")
|
||||
if i == 0 && trimmed == "---" {
|
||||
inFrontmatter = true
|
||||
continue
|
||||
}
|
||||
if inFrontmatter {
|
||||
if trimmed == "---" {
|
||||
inFrontmatter = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if trimmed != "" && !strings.HasPrefix(trimmed, "#") {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
@ -139,7 +171,12 @@ func extractField(content string, field string) string {
|
||||
if strings.HasPrefix(strings.ToLower(trimmed), lowerPrefix) {
|
||||
for i := 0; i < len(trimmed); i++ {
|
||||
if trimmed[i] == ':' {
|
||||
return strings.TrimSpace(trimmed[i+1:])
|
||||
v := strings.TrimSpace(trimmed[i+1:])
|
||||
// YAML 风格引号值:剥掉成对的首尾引号
|
||||
if len(v) >= 2 && (v[0] == '"' || v[0] == '\'') && v[len(v)-1] == v[0] {
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user