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
23 changes: 10 additions & 13 deletions packages/cli/src/__tests__/pi-transcript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ describe('Maka Pi TUI transcript', () => {
assert.ok(lines.every((line) => visibleWidth(line) <= 12));
});

test('labels assistant messages as maka', () => {
test('renders assistant messages as bare text without a speaker label', () => {
const state = createMakaPiTranscriptState();
applyMakaSessionEventToTranscript(state, event({
type: 'text_delta',
Expand All @@ -589,28 +589,25 @@ describe('Maka Pi TUI transcript', () => {
permissionMode: 'bypass',
}, 80).map(stripAnsi);

assert.ok(visibleLines.includes('maka'));
assert.ok(!visibleLines.includes('Assistant'));
assert.ok(visibleLines.some((line) => line.trim() === 'hello'));
assert.ok(!visibleLines.some((line) => line.includes('maka')));
assert.ok(!visibleLines.some((line) => line.includes('Assistant')));
});

test('uses logo blue instead of green for assistant headings', () => {
test('renders user messages with a > quote prefix instead of a speaker label', () => {
const state = createMakaPiTranscriptState();
applyMakaSessionEventToTranscript(state, event({
type: 'text_delta',
messageId: 'message-1',
text: 'hello',
}));
appendUserPrompt(state, 'hello world');

const rawOutput = renderMakaPiTranscript(state, {
const visibleLines = renderMakaPiTranscript(state, {
title: 'Maka',
cwd: '/tmp/project',
model: 'deepseek-v4-flash',
connectionSlug: 'deepseek',
permissionMode: 'bypass',
}, 80).join('\n');
}, 80).map(stripAnsi);

assert.match(rawOutput, /\x1b\[38;2;87;163;239mmaka\x1b\[39m/);
assert.doesNotMatch(rawOutput, /\x1b\[32mmaka/);
assert.ok(visibleLines.some((line) => line.startsWith('> ')), 'user row should start with >');
assert.ok(!visibleLines.some((line) => line.includes('User')), 'no User speaker label');
});

test('surfaces context compaction diagnostics as transcript notes', () => {
Expand Down
43 changes: 25 additions & 18 deletions packages/cli/src/pi-transcript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,9 +738,15 @@ export function renderMakaPiTranscript(
return renderWelcomeBlock(metadata, safeWidth);
}

for (const entry of state.entries) {
// A blank spacer above every entry, then its (memoized) rendered block.
lines.push('');
for (let i = 0; i < state.entries.length; i += 1) {
const entry = state.entries[i]!;
const prev = state.entries[i - 1];
// A blank gap separates human-facing boundaries (user/assistant/notice)
// and the edges of an agent-work stack; consecutive thinking/tool entries
// (the agent-work stack) have no blank line between them.
const continuesStack = (entry.kind === 'thinking' || entry.kind === 'tool')
&& (prev?.kind === 'thinking' || prev?.kind === 'tool');
if (!continuesStack) lines.push('');
lines.push(...renderTranscriptEntryMemoized(entry, safeWidth, state.expandAllTools, state.expandAllThinking));
}

Expand Down Expand Up @@ -862,9 +868,9 @@ function renderTranscriptEntryBlock(
): string[] {
switch (entry.kind) {
case 'user':
return renderTextBlock('User', entry.text, width, { markdown: false, heading: ansi.accent });
return renderUserBlock(entry.text, width);
case 'assistant':
return renderTextBlock('maka', entry.text, width, { markdown: true, heading: ansi.accent });
return renderAssistantBlock(entry.text, width);
case 'thinking':
return renderThinkingBlock(entry, width, expandAllThinking);
case 'tool':
Expand Down Expand Up @@ -1044,20 +1050,21 @@ function findShellRunParent(
&& entry.result.ref === ref);
}

function renderTextBlock(
label: string,
text: string,
width: number,
options: { markdown: boolean; heading: (text: string) => string },
): string[] {
const lines = [fitLine(options.heading(label), width)];
if (!text.trim()) return lines;
/** A user turn: a dim `>` quote prefix per line, no speaker label. */
function renderUserBlock(text: string, width: number): string[] {
if (!text.trim()) return [];
const prefix = ansi.dim('>');
// renderIndented reserves a 2-column gutter; reuse it and swap the two
// leading spaces for `> ` so wrapped lines stay aligned under the prefix.
return renderIndented(text, width, 2).map((line) => fitLine(`${prefix} ${line.slice(2)}`, width));
}

const bodyLines = options.markdown
? new Markdown(text, 2, 0, markdownTheme, undefined, { preserveOrderedListMarkers: true }).render(width)
: renderIndented(text, width, 2);
lines.push(...bodyLines.map((line) => fitLine(line, width)));
return lines;
/** An assistant turn: bare markdown prose, no speaker label or indent. */
function renderAssistantBlock(text: string, width: number): string[] {
if (!text.trim()) return [];
return new Markdown(text, 0, 0, markdownTheme, undefined, { preserveOrderedListMarkers: true })
.render(width)
.map((line) => fitLine(line, width));
}

function renderNotice(entry: MakaPiNoticeEntry, width: number): string[] {
Expand Down
Loading