write tui debug/crash logs into the configured pi agent dir (#6958)
fixes #6652
This commit is contained in:
@@ -22,7 +22,7 @@ export async function selectConfig(options: ConfigSelectorOptions): Promise<void
|
|||||||
initTheme(options.settingsManager.getTheme(), true);
|
initTheme(options.settingsManager.getTheme(), true);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const ui = new TUI(new ProcessTerminal());
|
const ui = new TUI(new ProcessTerminal(), undefined, options.agentDir);
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
|
|
||||||
const selector = new ConfigSelectorComponent(
|
const selector = new ConfigSelectorComponent(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export async function createStartupTui(settingsManager: SettingsManager): Promis
|
|||||||
const terminalTheme = detectTerminalBackgroundFromEnv().theme;
|
const terminalTheme = detectTerminalBackgroundFromEnv().theme;
|
||||||
initTheme(resolveThemeSetting(settingsManager.getThemeSetting(), terminalTheme) ?? terminalTheme);
|
initTheme(resolveThemeSetting(settingsManager.getThemeSetting(), terminalTheme) ?? terminalTheme);
|
||||||
setKeybindings(KeybindingsManager.create());
|
setKeybindings(KeybindingsManager.create());
|
||||||
const ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());
|
const ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor(), getAgentDir());
|
||||||
ui.setClearOnShrink(settingsManager.getClearOnShrink());
|
ui.setClearOnShrink(settingsManager.getClearOnShrink());
|
||||||
return ui;
|
return ui;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export class InteractiveMode {
|
|||||||
await this.rebindCurrentSession({ renderBeforeBind: true });
|
await this.rebindCurrentSession({ renderBeforeBind: true });
|
||||||
});
|
});
|
||||||
this.version = VERSION;
|
this.version = VERSION;
|
||||||
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor(), getAgentDir());
|
||||||
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
||||||
this.headerContainer = new Container();
|
this.headerContainer = new Container();
|
||||||
this.loadedResourcesContainer = new Container();
|
this.loadedResourcesContainer = new Container();
|
||||||
|
|||||||
@@ -319,15 +319,17 @@ export class TUI extends Container {
|
|||||||
private pendingOsc11BackgroundQueries: PendingOsc11BackgroundQuery[] = [];
|
private pendingOsc11BackgroundQueries: PendingOsc11BackgroundQuery[] = [];
|
||||||
private terminalColorSchemeListeners = new Set<(scheme: TerminalColorScheme) => void>();
|
private terminalColorSchemeListeners = new Set<(scheme: TerminalColorScheme) => void>();
|
||||||
private terminalColorSchemeNotificationsEnabled = false;
|
private terminalColorSchemeNotificationsEnabled = false;
|
||||||
|
private readonly logDirectory: string;
|
||||||
|
|
||||||
// Overlay stack for modal components rendered on top of base content
|
// Overlay stack for modal components rendered on top of base content
|
||||||
private focusOrderCounter = 0;
|
private focusOrderCounter = 0;
|
||||||
private overlayStack: OverlayStackEntry[] = [];
|
private overlayStack: OverlayStackEntry[] = [];
|
||||||
private overlayFocusRestore: OverlayFocusRestoreState = { status: "inactive" };
|
private overlayFocusRestore: OverlayFocusRestoreState = { status: "inactive" };
|
||||||
|
|
||||||
constructor(terminal: Terminal, showHardwareCursor?: boolean) {
|
constructor(terminal: Terminal, showHardwareCursor?: boolean, logDirectory?: string) {
|
||||||
super();
|
super();
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
|
this.logDirectory = logDirectory ?? process.env.PI_CODING_AGENT_DIR ?? path.join(os.homedir(), ".pi", "agent");
|
||||||
if (showHardwareCursor !== undefined) {
|
if (showHardwareCursor !== undefined) {
|
||||||
this.showHardwareCursor = showHardwareCursor;
|
this.showHardwareCursor = showHardwareCursor;
|
||||||
}
|
}
|
||||||
@@ -1329,8 +1331,9 @@ export class TUI extends Container {
|
|||||||
const debugRedraw = process.env.PI_DEBUG_REDRAW === "1";
|
const debugRedraw = process.env.PI_DEBUG_REDRAW === "1";
|
||||||
const logRedraw = (reason: string): void => {
|
const logRedraw = (reason: string): void => {
|
||||||
if (!debugRedraw) return;
|
if (!debugRedraw) return;
|
||||||
const logPath = path.join(os.homedir(), ".pi", "agent", "pi-debug.log");
|
const logPath = path.join(this.logDirectory, "pi-debug.log");
|
||||||
const msg = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.previousLines.length}, new=${newLines.length}, height=${height})\n`;
|
const msg = `[${new Date().toISOString()}] fullRender: ${reason} (prev=${this.previousLines.length}, new=${newLines.length}, height=${height})\n`;
|
||||||
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
||||||
fs.appendFileSync(logPath, msg);
|
fs.appendFileSync(logPath, msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1521,7 +1524,7 @@ export class TUI extends Container {
|
|||||||
buffer += "\x1b[2K"; // Clear current line
|
buffer += "\x1b[2K"; // Clear current line
|
||||||
if (!isImage && visibleWidth(line) > width) {
|
if (!isImage && visibleWidth(line) > width) {
|
||||||
// Log all lines to crash file for debugging
|
// Log all lines to crash file for debugging
|
||||||
const crashLogPath = path.join(os.homedir(), ".pi", "agent", "pi-crash.log");
|
const crashLogPath = path.join(this.logDirectory, "pi-crash.log");
|
||||||
const crashData = [
|
const crashData = [
|
||||||
`Crash at ${new Date().toISOString()}`,
|
`Crash at ${new Date().toISOString()}`,
|
||||||
`Terminal width: ${width}`,
|
`Terminal width: ${width}`,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
|
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
||||||
import { Image } from "../src/components/image.ts";
|
import { Image } from "../src/components/image.ts";
|
||||||
@@ -71,6 +74,28 @@ function getCellItalic(terminal: VirtualTerminal, row: number, col: number): num
|
|||||||
return cell.isItalic();
|
return cell.isItalic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("TUI debug logging", () => {
|
||||||
|
it("writes redraw logs to the provided directory", async () => {
|
||||||
|
const logDir = mkdtempSync(join(tmpdir(), "pi-tui-log-"));
|
||||||
|
try {
|
||||||
|
await withEnv({ PI_DEBUG_REDRAW: "1" }, async () => {
|
||||||
|
const terminal = new VirtualTerminal(40, 10);
|
||||||
|
const tui = new TUI(terminal, undefined, logDir);
|
||||||
|
const component = new TestComponent();
|
||||||
|
tui.addChild(component);
|
||||||
|
component.lines = ["test"];
|
||||||
|
tui.start();
|
||||||
|
await terminal.waitForRender();
|
||||||
|
|
||||||
|
assert.match(readFileSync(join(logDir, "pi-debug.log"), "utf-8"), /fullRender: first render/);
|
||||||
|
tui.stop();
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
rmSync(logDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("TUI Kitty image cleanup", () => {
|
describe("TUI Kitty image cleanup", () => {
|
||||||
it("clears reserved Kitty image rows before drawing appended image placements", async () => {
|
it("clears reserved Kitty image rows before drawing appended image placements", async () => {
|
||||||
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
setCapabilities({ images: "kitty", trueColor: true, hyperlinks: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user