Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7412daf
fix(tui): soften assistant output text color
kevinjosethomas Sep 10, 2026
7776db7
fix(tui): dim the assistant output text further
kevinjosethomas Sep 10, 2026
1019520
fix(tui): lighten the thinking block styling
kevinjosethomas Sep 10, 2026
48b971e
fix(tui): dim the collapsed tool call previews
kevinjosethomas Sep 10, 2026
ba8b4dc
fix(tui): lighten bold usage in the conversation surface
kevinjosethomas Sep 10, 2026
209b1d7
fix(tui): refine the conversation row text hierarchy
kevinjosethomas Sep 10, 2026
2aa2e3c
fix(tui): hide thinking rows by default
kevinjosethomas Sep 10, 2026
e6709ef
fix(tui): reveal all thinking blocks on the toggle
kevinjosethomas Sep 10, 2026
8bef2a5
Revert "fix(tui): reveal all thinking blocks on the toggle"
kevinjosethomas Sep 10, 2026
22adebd
Revert "fix(tui): hide thinking rows by default"
kevinjosethomas Sep 10, 2026
a1833f8
Revert "fix(tui): refine the conversation row text hierarchy"
kevinjosethomas Sep 10, 2026
1a4ce2d
Revert "fix(tui): lighten bold usage in the conversation surface"
kevinjosethomas Sep 10, 2026
0fe5213
Revert "fix(tui): lighten the thinking block styling"
kevinjosethomas Sep 10, 2026
6661248
fix(tui): revert the tool preview dim from this lane
kevinjosethomas Sep 10, 2026
f700fb1
fix(tui): fold the quiet conversation changes back into this pr
kevinjosethomas Sep 10, 2026
4d3fcdf
feat(tui): cycle conversation detail with Ctrl+O
kevinjosethomas Sep 10, 2026
ff6fd8c
fix(tui): compact shell completions and conversation detail
kevinjosethomas Sep 10, 2026
783d8d4
fix(tui): keep detail shortcut guidance in help
kevinjosethomas Sep 10, 2026
1884042
feat(tui): show conversation detail mode in status bar
kevinjosethomas Sep 10, 2026
08a72ca
fix(tui): remove separate agent message shortcut
kevinjosethomas Sep 10, 2026
9723a96
fix(tui): align shell completions and gate agent messages by detail
kevinjosethomas Sep 10, 2026
05f2829
test(tui): preserve compact agent message notices
kevinjosethomas Sep 10, 2026
baf5460
fix(tui): retain compact agent message notices in overview
kevinjosethomas Sep 10, 2026
efa801a
fix(tui): preserve multiline Python string highlighting
kevinjosethomas Sep 10, 2026
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
13 changes: 13 additions & 0 deletions packages/coding-agent/.changes/ui-chat-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- Changed assistant message body text to a new dimmed `mdBody` theme color, easing the wall of bright default-foreground text while headings, links, and code keep their styling.
- Changed prime-theme inline code to a darker neutral (#c8c8cd) so it stays distinct from the dimmed body text.
- Added a three-stage Ctrl+O cycle for overview, thinking and file diffs, and all output, replacing the separate Ctrl+J and Ctrl+T conversation shortcuts without changing saved traces.
- Changed thinking rows to stay hidden in overview and appear as dim text without a repeated heading in the other detail modes, including newly streamed thinking.
- Changed collapsed tool-call previews to render plain and dim instead of green or syntax-highlighted code, with dim line counts and durations, while expanded blocks keep full highlighting.
- Changed decorative bold text in the conversation surface to normal weight, keeping bold only where it marks state or a single critical item (selected rows, active tabs, the login verification code).
- Changed the conversation row hierarchy: event-row trailing detail (agent-message participants, tool command previews, line counts, durations) renders dim while leading labels keep their colors.
- Changed background shell completions to update identifiable command rows, with compact fallback notices and full notifications retained in all output.
- Replaced repeated conversation detail shortcut hints with a status label showing the current detail mode and configurable expand or collapse shortcut.
- Changed expanded file diffs to start at the normal chat inset while preserving code indentation and diff gutters.
- Changed sent and received agent messages to keep compact notices in overview and details, show full bodies only in all output, and use the shared detail cycle instead of a separate Ctrl+P toggle.
- Fixed spacing after background shell completions and matched unique literal assignment-only shell launches to their completion notices.
- Fixed multiline Python string colors across source lines and narrow wrapping, and kept embedded string content out of collapsed code previews.
5 changes: 2 additions & 3 deletions packages/coding-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,8 @@ See `/hotkeys` for the full list. Customize via `~/.prime/agent/keybindings.json
| Escape | Clear the input without interrupting active work |
| Escape twice | Open `/tree` |
| Ctrl+L | Open model selector |
| Ctrl+P / Shift+Ctrl+P | Cycle scoped models forward/backward |
| Ctrl+O | Collapse/expand tool output |
| Ctrl+T | Collapse/expand thinking blocks |
| Ctrl+P | Collapse/expand agent-to-agent messages |
| Ctrl+O | Cycle overview → thinking and file diffs → all output |

### Message Queue

Expand Down
9 changes: 4 additions & 5 deletions packages/coding-agent/docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
|--------|---------|-------------|
| `app.model.select` | `ctrl+l` | Open model selector |
| `app.model.toggleScope` | `alt+s` | Toggle between all and scoped models |
| `app.thinking.toggle` | `ctrl+t` | Collapse or expand thinking blocks |

### Configuration Menu

Expand All @@ -129,15 +128,15 @@ Use `tab` to cycle forward and `shift+tab` to cycle backward through Providers,

| Keybinding id | Default | Description |
|--------|---------|-------------|
| `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
| `app.messages.expand` | `ctrl+p` | Collapse or expand agent-to-agent messages |
| `app.edits.expand` | `ctrl+j` | Collapse or expand edit diffs |
| `app.tools.expand` | `ctrl+o` | Cycle overview → thinking and file diffs → all output |
| `app.message.followUp` | `alt+enter` | Queue follow-up message |
| `app.message.navigateOlder` | `alt+up` | Select the next older pending message |
| `app.message.navigateNewer` | `alt+down` | Select the next newer pending message or restore the draft |
| `app.message.moveEarlier` | `ctrl+alt+up` | Move the selected pending message one place earlier in its queue |
| `app.message.moveLater` | `ctrl+alt+down` | Move the selected pending message one place later in its queue |

Ctrl+O changes presentation only: the default hides thinking and collapses tools and diffs; the first press reveals thinking and file diffs; the second expands tool output and full agent-to-agent message bodies; the third returns to the default. Compact sent and received message notices remain visible in every mode. This also works for restored conversations and new streaming content. Ctrl+J, Ctrl+T, and Ctrl+P no longer control conversation expansion.

### Tree Navigation

| Keybinding id | Default | Description |
Expand Down Expand Up @@ -185,7 +184,7 @@ On native Windows, `app.suspend` has no default binding because Windows terminal

### Emacs Example

Binding `ctrl+p` below releases the conflicting `app.messages.expand` default; shortcuts in other views keep their defaults.
Binding `ctrl+p` below moves the editor cursor up; shortcuts in other views keep their defaults.

```json
{
Expand Down
11 changes: 8 additions & 3 deletions packages/coding-agent/docs/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ vim ~/.prime/agent/themes/my-theme.json
"toolPendingBg": "#1e1e2e",
"toolSuccessBg": "#1e2e1e",
"toolErrorBg": "#2e1e1e",
"toolDiffAddedBg": "#0a2f0a",
"toolDiffRemovedBg": "#2f0a0a",
"toolPanelBg": "#2d2d38",
"toolTitle": "primary",
"toolOutput": "",
"mdBody": "",
"mdHeading": "#ffaa00",
"mdLink": "primary",
"mdLinkUrl": "secondary",
Expand All @@ -94,6 +97,7 @@ vim ~/.prime/agent/themes/my-theme.json
"mdListBullet": "#00ffff",
"toolDiffAdded": "#00ff00",
"toolDiffRemoved": "#ff0000",
"toolDiffText": "secondary",
"toolDiffContext": "secondary",
"syntaxComment": "secondary",
"syntaxKeyword": "primary",
Expand Down Expand Up @@ -140,13 +144,13 @@ vim ~/.prime/agent/themes/my-theme.json

- `name` is required and must be unique.
- `vars` is optional. Define reusable colors here, then reference them in `colors`.
- `colors` must define all 51 required tokens.
- `colors` must define all 56 required tokens.

The `$schema` field enables editor auto-completion and validation.

## Color Tokens

Every theme must define all 51 color tokens. There are no optional colors.
Every theme must define all 56 color tokens. There are no optional colors.

### Core UI (11 colors)

Expand Down Expand Up @@ -181,10 +185,11 @@ Every theme must define all 51 color tokens. There are no optional colors.
| `toolTitle` | Tool title |
| `toolOutput` | Tool output text |

### Markdown (10 colors)
### Markdown (11 colors)

| Token | Purpose |
|-------|---------|
| `mdBody` | Assistant output body text |
| `mdHeading` | Headings |
| `mdLink` | Link text |
| `mdLinkUrl` | Link URL |
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/docs/tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ renderResult(result, options, theme, context) {
| Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
| Tools | `toolTitle`, `toolOutput` |
| Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
| Markdown | `mdBody`, `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
| Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
| Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
| Modes | `bashMode` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
* Hidden Thinking Label Extension
*
* Demonstrates `ctx.ui.setHiddenThinkingLabel()` for customizing the label shown
* when thinking blocks are hidden.
* above visible thinking blocks.
*
* Usage:
* pi --extension examples/extensions/hidden-thinking-label.ts
*
* Test:
* 1. Load this extension
* 2. Hide thinking blocks with Ctrl+T
* 2. Press Ctrl+O once to show thinking and file diffs
* 3. Ask for something that produces reasoning output
* 4. The collapsed thinking block label will show the custom text
* 4. The thinking block label will show the custom text
*
* Commands:
* /thinking-label <text> Set a custom hidden thinking label
Expand Down
3 changes: 0 additions & 3 deletions packages/coding-agent/src/core/extensions/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
"app.suspend",
"app.model.select",
"app.tools.expand",
"app.messages.expand",
"app.edits.expand",
"app.thinking.toggle",
"app.subagents.focus",
"app.editor.external",
"app.message.followUp",
Expand Down
17 changes: 1 addition & 16 deletions packages/coding-agent/src/core/keybindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export interface AppKeybindings {
"app.model.toggleScope": true;
"app.configuration.previousTab": true;
"app.tools.expand": true;
"app.messages.expand": true;
"app.edits.expand": true;
"app.thinking.toggle": true;
"app.subagents.focus": true;
"app.heartbeats.open": true;
"app.heartbeats.openSelected": true;
Expand Down Expand Up @@ -88,18 +85,7 @@ export const KEYBINDINGS = {
"app.model.select": { defaultKeys: "ctrl+l", description: "Open model selector" },
"app.model.toggleScope": { defaultKeys: "alt+s", description: "Toggle model selector scope" },
"app.configuration.previousTab": { defaultKeys: "shift+tab", description: "Select previous configuration tab" },
"app.tools.expand": { defaultKeys: "ctrl+o", description: "Toggle tool output", defaultKeyScope: "editor" },
"app.messages.expand": {
defaultKeys: "ctrl+p",
description: "Toggle agent message expansion",
defaultKeyScope: "editor",
},
"app.edits.expand": { defaultKeys: "ctrl+j", description: "Toggle edit diffs", defaultKeyScope: "editor" },
"app.thinking.toggle": {
defaultKeys: "ctrl+t",
description: "Toggle thinking blocks",
defaultKeyScope: "editor",
},
"app.tools.expand": { defaultKeys: "ctrl+o", description: "Cycle conversation detail", defaultKeyScope: "editor" },
"app.subagents.focus": {
defaultKeys: "alt+a",
description: "Open child agents",
Expand Down Expand Up @@ -271,7 +257,6 @@ const KEYBINDING_NAME_MIGRATIONS = {
suspend: "app.suspend",
selectModel: "app.model.select",
expandTools: "app.tools.expand",
toggleThinking: "app.thinking.toggle",
focusSubagents: "app.subagents.focus",
externalEditor: "app.editor.external",
followUp: "app.message.followUp",
Expand Down
3 changes: 2 additions & 1 deletion packages/coding-agent/src/core/settings-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,8 @@ export class SettingsManager {
}

getHideThinkingBlock(): boolean {
return this.settings.hideThinkingBlock ?? false;
// Defaults to true: thinking rows stay hidden unless explicitly opted out.
return this.settings.hideThinkingBlock ?? true;
}

setHideThinkingBlock(hide: boolean): void {
Expand Down
10 changes: 5 additions & 5 deletions packages/coding-agent/src/core/tools/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ function formatDuration(ms: number): string {
function formatBashCall(args: { command?: string; timeout?: number } | undefined): string {
const command = str(args?.command);
const timeout = args?.timeout as number | undefined;
const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
const timeoutSuffix = timeout ? theme.fg("dim", ` (timeout ${timeout}s)`) : "";
let commandDisplay: string;
if (command === null) {
commandDisplay = invalidArgText(theme);
Expand All @@ -188,7 +188,7 @@ function formatBashCall(args: { command?: string; timeout?: number } | undefined
} else {
commandDisplay = theme.fg("toolOutput", "...");
}
return theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`)) + timeoutSuffix;
return theme.fg("dim", `$ ${commandDisplay}`) + timeoutSuffix;
}

function rebuildBashResultRenderComponent(
Expand Down Expand Up @@ -228,8 +228,8 @@ function rebuildBashResultRenderComponent(
}
if (state.cachedSkipped && state.cachedSkipped > 0) {
const hint = showExpandHint
? `${theme.fg("muted", `... ${state.cachedSkipped} earlier lines`)} ${expandCollapseHint("app.tools.expand", false)}`
: theme.fg("muted", `... (${state.cachedSkipped} earlier lines)`);
? `${theme.fg("dim", `... ${state.cachedSkipped} earlier lines`)} ${expandCollapseHint("app.tools.expand", false)}`
: theme.fg("dim", `... (${state.cachedSkipped} earlier lines)`);
return ["", truncateToWidth(hint, width, "..."), ...(state.cachedLines ?? [])];
}
return ["", ...(state.cachedLines ?? [])];
Expand Down Expand Up @@ -265,7 +265,7 @@ function rebuildBashResultRenderComponent(
if (startedAt !== undefined) {
const label = options.isPartial ? "Elapsed" : "Took";
const endTime = endedAt ?? Date.now();
component.addChild(new Text(`\n${theme.fg("muted", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));
component.addChild(new Text(`\n${theme.fg("dim", `${label} ${formatDuration(endTime - startedAt)}`)}`, 0, 0));
}
}

Expand Down
39 changes: 20 additions & 19 deletions packages/coding-agent/src/core/tools/code-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,30 +458,32 @@ function scanPythonStringLiteral(code: string, start: number, quote: string, raw
return { value, end: i, closed: false, unsupportedEscape };
}

// True when the lines end inside an unterminated triple-quoted string.
function endsInsideMultilineString(lines: readonly string[]): boolean {
const text = lines.join("\n");
/** Keep source-line positions while excluding lines that begin inside string literals. */
export function pythonStatementLines(code: string): string[] {
const lines = code.split("\n");
let line = 0;
let i = 0;
while (i < text.length) {
const char = text[i] ?? "";
while (i < code.length) {
const char = code[i]!;
if (char === "#") {
const newline = text.indexOf("\n", i);
if (newline < 0) return false;
i = newline + 1;
const newline = code.indexOf("\n", i);
if (newline < 0) break;
i = newline;
continue;
}
if (char === '"' || char === "'") {
const quote = text.startsWith(char.repeat(3), i) ? char.repeat(3) : char;
const scan = scanPythonStringLiteral(text, i + quote.length, quote, true);
if (!scan.closed && scan.end >= text.length) {
return quote.length === 3;
const quote = code.startsWith(char.repeat(3), i) ? char.repeat(3) : char;
const scan = scanPythonStringLiteral(code, i + quote.length, quote, true);
for (let end = i; end < scan.end; end++) {
if (code[end] === "\n") lines[++line] = "";
}
i = scan.end;
continue;
}
i += 1;
if (char === "\n") line++;
i++;
}
return false;
return lines;
}

function extractBashSkillCommand(code: string): string | undefined {
Expand All @@ -499,7 +501,8 @@ function extractBashSkillCommand(code: string): string | undefined {
}

export function previewPythonCode(code: string): CodePreview {
const lines = code.split("\n");
const rawLines = code.split("\n");
const lines = pythonStatementLines(code);
const paths = pythonPathVars(lines);
let bestIndex: number | undefined;
let bestScore = -1;
Expand All @@ -514,10 +517,8 @@ export function previewPythonCode(code: string): CodePreview {

if (bestIndex !== undefined && bestScore >= 0) {
const previewIndex = pythonPreviewIndex(lines, bestIndex);
// Extract from the full tail (literals may span lines), unless the chosen line is string text.
const bashCommand = endsInsideMultilineString(lines.slice(0, previewIndex))
? undefined
: extractBashSkillCommand(lines.slice(previewIndex).join("\n"));
// Extract from the original tail because a literal command may span lines.
const bashCommand = extractBashSkillCommand(rawLines.slice(previewIndex).join("\n"));
if (bashCommand) {
return previewBashCommand(bashCommand);
}
Expand Down
25 changes: 7 additions & 18 deletions packages/coding-agent/src/core/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { constants } from "fs";
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
import { type Static, Type } from "typebox";
import { renderDiff } from "../../modes/interactive/components/diff.js";
import {
countChangedLines,
FILE_CHANGE_DIFF_INDENT,
formatFileChangeSummaryLine,
} from "../../modes/interactive/components/edit-summary.js";
import { countChangedLines, formatFileChangeSummaryLine } from "../../modes/interactive/components/edit-summary.js";
import type { ToolDefinition } from "../extensions/types.js";
import {
applyEditsToNormalizedContent,
Expand Down Expand Up @@ -202,8 +198,8 @@ function formatEditCall(
const invalidArg = invalidArgText(theme);
const rawPath = str(args?.file_path ?? args?.path);
const path = rawPath !== null ? shortenPath(rawPath) : null;
const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
const pathDisplay = path === null ? invalidArg : path ? theme.fg("dim", path) : theme.fg("toolOutput", "...");
return `${theme.fg("toolTitle", "edit")} ${pathDisplay}`;
}

function formatEditResult(
Expand Down Expand Up @@ -249,8 +245,7 @@ function getEditHeaderBg(
return (text: string) => theme.bg("toolPendingBg", text);
}

// Width-aware `╰─ <path> +N -M` summary plus optional indented diff rows: the
// summary truncates to one row and wrapped diff lines keep the indent column.
// Width-aware file summary with optional diff rows at the containing box's content margin.
class EditChangeSummaryComponent implements Component {
constructor(
private readonly rawPath: string,
Expand All @@ -264,11 +259,9 @@ class EditChangeSummaryComponent implements Component {
const safeWidth = Math.max(1, width);
const lines = [formatFileChangeSummaryLine(this.rawPath, this.cwd, this.change, this.diffsExpanded, safeWidth)];
if (this.diffLines !== undefined) {
const indent = FILE_CHANGE_DIFF_INDENT.slice(0, Math.max(0, safeWidth - 1));
const contentWidth = Math.max(1, safeWidth - indent.length);
for (const line of this.diffLines) {
for (const row of wrapTextWithAnsi(line, contentWidth)) {
lines.push(`${indent}${row}`);
for (const row of wrapTextWithAnsi(line, safeWidth)) {
lines.push(row);
}
}
}
Expand Down Expand Up @@ -299,8 +292,7 @@ function buildEditCallComponent(
return component;
}

// The `╰─ <path> +N -M` summary line renders in both states; ctrl+j only
// attaches or removes the indented diff lines underneath it.
// Keep the file summary visible while detail expansion reveals the diff.
const rawPath = str(args?.file_path ?? args?.path);
const change = countChangedLines(component.preview.diff);
component.addChild(new Spacer(1));
Expand All @@ -309,9 +301,6 @@ function buildEditCallComponent(
rawPath ?? "...",
cwd,
change,
// The ctrl+j hint renders on every edit summary row (unlike the ctrl+o
// hint, which the latest tool row owns), matching thinking and
// agent-message hints.
expanded,
expanded ? renderDiff(component.preview.diff).split("\n") : undefined,
),
Expand Down
Loading
Loading