diff --git a/apps/desktop/src/main/lib/tmux-manager.ts b/apps/desktop/src/main/lib/tmux-manager.ts index d40b2736339..74c4bd0d95b 100644 --- a/apps/desktop/src/main/lib/tmux-manager.ts +++ b/apps/desktop/src/main/lib/tmux-manager.ts @@ -1,7 +1,6 @@ import { spawnSync } from "node:child_process"; import { randomUUID } from "node:crypto"; -import { existsSync, readFileSync, writeFileSync } from "node:fs"; -import { mkdir } from "node:fs/promises"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import { dirname, join } from "node:path"; import type { BrowserWindow } from "electron"; @@ -515,8 +514,15 @@ class TmuxManager { */ kill(sid: string): boolean { try { - // Kill PTY client if attached + // Get session before killing to save output history const session = this.sessions.get(sid); + + // Save terminal output to log file + if (session) { + this.saveTerminalLog(sid, session.outputHistory); + } + + // Kill PTY client if attached if (session?.pty) { session.pty.kill(); } @@ -641,7 +647,7 @@ class TmuxManager { // Ensure directory exists const dir = dirname(this.sessionRegistryPath); if (!existsSync(dir)) { - mkdir(dir, { recursive: true }); + mkdirSync(dir, { recursive: true }); } writeFileSync( @@ -653,6 +659,42 @@ class TmuxManager { console.error("[TmuxManager] Failed to save sessions to disk:", error); } } + + /** + * Save terminal output log to ~/.superset/logs/processes + */ + private saveTerminalLog(sid: string, outputHistory: string): void { + try { + // Skip if no output history + if (!outputHistory || outputHistory.length === 0) { + return; + } + + // Create log directory path + const logsDir = join(os.homedir(), ".superset", "logs", "processes"); + + // Ensure directory exists + if (!existsSync(logsDir)) { + mkdirSync(logsDir, { recursive: true }); + } + + // Generate filename with timestamp + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const logFilePath = join(logsDir, `${sid}_${timestamp}.txt`); + + // Write log file + writeFileSync(logFilePath, outputHistory, "utf-8"); + + console.log( + `[TmuxManager] Saved terminal log for ${sid} to ${logFilePath}`, + ); + } catch (error) { + console.error( + `[TmuxManager] Failed to save terminal log for ${sid}:`, + error, + ); + } + } } export default TmuxManager.getInstance(); diff --git a/apps/desktop/src/main/lib/workspace/tab-operations.ts b/apps/desktop/src/main/lib/workspace/tab-operations.ts index 6e74dee9f0a..17e990b13ea 100644 --- a/apps/desktop/src/main/lib/workspace/tab-operations.ts +++ b/apps/desktop/src/main/lib/workspace/tab-operations.ts @@ -172,6 +172,24 @@ export async function updatePreviewTabUrl( } } +/** + * Helper function to recursively kill all terminal processes in a tab tree + */ +async function killTerminalProcesses(tab: Tab): Promise { + const tmuxManager = await import("../tmux-manager").then((m) => m.default); + + if (tab.type === "terminal") { + // Kill the terminal process for this tab + // This will trigger log saving in tmuxManager.kill() + tmuxManager.kill(tab.id); + } else if (tab.type === "group" && tab.tabs) { + // Recursively kill terminals in child tabs + for (const childTab of tab.tabs) { + await killTerminalProcesses(childTab); + } + } +} + /** * Delete a tab from a worktree * Also removes the tab from the parent group's mosaic tree if applicable @@ -191,6 +209,15 @@ export async function deleteTab( return { success: false, error: "Worktree not found" }; } + // Find the tab before deleting it so we can clean up terminal processes + const tab = findTab(worktree.tabs, input.tabId); + if (!tab) { + return { success: false, error: "Tab not found" }; + } + + // Kill terminal processes before removing the tab + await killTerminalProcesses(tab); + // Find the parent tab (if this tab is inside a group) const parentTab = findParentTab(worktree.tabs, input.tabId);