From da046b2520acfc3440c282b4633fc0b329f4a30c Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 13 Sep 2026 12:23:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdk):=20UnregisterOutputChannel=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E5=8A=A8=E6=80=81=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E9=80=9A=E9=81=93=EF=BC=88=E8=BF=9C=E7=A8=8B=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=EF=BC=89=E9=9A=8F=E8=B5=84=E6=BA=90=E7=94=9F=E7=81=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景:输出通道不止有"启动时注册一次"的静态通道。远程设备是动态的: `device/` 只在设备在线期间存在,设备掉线后必须注销 —— 不注销, `output_list_channels` 会一直列着死通道,模型会往它发消息并拿到"发送已提交"式假回执。 - 新增 `OutputChannelUnregistrar` 类型 + `UnregisterOutputChannel(name)` + `SetOutputChannelUnregistrar`(用 setter 而不是给公开的 `New(...)` 加参数,避免破坏调用方)。 - 与既有 `SetInputChannelRegistrar` 同一套注入模式:内核在装载插件时注入。 --- sdk/plugin.go | 58 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/sdk/plugin.go b/sdk/plugin.go index 9f10d90..1f65774 100644 --- a/sdk/plugin.go +++ b/sdk/plugin.go @@ -284,6 +284,13 @@ type InputChannelRegistrar func(name string, def ChannelDef) error // OutputChannelRegistrar registers an output channel that the output_send tool can use. type OutputChannelRegistrar func(name string, caps int, desc string, def ChannelDef, handler ToolHandler) error +// OutputChannelUnregistrar 注销一个输出通道。 +// +// 为什么需要它:输出通道不止有"启动时注册一次"的静态通道,还有**随外部资源生灭**的 +// 动态通道 —— 典型是远程设备:`device/` 只在设备在线期间存在,设备掉线后 +// 必须注销,否则 output_list_channels 会一直列着它、模型会往一个死通道发消息。 +type OutputChannelUnregistrar func(name string) error + // Output capability flags const ( CapText = 1 @@ -296,22 +303,23 @@ const ( // PluginSDK is the main API surface provided to plugins at runtime. // It wraps tool registration, settings, memory, knowledge, LLM, and IO injection. type PluginSDK struct { - name string - regTool ToolRegistrar - regStage StageRegistrar - regAPI APIRegistrar - regOutput OutputChannelRegistrar - regInput InputChannelRegistrar - io IOInjector - mem MemoryAPI - textMem TextMemoryAPI - docMem DocMemoryAPI - know KnowledgeAPI - llm LLMAPI - sett SettingsAPI - social SocialAPI - events EventSubscriber - plgMgr PluginMgrAPI + name string + regTool ToolRegistrar + regStage StageRegistrar + regAPI APIRegistrar + regOutput OutputChannelRegistrar + regOutputUnreg OutputChannelUnregistrar + regInput InputChannelRegistrar + io IOInjector + mem MemoryAPI + textMem TextMemoryAPI + docMem DocMemoryAPI + know KnowledgeAPI + llm LLMAPI + sett SettingsAPI + social SocialAPI + events EventSubscriber + plgMgr PluginMgrAPI // apiMu 保护上面这些由内核注入的 API 字段,以及 autoRestart。 // @@ -487,6 +495,17 @@ func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, de return nil } +// UnregisterOutputChannel 注销一个输出通道(动态通道随资源生灭时必须调用)。 +func (s *PluginSDK) UnregisterOutputChannel(name string) error { + s.apiMu.RLock() + reg := s.regOutputUnreg + s.apiMu.RUnlock() + if reg != nil { + return reg(name) + } + return nil +} + // RegisterInputChannel registers an input channel with its memory behavior. // // 契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "", ...) @@ -518,6 +537,13 @@ func (s *PluginSDK) SetOutputChannelRegistrar(r OutputChannelRegistrar) { s.apiMu.Unlock() } +// SetOutputChannelUnregistrar sets the output channel unregistrar (called by the core at startup). +func (s *PluginSDK) SetOutputChannelUnregistrar(r OutputChannelUnregistrar) { + s.apiMu.Lock() + s.regOutputUnreg = r + s.apiMu.Unlock() +} + // SetInputChannelRegistrar sets the input channel registrar (called by the core at startup). func (s *PluginSDK) SetInputChannelRegistrar(r InputChannelRegistrar) { s.apiMu.Lock()