From 75f1b58ca080bb49dc0a3f041a1bc1a581db7a47 Mon Sep 17 00:00:00 2001 From: mingmxren Date: Thu, 5 Mar 2026 22:10:27 +0800 Subject: [PATCH] fix(agent): use routed agent for model commands, restore Telegram command diff - Remove modelMu: message processing is serial, no concurrent writes - Pass routed agent to handleCommand/buildCommandsRuntime instead of always using default agent - GetModelInfo/SwitchModel are nil when agent is nil (route failed), handlers reply "unavailable" - Restore GetMyCommands + slices.Equal check before SetMyCommands to avoid unnecessary Telegram API calls on restart Co-Authored-By: Claude Opus 4.6 --- pkg/agent/loop.go | 47 ++++++++----------- pkg/channels/telegram/command_registration.go | 11 +++++ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index b8525c8ec..195623dc5 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -48,7 +48,6 @@ type AgentLoop struct { mediaStore media.MediaStore transcriber voice.Transcriber cmdRegistry *commands.Registry - modelMu sync.Mutex // protects AgentInstance.Model writes in SwitchModel } // processOptions configures how a message is processed @@ -538,7 +537,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage) // Global commands (/help, /show, /switch) work even when routing fails; // context-dependent commands check their own Runtime fields and report // "unavailable" when the required capability is nil. - if response, handled := al.handleCommand(ctx, msg); handled { + if response, handled := al.handleCommand(ctx, msg, agent); handled { return response, nil } @@ -1466,6 +1465,7 @@ func (al *AgentLoop) estimateTokens(messages []providers.Message) int { func (al *AgentLoop) handleCommand( ctx context.Context, msg bus.InboundMessage, + agent *AgentInstance, ) (string, bool) { if !commands.HasCommandPrefix(msg.Content) { return "", false @@ -1475,7 +1475,7 @@ func (al *AgentLoop) handleCommand( return "", false } - rt := al.buildCommandsRuntime() + rt := al.buildCommandsRuntime(agent) executor := commands.NewExecutor(al.cmdRegistry, rt) var commandReply string @@ -1504,16 +1504,9 @@ func (al *AgentLoop) handleCommand( } } -func (al *AgentLoop) buildCommandsRuntime() *commands.Runtime { - return &commands.Runtime{ - Config: al.cfg, - GetModelInfo: func() (string, string) { - agent := al.registry.GetDefaultAgent() - if agent == nil { - return al.cfg.Agents.Defaults.GetModelName(), al.cfg.Agents.Defaults.Provider - } - return agent.Model, al.cfg.Agents.Defaults.Provider - }, +func (al *AgentLoop) buildCommandsRuntime(agent *AgentInstance) *commands.Runtime { + rt := &commands.Runtime{ + Config: al.cfg, ListAgentIDs: al.registry.ListAgentIDs, ListDefinitions: al.cmdRegistry.Definitions, GetEnabledChannels: func() []string { @@ -1522,20 +1515,6 @@ func (al *AgentLoop) buildCommandsRuntime() *commands.Runtime { } return al.channelManager.GetEnabledChannels() }, - SwitchModel: func(value string) (string, error) { - al.modelMu.Lock() - defer al.modelMu.Unlock() - defaultAgent := al.registry.GetDefaultAgent() - if defaultAgent == nil { - return "", fmt.Errorf("no default agent configured") - } - oldModel := defaultAgent.Model - defaultAgent.Model = value - if al.cfg != nil { - al.cfg.Agents.Defaults.ModelName = value - } - return oldModel, nil - }, SwitchChannel: func(value string) error { if al.channelManager == nil { return fmt.Errorf("channel manager not initialized") @@ -1546,6 +1525,20 @@ func (al *AgentLoop) buildCommandsRuntime() *commands.Runtime { return nil }, } + if agent != nil { + rt.GetModelInfo = func() (string, string) { + return agent.Model, al.cfg.Agents.Defaults.Provider + } + rt.SwitchModel = func(value string) (string, error) { + oldModel := agent.Model + agent.Model = value + if al.cfg != nil { + al.cfg.Agents.Defaults.ModelName = value + } + return oldModel, nil + } + } + return rt } func mapCommandError(result commands.ExecuteResult) string { diff --git a/pkg/channels/telegram/command_registration.go b/pkg/channels/telegram/command_registration.go index d674e523d..d3152ec3d 100644 --- a/pkg/channels/telegram/command_registration.go +++ b/pkg/channels/telegram/command_registration.go @@ -3,6 +3,7 @@ package telegram import ( "context" "math/rand" + "slices" "time" "github.com/mymmrac/telego" @@ -41,6 +42,16 @@ func (c *TelegramChannel) RegisterCommands(ctx context.Context, defs []commands. }) } + current, err := c.bot.GetMyCommands(ctx, &telego.GetMyCommandsParams{}) + if err != nil { + // If we can't read current commands, fall through to set them. + logger.WarnCF("telegram", "Failed to get current commands, will set unconditionally", + map[string]any{"error": err.Error()}) + } else if slices.Equal(current, botCommands) { + logger.DebugCF("telegram", "Bot commands are up to date", nil) + return nil + } + return c.bot.SetMyCommands(ctx, &telego.SetMyCommandsParams{ Commands: botCommands, })