fix(agent): clear routed agent session

This commit is contained in:
Ethan1918 2026-07-04 04:14:03 +08:00
parent 4c5adcd78e
commit c4fb7a2001
3 changed files with 139 additions and 2 deletions

View file

@ -333,7 +333,7 @@ func (al *AgentLoop) buildCommandsRuntime(
if opts == nil { if opts == nil {
return fmt.Errorf("process options not available") return fmt.Errorf("process options not available")
} }
return al.contextManager.Clear(ctx, opts.SessionKey) return al.clearAgentSessionContext(ctx, agent, opts.SessionKey)
} }
rt.AskSideQuestion = func(ctx context.Context, question string) (string, error) { rt.AskSideQuestion = func(ctx context.Context, question string) (string, error) {
@ -363,6 +363,40 @@ func (al *AgentLoop) buildCommandsRuntime(
return rt return rt
} }
type contextStoreClearer interface {
ClearContextStore(ctx context.Context, sessionKey string) error
}
func (al *AgentLoop) clearAgentSessionContext(
ctx context.Context,
agent *AgentInstance,
sessionKey string,
) error {
if agent == nil || agent.Sessions == nil {
return fmt.Errorf("sessions not initialized")
}
if al != nil && al.registry != nil && agent == al.registry.GetDefaultAgent() {
if al.contextManager != nil {
return al.contextManager.Clear(ctx, sessionKey)
}
return clearAgentSessionStore(agent, sessionKey)
}
if al != nil && al.contextManager != nil {
if clearer, ok := al.contextManager.(contextStoreClearer); ok {
if err := clearer.ClearContextStore(ctx, sessionKey); err != nil {
return err
}
}
}
return clearAgentSessionStore(agent, sessionKey)
}
func clearAgentSessionStore(agent *AgentInstance, sessionKey string) error {
agent.Sessions.SetHistory(sessionKey, []providers.Message{})
agent.Sessions.SetSummary(sessionKey, "")
return agent.Sessions.Save(sessionKey)
}
func summarizeMCPToolParameters(schema any) []commands.MCPToolParameterInfo { func summarizeMCPToolParameters(schema any) []commands.MCPToolParameterInfo {
schemaMap := normalizeMCPSchema(schema) schemaMap := normalizeMCPSchema(schema)
properties, ok := schemaMap["properties"].(map[string]any) properties, ok := schemaMap["properties"].(map[string]any)

View file

@ -3365,6 +3365,105 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
} }
} }
func TestProcessMessage_ClearCommandClearsRoutedAgentSession(t *testing.T) {
workspace := t.TempDir()
cfg := &config.Config{
Agents: config.AgentsConfig{
Defaults: config.AgentDefaults{
Workspace: filepath.Join(workspace, "default"),
ModelName: "test-model",
MaxTokens: 4096,
MaxToolIterations: 10,
},
List: []config.AgentConfig{
{
ID: "main",
Default: true,
Workspace: filepath.Join(workspace, "main"),
},
{
ID: "support",
Workspace: filepath.Join(workspace, "support"),
},
},
Dispatch: &config.DispatchConfig{
Rules: []config.DispatchRule{
{
Name: "support-dingtalk",
Agent: "support",
When: config.DispatchSelector{
Channel: "dingtalk",
},
},
},
},
},
Session: config.SessionConfig{
Dimensions: []string{"chat"},
},
}
al := NewAgentLoop(cfg, bus.NewMessageBus(), &countingMockProvider{response: "LLM reply"})
mainAgent, ok := al.registry.GetAgent("main")
if !ok {
t.Fatal("expected main agent")
}
supportAgent, ok := al.registry.GetAgent("support")
if !ok {
t.Fatal("expected support agent")
}
msg := testInboundMessage(bus.InboundMessage{
Context: bus.InboundContext{
Channel: "dingtalk",
ChatID: "chat1",
ChatType: "direct",
SenderID: "user1",
},
Content: "/clear",
})
route, routedAgent, err := al.resolveMessageRoute(msg)
if err != nil {
t.Fatalf("resolveMessageRoute() error = %v", err)
}
if routedAgent != supportAgent {
t.Fatalf("routed agent = %s, want support", routedAgent.ID)
}
sessionKey := al.allocateRouteSession(route, msg).SessionKey
mainHistory := []providers.Message{{Role: "user", Content: "main history"}}
supportHistory := []providers.Message{{Role: "user", Content: "support history"}}
mainAgent.Sessions.SetHistory(sessionKey, mainHistory)
mainAgent.Sessions.SetSummary(sessionKey, "main summary")
supportAgent.Sessions.SetHistory(sessionKey, supportHistory)
supportAgent.Sessions.SetSummary(sessionKey, "support summary")
response, err := al.processMessage(context.Background(), msg)
if err != nil {
t.Fatalf("processMessage() error = %v", err)
}
if response != "Chat history cleared!" {
t.Fatalf("response = %q, want clear confirmation", response)
}
if got := supportAgent.Sessions.GetHistory(sessionKey); len(got) != 0 {
t.Fatalf("support history len = %d, want 0", len(got))
}
if got := supportAgent.Sessions.GetSummary(sessionKey); got != "" {
t.Fatalf("support summary = %q, want empty", got)
}
if got := mainAgent.Sessions.GetHistory(sessionKey); len(got) != len(mainHistory) {
t.Fatalf("main history len = %d, want %d", len(got), len(mainHistory))
} else if got[0].Role != mainHistory[0].Role {
t.Fatalf("main history[0].Role = %q, want %q", got[0].Role, mainHistory[0].Role)
} else if got[0].Content != mainHistory[0].Content {
t.Fatalf("main history[0].Content = %q, want %q", got[0].Content, mainHistory[0].Content)
}
if got := mainAgent.Sessions.GetSummary(sessionKey); got != "main summary" {
t.Fatalf("main summary = %q, want %q", got, "main summary")
}
}
func TestProcessMessage_MCPCommandsHandledWithoutLLMCall(t *testing.T) { func TestProcessMessage_MCPCommandsHandledWithoutLLMCall(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "agent-test-*") tmpDir, err := os.MkdirTemp("", "agent-test-*")
if err != nil { if err != nil {

View file

@ -157,7 +157,7 @@ func (m *seahorseContextManager) Ingest(ctx context.Context, req *IngestRequest)
// Clear removes all stored context for a session (seahorse DB + JSONL). // Clear removes all stored context for a session (seahorse DB + JSONL).
func (m *seahorseContextManager) Clear(ctx context.Context, sessionKey string) error { func (m *seahorseContextManager) Clear(ctx context.Context, sessionKey string) error {
if err := m.engine.ClearSession(ctx, sessionKey); err != nil { if err := m.ClearContextStore(ctx, sessionKey); err != nil {
return err return err
} }
if m.sessions != nil { if m.sessions != nil {
@ -168,6 +168,10 @@ func (m *seahorseContextManager) Clear(ctx context.Context, sessionKey string) e
return nil return nil
} }
func (m *seahorseContextManager) ClearContextStore(ctx context.Context, sessionKey string) error {
return m.engine.ClearSession(ctx, sessionKey)
}
// bootstrapSession reconciles JSONL session history into seahorse SQLite. // bootstrapSession reconciles JSONL session history into seahorse SQLite.
func (m *seahorseContextManager) bootstrapSession(ctx context.Context, sessionKey string) { func (m *seahorseContextManager) bootstrapSession(ctx context.Context, sessionKey string) {
if m.sessions == nil { if m.sessions == nil {