diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index a5a7c0e74..b6aee338d 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -56,6 +56,7 @@ export interface Args { noExtensions?: boolean; pluginDirs?: string[]; print?: boolean; + printThoughts?: boolean; export?: string; noSkills?: boolean; skills?: string[]; @@ -200,6 +201,8 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map = new Set([ "--hide-thinking", "--advisor", "--print", + "--print-thoughts", "--no-extensions", "--no-skills", "--no-rules", diff --git a/packages/coding-agent/src/commands/launch.ts b/packages/coding-agent/src/commands/launch.ts index 3b2aba03a..d0a623bf1 100644 --- a/packages/coding-agent/src/commands/launch.ts +++ b/packages/coding-agent/src/commands/launch.ts @@ -136,6 +136,9 @@ export default class Index extends Command { "no-title": Flags.boolean({ description: "Disable title auto-generation", }), + "print-thoughts": Flags.boolean({ + description: "Include thinking blocks in print mode text output", + }), "max-time": Flags.string({ description: "Stop the session after this many seconds", }), diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 6b0539ef4..521ed719c 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -1373,6 +1373,7 @@ export async function runRootCommand( messages: initialArgs.messages, initialMessage, initialImages, + printThoughts: initialArgs.printThoughts, }); if ($env.PI_TIMING) { logger.printTimings(); diff --git a/packages/coding-agent/src/modes/print-mode.ts b/packages/coding-agent/src/modes/print-mode.ts index af7f92d1b..711a25c33 100644 --- a/packages/coding-agent/src/modes/print-mode.ts +++ b/packages/coding-agent/src/modes/print-mode.ts @@ -24,6 +24,8 @@ export interface PrintModeOptions { initialMessage?: string; /** Images to attach to the initial message */ initialImages?: ImageContent[]; + /** If true, include thinking blocks in text output */ + printThoughts?: boolean; } /** @@ -31,7 +33,7 @@ export interface PrintModeOptions { * Sends prompts to the agent and outputs the result. */ export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise { - const { mode, messages = [], initialMessage, initialImages } = options; + const { mode, messages = [], initialMessage, initialImages, printThoughts } = options; // Emit session header for JSON mode if (mode === "json") { @@ -108,6 +110,8 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti for (const content of assistantMsg.content) { if (content.type === "text") { process.stdout.write(`${sanitizeText(content.text)}\n`); + } else if (printThoughts && content.type === "thinking" && content.thinking.trim().length > 0) { + process.stdout.write(`${sanitizeText(content.thinking)}\n`); } } } diff --git a/packages/coding-agent/test/cli-print-thoughts-flag.test.ts b/packages/coding-agent/test/cli-print-thoughts-flag.test.ts new file mode 100644 index 000000000..b529f92e7 --- /dev/null +++ b/packages/coding-agent/test/cli-print-thoughts-flag.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "bun:test"; +import { parseArgs } from "@oh-my-pi/pi-coding-agent/cli/args"; + +describe("parseArgs — --print-thoughts flag", () => { + it("parses --print-thoughts as a boolean flag", () => { + const result = parseArgs(["--print-thoughts"]); + expect(result.printThoughts).toBe(true); + }); + + it("does not consume the next argument", () => { + const result = parseArgs(["--print", "--print-thoughts", "explain"]); + expect(result.print).toBe(true); + expect(result.printThoughts).toBe(true); + expect(result.messages).toEqual(["explain"]); + }); +}); diff --git a/packages/coding-agent/test/silent-abort-print-mode.test.ts b/packages/coding-agent/test/silent-abort-print-mode.test.ts index 34f48801a..c9e8e9559 100644 --- a/packages/coding-agent/test/silent-abort-print-mode.test.ts +++ b/packages/coding-agent/test/silent-abort-print-mode.test.ts @@ -48,15 +48,19 @@ function createMockSession(messages: AssistantMessage[]): AgentSession { describe("Print-mode silent-abort regression", () => { let exitSpy: ReturnType; let stderrOutput: string[]; + let stdoutOutput: string[]; beforeEach(() => { stderrOutput = []; + stdoutOutput = []; vi.spyOn(process.stderr, "write").mockImplementation((chunk: unknown) => { stderrOutput.push(String(chunk)); return true; }); exitSpy = vi.spyOn(process, "exit").mockImplementation(() => undefined as never); vi.spyOn(process.stdout, "write").mockImplementation((...args: unknown[]) => { + const chunk = args[0]; + if (typeof chunk === "string") stdoutOutput.push(chunk); // Invoke callback if present (runPrintMode flushes stdout before returning) const last = args[args.length - 1]; if (typeof last === "function") last(); @@ -105,4 +109,22 @@ describe("Print-mode silent-abort regression", () => { // process.exit(1) SHOULD have been called expect(exitSpy).toHaveBeenCalledWith(1); }); + + it("prints thinking blocks only when printThoughts is enabled", async () => { + const { runPrintMode } = await import("@oh-my-pi/pi-coding-agent/modes/print-mode"); + + const message = makeAssistantMessage({ + content: [ + { type: "thinking", thinking: "inspect hidden branch" }, + { type: "text", text: "final answer" }, + ], + }); + + await runPrintMode(createMockSession([message]), { mode: "text" }); + expect(stdoutOutput.join("")).toBe("final answer\n"); + + stdoutOutput = []; + await runPrintMode(createMockSession([message]), { mode: "text", printThoughts: true }); + expect(stdoutOutput.join("")).toBe("inspect hidden branch\nfinal answer\n"); + }); });