Skip to content

Commit e7e3905

Browse files
odedha-drclaude
andcommitted
feat: show tool-result turns with truncated stdout/stderr instead of empty user blocks
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent ed19c39 commit e7e3905

3 files changed

Lines changed: 42 additions & 5 deletions

File tree

src/parser.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,12 @@ export async function parseSessionFileDetailed(filePath: string, project: string
248248
const msg = raw.message;
249249
if (!msg) continue;
250250

251-
const role = msg.role as 'user' | 'assistant' | undefined;
252-
if (!role || (role !== 'user' && role !== 'assistant')) continue;
251+
const rawRole = msg.role as 'user' | 'assistant' | undefined;
252+
if (!rawRole || (rawRole !== 'user' && rawRole !== 'assistant')) continue;
253+
254+
// Detect if this user message is actually a tool result
255+
const isToolResult = rawRole === 'user' && !!raw.toolUseResult;
256+
const role: 'user' | 'assistant' | 'tool-result' = isToolResult ? 'tool-result' : rawRole;
253257

254258
// Track turn changes
255259
if (role !== lastRole) {

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export interface WatcherEvent {
9494
// Turn-level tracking
9595
export interface Turn {
9696
number: number;
97-
role: 'user' | 'assistant';
97+
role: 'user' | 'assistant' | 'tool-result';
9898
timestamp: string | null;
9999
durationMs: number | null; // time from this entry to next entry (latency)
100100
toolCalls: string[];

src/web/public/index.html

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@
688688
.turn-row:hover { border-color: var(--accent); }
689689
.turn-row.role-user { border-left: 3px solid #4a9eff; }
690690
.turn-row.role-assistant { border-left: 3px solid var(--accent); }
691+
.turn-row.role-tool-result { border-left: 3px solid #f59e0b; }
691692
.turn-row-header {
692693
display: flex;
693694
align-items: center;
@@ -713,6 +714,7 @@
713714
}
714715
.role-badge.user { background: rgba(74,158,255,0.15); color: #4a9eff; }
715716
.role-badge.assistant { background: rgba(0,210,255,0.15); color: var(--accent); }
717+
.role-badge.tool-result { background: rgba(245,158,11,0.15); color: #f59e0b; }
716718
.turn-timestamp {
717719
font-size: 11px;
718720
color: var(--text-muted);
@@ -1489,7 +1491,7 @@ <h3>Cost Breakdown</h3>
14891491
html += '<div class="turn-list">';
14901492
reversed.forEach((t, idx) => {
14911493
const isExpanded = expandedTurns.has(t.number);
1492-
const roleClass = t.role === 'user' ? 'role-user' : 'role-assistant';
1494+
const roleClass = t.role === 'tool-result' ? 'role-tool-result' : t.role === 'user' ? 'role-user' : 'role-assistant';
14931495
const durClass = durationClass(t.durationMs);
14941496
const tok = t.tokens || {};
14951497

@@ -1525,7 +1527,9 @@ <h3>Cost Breakdown</h3>
15251527

15261528
// Expanded content
15271529
html += '<div class="turn-expanded">';
1528-
const content = (t.content || []).filter(c => c.type !== 'tool_result');
1530+
const content = t.role === 'tool-result'
1531+
? (t.content || []) // show tool_result blocks for tool-result turns
1532+
: (t.content || []).filter(c => c.type !== 'tool_result');
15291533
if (content.length === 0) {
15301534
html += '<div style="color:var(--text-muted);font-size:12px;">No content blocks.</div>';
15311535
}
@@ -1596,6 +1600,35 @@ <h3>Cost Breakdown</h3>
15961600
}
15971601
}
15981602
}
1603+
} else if (c.type === 'tool_result') {
1604+
const tr = c.toolResult;
1605+
if (tr) {
1606+
if (tr.stdout) {
1607+
const outTrunc = truncate(tr.stdout, 500);
1608+
const outKey = `tr-stdout-${blockKey}`;
1609+
const isOutExpanded = expandedOutputs.has(outKey);
1610+
html += `<div class="tcb-result-label">stdout</div>`;
1611+
html += `<div class="tcb-code tcb-stdout">${escHtml(isOutExpanded || !outTrunc.truncated ? tr.stdout : outTrunc.text)}</div>`;
1612+
if (outTrunc.truncated) {
1613+
html += `<span class="tcb-code-toggle" data-output-toggle="${outKey}">${isOutExpanded ? 'Show less' : 'Show full output'}</span>`;
1614+
}
1615+
}
1616+
if (tr.stderr) {
1617+
const errTrunc = truncate(tr.stderr, 500);
1618+
const errKey = `tr-stderr-${blockKey}`;
1619+
const isErrExpanded = expandedOutputs.has(errKey);
1620+
html += `<div class="tcb-result-label">stderr</div>`;
1621+
html += `<div class="tcb-code tcb-stderr">${escHtml(isErrExpanded || !errTrunc.truncated ? tr.stderr : errTrunc.text)}</div>`;
1622+
if (errTrunc.truncated) {
1623+
html += `<span class="tcb-code-toggle" data-output-toggle="${errKey}">${isErrExpanded ? 'Show less' : 'Show full output'}</span>`;
1624+
}
1625+
}
1626+
if (tr.interrupted) html += `<span class="interrupted-badge">interrupted</span>`;
1627+
if (tr.isImage) html += `<div style="color:var(--text-muted);font-size:11px;font-style:italic;">Image content</div>`;
1628+
if (!tr.stdout && !tr.stderr && !tr.isImage) {
1629+
html += '<div style="color:var(--text-muted);font-size:12px;">No output.</div>';
1630+
}
1631+
}
15991632
} else if (c.type === 'other') {
16001633
html += `<div class="tcb-code">${escHtml(c.text || '')}</div>`;
16011634
}

0 commit comments

Comments
 (0)