fix: prefer freshest duplicate rows in session loads

This commit is contained in:
Tak Hoffman
2026-03-24 22:36:50 -05:00
parent 40f820ff7f
commit f48571bec6
2 changed files with 61 additions and 8 deletions

View File

@@ -337,6 +337,43 @@ describe("gateway session utils", () => {
}
});
test("loadSessionEntry prefers the freshest duplicate row for a logical key", async () => {
clearConfigCache();
try {
await withStateDirEnv("session-utils-load-entry-freshest-", async ({ stateDir }) => {
const sessionsDir = path.join(stateDir, "agents", "main", "sessions");
fs.mkdirSync(sessionsDir, { recursive: true });
const storePath = path.join(sessionsDir, "sessions.json");
fs.writeFileSync(
storePath,
JSON.stringify(
{
"agent:main:main": { sessionId: "sess-stale", updatedAt: 1 },
"agent:main:MAIN": { sessionId: "sess-fresh", updatedAt: 2 },
},
null,
2,
),
"utf8",
);
await writeConfigFile({
session: {
mainKey: "main",
store: path.join(stateDir, "agents", "{agentId}", "sessions", "sessions.json"),
},
agents: { list: [{ id: "main", default: true }] },
});
clearConfigCache();
const loaded = loadSessionEntry("agent:main:main");
expect(loaded.entry?.sessionId).toBe("sess-fresh");
});
} finally {
clearConfigCache();
}
});
test("pruneLegacyStoreKeys removes alias and case-variant ghost keys", () => {
const store: Record<string, unknown> = {
"agent:ops:work": { sessionId: "canonical", updatedAt: 3 },

View File

@@ -357,30 +357,46 @@ export function loadSessionEntry(sessionKey: string) {
const cfg = loadConfig();
const canonicalKey = resolveSessionStoreKey({ cfg, sessionKey });
const agentId = resolveSessionStoreAgentId(cfg, canonicalKey);
const { storePath, store, match } = resolveGatewaySessionStoreLookup({
const { storePath, store } = resolveGatewaySessionStoreLookup({
cfg,
key: sessionKey.trim(),
canonicalKey,
agentId,
});
const legacyKey = match?.key !== canonicalKey ? match?.key : undefined;
return { cfg, storePath, store, entry: match?.entry, canonicalKey, legacyKey };
const target = resolveGatewaySessionStoreTarget({
cfg,
key: sessionKey.trim(),
store,
});
const freshestMatch = resolveFreshestSessionStoreMatchFromStoreKeys(store, target.storeKeys);
const legacyKey = freshestMatch?.key !== canonicalKey ? freshestMatch?.key : undefined;
return { cfg, storePath, store, entry: freshestMatch?.entry, canonicalKey, legacyKey };
}
export function resolveFreshestSessionEntryFromStoreKeys(
export function resolveFreshestSessionStoreMatchFromStoreKeys(
store: Record<string, SessionEntry>,
storeKeys: string[],
): SessionEntry | undefined {
): { key: string; entry: SessionEntry } | undefined {
const matches = storeKeys
.map((key) => store[key])
.filter((entry): entry is SessionEntry => Boolean(entry));
.map((key) => {
const entry = store[key];
return entry ? { key, entry } : undefined;
})
.filter((match): match is { key: string; entry: SessionEntry } => match !== undefined);
if (matches.length === 0) {
return undefined;
}
if (matches.length === 1) {
return matches[0];
}
return [...matches].toSorted((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))[0];
return [...matches].toSorted((a, b) => (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0))[0];
}
export function resolveFreshestSessionEntryFromStoreKeys(
store: Record<string, SessionEntry>,
storeKeys: string[],
): SessionEntry | undefined {
return resolveFreshestSessionStoreMatchFromStoreKeys(store, storeKeys)?.entry;
}
/**