Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions apps/desktop/src/main/lib/tmux-manager.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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(
Expand All @@ -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();
27 changes: 27 additions & 0 deletions apps/desktop/src/main/lib/workspace/tab-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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
Expand All @@ -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);

Expand Down
Loading