diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 01d4bba4e..e20570b50 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -9,6 +9,9 @@ - Fixed `session.abort()` not clearing `promptInFlight` flag due to microtask ordering, which blocked subsequent prompts +### Fixed +- Sanitized debug log display to strip control codes, normalize tabs, and trim width + ## [12.4.0] - 2026-02-14 ### Changed @@ -69,6 +72,7 @@ - Improved error reporting in fetch tool to include HTTP status codes when URL fetching fails - Fixed fetch tool to preserve actual response metadata (finalUrl, contentType) instead of defaults when requests fail +||||||| parent of a70a34c8b (fix(coding-agent/debug): Sanitized debug log rendering) ## [12.1.0] - 2026-02-13 ### Added diff --git a/packages/coding-agent/src/debug/index.ts b/packages/coding-agent/src/debug/index.ts index 0ed83c130..7981efa86 100644 --- a/packages/coding-agent/src/debug/index.ts +++ b/packages/coding-agent/src/debug/index.ts @@ -11,6 +11,7 @@ import { DynamicBorder } from "../modes/components/dynamic-border"; import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme"; import type { InteractiveModeContext } from "../modes/types"; import { openPath } from "../utils/open"; +import { formatDebugLogLine } from "./log-formatting"; import { generateHeapSnapshotData, type ProfilerSession, startCpuProfile } from "./profiler"; import { clearArtifactCache, createReportBundle, getArtifactCacheStats, getRecentLogs } from "./report-bundle"; import { collectSystemInfo, formatSystemInfo } from "./system-info"; @@ -284,10 +285,12 @@ export class DebugSelectorComponent extends Container { this.ctx.chatContainer.addChild(new Spacer(1)); // Display logs with dim styling + const maxWidth = Math.max(1, this.ctx.ui.terminal.columns - 2); const lines = logs.split("\n").slice(-50); for (const line of lines) { - if (line.trim()) { - this.ctx.chatContainer.addChild(new Text(theme.fg("dim", line), 1, 0)); + const formatted = formatDebugLogLine(line, maxWidth); + if (formatted.trim()) { + this.ctx.chatContainer.addChild(new Text(theme.fg("dim", formatted), 1, 0)); } } diff --git a/packages/coding-agent/src/debug/log-formatting.ts b/packages/coding-agent/src/debug/log-formatting.ts new file mode 100644 index 000000000..0602a053d --- /dev/null +++ b/packages/coding-agent/src/debug/log-formatting.ts @@ -0,0 +1,9 @@ +import { sanitizeText } from "@oh-my-pi/pi-utils"; +import { replaceTabs, truncateToWidth } from "../tools/render-utils"; + +export function formatDebugLogLine(line: string, maxWidth: number): string { + const sanitized = sanitizeText(line); + const normalized = replaceTabs(sanitized); + const width = Math.max(1, maxWidth); + return truncateToWidth(normalized, width); +} diff --git a/packages/coding-agent/test/debug/log-formatting.test.ts b/packages/coding-agent/test/debug/log-formatting.test.ts new file mode 100644 index 000000000..d6dbb9cf9 --- /dev/null +++ b/packages/coding-agent/test/debug/log-formatting.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "bun:test"; +import { formatDebugLogLine } from "../../src/debug/log-formatting"; + +describe("formatDebugLogLine", () => { + it("strips ANSI codes and carriage returns", () => { + const input = "\u001b[31merror\r\u001b[0m"; + const result = formatDebugLogLine(input, 80); + expect(result).toBe("error"); + }); + + it("replaces tabs with spaces", () => { + const input = "col1\tcol2"; + const result = formatDebugLogLine(input, 80); + expect(result).toBe("col1 col2"); + }); + + it("removes unsafe control characters", () => { + const input = "ok\u0007bad"; + const result = formatDebugLogLine(input, 80); + expect(result).toBe("okbad"); + }); + + it("truncates long lines", () => { + const input = "0123456789ABCDEFGHIJ"; + const result = formatDebugLogLine(input, 10); + expect(Bun.stringWidth(result)).toBeLessThanOrEqual(10); + expect(result.startsWith("012345")).toBe(true); + }); +});