From 72bf2e090154b3bf3605fcee5c1a67ec40d1a1f0 Mon Sep 17 00:00:00 2001 From: mingmxren Date: Sun, 1 Mar 2026 15:57:24 +0800 Subject: [PATCH] fix(agent): restore generic command parity across channels --- pkg/agent/loop.go | 4 ++ pkg/agent/loop_test.go | 108 +++++++++++++++++++++++++++++++++++ pkg/commands/builtin.go | 4 +- pkg/commands/builtin_test.go | 14 +++++ 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 85316dc13..d828ec553 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1329,6 +1329,10 @@ func (al *AgentLoop) handleCommand( } oldModel := defaultAgent.Model defaultAgent.Model = value + if al.cfg != nil { + al.cfg.Agents.Defaults.ModelName = value + al.cfg.Agents.Defaults.Model = value + } return fmt.Sprintf("Switched model from %s to %s", oldModel, value), true case "channel": if al.channelManager == nil { diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index b07d3950e..876adb15e 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -715,6 +715,114 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) { } } +func TestProcessMessage_CLI_SessionCommandsStillWork(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, + Model: "test-model", + MaxTokens: 4096, + MaxToolIterations: 10, + }, + }, + Session: config.SessionConfig{ + BacklogLimit: 20, + }, + } + + msgBus := bus.NewMessageBus() + provider := &countingMockProvider{response: "LLM reply"} + al := NewAgentLoop(cfg, msgBus, provider) + helper := testHelper{al: al} + + newResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{ + Channel: "cli", + SenderID: "user1", + ChatID: "cli", + Content: "/new", + }) + if !strings.Contains(newResp, "Started new session:") { + t.Fatalf("unexpected /new reply on cli: %q", newResp) + } + + listResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{ + Channel: "cli", + SenderID: "user1", + ChatID: "cli", + Content: "/session list", + }) + if !strings.Contains(listResp, "Sessions for current chat:") { + t.Fatalf("unexpected /session list reply on cli: %q", listResp) + } + + if provider.calls != 0 { + t.Fatalf("LLM should not be called for handled cli session commands, calls=%d", provider.calls) + } +} + +func TestProcessMessage_SwitchModelShowModelConsistency(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, + Provider: "openai", + Model: "before-switch", + MaxTokens: 4096, + MaxToolIterations: 10, + }, + }, + } + + msgBus := bus.NewMessageBus() + provider := &countingMockProvider{response: "LLM reply"} + al := NewAgentLoop(cfg, msgBus, provider) + helper := testHelper{al: al} + + switchResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Content: "/switch model to after-switch", + Peer: bus.Peer{ + Kind: "direct", + ID: "user1", + }, + }) + if !strings.Contains(switchResp, "Switched model from before-switch to after-switch") { + t.Fatalf("unexpected /switch reply: %q", switchResp) + } + + showResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Content: "/show model", + Peer: bus.Peer{ + Kind: "direct", + ID: "user1", + }, + }) + if !strings.Contains(showResp, "Current Model: after-switch (Provider: openai)") { + t.Fatalf("unexpected /show model reply after switch: %q", showResp) + } + + if provider.calls != 0 { + t.Fatalf("LLM should not be called for /switch and /show, calls=%d", provider.calls) + } +} + // TestToolResult_SilentToolDoesNotSendUserMessage verifies silent tools don't trigger outbound func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) { tmpDir, err := os.MkdirTemp("", "agent-test-*") diff --git a/pkg/commands/builtin.go b/pkg/commands/builtin.go index ecb4c6ea5..52e339f8a 100644 --- a/pkg/commands/builtin.go +++ b/pkg/commands/builtin.go @@ -59,14 +59,14 @@ func builtinDefinitions(cfg *config.Config, runtime Runtime) []Definition { Aliases: []string{"reset"}, Description: "Start a new chat session", Usage: "/new", - Channels: []string{"telegram", "whatsapp", "whatsapp_native"}, + Channels: nil, Handler: newHandler, }, { Name: "session", Description: "Manage chat sessions", Usage: "/session [list|resume ]", - Channels: []string{"telegram", "whatsapp", "whatsapp_native"}, + Channels: nil, Handler: sessionHandler, }, { diff --git a/pkg/commands/builtin_test.go b/pkg/commands/builtin_test.go index c18e281cd..bb632d06b 100644 --- a/pkg/commands/builtin_test.go +++ b/pkg/commands/builtin_test.go @@ -37,6 +37,20 @@ func TestBuiltinDefinitions_WhatsAppOnlyHasBasicCommands(t *testing.T) { } } +func TestBuiltinDefinitions_CLIHasSessionCommands(t *testing.T) { + defs := NewRegistry(BuiltinDefinitions(nil)).ForChannel("cli") + names := map[string]bool{} + for _, d := range defs { + names[d.Name] = true + } + if !names["new"] || !names["session"] { + t.Fatalf("cli should include new/session, got %+v", names) + } + if names["show"] || names["list"] { + t.Fatalf("cli should not include show/list, got %+v", names) + } +} + func TestBuiltinDefinitions_DefaultSessionCommandsArePassthrough(t *testing.T) { defs := BuiltinDefinitions(nil)