authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-12 21:14:31-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-12 22:20:01-08:00
logd27b08bae68ac514f5f3a497bc4aa91c0cbfc101
treeff6073170063f3eb81ec9b985d25614b8072ba99
parent485747b892fc9825a7b87719d70211fd8d794c5b

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

2 files changed, 21 insertions(+), 8 deletions(-)

apps/server/src/terminalManager.test.ts+5-2
...@@ -334,15 +334,18 @@ describe("TerminalManager", () => {...@@ -334,15 +334,18 @@ describe("TerminalManager", () => {
334 manager.dispose();334 manager.dispose();
335 });335 });
336336
337 it("loads existing legacy transcript filenames and keeps new naming for writes", async () => {337 it("migrates legacy transcript filenames to terminal-scoped history path on open", async () => {
338 const { manager, logsDir } = makeManager();338 const { manager, logsDir } = makeManager();
339 const legacyPath = path.join(logsDir, "thread-1.log");339 const legacyPath = path.join(logsDir, "thread-1.log");
340 const nextPath = historyLogPath(logsDir);
340 fs.writeFileSync(legacyPath, "legacy-line\n", "utf8");341 fs.writeFileSync(legacyPath, "legacy-line\n", "utf8");
341342
342 const snapshot = await manager.open(openInput());343 const snapshot = await manager.open(openInput());
343344
344 expect(snapshot.history).toBe("legacy-line\n");345 expect(snapshot.history).toBe("legacy-line\n");
345 expect(fs.existsSync(legacyPath)).toBe(true);346 expect(fs.existsSync(nextPath)).toBe(true);
347 expect(fs.readFileSync(nextPath, "utf8")).toBe("legacy-line\n");
348 expect(fs.existsSync(legacyPath)).toBe(false);
346349
347 manager.dispose();350 manager.dispose();
348 });351 });
apps/server/src/terminalManager.ts+16-6
...@@ -582,12 +582,12 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> {...@@ -582,12 +582,12 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> {
582 }582 }
583583
584 private async readHistory(threadId: string, terminalId: string): Promise<string> {584 private async readHistory(threadId: string, terminalId: string): Promise<string> {
585 const historyPath = this.historyPath(threadId, terminalId);585 const nextPath = this.historyPath(threadId, terminalId);
586 try {586 try {
587 const raw = await fs.promises.readFile(historyPath, "utf8");587 const raw = await fs.promises.readFile(nextPath, "utf8");
588 const capped = capHistory(raw, this.historyLineLimit);588 const capped = capHistory(raw, this.historyLineLimit);
589 if (capped !== raw) {589 if (capped !== raw) {
590 await fs.promises.writeFile(historyPath, capped, "utf8");590 await fs.promises.writeFile(nextPath, capped, "utf8");
591 }591 }
592 return capped;592 return capped;
593 } catch (error) {593 } catch (error) {
...@@ -600,12 +600,22 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> {...@@ -600,12 +600,22 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> {
600 return "";600 return "";
601 }601 }
602602
603 const legacyPath = this.legacyHistoryPath(threadId);
603 try {604 try {
604 const raw = await fs.promises.readFile(this.legacyHistoryPath(threadId), "utf8");605 const raw = await fs.promises.readFile(legacyPath, "utf8");
605 const capped = capHistory(raw, this.historyLineLimit);606 const capped = capHistory(raw, this.historyLineLimit);
606 if (capped !== raw) {607
607 await fs.promises.writeFile(this.legacyHistoryPath(threadId), capped, "utf8");608 // Migrate legacy transcript filename to the terminal-scoped path.
609 await fs.promises.writeFile(nextPath, capped, "utf8");
610 try {
611 await fs.promises.rm(legacyPath, { force: true });
612 } catch (cleanupError) {
613 this.logger.warn("failed to remove legacy terminal history", {
614 threadId,
615 error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
616 });
608 }617 }
618
609 return capped;619 return capped;
610 } catch (error) {620 } catch (error) {
611 if ((error as NodeJS.ErrnoException).code === "ENOENT") {621 if ((error as NodeJS.ErrnoException).code === "ENOENT") {