feat(sdk): 通道方向契约落地 + 模板工程/示例插件显式登记 inputch + 生成器两处修正

## 背景:内核侧发现的真问题

在真实二进制压力测试里发现:插件只调 `RegisterOutputChannel("cli", ...)`,
却用同一个通道名 `InjectTextSync("cli", ...)` 注入输入 ⇒ 内核 inputch 登记表里
**没有**这个通道,"把 inputch 划给驻留子"直接失败(`划入 inputch cli: inputch 未注册`)。

根因是**契约没有落到插件与 SDK 面上**:inputch 是内核最基本的**输入路由单位**,
"谁会往这个通道注入输入"必须显式声明,而 SDK 文档没说清它与 RegisterOutputChannel
的分工,示例与模板工程也没有示范。

## SDK 面

- `RegisterInputChannel` / `RegisterOutputChannel` 的文档补齐**方向契约**:
  入站(谁会注入)与出站(output_send__<name> 的回复发给谁)是分开登记的两件事;
  凡是用 `InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...)` 注入的
  通道名都要 RegisterInputChannel。README 同步补了一段契约说明。

## 示例插件(全部补齐,之前只有 qq/weather 是对的)

`a2a`、`acp`、`browser`、`memo`:注入用 `p.name` ⇒ 登记 `p.name`;
`calendar`、`rss`:注入用字面量通道名 ⇒ 登记同名通道。
(这些插件此前是"能注入、但通道不在登记表里",与 cli 同类问题。)

## 模板工程(生成器 templates.go)

- `tmplPluginGo`:示范入站+出站两个方向(含 ChannelDef/NoMemory 说明与 `inputch 未注册` 的成因)。
- `tmplMainLua`:同样两个方向(`register_input_channel` / `register_output_channel`)。
- `tmplReadme`:新增 "Channels" 一节(方向对照表 + 兜底告警说明)。
- 实测:`hmapdev init` 生成的 Go/Lua 工程都含通道代码,Go 工程可构建打包出 `.hmap`;
  `--lua` 工程同样生成通道代码。

## 生成器两处修正(都是实测踩出来的)

1. `sdk install --from <dir>`:install 原本只能从 Release 归档下载,而 SDK 开发期的新能力
   (如 proc 桥要透传的 `InjectOptions.Priority`)还没发版 ⇒ 生成的工程必然编译失败
   (`z_proc_gen.go: opts.Priority undefined`)。现在可用本地源码装一个版本并激活。
   实测:`hmapdev sdk install --from <local sdk>` → 装成 v1.3.0 并激活 → 工程构建通过。
2. 构建前置校验 `sdkHasInjectPriority`:proc 桥模板需要 `InjectOptions.Priority`,
   旧 SDK 没有时应给出**可执行**的报错(升级 SDK 或用 `--from`),
   而不是把两条 `opts.Priority undefined` 编译错误甩给用户(那些错误指向生成物,
   完全看不出是 SDK 版本问题)。实测:声明 sdk=1.2.0 的工程构建时正确命中该提示。

## 未决(发布期事项)

`InjectOptions.Priority` 属本特性线新增能力,**已发布的 SDK v1.2.0 不含它**;
发版时 SDK 版本需随之内含该能力(当前源码 meta 已是 1.3.0),否则外部开发者
按文档生成的工程会撞上上面那条守卫。
This commit is contained in:
JianFeeeee
2026-09-13 11:37:36 +08:00
parent e50bffa34f
commit 4cb3a0bda4
11 changed files with 219 additions and 49 deletions

View File

@ -49,6 +49,22 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
DisplayName: "示例配置", Description: "An example configuration key",
Category: "{{.Plg.Name}}",
})
// ---- 通道channel两个方向是分开的两件事 ----
//
// 入站 inputch ——「谁会往这个通道注入输入」。
// 凡是用 s.InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...) 注入的通道名,
// 都要在这里登记inputch 是内核最基本的**输入路由单位**,只有登记过的通道
// 才能被「划给驻留子resident sub-agent没登记就划分会失败inputch 未注册)。
// 只登记出站通道时内核会兜底登记同名 inputch **并打告警**(兼容老插件)。
chName := p.name
_ = s.RegisterInputChannel(chName, sdk.ChannelDef{NoMemory: true})
// 出站 output ——「output_send__<name> 的回复发给谁」。
// handler 收到 mappayload(string) / type(string) / meta(string|optional)。
_ = s.RegisterOutputChannel(chName, sdk.CapText, "示例通道(回复由此返回)",
sdk.ChannelDef{NoMemory: true}, func(args map[string]interface{}) (interface{}, error) {
return map[string]interface{}{"status": "ok"}, nil
})
tp := p.name + "_"
s.RegisterTool(tp+"hello", sdk.ToolDef{
Name: tp + "hello",
@ -148,6 +164,15 @@ const tmplMainLua = `-- {{.Plg.Name}} plugin
local plugin = { name = "{{.Plg.Name}}" }
function plugin.start(sdk)
sdk.log("info", "{{.Plg.Name}} starting...")
-- 通道:入站与出站分开登记。
-- 入站 inputch凡是用 sdk.inject_text/sdk.inject_interrupt(source, "<name>", ...) 注入的通道名
-- 都要登记;只有登记过的通道才能被「划给驻留子」(没登记会报 inputch 未注册)。
sdk.register_input_channel("{{.Plg.Name}}", { no_memory = true })
-- 出站 outputoutput_send__<name> 的回复由 handler 处理
sdk.register_output_channel("{{.Plg.Name}}", 1, "示例通道(回复由此返回)", { no_memory = true },
function(args) return { status = "ok" } end)
sdk.register_tool("{{.Plg.Name}}_hello", {
description = "A hello world tool",
parameters = { type = "object", properties = {} }
@ -515,6 +540,21 @@ const tmplReadme = `# {{.Plg.Name}}
hmapdev build
` + "```" + `
## Channels
入站与出站是分开登记的两件事:
| 方向 | API | 用途 |
|---|---|---|
| 入站 inputch | RegisterInputChannel(name, def) | 声明「谁会往这个通道注入输入」。**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...) 注入的通道名都要登记** |
| 出站 output | RegisterOutputChannel(name, caps, desc, def, handler) | 声明 output_send__<name> 的回复发给谁handler 收到 {payload,type,meta} |
defChannelDef描述该通道在记忆计算层的行为NoMemory: true = 该通道输入不进记忆;
Cleaner = 计算层清洗后再向量化/提关键词(原文不改)。
> 只登记出站通道、却用同名通道注入输入时,内核会兜底登记同名 inputch 并在日志里告警。
> 兜底只为兼容老插件 —— 请显式登记,让「这是入站通道」成为插件的明确意图。
## Install
Upload the .hmap file through the Plugin Manager API.