Files
HomeAgent/internal/plugins/seq/skill.go
JianFeeeee 55fa2685ae feat(security,device): security 成为独立内核模块(闸门+策略+限式),device 接管内置工具路由
security(新模块 internal/kernelmod/security)
- 凭据闸门:出向拦截成 @{别名}、入向清洗、执行前回填(internal/security/{gate,runtime}.go)
- 工具黑白名单 + 沙箱(读写根、网络、.. 逃逸):internal/security/policy.go
- 敏感词过滤 + 提示词注入预警(Finding 不带原文):internal/security/content.go
- 按主体授权(hard-private 对非内部主体永久拒绝):internal/security/principal.go
- 插件命名限式(可选档位,插件声明/内核执行,只能收窄):internal/security/profile.go
- 启动装配从 cmd/homed 迁入模块;启动顺序判据改盯真实装配点

device(新模块)
- 正式内核模块,Provides FacDevice,KernelState.Device
- 内置工具路由表下沉到 internal/toolroute(避免 core↔kernelboot 环),
  device 保留对外入口;agent 未挂载时退化本地 switch
- Agent 侧只留 api.ToolHost/DeviceSurface 适配层

SDK
- InjectOptions.SecurityProfile:插件按 inputch 声明本轮安全档位(可选)
- InjectOptions.TurnHint:插件注入**本轮事实**,内核以独立 system 消息注入,
  不混进用户正文(人设负责怎么说话,TurnHint 只放这一轮成立的事实)
- FacilitySecurity / FacilityDevice

提示词
- 内核自带 prompt/skill/工具返回全面英文化(中文提示会让模型行为漂移)
- 机制解释下沉到 4 个内置 skill(memory/output/tool-order/plugins),
  skill 描述压到 80 runes 内(原来 94–108 全被索引截断)
- 默认人格模板英文化;[Voice] 段删除,规则并入 persona 块(单一段落)
- 历史记忆工具改名 coldcontext_query/commit,图记忆语义改称 stable memory

修复
- tracker: exec mount → unix.Mount(降权运行 + AmbientCapabilities 下才能挂)、
  区分 EPERM 与不支持;启动日志说明后果与恢复方式
- healthcheck_kernel 输出瘦身:status.Tools 只给摘要(原 98,989 tokens/151% 预算)
- plugin_list 描述讲清 auto_start(按需)与 alive(此刻是否在跑),
  明确不能由 alive=false 推断能力不存在
- pluginmgr/webui 去掉写死的 /home/newqqagent 回退路径(env → pluginDir 反推 → 报错)
- lua: 盘上适配器与内置版本不一致且无基线时响亮告警(deepseek 流式丢失
  tool_calls 导致'模型不调工具'的真因)

测试:go build ./... 通过;go test ./... 60 包全绿
2026-10-26 20:55:25 +08:00

109 lines
5.5 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 seq
// seq 的随包 SKILL 文档。
//
// ## 插件这边只做一件事:交出文档正文
//
// 写盘、幂等、事件通知、停用时清理——全部由内核统一处理
// (internal/plugin/builtin_skill.go,挂载在 loadOne / StopPlugin / StopAll)。
//
// 第一版我让插件自己写盘 + 发事件,那是错的:
// · 每个内置插件都要重复实现一遍「释放/清理/幂等」
// · 更要命的是**禁用插件后 skill 还留着** —— 模型看得到 skill,
// 点进去工具却不存在,只会拿到一连串"工具不存在"且无从知道原因
//
// 正确语义:skill 是插件能力的一部分,生命周期必须与插件同生共死。
//
// ## 为什么要有 skill(而不只靠 ToolDef.Description)
//
// skill 会被 skillmgr 汇总成索引注入系统提示词(buildSystemPrompt 的 skillIndex 段):
// 模型**每轮**都能看到"有这么个能力"的一行摘要。只写在 ToolDef.Description 里,
// 模型不主动翻工具列表就看不到——这正是此前 seq 几乎不被使用的原因
// (与 coldcontext_query 的历史教训同构:内核从没解释过 L1/L2/L3,agent 就只能靠猜)。
const builtinSkillName = "seq-tools"
// BuiltinSkills 实现 plugin.SkillProvider —— 内核据此让 SKILL 随插件启停出入。
func (p *Plugin) BuiltinSkills() map[string]string {
return map[string]string{
builtinSkillName: seqSkillMarkdown,
}
}
// seqSkillMarkdown 是 SKILL 正文。
//
// frontmatter 的 name 必须与 map 的键(也就是目录名)一致。
//
// ⚠️ 注意 description 的取值:内核的 extractDescription **跳过 frontmatter**,
// 取正文第一个非空非标题段落。所以正文首段必须能独立看懂——
// 它才是注入系统提示词的那一行。
const seqSkillMarkdown = `---
name: seq-tools
description: 把反复出现的同类工具操作编成序列存起来,以后一次执行。仅适合要反复跑同一套流程的批处理。
---
把反复出现的同类工具操作编成「序列」存起来,以后一次执行。只适合**要反复跑同一套流程**的批处理场景;一次性的事直接调工具更快。
## 什么时候用(**多数时候不该用**)
序列是**批量脚本**工具,不是常规调用方式。常见做法是直接调工具,那是对的。
**真的划算只有一种情形:同一套流程要被反复执行。**
判据是「这套东西**以后还要跑第二遍、第三遍**吗」:
- ✅ 每天/每周都要做的固定检查(拉日志 → 过滤 → 汇总 → 发通知)
- ✅ 同一套操作要在**很多对象**上跑(对 20 个仓库各跑同样四步);对象越多越划算
- ✅ 参数很长、容易超长度上限 —— 写成文件(` + "`seq_create`" + ` 的 file 参数)
- ✅ 上次已经编过 —— 用 ` + "`seq_list`" + ` 找现成的,` + "`seq_run`" + ` 直接跑
**不划算(直接调工具更快):**
- ❌ **只做一次**的事。先 ` + "`seq_create`" + ` 写序列再 ` + "`seq_run`" + `,比直接调多两步
- ❌ 只有两三个工具。省不下时间
- ❌ 几个工具可以**同批并行**发出(如同时读 5 个文件)—— 那已经是最优路径,
包进序列只是多一层壳
- ❌ 后一步要依赖前一步的**实际返回值** —— 组内并行拿不到彼此结果,得拆组,不如分多轮
> 曾经有过「应该多用序列」的说法,但实测不成立:对一次性的多文件读,
> 模型直接并行 ` + "`files_read`" + ` 就是最优解,包成序列纯亏。
> 所以这份文档的第一句话就是"多数时候不该用"。
## 核心概念
一条序列由若干 **group(组)** 组成:
- **组内并行** —— 同一个 group 的 tools 同时执行
- **组间串行** —— 按 groups 数组顺序,一组跑完再下一组
- 每组有自己的签名:` + "`in`" + `(入参)/ ` + "`out`" + `(出参槽名)
- 组可以被 ` + "`seq_call`" + ` 按名单独调用
理解这两条决定了你怎么分组:**能并行的放同一组,要前一步结果的放后一组**。
## 工具
| 工具 | 用途 |
|---|---|
| ` + "`seq_help`" + ` | **写序列前先查这个** —— tools 字段的分隔符、in/out 写法、as 与 out 的关系都在这里 |
| ` + "`seq_create`" + ` | 创建/更新序列。groups 与 file 二选一 |
| ` + "`seq_list`" + ` | 列出全部序列及各组签名 |
| ` + "`seq_run`" + ` | 执行整条序列(按组顺序,每组先判 when) |
| ` + "`seq_call`" + ` | 按名调用单个 group。target 写组名,或 #序列名 跨序列调用 |
| ` + "`seq_when_call`" + ` | 条件按名调用:when 为真才执行,否则整次跳过(不产出槽) |
| ` + "`seq_delete`" + ` | 删除序列 |
## 正确用法
1. **第一次编序列前先 ` + "`seq_help`" + `** —— 不要凭猜写 tools 字段,它的格式有坑
(每个 tool 后都要有 ` + "`;`" + `,含最后一个;in 必须是对象,无入参写 ` + "`{}`" + `)。
2. 用 ` + "`seq_list`" + ` 看有没有现成的复用,别重复造。
3. 长序列用 ` + "`file`" + ` 参数从文件加载,比在参数里写一大坨 JSON 稳。
4. 执行后看返回的**变量槽快照**,后续步骤用 ` + "`$槽名`" + ` 引用。
## 常见错误
- 把「后一步要用前一步返回值」的流程硬塞进一个 group —— 组内并行,拿不到彼此的槽。
跨组可以(组间串行),组内不行。
- 不查 ` + "`seq_help`" + ` 就写 tools,分隔符或 ` + "`as`" + `/` + "`out`" + ` 对不上,静态校验会直接报错。
`