fix(sessions): rewrite migrated transcript paths

This commit is contained in:
Vincent Koc
2026-06-11 06:41:30 +09:00
parent e15b646f18
commit 0923ee251e
3 changed files with 53 additions and 16 deletions

View File

@@ -479,15 +479,9 @@ function assertSessionMetadataMigrated(stateDir) {
const main = store["agent:main:main"];
const direct = store["agent:main:+15551234567"];
const group = store["agent:main:slack:channel:cupgrade"];
assert(main?.sessionId === LEGACY_SESSION_MAIN_ID, "main legacy session row missing from SQLite");
assert(
direct?.sessionId === LEGACY_SESSION_DIRECT_ID,
"direct legacy session row missing from SQLite",
);
assert(
group?.sessionId === LEGACY_SESSION_GROUP_ID,
"channel legacy session row missing from SQLite",
);
assert(main?.sessionId === LEGACY_SESSION_MAIN_ID, "main legacy session row missing");
assert(direct?.sessionId === LEGACY_SESSION_DIRECT_ID, "direct legacy session row missing");
assert(group?.sessionId === LEGACY_SESSION_GROUP_ID, "channel legacy session row missing");
assert(
main?.sessionFile === path.join(agentSessionsDir, `${LEGACY_SESSION_MAIN_ID}.jsonl`),
"main legacy session row still points at the old sessions directory",
@@ -506,7 +500,7 @@ function assertSessionMetadataMigrated(stateDir) {
);
assert(
main.skillsSnapshot?.resolvedSkills === undefined,
"heavy resolvedSkills cache was persisted into SQLite session metadata",
"heavy resolvedSkills cache was persisted into migrated session metadata",
);
}

View File

@@ -629,7 +629,7 @@ describe("doctor legacy state migrations", () => {
result: Awaited<ReturnType<typeof runLegacyStateMigrations>>;
targetDir: string;
legacySessionsDir: string;
store: Record<string, { sessionId: string }>;
store: Record<string, { sessionId: string; sessionFile?: string }>;
};
beforeAll(async () => {
@@ -638,8 +638,12 @@ describe("doctor legacy state migrations", () => {
const legacySessionsDir = writeLegacySessionsFixture({
root,
sessions: {
"+1555": { sessionId: "a", updatedAt: 10 },
"+1666": { sessionId: "b", updatedAt: 20 },
"+1555": {
sessionId: "a",
sessionFile: path.join(root, "sessions", "a.jsonl"),
updatedAt: 10,
},
"+1666": { sessionId: "b", sessionFile: "b.jsonl", updatedAt: 20 },
"slack:channel:C123": { sessionId: "c", updatedAt: 30 },
"group:abc": { sessionId: "d", updatedAt: 40 },
"subagent:xyz": { sessionId: "e", updatedAt: 50 },
@@ -661,7 +665,7 @@ describe("doctor legacy state migrations", () => {
const targetDir = path.join(root, "agents", "main", "sessions");
const store = JSON.parse(
fs.readFileSync(path.join(targetDir, "sessions.json"), "utf-8"),
) as Record<string, { sessionId: string }>;
) as Record<string, { sessionId: string; sessionFile?: string }>;
migratedLegacySessionsCase = { result, targetDir, legacySessionsDir, store };
});
@@ -676,6 +680,8 @@ describe("doctor legacy state migrations", () => {
expect(store["agent:main:main"]?.sessionId).toBe("b");
expect(store["agent:main:+1555"]?.sessionId).toBe("a");
expect(store["agent:main:+1666"]?.sessionId).toBe("b");
expect(store["agent:main:+1555"]?.sessionFile).toBe(path.join(targetDir, "a.jsonl"));
expect(store["agent:main:+1666"]?.sessionFile).toBe(path.join(targetDir, "b.jsonl"));
expect(store["+1555"]).toBeUndefined();
expect(store["+1666"]).toBeUndefined();
expect(store["agent:main:slack:channel:c123"]?.sessionId).toBe("c");

View File

@@ -2878,6 +2878,7 @@ async function migrateLegacySessions(
return { changes, warnings };
}
const movedSessionFiles = new Map<string, string>();
const entries = safeReadDir(detected.sessions.legacyDir);
for (const entry of entries) {
if (!entry.isFile()) {
@@ -2887,18 +2888,54 @@ async function migrateLegacySessions(
continue;
}
const from = path.join(detected.sessions.legacyDir, entry.name);
const to = path.join(detected.sessions.targetDir, entry.name);
let to = path.join(detected.sessions.targetDir, entry.name);
if (fileExists(to)) {
continue;
const parsed = path.parse(entry.name);
to = path.join(detected.sessions.targetDir, `${parsed.name}.legacy-${now()}${parsed.ext}`);
}
try {
fs.renameSync(from, to);
movedSessionFiles.set(path.resolve(from), to);
changes.push(`Moved ${entry.name} → agents/${detected.targetAgentId}/sessions`);
} catch (err) {
warnings.push(`Failed moving ${from}: ${String(err)}`);
}
}
if (movedSessionFiles.size > 0) {
let rewroteSessionFiles = false;
for (const entry of Object.values(merged)) {
const rawSessionFile = entry.sessionFile;
const legacySessionFile =
typeof rawSessionFile === "string"
? path.resolve(detected.sessions.legacyDir, rawSessionFile)
: typeof entry.sessionId === "string"
? path.join(detected.sessions.legacyDir, `${entry.sessionId}.jsonl`)
: undefined;
const movedSessionFile = legacySessionFile
? movedSessionFiles.get(path.resolve(legacySessionFile))
: undefined;
if (!movedSessionFile) {
continue;
}
entry.sessionFile = movedSessionFile;
rewroteSessionFiles = true;
}
if (rewroteSessionFiles) {
const normalized: Record<string, SessionEntry> = {};
for (const [key, entry] of Object.entries(merged)) {
const normalizedEntry = normalizeSessionEntry(entry);
if (normalizedEntry) {
normalized[key] = normalizedEntry;
}
}
await saveSessionStore(detected.sessions.targetStorePath, normalized, {
skipMaintenance: true,
});
changes.push("Rewrote migrated session transcript paths");
}
}
if (legacyParsed.ok && targetReadable) {
try {
if (fileExists(detected.sessions.legacyStorePath)) {