fix(clownfish): address review for ghcrawl-156648-autonomous-smoke (1)

This commit is contained in:
vincentkoc
2026-04-27 22:40:11 +00:00
parent 36c982e060
commit 3e528fc2ca
3 changed files with 51 additions and 10 deletions

View File

@@ -107,6 +107,40 @@ describe("Session Store Cache", () => {
expect(loaded2["session:1"].skillsSnapshot?.skills?.[0]?.name).toBe("alpha");
});
it("does not cache pre-migration or pre-normalization disk JSON", () => {
fs.writeFileSync(
storePath,
JSON.stringify({
"session:1": {
sessionId: "id-1",
updatedAt: Date.now(),
provider: "telegram",
room: "room-1",
modelProvider: " openai ",
model: " gpt-5.4 ",
},
}),
);
const loaded1 = loadSessionStore(storePath);
const entry1 = loaded1["session:1"] as SessionEntry & { provider?: string; room?: string };
expect(entry1.channel).toBe("telegram");
expect(entry1.groupChannel).toBe("room-1");
expect(entry1.provider).toBeUndefined();
expect(entry1.room).toBeUndefined();
expect(entry1.modelProvider).toBe("openai");
expect(entry1.model).toBe("gpt-5.4");
const loaded2 = loadSessionStore(storePath);
const entry2 = loaded2["session:1"] as SessionEntry & { provider?: string; room?: string };
expect(entry2.channel).toBe("telegram");
expect(entry2.groupChannel).toBe("room-1");
expect(entry2.provider).toBeUndefined();
expect(entry2.room).toBeUndefined();
expect(entry2.modelProvider).toBe("openai");
expect(entry2.model).toBe("gpt-5.4");
});
it("isolates cached session stores without structuredClone", async () => {
const structuredCloneSpy = vi.spyOn(globalThis, "structuredClone");
const testStore = createSingleSessionStore(

View File

@@ -64,7 +64,8 @@ function normalizeSessionEntryDelivery(entry: SessionEntry): SessionEntry {
};
}
export function normalizeSessionStore(store: Record<string, SessionEntry>): void {
export function normalizeSessionStore(store: Record<string, SessionEntry>): boolean {
let changed = false;
for (const [key, entry] of Object.entries(store)) {
if (!entry) {
continue;
@@ -72,8 +73,10 @@ export function normalizeSessionStore(store: Record<string, SessionEntry>): void
const normalized = normalizeSessionEntryDelivery(normalizeSessionRuntimeModelFields(entry));
if (normalized !== entry) {
store[key] = normalized;
changed = true;
}
}
return changed;
}
export function loadSessionStore(
@@ -123,14 +126,11 @@ export function loadSessionStore(
}
}
if (serializedFromDisk !== undefined) {
setSerializedSessionStore(storePath, serializedFromDisk);
} else {
setSerializedSessionStore(storePath, undefined);
const migrated = applySessionStoreMigrations(store);
const normalized = normalizeSessionStore(store);
if (migrated || normalized) {
serializedFromDisk = undefined;
}
applySessionStoreMigrations(store);
normalizeSessionStore(store);
const maintenance = opts.maintenanceConfig ?? resolveMaintenanceConfig();
const beforeCount = Object.keys(store).length;
if (maintenance.mode === "enforce" && beforeCount > maintenance.maxEntries) {
@@ -145,7 +145,6 @@ export function loadSessionStore(
const afterCount = Object.keys(store).length;
if (pruned > 0 || capped > 0) {
serializedFromDisk = undefined;
setSerializedSessionStore(storePath, undefined);
log.info("applied load-time maintenance to oversized session store", {
storePath,
before: beforeCount,
@@ -157,6 +156,8 @@ export function loadSessionStore(
}
}
setSerializedSessionStore(storePath, serializedFromDisk);
if (!opts.skipCache && isSessionStoreCacheEnabled()) {
writeSessionStoreCache({
storePath,

View File

@@ -1,6 +1,7 @@
import type { SessionEntry } from "./types.js";
export function applySessionStoreMigrations(store: Record<string, SessionEntry>): void {
export function applySessionStoreMigrations(store: Record<string, SessionEntry>): boolean {
let changed = false;
// Best-effort migration: message provider → channel naming.
for (const entry of Object.values(store)) {
if (!entry || typeof entry !== "object") {
@@ -10,18 +11,23 @@ export function applySessionStoreMigrations(store: Record<string, SessionEntry>)
if (typeof rec.channel !== "string" && typeof rec.provider === "string") {
rec.channel = rec.provider;
delete rec.provider;
changed = true;
}
if (typeof rec.lastChannel !== "string" && typeof rec.lastProvider === "string") {
rec.lastChannel = rec.lastProvider;
delete rec.lastProvider;
changed = true;
}
// Best-effort migration: legacy `room` field → `groupChannel` (keep value, prune old key).
if (typeof rec.groupChannel !== "string" && typeof rec.room === "string") {
rec.groupChannel = rec.room;
delete rec.room;
changed = true;
} else if ("room" in rec) {
delete rec.room;
changed = true;
}
}
return changed;
}