mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 21:03:16 +00:00
内核缺口(都是 P3 落地时暴露的真实缺陷): · **GetAllTools 丢 ParallelSafe**:它只带出 Name/Description/Parameters, 插件看到的设备工具一律"不可并发" ⇒ 设备工具的并发声明**对插件不可见**。 · **ToolAPI 缺按名查**:新增 ToolDefByName。插件需要在**运行前**判断目标 是否存在/是否并发安全(工具动态注册,"不存在"是常态), 而 GetAllTools 只能拿到全量列表。查不到返回 nil,不 panic。 seq 插件: · plugin.go:插件骨架 + kernelRunner(把 sdk.ToolAPI 收窄成三个方法, 判据因此能用假实现驱动,不必构造整个内核) · tools.go:六个工具定义(独立真相源,注册/判据/文档都从它取) · handlers.go:seq_create/list/delete/run/call/when_call 的实现 · register.go + all.go:按 skillmgr 同一范式 init 注册 ★ 过程中解决一个**我自己的设计矛盾**: 判据原先要求 `seq_call` / `seq_when_call` 进黑名单,但"按名调用 group/序列"恰恰是本包的核心能力——禁掉它,序列就退化成单层脚本。 分层澄清后:黑名单只管**对外发消息 / 起子 agent / 改插件表 / 再跑整条 序列**;seq_call 系列留给序列内部组合,其递归由 maxCallDepth + 环检测 负责(设计文档 §8.3 本来就是这么定的,我把两层混了)。 `seq_run` 留在黑名单:序列内再跑整条序列语义上是递归。 **六个工具一律不声明 ParallelSafe**:seq_run/seq_call 会执行一串工具, 其中可能含写操作;标成并发安全会让内核把两条 seq_run 并发跑, 两个序列的执行顺序交错、变量表互相污染。 安全性:序列名与文件路径都做穿越防护(`..` 段、分隔符、空名)。 过程中四次自伤: 1. 臆造 `jsonMarshalIndent`(不存在)→ 改 encoding/json.MarshalIndent; 并把 execGroup 的 runner 传错成 p(应 p.runner)。 2. seq 判据里写了 `black(name)`,而 blacklisted 是**谓词**不是函数。 3. 一次 python 替换删漏,把「跨序列目标存在性检查」那段从 CheckNew 里整段摘掉又贴回原处——靠编译错误发现。 4. ★ 注册失败我写了 panic:内置插件在 main() 装配期加载,panic 会 **直接拖垮内核启动**,而"某个工具没注册上"只该让该工具不可用。 已改为 log.Printf + 继续(与 clawhubadapter / mcp 一致)。 变异验证:把 seq_run 移出黑名单 ⇒ 黑名单判据 FAIL。 判据(plugin_test.go,6 条):六个工具全部注册且 description/参数 schema 非空;seq_create 声明 required 并说明 groups/file 二选一; seq_run 说明"按数组顺序";六个工具均未声明 ParallelSafe; 黑名单含递归风险项且**不误伤** seq_call 与普通工具。 回归:seq -race 全绿;internal/sdk/... internal/plugins/... 12 包全绿。
145 lines
5.3 KiB
Go
145 lines
5.3 KiB
Go
package seq
|
||
|
||
import "strings"
|
||
|
||
// seqToolDefs 返回本插件导出的六个工具定义。
|
||
//
|
||
// ⚠️ 全部**不声明** ParallelSafe:seq_run / seq_call 会执行**一串**工具,
|
||
// 其中可能含写操作。标成并发安全会让内核把两条 seq_run 并发跑起来,
|
||
// 两个序列的执行顺序交错、变量表互相污染。
|
||
//
|
||
// 为独立真相源:注册、判据、文档都从这里取,避免三处各写一份。
|
||
func seqToolDefs() []toolDefInfo {
|
||
return []toolDefInfo{
|
||
{
|
||
Name: "seq_create",
|
||
Description: "创建/更新一条工具序列。**groups 与 file 二选一**:传 groups 直接给结构," +
|
||
"或用 file 加载你已写好的序列文件(适合长序列——长参数会被 max_tokens 截断,写文件更稳)。\n" +
|
||
"序列由若干 group 组成:**组内并行、组间串行**;group 拥有独立签名(in 入参 / out 出参)," +
|
||
"可被 seq_call 按名调用。\n" +
|
||
"保存时会做静态校验:as 必须已在 out 声明、$args.x 必须已在 in 声明、组名不重复、" +
|
||
"组内 ; 分隔符必须完整。任一项不满足都会报错并说明原因。\n" +
|
||
"格式:JSON;每个 group 的 tools 是一个字符串,内含若干以 ' ; ' 分隔的 JSON 对象," +
|
||
"形如 {\"tool\":\"cmd_run\",\"args\":{...},\"as\":\"槽名\"}。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"name": map[string]interface{}{
|
||
"type": "string", "description": "序列名(唯一)",
|
||
},
|
||
"description": map[string]interface{}{
|
||
"type": "string", "description": "一句话说明这条序列做什么",
|
||
},
|
||
"groups": map[string]interface{}{
|
||
"type": "array",
|
||
"description": "组数组,按数组顺序执行;与 file 二选一",
|
||
"items": map[string]interface{}{"type": "object"},
|
||
},
|
||
"file": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "序列文件路径(与 groups 二选一)。目录内文件优先用 files 工具查看",
|
||
},
|
||
},
|
||
"required": []string{"name"},
|
||
},
|
||
},
|
||
{
|
||
Name: "seq_list",
|
||
Description: "列出全部可用序列:名称、描述、组数与各组的签名(in/out)。" +
|
||
"按名调用前先用它确认名称与签名。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{},
|
||
},
|
||
},
|
||
{
|
||
Name: "seq_delete",
|
||
Description: "删除一条序列。序列不存在时会报错(不会静默成功)。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"name": map[string]interface{}{
|
||
"type": "string", "description": "要删除的序列名",
|
||
},
|
||
},
|
||
"required": []string{"name"},
|
||
},
|
||
},
|
||
{
|
||
Name: "seq_run",
|
||
Description: "执行一条序列:按 groups 数组的顺序逐组执行,组内并行、组间串行。" +
|
||
"每组先求值 when 条件,为真才执行。返回逐组摘要与最终变量槽快照。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"name": map[string]interface{}{
|
||
"type": "string", "description": "要执行的序列名",
|
||
},
|
||
"args": map[string]interface{}{
|
||
"type": "object",
|
||
"description": "传给各组的入参(组内用 $args.<键> 读取)",
|
||
},
|
||
},
|
||
"required": []string{"name"},
|
||
},
|
||
},
|
||
{
|
||
Name: "seq_call",
|
||
Description: "按名调用一条序列里的 group:target 写组名(限本序列内)或 " +
|
||
"#序列名(跨序列)。返回该组的 out 槽。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"name": map[string]interface{}{
|
||
"type": "string", "description": "所属序列名(组名调用时必填)",
|
||
},
|
||
"target": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "组名,或 #序列名 表示跨序列调用",
|
||
},
|
||
"args": map[string]interface{}{
|
||
"type": "object",
|
||
"description": "传给该组的入参",
|
||
},
|
||
},
|
||
"required": []string{"target"},
|
||
},
|
||
},
|
||
{
|
||
Name: "seq_when_call",
|
||
Description: "条件按名调用:when 表达式为真才执行,否则整次调用跳过(不产出任何槽)。" +
|
||
"when 只可读传入的 args(如 $args.flag == true)。",
|
||
Parameters: map[string]interface{}{
|
||
"type": "object",
|
||
"properties": map[string]interface{}{
|
||
"name": map[string]interface{}{
|
||
"type": "string", "description": "所属序列名(组名调用时必填)",
|
||
},
|
||
"target": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "组名,或 #序列名 表示跨序列调用",
|
||
},
|
||
"args": map[string]interface{}{
|
||
"type": "object",
|
||
"description": "传给该组的入参",
|
||
},
|
||
"when": map[string]interface{}{
|
||
"type": "string",
|
||
"description": "条件表达式,如 $args.flag == true",
|
||
},
|
||
},
|
||
"required": []string{"target", "when"},
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
// 内部小工具:把 args 里的字符串字段取出来并 trim。
|
||
func argString(args map[string]interface{}, key string) string {
|
||
if args == nil {
|
||
return ""
|
||
}
|
||
s, _ := args[key].(string)
|
||
return strings.TrimSpace(s)
|
||
}
|