Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions cli/src/components/ChatMessage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,61 @@ describe("ChatMessage subagent rendering", () => {
expect(frame).toContain("5 tool uses · 28.9k tokens · $0.00")
})
})

describe("ChatMessage reasoning rendering", () => {
it("renders the reasoning (chain-of-thought) text instead of hiding it", () => {
const message: DiracMessage = {
ts: Date.now(),
type: "say",
say: "reasoning",
text: "Let me first inspect the failing test before editing.",
}
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
const frame = lastFrame() || ""
expect(frame).toContain("Let me first inspect the failing test before editing.")
})

it("renders streaming reasoning while partial", () => {
const message: DiracMessage = {
ts: Date.now(),
type: "say",
say: "reasoning",
text: "Thinking through the approach",
partial: true,
}
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act", isStreaming: true }))
expect(lastFrame() || "").toContain("Thinking through the approach")
})

it("renders nothing for empty reasoning", () => {
const message: DiracMessage = { ts: Date.now(), type: "say", say: "reasoning", text: " " }
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
expect((lastFrame() || "").trim()).toBe("")
})
})

describe("ChatMessage activity journal", () => {
it("shows the queried model for api_req_started", () => {
const message = {
ts: Date.now(),
type: "say",
say: "api_req_started",
text: "{}",
modelInfo: { modelId: "ailiance-reasoning-r1" },
} as unknown as DiracMessage
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
expect(lastFrame() || "").toContain("querying ailiance-reasoning-r1")
})

it("shows a checkpoint marker", () => {
const message: DiracMessage = { ts: Date.now(), type: "say", say: "checkpoint_created" }
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
expect(lastFrame() || "").toContain("checkpoint saved")
})

it("shows a retry marker", () => {
const message: DiracMessage = { ts: Date.now(), type: "say", say: "api_req_retried" }
const { lastFrame } = render(React.createElement(ChatMessage, { message, mode: "act" }))
expect(lastFrame() || "").toContain("retrying")
})
})
53 changes: 45 additions & 8 deletions cli/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,50 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
)
}

// Assistant text response (hide reasoning traces - they're verbose and clutter the UI)
// Activity journal — compact dim markers so the user sees what the agent is
// doing between answers (which model is queried, checkpoints, retries).
if (say === "api_req_started") {
const modelId = (message as { modelInfo?: { modelId?: string } }).modelInfo?.modelId
return (
Comment on lines +316 to +318
Comment on lines +316 to +318
<Box marginBottom={1}>
<Text color="gray" dimColor>
→ {modelId ? `querying ${modelId}` : "querying model"}
</Text>
</Box>
)
}
if (say === "checkpoint_created") {
return (
<Box marginBottom={1}>
<Text color="gray" dimColor>
✓ checkpoint saved
</Text>
</Box>
)
}
if (say === "api_req_retried") {
return (
<Box marginBottom={1}>
<Text color="gray" dimColor>
⟳ retrying request…
</Text>
</Box>
)
}

// Reasoning / chain-of-thought. Streamed live and kept in history, dimmed
// and italic so it stays visually distinct from the assistant's answer.
if (say === "reasoning") {
return null
if (!text?.trim()) return null
return (
<Box flexDirection="column" marginBottom={1} width="100%">
<DotRow color="gray" flashing={partial === true && isStreaming}>
<Text color="gray" dimColor italic>
{text}
</Text>
</DotRow>
</Box>
)
}
if (say === "text") {
if (!text?.trim()) return null
Expand Down Expand Up @@ -454,7 +495,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
)}
{output && (
<Box flexDirection="column" marginLeft={2} width="100%">
{formatToolResult(output, 8).map((line, idx) => (
{formatToolResult(output, isExecuting ? 20 : Number.MAX_SAFE_INTEGER).map((line, idx) => (
<ResultRow isFirst={idx === 0} key={idx}>
<Text color="gray">{line}</Text>
</ResultRow>
Expand Down Expand Up @@ -602,11 +643,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
)
}

// API request info (show cost/tokens inline)
if (say === "api_req_started" && text) {
// Skip showing these - they're summarized in the status bar
return null
}
// (api_req_started is handled earlier as a compact activity marker.)

// Browser actions
if (say === "browser_action" || say === "browser_action_launch") {
Expand Down
7 changes: 3 additions & 4 deletions cli/src/hooks/useChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ export function useChatMessages(messages: any[]) {
const displayMessages = useMemo(() => {
const filtered = messages.filter((m) => {
if (m.say === "api_req_finished") return false
if (m.say === "checkpoint_created") return false
if (m.say === "api_req_started") return false
if (m.say === "api_req_retried") return false
if (m.say === "reasoning") return false
// Activity journal: api_req_started, checkpoint_created, api_req_retried
// and reasoning are all surfaced — rendered as compact dim lines in
// ChatMessage so the user sees what the agent is doing.
return true
})
const withHooks = combineHookSequences(filtered)
Expand Down
Loading