Fix the issue of not being able to reply promptly on /btw.
This commit is contained in:
parent
22f170450a
commit
3c3c520602
2 changed files with 178 additions and 0 deletions
|
|
@ -653,6 +653,18 @@ func (al *AgentLoop) drainBusToSteering(ctx context.Context, activeScope, active
|
||||||
// Transcribe audio if needed before steering, so the agent sees text.
|
// Transcribe audio if needed before steering, so the agent sees text.
|
||||||
msg, _ = al.transcribeAudioInMessage(ctx, msg)
|
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,
|
||||||
|
Content: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
logger.InfoCF("agent", "Redirecting inbound message to steering queue",
|
logger.InfoCF("agent", "Redirecting inbound message to steering queue",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"channel": msg.Channel,
|
"channel": msg.Channel,
|
||||||
|
|
@ -4047,6 +4059,39 @@ 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) {
|
||||||
|
cmdName, ok := commands.CommandName(msg.Content)
|
||||||
|
if !ok || cmdName != "btw" {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
logger.WarnCF("agent", "/btw command unavailable: no agent resolved", nil)
|
||||||
|
return true, "Command unavailable in current context."
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation := al.allocateRouteSession(route, msg)
|
||||||
|
opts := processOptions{
|
||||||
|
SessionKey: resolveScopeKey(allocation.SessionKey, msg.SessionKey),
|
||||||
|
Channel: msg.Channel,
|
||||||
|
ChatID: msg.ChatID,
|
||||||
|
SenderID: msg.SenderID,
|
||||||
|
SenderDisplayName: msg.Sender.DisplayName,
|
||||||
|
Media: msg.Media,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, handled := al.handleCommand(ctx, msg, agent, &opts)
|
||||||
|
if !handled {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
return true, response
|
||||||
|
}
|
||||||
|
|
||||||
// isNativeSearchProvider reports whether the given LLM provider implements
|
// isNativeSearchProvider reports whether the given LLM provider implements
|
||||||
// NativeSearchCapable and returns true for SupportsNativeSearch.
|
// NativeSearchCapable and returns true for SupportsNativeSearch.
|
||||||
func isNativeSearchProvider(p providers.LLMProvider) bool {
|
func isNativeSearchProvider(p providers.LLMProvider) bool {
|
||||||
|
|
|
||||||
|
|
@ -1010,6 +1010,139 @@ func TestAgentLoop_Steering_DirectResponseContinuesWithQueuedMessage(t *testing.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgentLoop_Steering_BtwCommandBypassesQueuedTurn(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
Agents: config.AgentsConfig{
|
||||||
|
Defaults: config.AgentDefaults{
|
||||||
|
Workspace: tmpDir,
|
||||||
|
ModelName: "test-model",
|
||||||
|
MaxTokens: 4096,
|
||||||
|
MaxToolIterations: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &blockingDirectProvider{
|
||||||
|
firstStarted: make(chan struct{}),
|
||||||
|
releaseFirst: make(chan struct{}),
|
||||||
|
firstResp: "long turn finished",
|
||||||
|
finalResp: "btw immediate reply",
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBus := bus.NewMessageBus()
|
||||||
|
al := NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
|
runCtx, cancelRun := context.WithCancel(context.Background())
|
||||||
|
defer cancelRun()
|
||||||
|
runErrCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
runErrCh <- al.Run(runCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
first := bus.InboundMessage{
|
||||||
|
Context: bus.InboundContext{
|
||||||
|
Channel: "test",
|
||||||
|
ChatID: "chat1",
|
||||||
|
ChatType: "direct",
|
||||||
|
SenderID: "user1",
|
||||||
|
},
|
||||||
|
Content: "execute sleep 60, then send OK",
|
||||||
|
}
|
||||||
|
btw := bus.InboundMessage{
|
||||||
|
Context: bus.InboundContext{
|
||||||
|
Channel: "test",
|
||||||
|
ChatID: "chat1",
|
||||||
|
ChatType: "direct",
|
||||||
|
SenderID: "user1",
|
||||||
|
},
|
||||||
|
Content: "/btw what is the current progress?",
|
||||||
|
}
|
||||||
|
|
||||||
|
pubCtx, pubCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer pubCancel()
|
||||||
|
if err := msgBus.PublishInbound(pubCtx, first); err != nil {
|
||||||
|
t.Fatalf("publish first inbound: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-provider.firstStarted:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("timeout waiting for first LLM call to start")
|
||||||
|
}
|
||||||
|
|
||||||
|
messageTool, ok := al.GetRegistry().GetDefaultAgent().Tools.Get("message")
|
||||||
|
var mt *tools.MessageTool
|
||||||
|
if !ok {
|
||||||
|
mt = tools.NewMessageTool()
|
||||||
|
al.RegisterTool(mt)
|
||||||
|
} else {
|
||||||
|
var typeOK bool
|
||||||
|
mt, typeOK = messageTool.(*tools.MessageTool)
|
||||||
|
if !typeOK {
|
||||||
|
t.Fatal("expected message tool type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mt.SetSendCallback(func(ctx context.Context, channel, chatID, content, replyToMessageID string) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if result := mt.Execute(context.Background(), map[string]any{
|
||||||
|
"channel": "test",
|
||||||
|
"chat_id": "chat1",
|
||||||
|
"content": "already sent from busy turn",
|
||||||
|
}); result == nil || result.IsError {
|
||||||
|
t.Fatalf("message tool setup result = %+v, want successful send", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := msgBus.PublishInbound(pubCtx, btw); err != nil {
|
||||||
|
t.Fatalf("publish /btw inbound: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case outbound := <-msgBus.OutboundChan():
|
||||||
|
if outbound.Content != "btw immediate reply" {
|
||||||
|
t.Fatalf("expected /btw reply before long turn completion, got %q", outbound.Content)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("timeout waiting for /btw outbound response")
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey := session.BuildMainSessionKey(routing.DefaultAgentID)
|
||||||
|
if msgs := al.dequeueSteeringMessagesForScope(sessionKey); len(msgs) != 0 {
|
||||||
|
t.Fatalf("expected /btw to bypass steering queue, got %v", msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(provider.releaseFirst)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case outbound := <-msgBus.OutboundChan():
|
||||||
|
t.Fatalf("expected busy turn final response to stay suppressed, got %q", outbound.Content)
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.mu.Lock()
|
||||||
|
callCount := provider.calls
|
||||||
|
provider.mu.Unlock()
|
||||||
|
if callCount != 2 {
|
||||||
|
t.Fatalf("provider call count = %d, want 2", callCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelRun()
|
||||||
|
select {
|
||||||
|
case err := <-runErrCh:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run returned error: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("timeout waiting for Run to stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAgentLoop_AgentForSession_UsesStoredScopeMetadata(t *testing.T) {
|
func TestAgentLoop_AgentForSession_UsesStoredScopeMetadata(t *testing.T) {
|
||||||
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue