From b41667cf069c7b265e15a56cd16bb7192cd34bea Mon Sep 17 00:00:00 2001 From: Rahul Bansal Date: Tue, 3 Mar 2026 18:56:46 +0530 Subject: [PATCH] feat: add /new and /clear commands to reset current chat session (#572) --- pkg/agent/loop.go | 29 +++++++++++++++++++++++++++++ pkg/agent/loop_test.go | 36 ++++++++++++++++++++++++++++++++++++ pkg/session/manager.go | 23 +++++++++++++++++++++++ pkg/session/manager_test.go | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index b803187b1..e025e0c2e 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1453,6 +1453,35 @@ func (al *AgentLoop) handleCommand(ctx context.Context, msg bus.InboundMessage) default: return fmt.Sprintf("Unknown switch target: %s", target), true } + + case "/new", "/clear": + route := al.registry.ResolveRoute(routing.RouteInput{ + Channel: msg.Channel, + AccountID: msg.Metadata["account_id"], + Peer: extractPeer(msg), + ParentPeer: extractParentPeer(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 available for this conversation", true + } + + sessionKey := route.SessionKey + if msg.SessionKey != "" && strings.HasPrefix(msg.SessionKey, "agent:") { + sessionKey = msg.SessionKey + } + + agent.Sessions.Save(sessionKey) + agent.Sessions.ResetSession(sessionKey) + agent.ContextBuilder.InvalidateCache() + + return "Started a new conversation. Previous context has been cleared for this chat.", true } return "", false diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 023286f02..cd60c86dd 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -15,6 +15,7 @@ import ( "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/media" "github.com/sipeed/picoclaw/pkg/providers" + "github.com/sipeed/picoclaw/pkg/routing" "github.com/sipeed/picoclaw/pkg/tools" ) @@ -949,3 +950,38 @@ func TestResolveMediaRefs_UsesMetaContentType(t *testing.T) { t.Fatalf("expected jpeg prefix, got %q", result[0].Media[0][:30]) } } + + +func TestProcessMessage_NewCommandResetsSession(t *testing.T) { + al, _, _, _, cleanup := newTestAgentLoop(t) + defer cleanup() + + agent := al.registry.GetDefaultAgent() + if agent == nil { + t.Fatal("no default agent") + } + + sessionKey := routing.BuildAgentMainSessionKey(agent.ID) + agent.Sessions.AddMessage(sessionKey, "user", "old message") + agent.Sessions.SetSummary(sessionKey, "old summary") + + resp, err := al.processMessage(context.Background(), bus.InboundMessage{ + Channel: "webchat", + ChatID: "chat1", + Content: "/new", + }) + if err != nil { + t.Fatalf("processMessage error: %v", err) + } + if !strings.Contains(resp, "Started a new conversation") { + t.Fatalf("unexpected response: %q", resp) + } + + history := agent.Sessions.GetHistory(sessionKey) + if len(history) != 0 { + t.Fatalf("expected session history to be cleared, got %d messages", len(history)) + } + if summary := agent.Sessions.GetSummary(sessionKey); summary != "" { + t.Fatalf("expected summary to be cleared, got %q", summary) + } +} diff --git a/pkg/session/manager.go b/pkg/session/manager.go index 08f0b0ad2..f675dd60b 100644 --- a/pkg/session/manager.go +++ b/pkg/session/manager.go @@ -280,3 +280,26 @@ func (sm *SessionManager) SetHistory(key string, history []providers.Message) { session.Updated = time.Now() } } + +// ResetSession clears the conversation history and summary for a session key +// while preserving the same session identity. +func (sm *SessionManager) ResetSession(key string) { + sm.mu.Lock() + defer sm.mu.Unlock() + + session, ok := sm.sessions[key] + if !ok { + session = &Session{ + Key: key, + Messages: []providers.Message{}, + Created: time.Now(), + Updated: time.Now(), + } + sm.sessions[key] = session + return + } + + session.Messages = []providers.Message{} + session.Summary = "" + session.Updated = time.Now() +} diff --git a/pkg/session/manager_test.go b/pkg/session/manager_test.go index 5ef5f4349..7cfe74760 100644 --- a/pkg/session/manager_test.go +++ b/pkg/session/manager_test.go @@ -72,3 +72,35 @@ func TestSave_RejectsPathTraversal(t *testing.T) { } } } + +func TestResetSession_ClearsHistoryAndSummary(t *testing.T) { + tmpDir := t.TempDir() + sm := NewSessionManager(tmpDir) + key := "telegram:123456" + + sm.AddMessage(key, "user", "hello") + sm.SetSummary(key, "old summary") + + sm.ResetSession(key) + + history := sm.GetHistory(key) + if len(history) != 0 { + t.Fatalf("expected empty history after reset, got %d", len(history)) + } + if got := sm.GetSummary(key); got != "" { + t.Fatalf("expected empty summary after reset, got %q", got) + } +} + +func TestResetSession_CreatesSessionIfMissing(t *testing.T) { + tmpDir := t.TempDir() + sm := NewSessionManager(tmpDir) + key := "discord:abc" + + sm.ResetSession(key) + + history := sm.GetHistory(key) + if len(history) != 0 { + t.Fatalf("expected empty history for new reset session, got %d", len(history)) + } +}