refactor(sessions): add explicit merge activity policies

This commit is contained in:
Peter Steinberger
2026-03-03 02:19:15 +00:00
parent d380ed710d
commit 4d52dfe85b
2 changed files with 41 additions and 11 deletions

View File

@@ -1,4 +1,3 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { acquireSessionWriteLock } from "../../agents/session-write-lock.js";
@@ -39,6 +38,7 @@ import {
import { applySessionStoreMigrations } from "./store-migrations.js";
import {
mergeSessionEntry,
mergeSessionEntryPreserveActivity,
normalizeSessionRuntimeModelFields,
type SessionEntry,
} from "./types.js";
@@ -738,14 +738,9 @@ export async function recordSessionMetaFromInbound(params: {
return null;
}
const next = existing
? normalizeSessionRuntimeModelFields({
...existing,
...patch,
// Inbound metadata updates must not refresh activity timestamps;
// idle reset evaluation relies on updatedAt from actual session turns.
sessionId: existing.sessionId ?? crypto.randomUUID(),
updatedAt: existing.updatedAt ?? Date.now(),
})
? // Inbound metadata updates must not refresh activity timestamps;
// idle reset evaluation relies on updatedAt from actual session turns.
mergeSessionEntryPreserveActivity(existing, patch)
: mergeSessionEntry(existing, patch);
store[resolved.normalizedKey] = next;
for (const legacyKey of resolved.legacyKeys) {

View File

@@ -225,12 +225,31 @@ export function setSessionRuntimeModel(
return true;
}
export function mergeSessionEntry(
export type SessionEntryMergePolicy = "touch-activity" | "preserve-activity";
type MergeSessionEntryOptions = {
policy?: SessionEntryMergePolicy;
now?: number;
};
function resolveMergedUpdatedAt(
existing: SessionEntry | undefined,
patch: Partial<SessionEntry>,
options?: MergeSessionEntryOptions,
): number {
if (options?.policy === "preserve-activity" && existing) {
return existing.updatedAt ?? patch.updatedAt ?? options.now ?? Date.now();
}
return Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, options?.now ?? Date.now());
}
export function mergeSessionEntryWithPolicy(
existing: SessionEntry | undefined,
patch: Partial<SessionEntry>,
options?: MergeSessionEntryOptions,
): SessionEntry {
const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID();
const updatedAt = Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, Date.now());
const updatedAt = resolveMergedUpdatedAt(existing, patch, options);
if (!existing) {
return normalizeSessionRuntimeModelFields({ ...patch, sessionId, updatedAt });
}
@@ -248,6 +267,22 @@ export function mergeSessionEntry(
return normalizeSessionRuntimeModelFields(next);
}
export function mergeSessionEntry(
existing: SessionEntry | undefined,
patch: Partial<SessionEntry>,
): SessionEntry {
return mergeSessionEntryWithPolicy(existing, patch);
}
export function mergeSessionEntryPreserveActivity(
existing: SessionEntry | undefined,
patch: Partial<SessionEntry>,
): SessionEntry {
return mergeSessionEntryWithPolicy(existing, patch, {
policy: "preserve-activity",
});
}
export function resolveFreshSessionTotalTokens(
entry?: Pick<SessionEntry, "totalTokens" | "totalTokensFresh"> | null,
): number | undefined {