feat(sdk): UnregisterOutputChannel —— 动态输出通道(远程设备)随资源生灭

背景:输出通道不止有"启动时注册一次"的静态通道。远程设备是动态的:
`device/<id>` 只在设备在线期间存在,设备掉线后必须注销 —— 不注销,
`output_list_channels` 会一直列着死通道,模型会往它发消息并拿到"发送已提交"式假回执。

- 新增 `OutputChannelUnregistrar` 类型 + `UnregisterOutputChannel(name)` +
  `SetOutputChannelUnregistrar`(用 setter 而不是给公开的 `New(...)` 加参数,避免破坏调用方)。
- 与既有 `SetInputChannelRegistrar` 同一套注入模式:内核在装载插件时注入。
This commit is contained in:
JianFeeeee
2026-09-13 12:23:15 +08:00
parent 4cb3a0bda4
commit 1db7814c7a

View File

@ -284,6 +284,13 @@ type InputChannelRegistrar func(name string, def ChannelDef) error
// OutputChannelRegistrar registers an output channel that the output_send tool can use. // 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 type OutputChannelRegistrar func(name string, caps int, desc string, def ChannelDef, handler ToolHandler) error
// OutputChannelUnregistrar 注销一个输出通道。
//
// 为什么需要它:输出通道不止有"启动时注册一次"的静态通道,还有**随外部资源生灭**的
// 动态通道 —— 典型是远程设备:`device/<id>` 只在设备在线期间存在,设备掉线后
// 必须注销,否则 output_list_channels 会一直列着它、模型会往一个死通道发消息。
type OutputChannelUnregistrar func(name string) error
// Output capability flags // Output capability flags
const ( const (
CapText = 1 CapText = 1
@ -296,22 +303,23 @@ const (
// PluginSDK is the main API surface provided to plugins at runtime. // PluginSDK is the main API surface provided to plugins at runtime.
// It wraps tool registration, settings, memory, knowledge, LLM, and IO injection. // It wraps tool registration, settings, memory, knowledge, LLM, and IO injection.
type PluginSDK struct { type PluginSDK struct {
name string name string
regTool ToolRegistrar regTool ToolRegistrar
regStage StageRegistrar regStage StageRegistrar
regAPI APIRegistrar regAPI APIRegistrar
regOutput OutputChannelRegistrar regOutput OutputChannelRegistrar
regInput InputChannelRegistrar regOutputUnreg OutputChannelUnregistrar
io IOInjector regInput InputChannelRegistrar
mem MemoryAPI io IOInjector
textMem TextMemoryAPI mem MemoryAPI
docMem DocMemoryAPI textMem TextMemoryAPI
know KnowledgeAPI docMem DocMemoryAPI
llm LLMAPI know KnowledgeAPI
sett SettingsAPI llm LLMAPI
social SocialAPI sett SettingsAPI
events EventSubscriber social SocialAPI
plgMgr PluginMgrAPI events EventSubscriber
plgMgr PluginMgrAPI
// apiMu 保护上面这些由内核注入的 API 字段,以及 autoRestart。 // apiMu 保护上面这些由内核注入的 API 字段,以及 autoRestart。
// //
@ -487,6 +495,17 @@ func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, de
return nil 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. // RegisterInputChannel registers an input channel with its memory behavior.
// //
// 契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...) // 契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...)
@ -518,6 +537,13 @@ func (s *PluginSDK) SetOutputChannelRegistrar(r OutputChannelRegistrar) {
s.apiMu.Unlock() 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). // SetInputChannelRegistrar sets the input channel registrar (called by the core at startup).
func (s *PluginSDK) SetInputChannelRegistrar(r InputChannelRegistrar) { func (s *PluginSDK) SetInputChannelRegistrar(r InputChannelRegistrar) {
s.apiMu.Lock() s.apiMu.Lock()