diff --git a/cmd/cmd_gateway_test.go b/cmd/cmd_gateway_test.go index 123ca5a..69a350b 100644 --- a/cmd/cmd_gateway_test.go +++ b/cmd/cmd_gateway_test.go @@ -4,9 +4,12 @@ import ( "context" "os" "path/filepath" + "reflect" "sync/atomic" "testing" "time" + + "github.com/YspCoder/clawgo/pkg/config" ) func TestConfigFileFingerprintSameContentIgnoresTouch(t *testing.T) { @@ -115,3 +118,43 @@ func TestGatewayConfigWatcherTouchDoesNotReload(t *testing.T) { t.Fatalf("expected touch-only update to skip reload, got %d", got) } } + +func TestNormalizeHotReloadChannelsConfigIgnoresWeixinRuntimeState(t *testing.T) { + t.Parallel() + + base := config.ChannelsConfig{ + Weixin: config.WeixinConfig{ + Enabled: true, + BaseURL: "https://ilinkai.weixin.qq.com", + DefaultBotID: "bot-a", + Accounts: []config.WeixinAccountConfig{ + { + BotID: "bot-a", + BotToken: "token-a", + IlinkUserID: "u-1", + ContextToken: "ctx-a", + GetUpdatesBuf: "buf-a", + }, + }, + ContextToken: "root-ctx", + GetUpdatesBuf: "root-buf", + }, + } + next := base + next.Weixin.ContextToken = "root-ctx-next" + next.Weixin.GetUpdatesBuf = "root-buf-next" + next.Weixin.Accounts[0].ContextToken = "ctx-b" + next.Weixin.Accounts[0].GetUpdatesBuf = "buf-b" + + left := normalizeHotReloadChannelsConfig(base) + right := normalizeHotReloadChannelsConfig(next) + if !reflect.DeepEqual(left, right) { + t.Fatalf("expected weixin runtime state changes to be ignored during hot reload comparison") + } + + next.Weixin.BaseURL = "https://redirect.example" + right = normalizeHotReloadChannelsConfig(next) + if reflect.DeepEqual(left, right) { + t.Fatalf("expected durable weixin config changes to remain visible to hot reload comparison") + } +} diff --git a/cmd/gateway_reload.go b/cmd/gateway_reload.go index 2e13355..abb9e02 100644 --- a/cmd/gateway_reload.go +++ b/cmd/gateway_reload.go @@ -67,10 +67,12 @@ func (r *gatewayReloader) trigger(source string, forceRuntimeReload bool) error r.state.cfg.Gateway.Host, r.state.cfg.Gateway.Port, newCfg.Gateway.Host, newCfg.Gateway.Port) } + currentChannels := normalizeHotReloadChannelsConfig(r.state.cfg.Channels) + nextChannels := normalizeHotReloadChannelsConfig(newCfg.Channels) runtimeSame := reflect.DeepEqual(r.state.cfg.Agents, newCfg.Agents) && reflect.DeepEqual(r.state.cfg.Models, newCfg.Models) && reflect.DeepEqual(r.state.cfg.Tools, newCfg.Tools) && - reflect.DeepEqual(r.state.cfg.Channels, newCfg.Channels) + reflect.DeepEqual(currentChannels, nextChannels) if runtimeSame && !forceRuntimeReload { configureLogging(newCfg) @@ -146,6 +148,16 @@ func (r *gatewayReloader) bindWeixinChannel() { } } +func normalizeHotReloadChannelsConfig(cfg config.ChannelsConfig) config.ChannelsConfig { + cfg.Weixin.ContextToken = "" + cfg.Weixin.GetUpdatesBuf = "" + for i := range cfg.Weixin.Accounts { + cfg.Weixin.Accounts[i].ContextToken = "" + cfg.Weixin.Accounts[i].GetUpdatesBuf = "" + } + return cfg +} + type configFileFingerprint struct { Size int64 ModUnixNano int64