diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 47d4e748..df5d9be2 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -1156,6 +1156,19 @@ import { getPackageDir, getThemeDir, getPackageJsonPath, getReadmePath, getChang **Never use `__dirname` directly** for resolving package assets. The `paths.ts` module handles the differences between execution modes automatically. +### Debug Command + +The `/debug` command is a hidden development feature (not shown in autocomplete) that writes all currently rendered lines with their visible widths and ANSI escape sequences to `~/.pi/agent/pi-debug.log`. This is useful for debugging TUI rendering issues, especially when lines don't extend to the terminal edge or contain unexpected invisible characters. + +``` +/debug +``` + +The debug log includes: +- Terminal width at time of capture +- Total number of rendered lines +- Each line with its index, visible width, and JSON-escaped content showing all ANSI codes + ## License MIT diff --git a/packages/coding-agent/src/tui/tool-execution.ts b/packages/coding-agent/src/tui/tool-execution.ts index 390f4f3f..cca9ac07 100644 --- a/packages/coding-agent/src/tui/tool-execution.ts +++ b/packages/coding-agent/src/tui/tool-execution.ts @@ -83,8 +83,9 @@ export class ToolExecutionComponent extends Container { const textBlocks = this.result.content?.filter((c: any) => c.type === "text") || []; const imageBlocks = this.result.content?.filter((c: any) => c.type === "image") || []; - // Strip ANSI codes from raw output (bash may emit colors/formatting) - let output = textBlocks.map((c: any) => stripAnsi(c.text || "")).join("\n"); + // Strip ANSI codes and carriage returns from raw output + // (bash may emit colors/formatting, and Windows may include \r) + let output = textBlocks.map((c: any) => stripAnsi(c.text || "").replace(/\r/g, "")).join("\n"); // Add indicator for images if (imageBlocks.length > 0) { diff --git a/packages/coding-agent/src/tui/tui-renderer.ts b/packages/coding-agent/src/tui/tui-renderer.ts index 98475b94..2f3d0ee3 100644 --- a/packages/coding-agent/src/tui/tui-renderer.ts +++ b/packages/coding-agent/src/tui/tui-renderer.ts @@ -1,3 +1,6 @@ +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; import type { Agent, AgentEvent, AgentState, ThinkingLevel } from "@mariozechner/pi-agent-core"; import type { AssistantMessage, Message, Model } from "@mariozechner/pi-ai"; import type { SlashCommand } from "@mariozechner/pi-tui"; @@ -12,8 +15,8 @@ import { Text, TruncatedText, TUI, + visibleWidth, } from "@mariozechner/pi-tui"; - import { exec } from "child_process"; import { getChangelogPath, parseChangelog } from "../changelog.js"; import { exportSessionToHtml } from "../export-html.js"; @@ -415,6 +418,13 @@ export class TuiRenderer { return; } + // Check for /debug command + if (text === "/debug") { + this.handleDebugCommand(); + this.editor.setText(""); + return; + } + // Check for file-based slash commands text = expandSlashCommand(text, this.fileCommands); @@ -1530,6 +1540,42 @@ export class TuiRenderer { this.ui.requestRender(); } + private handleDebugCommand(): void { + // Force a render and capture all lines with their widths + const width = (this.ui as any).terminal.columns; + const allLines = this.ui.render(width); + + const debugLogPath = path.join(os.homedir(), ".pi", "agent", "pi-debug.log"); + const debugData = [ + `Debug output at ${new Date().toISOString()}`, + `Terminal width: ${width}`, + `Total lines: ${allLines.length}`, + "", + "=== All rendered lines with visible widths ===", + ...allLines.map((line, idx) => { + const vw = visibleWidth(line); + const escaped = JSON.stringify(line); + return `[${idx}] (w=${vw}) ${escaped}`; + }), + "", + ].join("\n"); + + fs.mkdirSync(path.dirname(debugLogPath), { recursive: true }); + fs.writeFileSync(debugLogPath, debugData); + + // Show confirmation + this.chatContainer.addChild(new Spacer(1)); + this.chatContainer.addChild( + new Text( + theme.fg("accent", "✓ Debug log written") + "\n" + theme.fg("muted", `~/.pi/agent/pi-debug.log`), + 1, + 1, + ), + ); + + this.ui.requestRender(); + } + private updatePendingMessagesDisplay(): void { this.pendingMessagesContainer.clear();