fix(agent): clear routed agent session
This commit is contained in:
parent
4c5adcd78e
commit
c4fb7a2001
3 changed files with 139 additions and 2 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue