feat(tui,dashboard,docs): add context command parity and context health panel

This commit is contained in:
William Valentin
2026-02-16 18:08:19 -08:00
parent 21d57d991c
commit 409ab04ca1
8 changed files with 146 additions and 4 deletions
+5
View File
@@ -35,6 +35,10 @@ describe('parseCommand', () => {
expect(parseCommand('/usage')).toEqual({ type: 'usage' });
});
it('parses /context command', () => {
expect(parseCommand('/context')).toEqual({ type: 'context' });
});
it('parses /verbose command', () => {
expect(parseCommand('/verbose')).toEqual({ type: 'verbose' });
});
@@ -117,6 +121,7 @@ describe('getHelpText', () => {
expect(help).toContain('/reset');
expect(help).toContain('/compact');
expect(help).toContain('/usage');
expect(help).toContain('/context');
expect(help).toContain('/verbose');
expect(help).toContain('/queue');
expect(help).toContain('/elevate');
+9
View File
@@ -6,6 +6,7 @@ export type Command =
| { type: 'fullscreen' }
| { type: 'compact' }
| { type: 'usage' }
| { type: 'context' }
| { type: 'verbose' }
| { type: 'model'; name?: string; providerModel?: string }
| { type: 'backend'; provider?: string }
@@ -55,6 +56,11 @@ export function parseCommand(input: string): Command | null {
return { type: 'usage' };
}
// Context
if (trimmed === '/context') {
return { type: 'context' };
}
// Verbose
if (trimmed === '/verbose') {
return { type: 'verbose' };
@@ -162,6 +168,7 @@ Commands:
/reset, /clear, /new Clear conversation history
/compact Compact conversation history
/usage Show token usage and estimated cost
/context Show estimated context-window usage
/verbose Toggle verbose mode (show raw streaming and tool output)
/status Show session info and token usage
/fullscreen, /fs Switch to fullscreen mode
@@ -184,6 +191,7 @@ export const SLASH_COMMANDS = [
'/new',
'/compact',
'/usage',
'/context',
'/verbose',
'/status',
'/fullscreen',
@@ -207,6 +215,7 @@ export const COMMAND_TOOLTIPS: Record<string, string> = {
'/new': 'Start a new conversation',
'/compact': 'Compact conversation history to save context space',
'/usage': 'Show token usage and estimated cost',
'/context': 'Show estimated context-window usage',
'/verbose': 'Toggle verbose mode (show raw streaming and tool output)',
'/status': 'Show session info and token usage',
'/fullscreen': 'Switch to fullscreen mode',
+24
View File
@@ -12,6 +12,7 @@ import type { HookEngine, HookResult } from '../../../hooks/index.js';
import type { ModelConfig, ModelProvider } from '../../../config/schema.js';
import { MODEL_PROVIDERS } from '../../../config/schema.js';
import { createClientFromConfig } from '../../../daemon/index.js';
import { estimateMessageTokens, getContextWindow } from '../../../context/tokens.js';
/** Format a tool name like "gmail.list" -> "Gmail: List" */
function formatToolName(name: string): string {
@@ -239,6 +240,29 @@ export function App({
return;
}
case 'context': {
const history = session.getHistory();
const estimated = estimateMessageTokens(history);
const tier = modelRouter?.getTier() ?? 'default';
const modelName = modelRouter?.getLabel(tier) ?? model;
const window = getContextWindow(modelName);
const usagePct = window > 0 ? (estimated / window) * 100 : 0;
const thresholdPct = 80;
const thresholdTokens = Math.floor((thresholdPct / 100) * window);
const remaining = Math.max(0, window - estimated);
const text = [
'Context Usage (estimated)',
'',
`Model: ${modelName}`,
`Used: ${estimated.toLocaleString()} / ${window.toLocaleString()} tokens (${usagePct.toFixed(1)}%)`,
`Remaining: ${remaining.toLocaleString()} tokens`,
`Compaction threshold: ${thresholdPct}% (${thresholdTokens.toLocaleString()} tokens)`,
`Should compact: ${estimated > thresholdTokens ? 'yes' : 'no'}`,
].join('\n');
setMessages(prev => [...prev, session.addMessage({ role: 'assistant', content: text })]);
return;
}
case 'verbose': {
const next = !verbose;
setVerbose(next);
+24
View File
@@ -25,6 +25,7 @@ import {
import type { PairingManager } from '../../channels/pairing.js';
import { getColoredBanner } from './banner.js';
import type { HookEngine } from '../../hooks/index.js';
import { estimateMessageTokens, getContextWindow } from '../../context/tokens.js';
export { parseCommand, type Command };
@@ -331,6 +332,10 @@ export class MinimalTui {
this.handleUsageCommand();
break;
case 'context':
this.handleContextCommand();
break;
case 'verbose':
this.handleVerboseCommand();
break;
@@ -382,6 +387,25 @@ export class MinimalTui {
this.printStatus();
}
private handleContextCommand(): void {
const history = this.config.session.getHistory();
const estimated = estimateMessageTokens(history);
const tier = this.config.modelRouter?.getTier() ?? 'default';
const modelName = this.config.modelRouter?.getLabel(tier) ?? 'unknown';
const window = getContextWindow(modelName);
const usagePct = window > 0 ? (estimated / window) * 100 : 0;
const thresholdPct = 80;
const thresholdTokens = Math.floor((thresholdPct / 100) * window);
const remaining = Math.max(0, window - estimated);
console.log(`${colors.gray}Context usage (estimated):${colors.reset}`);
console.log(` model: ${modelName}`);
console.log(` used: ${estimated.toLocaleString()} / ${window.toLocaleString()} tokens (${usagePct.toFixed(1)}%)`);
console.log(` remaining: ${remaining.toLocaleString()} tokens`);
console.log(` compaction threshold: ${thresholdPct}% (${thresholdTokens.toLocaleString()} tokens)`);
console.log(` should compact: ${estimated > thresholdTokens ? 'yes' : 'no'}\n`);
}
private handleVerboseCommand(): void {
this.verbose = !this.verbose;
console.log(`${colors.gray}Verbose mode:${colors.reset} ${this.verbose ? 'on' : 'off'}\n`);