diff --git a/core/engine_test.go b/core/engine_test.go index 44b254557..e3f7b9316 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -3212,6 +3212,7 @@ func TestHandleMessage_AutoResetOnIdle_RotatesToNewSession(t *testing.T) { old.SetAgentSessionID("old-session", "stub") staleAt := time.Now().Add(-2 * time.Hour) old.mu.Lock() + old.LastUserActivity = staleAt old.UpdatedAt = staleAt old.mu.Unlock() @@ -3287,6 +3288,7 @@ func TestHandleMessage_AutoResetOnIdle_DoesNotRotateFreshSession(t *testing.T) { session.SetAgentSessionID("existing-session", "stub") recentAt := time.Now().Add(-5 * time.Minute) session.mu.Lock() + session.LastUserActivity = recentAt session.UpdatedAt = recentAt session.mu.Unlock() @@ -3325,6 +3327,71 @@ func TestHandleMessage_AutoResetOnIdle_DoesNotRotateFreshSession(t *testing.T) { } } +func TestHandleMessage_AutoResetOnIdle_FiresWhenHeartbeatBumpedUpdatedAt(t *testing.T) { + p := &stubPlatformEngine{n: "test"} + agentSession := newResultAgentSession("fresh reply") + agent := &resultAgent{session: agentSession} + e := NewEngine("test", agent, []Platform{p}, "", LangEnglish) + e.SetResetOnIdle(60 * time.Minute) + + key := "test:user1" + old := e.sessions.GetOrCreateActive(key) + old.AddHistory("user", "stale context") + old.SetAgentSessionID("old-session", "stub") + + // Last user message was a long time ago — well past the idle threshold. + staleAt := time.Now().Add(-2 * time.Hour) + old.mu.Lock() + old.LastUserActivity = staleAt + old.mu.Unlock() + + // Simulate a heartbeat (or unsolicited agent response) finishing right + // before this test's user message: Unlock() bumps UpdatedAt to now, but + // LastUserActivity is intentionally NOT touched by those code paths. + old.Unlock() + + if !old.GetUpdatedAt().After(staleAt) { + t.Fatalf("expected Unlock to bump UpdatedAt, got %v vs %v", old.GetUpdatedAt(), staleAt) + } + if !old.GetLastUserActivity().Equal(staleAt) { + t.Fatalf("expected LastUserActivity to remain at %v, got %v", staleAt, old.GetLastUserActivity()) + } + + msg := &Message{ + SessionKey: key, + Platform: "test", + UserID: "u1", + UserName: "user", + Content: "hello after idle", + ReplyCtx: "ctx", + } + e.handleMessage(p, msg) + + deadline := time.After(2 * time.Second) + for { + active := e.sessions.GetOrCreateActive(key) + sent := p.getSent() + if active.ID != old.ID && len(active.GetHistory(0)) >= 2 && len(sent) >= 2 { + break + } + select { + case <-deadline: + t.Fatalf("timed out waiting for idle auto-reset despite heartbeat-bumped UpdatedAt, sent=%v active=%s old=%s", sent, active.ID, old.ID) + default: + time.Sleep(10 * time.Millisecond) + } + } + + active := e.sessions.GetOrCreateActive(key) + if active.ID == old.ID { + t.Fatal("expected a new active session after idle auto-reset") + } + sent := p.getSent() + if !strings.Contains(sent[0], "Session auto-reset") { + t.Fatalf("first reply = %q, want auto-reset notice", sent[0]) + } +} + func TestHandleMessage_AutoResetOnIdle_DoesNotTriggerForSlashCommand(t *testing.T) { p := &stubPlatformEngine{n: "test"} e := NewEngine("test", &stubAgent{}, []Platform{p}, "", LangEnglish) @@ -3336,6 +3403,7 @@ func TestHandleMessage_AutoResetOnIdle_DoesNotTriggerForSlashCommand(t *testing. session.SetAgentSessionID("old-session", "stub") staleAt := time.Now().Add(-2 * time.Hour) session.mu.Lock() + session.LastUserActivity = staleAt session.UpdatedAt = staleAt session.mu.Unlock()