feat(skills): 渐进披露接线(L2)—— 清单投影 + 命中才展开 + 工具面只收不放

- 清单只给 name+description(+when-to-use),单条 ≤1536 字符 + 全局条数上限,超限如实标 truncated
- 命中才把正文作为一条消息注入:跨回合保留、按内容指纹缓存不重复读盘、注入 ≤16 KiB 标 .truncated
- invocation 三态与清单逐格对齐(model-only 清单不含 description;user-only 清单有但模型不可调)
- 激活轮的工具面 = allowed-tools ∩ 安全闸 ∩ 注册表 再减 disallowed-tools(owner 也看不见未允许的工具)
- 激活状态进程内按会话(不落库);注入量已界化(压缩器估值未含,超我文件面)

裁定(DECISIONS #3/#4/#5):删掉 paths 启发式(只认显式挂载);多技能并集;收缩作用域=激活的那个回合。
This commit is contained in:
AskaEth committed 2026-10-06 14:12:24 +08:00
1 parent 2e1c3ed3fd
commit 6475eb875d
10 files changed
+1448 -3

No files matched your search

+38
View File
@@ -7,6 +7,7 @@ import (
"strings"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
@@ -15,9 +16,11 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/documents"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/exec"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/task"
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/automations"
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/backthink"
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
)
// wireAuxiliary 组装执行提供者、任务管理器、IoT 管理器、渠道会话管理器、
@@ -80,6 +83,11 @@ func (a *app) wireAuxiliary() error {
a.wireVoice()
a.wireMCP()
a.wireSkills()
// SK-5(design/09 §6.3):技能注册表填好之后,把技能面接到循环的模型面装配上
// —— 清单投影 + 命中才展开正文 + 技能轮次的工具面收缩。技能来源就是上面刚
// 注册进去的能力注册表(每轮实时读,因此面板热装的技能下一轮即出现在清单里)。
// 必须排在 wireSkills 之后、且在任何 Run 之前(wireAuxiliary 仍在装配期)。
a.loop.SetSkillFace(a.buildSkillFace())
a.wireTerminalTool()
a.wireSubagentTool()
a.wireSessionReadTool()
@@ -90,6 +98,36 @@ func (a *app) wireAuxiliary() error {
return nil
}
// buildSkillFace 构造技能面(SK-5,design/09 §6.3):来源是能力注册表里的
// KindSkill 条目 —— 启动时 wireSkills 载入的与面板热装的技能都在同一处,且
// **每轮实时读取**,因此热装技能下一轮就出现在模型清单里,无需重启。
//
// 这里只给来源与日志,不配任何旋钮:单条清单上限(1536 字符)、清单条数上限、
// 正文注入字节上限都由代码常量给出(技能包的 listing.go 与 loop 的
// assemble_skill.go 各有带依据的注释),既不新增 `.env` 键,也不引入第二套
// 配置面(主人 2026-10-06 红线:.env 冻结为最小化引导面)。
func (a *app) buildSkillFace() *loop.SkillFace {
return loop.NewSkillFace(loop.SkillFaceOptions{
Source: func() []sdk.Skill {
if a.caps == nil {
return nil
}
caps := a.caps.List(registry.KindSkill)
out := make([]sdk.Skill, 0, len(caps))
for _, cap := range caps {
// 注册表里不是技能的条目(理论上不会有,KindSkill 条目按契约就是
// sdk.Skill)直接跳过:宁可少一个技能,也不让类型断言失败的条目
// 把整轮装配拖垮。
if s, ok := cap.Value.(sdk.Skill); ok && s != nil {
out = append(out, s)
}
}
return out
},
Log: logger.WithSource(a.log, "cmd/cyrene/skills"),
})
}
// pushOutletConfig 把环境变量里的推送出口配置块映射为 backthink 的导出器配置。
// 个推(Getui)凭据只被 config 读取、并不消费:备用 SDK 尚未接入,因此出口在
// 待接入该档位时记日志,而不是伪造一次推送。token 只透传,绝不写日志。