Skip to content

Commit a04d3c5

Browse files
Add live context size tracking to conversation pane
Track lastContextSize on SessionActivity — the most recent turn's context size (cache_read + input + cache_write). Display as a colored badge [142K] in the conversation list: green <100K, yellow 100-200K, red 200K+, pulsing red >500K. Also show ctx: in status bar when viewing conversations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8123ff6 commit a04d3c5

8 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/cli/src/core/activity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export async function parseSessionActivity(filePath: string): Promise<SessionAct
5757
hourlyActivity: new Array(24).fill(0),
5858
assistantTurns: 0,
5959
toolUseTurns: 0,
60+
lastContextSize: 0,
6061
};
6162

6263
let firstTimestamp: number | null = null;
@@ -111,6 +112,8 @@ export async function parseSessionActivity(filePath: string): Promise<SessionAct
111112
activity.tokenBreakdown.cacheRead += cacheR;
112113
activity.tokenBreakdown.cacheWrite += cacheW;
113114
activity.inputPerMessage.push(inp);
115+
const turnCtx = cacheR + inp + cacheW;
116+
if (turnCtx > 0) activity.lastContextSize = turnCtx;
114117
}
115118

116119
// Tool use from content array

packages/cli/src/core/demo-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const demoActivity: SessionActivity = {
284284
duration: 1_800_000,
285285
assistantTurns: 38, // ~3.2 turns per user message
286286
toolUseTurns: 28, // ~74% of turns use tools
287+
lastContextSize: 142_000, // 142K context
287288
hourlyActivity: (() => {
288289
// Populate around the current hour so timeline shows data
289290
const h = new Array(24).fill(0);
@@ -483,6 +484,7 @@ export const DEMO_SESSION_ACTIVITY: SessionActivity = {
483484
duration: 5_400_000, // 1.5 hours
484485
assistantTurns: 156, // ~3.3 turns per user message
485486
toolUseTurns: 112, // ~72% of turns use tools
487+
lastContextSize: 387_000, // 387K context — deep into session
486488
hourlyActivity: [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 8, 12, 6, 4, 8, 5, 2, 0, 0, 0, 0, 0, 0, 0],
487489
};
488490

packages/cli/src/core/processes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const EMPTY_ACTIVITY: SessionActivity = {
4141
hourlyActivity: new Array(24).fill(0),
4242
assistantTurns: 0,
4343
toolUseTurns: 0,
44+
lastContextSize: 0,
4445
};
4546

4647
// ── Main detection function ──────────────────────────────────

packages/cli/src/core/tailer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ function createEmptyActivity(): SessionActivity {
113113
hourlyActivity: new Array(24).fill(0),
114114
assistantTurns: 0,
115115
toolUseTurns: 0,
116+
lastContextSize: 0,
116117
};
117118
}
118119

@@ -190,6 +191,9 @@ function processLine(line: string, state: TailerInternal): void {
190191
state.activity.tokenBreakdown.cacheRead += cacheR;
191192
state.activity.tokenBreakdown.cacheWrite += cacheW;
192193
state.activity.inputPerMessage.push(inp);
194+
// Track most recent turn's context size (what was actually sent to the API)
195+
const turnContext = cacheR + inp + cacheW;
196+
if (turnContext > 0) state.activity.lastContextSize = turnContext;
193197
}
194198

195199
// Collect assistant text output for round summaries

packages/cli/src/tui/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ export function App() {
707707
scanning={state.scanning}
708708
leftPaneMode={state.leftSection}
709709
updateAvailable={updateAvailable}
710+
selectedConversation={state.leftSection === 'conversations' ? sortedConversations[state.conversationIndex] ?? null : null}
710711
/>
711712

712713
{/* Matrix glitch Easter egg — rare, brief, subtle */}

packages/cli/src/tui/components/ConversationPane.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,26 @@ function projectName(projectPath: string): string {
2727
return parts[parts.length - 1] || projectPath;
2828
}
2929

30+
// Context size thresholds (tokens)
31+
const CTX_WARN = 100_000; // yellow warning
32+
const CTX_DANGER = 200_000; // red danger
33+
const CTX_CRITICAL = 500_000; // pulsing red
34+
35+
/** Format context size compactly: 142K, 1.2M */
36+
function formatContext(tokens: number): string {
37+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
38+
if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K`;
39+
return `${tokens}`;
40+
}
41+
42+
/** Color for context size */
43+
function contextColor(tokens: number): string {
44+
if (tokens >= CTX_CRITICAL) return INK_COLORS.red;
45+
if (tokens >= CTX_DANGER) return INK_COLORS.red;
46+
if (tokens >= CTX_WARN) return INK_COLORS.yellow;
47+
return INK_COLORS.green;
48+
}
49+
3050
export const ConversationPane = React.memo(function ConversationPane({
3151
conversations,
3252
selectedIndex,
@@ -81,6 +101,12 @@ export const ConversationPane = React.memo(function ConversationPane({
81101
const dur = formatDuration(Date.now() - session.startTime.getTime());
82102
const tok = formatTokenCount(session.stats.tokens);
83103

104+
// Current context size (what was sent to the API on the most recent turn)
105+
const ctxSize = session.stats.lastContextSize;
106+
const ctxStr = ctxSize > 1000 ? formatContext(ctxSize) : '';
107+
const ctxCol = contextColor(ctxSize);
108+
const ctxPulse = ctxSize >= CTX_CRITICAL && !pulse; // blink when critical
109+
84110
return (
85111
<Box key={session.sessionId || `${session.projectPath}:${i}`} paddingX={1}>
86112
<Text
@@ -93,10 +119,12 @@ export const ConversationPane = React.memo(function ConversationPane({
93119
<Text color={pulse ? dotColor : INK_COLORS.textDim}>{'●'}</Text>
94120
{' '}
95121
<Text color={INK_COLORS.textDim}>
96-
{action.slice(0, Math.max(4, innerWidth - nameWidth - tok.length - dur.length - 8))}{' '}
97122
{dur}{' '}
98123
</Text>
99124
<Text color={INK_COLORS.accent}>{tok}</Text>
125+
{ctxStr ? (
126+
<Text color={ctxPulse ? INK_COLORS.textDim : ctxCol}>{' '}[{ctxStr}]</Text>
127+
) : null}
100128
</Text>
101129
</Box>
102130
);

packages/cli/src/tui/components/StatusBar.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ interface StatusBarProps {
1919
scanning?: boolean;
2020
leftPaneMode?: string;
2121
updateAvailable?: string | null;
22+
selectedConversation?: { stats: { lastContextSize: number } } | null;
2223
}
2324

24-
export const StatusBar = React.memo(function StatusBar({ mode, stats, width, focusPane, launchMsg, usageBudget, scanning, leftPaneMode, updateAvailable }: StatusBarProps) {
25+
export const StatusBar = React.memo(function StatusBar({ mode, stats, width, focusPane, launchMsg, usageBudget, scanning, leftPaneMode, updateAvailable, selectedConversation }: StatusBarProps) {
2526
// Animated counters — count up smoothly when stats change
2627
const animatedTokens = useAnimatedCounter(stats?.tokens ?? 0, 1500);
2728
const animatedMessages = useAnimatedCounter(stats?.messages ?? 0, 800);
@@ -55,6 +56,16 @@ export const StatusBar = React.memo(function StatusBar({ mode, stats, width, foc
5556
: [['j/k', 'nav'], ['/', 'filter'], ['Tab', 'details'], ['l', 'live'], ['S', 'scan'], ['H', 'hidden'], [',', 'settings'], ['?', 'help'], ['q', 'quit']];
5657
const hintsText = hintPairs.map(([k, l]) => `${k}:${l}`).join(' ');
5758

59+
// Context size for selected conversation
60+
let ctxInfo = '';
61+
if (leftPaneMode === 'conversations' && selectedConversation) {
62+
const ctxSize = selectedConversation.stats.lastContextSize;
63+
if (ctxSize > 1000) {
64+
const ctxStr = ctxSize >= 1_000_000 ? `${(ctxSize / 1_000_000).toFixed(1)}M` : `${Math.round(ctxSize / 1_000)}K`;
65+
ctxInfo = `ctx:${ctxStr}`;
66+
}
67+
}
68+
5869
// Compact right-side: [Tier] X% or basic stats (full bars live in ProjectPane)
5970
const hasLiveData = usageBudget?.rateLimits != null;
6071
const hasBudget = usageBudget && usageBudget.limit > 0;
@@ -79,6 +90,11 @@ export const StatusBar = React.memo(function StatusBar({ mode, stats, width, foc
7990
rightSide = `${animatedMessages} msgs · ${formatTokenCount(animatedTokens)} tok`;
8091
}
8192

93+
// Append context size when viewing conversations
94+
if (ctxInfo) {
95+
rightSide = rightSide ? `${ctxInfo} · ${rightSide}` : ctxInfo;
96+
}
97+
8298
// Truncate hints if they'd overlap with rightSide
8399
const rightLen = rightSide ? tierPrefix.length + rightSide.length + 2 : 0;
84100
const maxHintLen = Math.max(10, width - 2 - rightLen);

packages/cli/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export interface SessionActivity {
146146
hourlyActivity: number[]; // 24 elements, hourly message counts
147147
assistantTurns: number; // total assistant API responses (each is an API round-trip)
148148
toolUseTurns: number; // assistant turns that contained at least one tool_use block
149+
lastContextSize: number; // most recent turn's context size (cache_read + input + cache_write)
149150
}
150151

151152
// ── Git commits ────────────────────────────────────────────

0 commit comments

Comments
 (0)