From 537cc355890752f38bcbf294f6aa66734224c1d7 Mon Sep 17 00:00:00 2001 From: lxowalle Date: Wed, 15 Apr 2026 11:12:28 +0800 Subject: [PATCH] fix(agent): preserve /btw immediate reply metadata --- pkg/agent/loop.go | 68 ++++++++++++++++++++++++++------------ pkg/agent/steering_test.go | 14 ++++++++ 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index cf89ce3d6..6341cbb4c 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -654,19 +654,9 @@ func (al *AgentLoop) drainBusToSteering(ctx context.Context, activeScope, active msg, _ = al.transcribeAudioInMessage(ctx, msg) // Handle priority commands (e.g. /btw) immediately instead of queueing them. - if handled, response := al.tryHandlePriorityCommand(ctx, msg); handled { - if response != "" { - al.bus.PublishOutbound(ctx, bus.OutboundMessage{ - Channel: msg.Channel, - ChatID: msg.ChatID, - Context: outboundContextFromInbound( - &msg.Context, - msg.Channel, - msg.ChatID, - msg.Context.ReplyToMessageID, - ), - Content: response, - }) + if handled, outbound := al.tryHandlePriorityCommand(ctx, msg); handled { + if outbound.Content != "" { + al.bus.PublishOutbound(ctx, outbound) } continue } @@ -1586,7 +1576,7 @@ func (al *AgentLoop) askSideQuestion( } messages := agent.ContextBuilder.BuildMessages( - nil, // system instructions are not relevant for side questions + nil, summary, question, media, @@ -4065,25 +4055,47 @@ func mapCommandError(result commands.ExecuteResult) string { return fmt.Sprintf("Failed to execute /%s: %v", result.Command, result.Err) } -func (al *AgentLoop) tryHandlePriorityCommand(ctx context.Context, msg bus.InboundMessage) (bool, string) { +func (al *AgentLoop) tryHandlePriorityCommand(ctx context.Context, msg bus.InboundMessage) (bool, bus.OutboundMessage) { cmdName, ok := commands.CommandName(msg.Content) if !ok || cmdName != "btw" { - return false, "" + return false, bus.OutboundMessage{} } route, agent, err := al.resolveMessageRoute(msg) if err != nil || agent == nil { if err != nil { logger.ErrorCF("agent", fmt.Sprintf("Error resolving route for /btw: %v", err), nil) - return true, fmt.Sprintf("Error processing message: %v", err) + return true, bus.OutboundMessage{ + Channel: msg.Channel, + ChatID: msg.ChatID, + Context: outboundContextFromInbound( + &msg.Context, + msg.Channel, + msg.ChatID, + msg.Context.ReplyToMessageID, + ), + Content: fmt.Sprintf("Error processing message: %v", err), + } } logger.WarnCF("agent", "/btw command unavailable: no agent resolved", nil) - return true, "Command unavailable in current context." + return true, bus.OutboundMessage{ + Channel: msg.Channel, + ChatID: msg.ChatID, + Context: outboundContextFromInbound( + &msg.Context, + msg.Channel, + msg.ChatID, + msg.Context.ReplyToMessageID, + ), + Content: "Command unavailable in current context.", + } } allocation := al.allocateRouteSession(route, msg) + sessionKey := resolveScopeKey(allocation.SessionKey, msg.SessionKey) + msg.SessionKey = sessionKey opts := processOptions{ - SessionKey: resolveScopeKey(allocation.SessionKey, msg.SessionKey), + SessionKey: sessionKey, Channel: msg.Channel, ChatID: msg.ChatID, SenderID: msg.SenderID, @@ -4096,9 +4108,23 @@ func (al *AgentLoop) tryHandlePriorityCommand(ctx context.Context, msg bus.Inbou response, handled := al.handleCommand(ctx, msg, agent, &opts) if !handled { - return false, "" + return false, bus.OutboundMessage{} + } + agentID, outboundSessionKey, scope := outboundTurnMetadata(agent.ID, sessionKey, &allocation.Scope) + return true, bus.OutboundMessage{ + Channel: msg.Channel, + ChatID: msg.ChatID, + Context: outboundContextFromInbound( + &msg.Context, + msg.Channel, + msg.ChatID, + msg.Context.ReplyToMessageID, + ), + AgentID: agentID, + SessionKey: outboundSessionKey, + Scope: scope, + Content: response, } - return true, response } // isNativeSearchProvider reports whether the given LLM provider implements diff --git a/pkg/agent/steering_test.go b/pkg/agent/steering_test.go index a414a211b..3d3fa7a51 100644 --- a/pkg/agent/steering_test.go +++ b/pkg/agent/steering_test.go @@ -1108,6 +1108,20 @@ func TestAgentLoop_Steering_BtwCommandBypassesQueuedTurn(t *testing.T) { if outbound.Content != "btw immediate reply" { t.Fatalf("expected /btw reply before long turn completion, got %q", outbound.Content) } + if outbound.AgentID != routing.DefaultAgentID { + t.Fatalf("expected /btw outbound agent_id %q, got %q", routing.DefaultAgentID, outbound.AgentID) + } + route, _, err := al.resolveMessageRoute(btw) + if err != nil { + t.Fatalf("resolveMessageRoute(/btw) error = %v", err) + } + expectedSessionKey := resolveScopeKey(al.allocateRouteSession(route, btw).SessionKey, btw.SessionKey) + if outbound.SessionKey != expectedSessionKey { + t.Fatalf("expected /btw outbound session_key %q, got %q", expectedSessionKey, outbound.SessionKey) + } + if outbound.Scope == nil || outbound.Scope.AgentID != routing.DefaultAgentID || outbound.Scope.Channel != "test" { + t.Fatalf("expected /btw outbound scope for agent %q on test channel, got %+v", routing.DefaultAgentID, outbound.Scope) + } case <-time.After(2 * time.Second): t.Fatal("timeout waiting for /btw outbound response") }