diff --git a/src/cli/run/event-handlers.ts b/src/cli/run/event-handlers.ts index 5f3e125a1..885cd4c54 100644 --- a/src/cli/run/event-handlers.ts +++ b/src/cli/run/event-handlers.ts @@ -20,7 +20,6 @@ import { closeThinkBlock, openThinkBlock, renderAgentHeader, - renderThinkingLine, writePaddedText, } from "./output-renderer" @@ -111,10 +110,15 @@ export function handleMessagePartUpdated(ctx: RunContext, payload: EventPayload, if (part.type === "reasoning") { ensureThinkBlockOpen(state) - const reasoningText = part.text ?? state.lastReasoningText - maybePrintThinkingLine(state, reasoningText) + const reasoningText = part.text ?? "" + const newText = reasoningText.slice(state.lastReasoningText.length) + if (newText) { + const padded = writePaddedText(newText, state.thinkingAtLineStart) + process.stdout.write(pc.dim(padded.output)) + state.thinkingAtLineStart = padded.atLineStart + state.hasReceivedMeaningfulWork = true + } state.lastReasoningText = reasoningText - state.hasReceivedMeaningfulWork = true return } @@ -157,9 +161,10 @@ export function handleMessagePartDelta(ctx: RunContext, payload: EventPayload, s if (partType === "reasoning") { ensureThinkBlockOpen(state) - const nextReasoningText = `${state.lastReasoningText}${delta}` - maybePrintThinkingLine(state, nextReasoningText) - state.lastReasoningText = nextReasoningText + const padded = writePaddedText(delta, state.thinkingAtLineStart) + process.stdout.write(pc.dim(padded.output)) + state.thinkingAtLineStart = padded.atLineStart + state.lastReasoningText += delta state.hasReceivedMeaningfulWork = true return } @@ -226,6 +231,7 @@ export function handleMessageUpdated(ctx: RunContext, payload: EventPayload, sta state.hasPrintedThinkingLine = false state.lastThinkingSummary = "" state.textAtLineStart = true + state.thinkingAtLineStart = false closeThinkBlockIfNeeded(state) const agent = props?.info?.agent ?? null @@ -298,6 +304,7 @@ function ensureThinkBlockOpen(state: EventState): void { openThinkBlock() state.inThinkBlock = true state.hasPrintedThinkingLine = false + state.thinkingAtLineStart = false } function closeThinkBlockIfNeeded(state: EventState): void { @@ -306,19 +313,5 @@ function closeThinkBlockIfNeeded(state: EventState): void { state.inThinkBlock = false state.lastThinkingLineWidth = 0 state.lastThinkingSummary = "" -} - -function maybePrintThinkingLine(state: EventState, text: string): void { - const normalized = text.replace(/\s+/g, " ").trim() - if (!normalized) return - - const summary = normalized - if (summary === state.lastThinkingSummary) return - - state.lastThinkingLineWidth = renderThinkingLine( - summary, - state.lastThinkingLineWidth, - ) - state.lastThinkingSummary = summary - state.hasPrintedThinkingLine = true + state.thinkingAtLineStart = false } diff --git a/src/cli/run/event-state.ts b/src/cli/run/event-state.ts index a49802414..db4857079 100644 --- a/src/cli/run/event-state.ts +++ b/src/cli/run/event-state.ts @@ -35,6 +35,8 @@ export interface EventState { lastThinkingSummary: string /** Whether text stream is currently at line start (for padding) */ textAtLineStart: boolean + /** Whether reasoning stream is currently at line start (for padding) */ + thinkingAtLineStart: boolean } export function createEventState(): EventState { @@ -60,5 +62,6 @@ export function createEventState(): EventState { messageRoleById: {}, lastThinkingSummary: "", textAtLineStart: true, + thinkingAtLineStart: false, } } diff --git a/src/cli/run/message-part-delta.test.ts b/src/cli/run/message-part-delta.test.ts index 7403a488b..379572c01 100644 --- a/src/cli/run/message-part-delta.test.ts +++ b/src/cli/run/message-part-delta.test.ts @@ -215,8 +215,7 @@ describe("message.part.delta handling", () => { //#then const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") - expect(rendered).toContain("\r") - expect(rendered).toContain("Thinking: Composing final summary") + expect(rendered).toContain("┃ Thinking: Composing final summary") expect(rendered).toContain("in Korean with specifics.") if (previous !== undefined) process.env.GITHUB_ACTIONS = previous @@ -282,7 +281,7 @@ describe("message.part.delta handling", () => { //#then const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") const renderCount = rendered.split("Thinking:").length - 1 - expect(renderCount).toBe(2) + expect(renderCount).toBe(1) if (previous !== undefined) process.env.GITHUB_ACTIONS = previous stdoutSpy.mockRestore() diff --git a/src/cli/run/output-renderer.ts b/src/cli/run/output-renderer.ts index 57e6de9cb..15d3308d9 100644 --- a/src/cli/run/output-renderer.ts +++ b/src/cli/run/output-renderer.ts @@ -25,29 +25,11 @@ export function renderAgentHeader( } export function openThinkBlock(): void { - return + process.stdout.write("\n ┃ Thinking: ") } export function closeThinkBlock(): void { - process.stdout.write("\n") -} - -export function renderThinkingLine( - summary: string, - previousWidth: number, -): number { - const line = ` ┃ Thinking: ${summary} ` - const isGitHubActions = process.env.GITHUB_ACTIONS === "true" - - if (isGitHubActions) { - process.stdout.write(`${pc.dim(line)}\n`) - return line.length - } - - const minPadding = 6 - const clearPadding = Math.max(previousWidth - line.length + minPadding, minPadding) - process.stdout.write(`\r${pc.dim(line)}${" ".repeat(clearPadding)}`) - return line.length + process.stdout.write(" \n") } export function writePaddedText(