fix(sessions): updateLastRoute must not bump updatedAt (#49515) (#49588)

updateLastRoute() used mergeSessionEntry which bumps updatedAt to
Date.now() on every inbound message. This prevented session idle
and daily reset from ever firing, since evaluateSessionFreshness()
always saw a fresh updatedAt.

The fix from #32379 patched recordSessionMetaFromInbound to use
mergeSessionEntryPreserveActivity, but missed updateLastRoute() in
the same inbound pipeline.

Changes:
- Remove explicit updatedAt from updateLastRoute basePatch
- Switch from mergeSessionEntry to mergeSessionEntryPreserveActivity
- Add regression test verifying updatedAt is preserved
- Update existing test assertion to match corrected behavior

Fixes #49515
This commit is contained in:
Eliot
2026-04-23 10:05:05 +02:00
committed by GitHub
parent 60341689fe
commit 94f703a845
2 changed files with 35 additions and 3 deletions

View File

@@ -243,7 +243,8 @@ describe("sessions", () => {
const store = loadSessionStore(storePath);
expect(store[mainSessionKey]?.sessionId).toBe("sess-1");
expect(store[mainSessionKey]?.updatedAt).toBeGreaterThanOrEqual(123);
// updateLastRoute must preserve existing updatedAt (activity timestamp)
expect(store[mainSessionKey]?.updatedAt).toBe(123);
expect(store[mainSessionKey]?.lastChannel).toBe("telegram");
expect(store[mainSessionKey]?.lastTo).toBe("12345");
expect(store[mainSessionKey]?.deliveryContext).toEqual({
@@ -355,6 +356,36 @@ describe("sessions", () => {
expect(store[sessionKey]?.origin?.chatType).toBe("group");
});
it("updateLastRoute does not bump updatedAt on existing sessions (#49515)", async () => {
const mainSessionKey = "agent:main:main";
const frozenUpdatedAt = 1000;
const { storePath } = await createSessionStoreFixture({
prefix: "updateLastRoute-preserve-activity",
entries: {
[mainSessionKey]: buildMainSessionEntry({
updatedAt: frozenUpdatedAt,
}),
},
});
await updateLastRoute({
storePath,
sessionKey: mainSessionKey,
deliveryContext: {
channel: "telegram",
to: "99999",
},
});
const store = loadSessionStore(storePath);
// Route updates must not refresh activity timestamps; idle/daily reset
// evaluation relies on updatedAt from actual session turns.
expect(store[mainSessionKey]?.updatedAt).toBe(frozenUpdatedAt);
// Routing fields should still be updated
expect(store[mainSessionKey]?.lastChannel).toBe("telegram");
expect(store[mainSessionKey]?.lastTo).toBe("99999");
});
it("updateSessionStoreEntry preserves existing fields when patching", async () => {
const sessionKey = "agent:main:main";
const { storePath } = await createSessionStoreFixture({

View File

@@ -772,14 +772,15 @@ export async function updateLastRoute(params: {
})
: null;
const basePatch: Partial<SessionEntry> = {
updatedAt: Math.max(existing?.updatedAt ?? 0, now),
deliveryContext: normalized.deliveryContext,
lastChannel: normalized.lastChannel,
lastTo: normalized.lastTo,
lastAccountId: normalized.lastAccountId,
lastThreadId: normalized.lastThreadId,
};
const next = mergeSessionEntry(
// Route updates must not refresh activity timestamps; idle/daily reset
// evaluation relies on updatedAt from actual session turns (#49515).
const next = mergeSessionEntryPreserveActivity(
existing,
metaPatch ? { ...basePatch, ...metaPatch } : basePatch,
);