Files
HomeAgent/internal/plugin/channel_warn_test.go
JianFeeeee 89db544671 fix(plugin): "只声明出站通道"的告警改为插件加载完成后判定(此前按注册顺序误报 qq)
## 现象

生产日志(v1.3.1 启动)出现:
`[plugin] qq 只声明了输出通道 "qq",已按双向通道兜底登记 inputch;若要明确意图请显式 RegisterInputChannel`
用户据此问"qq 插件你没更新?"

## 查证:qq 没漏,是我的判据错了

- SDK 示例 `example/qq/plugin.go`:`RegisterOutputChannel("qq")` 在 368 行、
  `RegisterInputChannel("qq", {NoMemory:true, Cleaner: inputCleaner})` 在 399 行 —— **先出站后入站**;
- 生产 `plugins/qq/plugin.bin`:版本 1.4.0,且二进制里含 `inputCleaner` 痕迹 ⇒ 确实调用了入站声明;
- 我的兜底告警在 **RegisterOutputChannel 的那一刻**判"有没有入站声明" ⇒ 对"先出站后入站"
  这种完全合法的写法必然误报(a2a/acp/weather 同理)。

## 修法

告警判据从"注册时刻"改为"**插件 Start 结束后最终声明了什么**":

- `regOutput` 只保留兜底登记(功能不变),不再告警;
- 新增 `warnOutputOnlyChannels(plugin)`,在插件加载/重载成功后统一判定:
  遍历该插件**最终**声明过的出站通道,只有始终没有对应入站声明的才告警,
  且措辞改为"内核已兜底登记 inputch,若这是有意为之可忽略"。
- 判据与顺序解耦后,告警才代表真实缺口(例:weather 的 `weather_out` 与
  `weather_in` 名字不同,出站名从未被声明为入站 —— 那条告警就是真的)。

## 验证

- 新增 `TestWarnOutputOnlyChannels`:①先出站后入站(qq 写法)**不告警**;
  ②只声明出站(weather 写法)**告警且只报那一个通道**。
- `go test ./internal/plugin/ ./internal/plugins/...` 全绿。
2026-09-13 13:40:46 +08:00

44 lines
1.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package plugin
import (
"bytes"
"log"
"strings"
"testing"
)
// 延迟判定的语义:看的是"插件 Start 结束后最终声明了什么"
// 而不是"注册出站通道的那一刻有没有入站声明"。
//
// 为什么必须这样判:声明顺序自由 —— qq/weather 都是**先** RegisterOutputChannel
// **后** RegisterInputChannel按注册时刻判会把它们误报成"只声明了输出通道"
// (实测发生过:用户据此以为 qq 插件没更新)。
func TestWarnOutputOnlyChannels(t *testing.T) {
r := NewRegistry()
var buf bytes.Buffer
oldOut := log.Writer()
log.SetOutput(&buf)
defer log.SetOutput(oldOut)
// ① 出站+入站都声明了(先出站后入站)⇒ 不该告警
r.noteChannel("qq", "qq", true)
r.noteChannel("qq", "qq", false)
r.warnOutputOnlyChannels("qq")
if s := buf.String(); s != "" {
t.Fatalf("qq 声明了入站通道,不应告警,实际: %s", s)
}
// ② 只声明出站 ⇒ 应告警,且只报这一个通道
buf.Reset()
r.noteChannel("weather", "weather_weather_out", true)
r.noteChannel("weather", "weather_weather_in", false)
r.warnOutputOnlyChannels("weather")
out := buf.String()
if !strings.Contains(out, "weather_weather_out") {
t.Fatalf("只声明出站的通道应被告警,实际: %q", out)
}
if strings.Contains(out, "weather_weather_in") {
t.Fatalf("已声明入站的通道不该被牵连,实际: %q", out)
}
}