fix(agent): preserve /btw immediate reply metadata

This commit is contained in:
lxowalle 2026-04-15 11:12:28 +08:00
parent 0cb5bdee0d
commit 537cc35589
2 changed files with 61 additions and 21 deletions

View file

@ -654,19 +654,9 @@ func (al *AgentLoop) drainBusToSteering(ctx context.Context, activeScope, active
msg, _ = al.transcribeAudioInMessage(ctx, msg) msg, _ = al.transcribeAudioInMessage(ctx, msg)
// Handle priority commands (e.g. /btw) immediately instead of queueing them. // Handle priority commands (e.g. /btw) immediately instead of queueing them.
if handled, response := al.tryHandlePriorityCommand(ctx, msg); handled { if handled, outbound := al.tryHandlePriorityCommand(ctx, msg); handled {
if response != "" { if outbound.Content != "" {
al.bus.PublishOutbound(ctx, bus.OutboundMessage{ al.bus.PublishOutbound(ctx, outbound)
Channel: msg.Channel,
ChatID: msg.ChatID,
Context: outboundContextFromInbound(
&msg.Context,
msg.Channel,
msg.ChatID,
msg.Context.ReplyToMessageID,
),
Content: response,
})
} }
continue continue
} }
@ -1586,7 +1576,7 @@ func (al *AgentLoop) askSideQuestion(
} }
messages := agent.ContextBuilder.BuildMessages( messages := agent.ContextBuilder.BuildMessages(
nil, // system instructions are not relevant for side questions nil,
summary, summary,
question, question,
media, media,
@ -4065,25 +4055,47 @@ func mapCommandError(result commands.ExecuteResult) string {
return fmt.Sprintf("Failed to execute /%s: %v", result.Command, result.Err) 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) cmdName, ok := commands.CommandName(msg.Content)
if !ok || cmdName != "btw" { if !ok || cmdName != "btw" {
return false, "" return false, bus.OutboundMessage{}
} }
route, agent, err := al.resolveMessageRoute(msg) route, agent, err := al.resolveMessageRoute(msg)
if err != nil || agent == nil { if err != nil || agent == nil {
if err != nil { if err != nil {
logger.ErrorCF("agent", fmt.Sprintf("Error resolving route for /btw: %v", 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) 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) allocation := al.allocateRouteSession(route, msg)
sessionKey := resolveScopeKey(allocation.SessionKey, msg.SessionKey)
msg.SessionKey = sessionKey
opts := processOptions{ opts := processOptions{
SessionKey: resolveScopeKey(allocation.SessionKey, msg.SessionKey), SessionKey: sessionKey,
Channel: msg.Channel, Channel: msg.Channel,
ChatID: msg.ChatID, ChatID: msg.ChatID,
SenderID: msg.SenderID, 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) response, handled := al.handleCommand(ctx, msg, agent, &opts)
if !handled { 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 // isNativeSearchProvider reports whether the given LLM provider implements

View file

@ -1108,6 +1108,20 @@ func TestAgentLoop_Steering_BtwCommandBypassesQueuedTurn(t *testing.T) {
if outbound.Content != "btw immediate reply" { if outbound.Content != "btw immediate reply" {
t.Fatalf("expected /btw reply before long turn completion, got %q", outbound.Content) 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): case <-time.After(2 * time.Second):
t.Fatal("timeout waiting for /btw outbound response") t.Fatal("timeout waiting for /btw outbound response")
} }