From d27b08bae68ac514f5f3a497bc4aa91c0cbfc101 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Feb 2026 21:14:31 -0800 Subject: [PATCH] Migrate legacy terminal history to terminal-scoped log path - On open, read legacy transcript files and write them to the new terminal-scoped history path - Remove legacy files after migration and warn if cleanup fails - Update tests to assert migration, content preservation, and legacy file removal --- apps/server/src/terminalManager.test.ts | 7 +++++-- apps/server/src/terminalManager.ts | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/server/src/terminalManager.test.ts b/apps/server/src/terminalManager.test.ts index b8257e07c649ab9d65b64e60c6313562a83fa83e..9b77d582bc21db4dfd4dee5c600fec64e3904a38 100644 --- a/apps/server/src/terminalManager.test.ts +++ b/apps/server/src/terminalManager.test.ts @@ -334,15 +334,18 @@ describe("TerminalManager", () => { manager.dispose(); }); - it("loads existing legacy transcript filenames and keeps new naming for writes", async () => { + it("migrates legacy transcript filenames to terminal-scoped history path on open", async () => { const { manager, logsDir } = makeManager(); const legacyPath = path.join(logsDir, "thread-1.log"); + const nextPath = historyLogPath(logsDir); fs.writeFileSync(legacyPath, "legacy-line\n", "utf8"); const snapshot = await manager.open(openInput()); expect(snapshot.history).toBe("legacy-line\n"); - expect(fs.existsSync(legacyPath)).toBe(true); + expect(fs.existsSync(nextPath)).toBe(true); + expect(fs.readFileSync(nextPath, "utf8")).toBe("legacy-line\n"); + expect(fs.existsSync(legacyPath)).toBe(false); manager.dispose(); }); diff --git a/apps/server/src/terminalManager.ts b/apps/server/src/terminalManager.ts index 8c8d18cdf55815461ff82e342bfb4bb13b7b07fa..8fe6d662e7db7c9e9486936d26525b065aa7bcfd 100644 --- a/apps/server/src/terminalManager.ts +++ b/apps/server/src/terminalManager.ts @@ -582,12 +582,12 @@ export class TerminalManager extends EventEmitter { } private async readHistory(threadId: string, terminalId: string): Promise { - const historyPath = this.historyPath(threadId, terminalId); + const nextPath = this.historyPath(threadId, terminalId); try { - const raw = await fs.promises.readFile(historyPath, "utf8"); + const raw = await fs.promises.readFile(nextPath, "utf8"); const capped = capHistory(raw, this.historyLineLimit); if (capped !== raw) { - await fs.promises.writeFile(historyPath, capped, "utf8"); + await fs.promises.writeFile(nextPath, capped, "utf8"); } return capped; } catch (error) { @@ -600,12 +600,22 @@ export class TerminalManager extends EventEmitter { return ""; } + const legacyPath = this.legacyHistoryPath(threadId); try { - const raw = await fs.promises.readFile(this.legacyHistoryPath(threadId), "utf8"); + const raw = await fs.promises.readFile(legacyPath, "utf8"); const capped = capHistory(raw, this.historyLineLimit); - if (capped !== raw) { - await fs.promises.writeFile(this.legacyHistoryPath(threadId), capped, "utf8"); + + // Migrate legacy transcript filename to the terminal-scoped path. + await fs.promises.writeFile(nextPath, capped, "utf8"); + try { + await fs.promises.rm(legacyPath, { force: true }); + } catch (cleanupError) { + this.logger.warn("failed to remove legacy terminal history", { + threadId, + error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError), + }); } + return capped; } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { -- 2.54.0