chore(sdk-mirror): 同步 SDK 仓 4cb3a0b —— 通道方向契约 + 示例插件显式登记 inputch

镜像文件(外层仓按策略只跟这些;`example/` 被 .gitignore 忽略,已跟踪文件用 -f 提交):
- `sdk/plugin.go`:RegisterInputChannel/RegisterOutputChannel 的方向契约文档
  (入站 vs 出站分开登记;用哪个通道名注入就要登记哪个)。
- `example/{a2a,acp,browser,memo,calendar,rss}/plugin.go`:补 RegisterInputChannel。

未镜像(外层 .gitignore 忽略,按策略不入镜像):
`README.md`(README*)、`tools/hmapdev/{templates.go,cmd_sdk.go,cmd_build.go}`(tools/)
—— 即"模板工程"与"`sdk install --from`"两项改动只存在于 SDK 仓,详见该仓提交 4cb3a0b。

注:这 6 个示例此前未 gofmt(`example/*` 共有 9 个未格式化文件),
本次改动文件被 gofmt 顺带规范,diff 里含格式噪音(calendar 49 行、a2a 33 行)。
This commit is contained in:
JianFeeeee
2026-09-13 11:38:10 +08:00
parent 7143c4e7d2
commit 842df93b48
7 changed files with 74 additions and 44 deletions

View File

@ -47,6 +47,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true) s.SetAutoRestart(true)
p.sdk = s p.sdk = s
p.sessions = make(map[string]*a2aSession) p.sessions = make(map[string]*a2aSession)
// 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})
tp := p.name + "_" tp := p.name + "_"
// 注册自身为输出通道:agent 回复 emit 到本通道时有落点, // 注册自身为输出通道:agent 回复 emit 到本通道时有落点,

View File

@ -47,6 +47,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true) s.SetAutoRestart(true)
p.sdk = s p.sdk = s
p.sessions = make(map[string]*sessionState) p.sessions = make(map[string]*sessionState)
// 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})
tp := p.name + "_" tp := p.name + "_"
// 注册自身为输出通道:agent 回复 emit 到本通道时有落点。 // 注册自身为输出通道:agent 回复 emit 到本通道时有落点。

View File

@ -204,6 +204,9 @@ func newHTTPClient(timeout int, proxyURL string) *http.Client {
func (p *Plugin) Start(s *sdk.PluginSDK) error { func (p *Plugin) Start(s *sdk.PluginSDK) error {
p.sdk = s p.sdk = s
s.SetAutoRestart(true) s.SetAutoRestart(true)
// 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})
s.Settings().RegisterDef(sdk.ConfigDef{ s.Settings().RegisterDef(sdk.ConfigDef{
Key: "timeout", Default: "30", Type: "int", Key: "timeout", Default: "30", Type: "int",

View File

@ -276,6 +276,9 @@ func nextLunarYearly(targetMonth, targetDay int, after time.Time) (time.Time, bo
func (p *Plugin) Start(s *sdk.PluginSDK) error { func (p *Plugin) Start(s *sdk.PluginSDK) error {
p.sdk = s p.sdk = s
// 入站通道:本插件用 "calendar" 通道注入输入(见 Inject* 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel("calendar", sdk.ChannelDef{NoMemory: true})
dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir") dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir")
if err != nil || dataDirVal == "" { if err != nil || dataDirVal == "" {
dataDirVal = "." dataDirVal = "."

View File

@ -48,6 +48,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true) s.SetAutoRestart(true)
p.sdk = s p.sdk = s
p.tp = p.name + "_" p.tp = p.name + "_"
// 入站通道:本插件用 p.name 通道注入输入(见 Inject* 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{NoMemory: true})
dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir") dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir")
if err != nil || dataDirVal == "" { if err != nil || dataDirVal == "" {

View File

@ -104,6 +104,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
s.SetAutoRestart(true) s.SetAutoRestart(true)
p.sdk = s p.sdk = s
p.client = &http.Client{Timeout: 30 * time.Second} p.client = &http.Client{Timeout: 30 * time.Second}
// 入站通道:本插件用 "rss" 通道注入输入(见 Inject* 调用),
// 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。
_ = s.RegisterInputChannel("rss", sdk.ChannelDef{NoMemory: true})
p.fp = gofeed.NewParser() p.fp = gofeed.NewParser()
p.stopCh = make(chan struct{}) p.stopCh = make(chan struct{})
p.seenGUIDs = make(map[string]bool) p.seenGUIDs = make(map[string]bool)
@ -488,8 +491,6 @@ func (p *Plugin) cleanupData() {
} }
} }
// atomicWriteJSON 原子写 JSON:先写临时文件再 rename,避免进程崩溃截断数据文件。 // atomicWriteJSON 原子写 JSON:先写临时文件再 rename,避免进程崩溃截断数据文件。
func atomicWriteJSON(path string, data []byte) error { func atomicWriteJSON(path string, data []byte) error {
tmp := path + ".tmp" tmp := path + ".tmp"

View File

@ -467,6 +467,11 @@ func (s *PluginSDK) RegisterPluginAPI(name string) error {
} }
// RegisterOutputChannel registers an output channel that the output_send tool can route to. // RegisterOutputChannel registers an output channel that the output_send tool can route to.
//
// 与 RegisterInputChannel 的分工:本函数声明**出站**(output_send__<name> 的回复发给谁);
// 入站(谁会往 <name> 注入输入)是另一件事,用 RegisterInputChannel 声明。
// 若该通道同时也是你的注入入口,两个都要登记。
//
// name: channel name (e.g. "qq", "webui") // name: channel name (e.g. "qq", "webui")
// caps: bitmask of supported output capabilities (CapText, CapFile, etc.) // caps: bitmask of supported output capabilities (CapText, CapFile, etc.)
// desc: description of the channel, expected meta format, and type enum // desc: description of the channel, expected meta format, and type enum
@ -483,6 +488,15 @@ func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, de
} }
// RegisterInputChannel registers an input channel with its memory behavior. // RegisterInputChannel registers an input channel with its memory behavior.
//
// 契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...)
// 注入的通道名,都应当在这里登记**。inputch 是内核里最基本的**输入路由单位**:
// 只有登记过的通道才能在 inputch 登记表里出现,父 agent 才能"把某个 inputch 划给驻留子";
// 没登记就划分会直接失败(`inputch 未注册`)。
//
// 只登记输出通道(RegisterOutputChannel)而没登记输入通道时,内核会兜底登记同名
// inputch 并打告警日志 —— 兜底只为兼容老插件,新插件请显式登记。
//
// def.NoMemory: 此通道输入不参与记忆计算 // def.NoMemory: 此通道输入不参与记忆计算
// def.Cleaner: 计算层对输入文本清洗后(不改原文)再向量化/提关键词 // def.Cleaner: 计算层对输入文本清洗后(不改原文)再向量化/提关键词
func (s *PluginSDK) RegisterInputChannel(name string, def ChannelDef) error { func (s *PluginSDK) RegisterInputChannel(name string, def ChannelDef) error {