diff --git a/src/output.test.ts b/src/output.test.ts index 64b1f95..94c46c6 100644 --- a/src/output.test.ts +++ b/src/output.test.ts @@ -25,4 +25,33 @@ describe("printResponse", () => { totalCount: 1, }); }); + + it("removes terminal control bytes from agent-mode output", () => { + let out = ""; + printResponse( + { message: "\u001b]0;PWNED\u0007\u001b[31mred\u001b[0m" }, + { pretty: false, agentMode: true }, + (s) => { + out += s; + }, + ); + + assert.equal(/[\u0000-\u001f\u007f-\u009f]/u.test(out.slice(0, -1)), false); + assert.match(out, /message: ".*PWNED.*red/); + assert.equal(out.endsWith("\n"), true); + }); + + it("keeps TOON structural newlines and escaped string controls", () => { + let out = ""; + printResponse( + { message: "line1\nline2\r\n\tindented" }, + { pretty: false, agentMode: true }, + (s) => { + out += s; + }, + ); + + assert.equal(out.split("\n").length, 2); + assert.match(out, /line1\\nline2\\r\\n\\tindented/); + }); }); diff --git a/src/output.ts b/src/output.ts index 9f77f2b..54f57c4 100644 --- a/src/output.ts +++ b/src/output.ts @@ -39,6 +39,10 @@ function unwrapResultsData(value: unknown): unknown { return normalized; } +function sanitizeTerminalControls(value: string): string { + return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, ""); +} + export function printResponse( value: unknown, options: OutputOptions, @@ -52,7 +56,7 @@ export function printResponse( const normalized = unwrapResultsData(value); if (agentModeEnabled(options.agentMode)) { - write(`${encode(normalized)}\n`); + write(`${sanitizeTerminalControls(encode(normalized))}\n`); return; }