diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 9877df4c1..0429b308d 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1071,6 +1071,8 @@ func (al *AgentLoop) handleCommand(ctx context.Context, msg bus.InboundMessage) case "/help": return `Available commands: /help Show this help message + /new Start a new conversation + /status Show current session info /show model Show current model /show channel Show current channel /show agents Show registered agents @@ -1139,6 +1141,63 @@ func (al *AgentLoop) handleCommand(ctx context.Context, msg bus.InboundMessage) return fmt.Sprintf("Unknown list target: %s", args[0]), true } + case "/new": + // Resolve the route to find the correct agent and session key + route := al.registry.ResolveRoute(routing.RouteInput{ + Channel: msg.Channel, + AccountID: msg.Metadata["account_id"], + Peer: extractPeer(msg), + GuildID: msg.Metadata["guild_id"], + TeamID: msg.Metadata["team_id"], + }) + + agent, ok := al.registry.GetAgent(route.AgentID) + if !ok { + agent = al.registry.GetDefaultAgent() + } + if agent == nil { + return "No agent configured", true + } + + // Save current session before clearing + sessionKey := route.SessionKey + agent.Sessions.Save(sessionKey) + + // Clear the in-memory conversation history and summary + agent.Sessions.TruncateHistory(sessionKey, 0) + agent.Sessions.SetSummary(sessionKey, "") + agent.Sessions.Save(sessionKey) + + return "Started a new conversation. Previous session saved.", true + + case "/status": + // Resolve the route to find the correct agent and session key + route := al.registry.ResolveRoute(routing.RouteInput{ + Channel: msg.Channel, + AccountID: msg.Metadata["account_id"], + Peer: extractPeer(msg), + GuildID: msg.Metadata["guild_id"], + TeamID: msg.Metadata["team_id"], + }) + + agent, ok := al.registry.GetAgent(route.AgentID) + if !ok { + agent = al.registry.GetDefaultAgent() + } + if agent == nil { + return "No agent configured", true + } + + sessionKey := route.SessionKey + history := agent.Sessions.GetHistory(sessionKey) + + return fmt.Sprintf(`Status: + Model: %s + Agent: %s + Channel: %s + Messages: %d in current session + Max iterations: %d`, agent.Model, agent.ID, msg.Channel, len(history), agent.MaxIterations), true + case "/switch": if len(args) < 3 || args[1] != "to" { return "Usage: /switch [model|channel] to ", true diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 94129e9cf..eae9442aa 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -789,3 +789,135 @@ func TestHandleCommand_NotACommand(t *testing.T) { }) } } + +func TestHandleCommand_New(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, + }, + }, + } + + msgBus := bus.NewMessageBus() + provider := &mockProvider{} + al := NewAgentLoop(cfg, msgBus, provider) + + // Add some messages to the session so we can verify they get cleared + defaultAgent := al.registry.GetDefaultAgent() + if defaultAgent == nil { + t.Fatal("No default agent found") + } + sessionKey := "agent:main:main" + defaultAgent.Sessions.AddMessage(sessionKey, "user", "hello") + defaultAgent.Sessions.AddMessage(sessionKey, "assistant", "hi there") + + history := defaultAgent.Sessions.GetHistory(sessionKey) + if len(history) != 2 { + t.Fatalf("Expected 2 messages before /new, got %d", len(history)) + } + + ctx := context.Background() + msg := bus.InboundMessage{ + Channel: "test", + SenderID: "user1", + ChatID: "chat1", + Content: "/new", + } + + response, handled := al.handleCommand(ctx, msg) + if !handled { + t.Fatal("Expected /new to be handled") + } + if !strings.Contains(response, "Started a new conversation") { + t.Errorf("Expected confirmation message, got: %s", response) + } + if !strings.Contains(response, "Previous session saved") { + t.Errorf("Expected 'Previous session saved' in response, got: %s", response) + } + + // Verify history was cleared + history = defaultAgent.Sessions.GetHistory(sessionKey) + if len(history) != 0 { + t.Errorf("Expected 0 messages after /new, got %d", len(history)) + } + + // Verify summary was cleared + summary := defaultAgent.Sessions.GetSummary(sessionKey) + if summary != "" { + t.Errorf("Expected empty summary after /new, got: %s", summary) + } +} + +func TestHandleCommand_Status(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: "claude-3-opus", + MaxTokens: 4096, + MaxToolIterations: 15, + }, + }, + } + + msgBus := bus.NewMessageBus() + provider := &mockProvider{} + al := NewAgentLoop(cfg, msgBus, provider) + + // Add some messages so the count is nonzero + defaultAgent := al.registry.GetDefaultAgent() + if defaultAgent == nil { + t.Fatal("No default agent found") + } + sessionKey := "agent:main:main" + defaultAgent.Sessions.AddMessage(sessionKey, "user", "hello") + defaultAgent.Sessions.AddMessage(sessionKey, "assistant", "hi") + defaultAgent.Sessions.AddMessage(sessionKey, "user", "how are you?") + + ctx := context.Background() + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Content: "/status", + } + + response, handled := al.handleCommand(ctx, msg) + if !handled { + t.Fatal("Expected /status to be handled") + } + + // Verify key fields are present + expectedFields := []struct { + label string + value string + }{ + {"Model", "claude-3-opus"}, + {"Agent", "main"}, + {"Channel", "telegram"}, + {"Messages", "3 in current session"}, + {"Max iterations", "15"}, + } + for _, f := range expectedFields { + expected := fmt.Sprintf("%s: %s", f.label, f.value) + if !strings.Contains(response, expected) { + t.Errorf("Expected status to contain %q, got:\n%s", expected, response) + } + } +}