feat(tui): use configured compaction threshold for /context output

This commit is contained in:
William Valentin
2026-02-16 18:10:54 -08:00
parent 409ab04ca1
commit fc6a79ed90
7 changed files with 53 additions and 4 deletions
+1
View File
@@ -439,6 +439,7 @@ pnpm tui:fs
| `/status` | Show session info |
| `/compact` | Compact conversation context |
| `/usage` | Show token usage and cost |
| `/context` | Show estimated context-window usage |
| `/verbose` | Toggle verbose output mode |
| `/pair` | Generate/list/revoke DM pairing codes |
| `/fullscreen` | Switch to fullscreen mode |
+3 -2
View File
@@ -3505,7 +3505,7 @@
"status": "completed",
"date": "2026-02-16",
"updated": "2026-02-17",
"summary": "Implemented proactive context-window management end-to-end: orchestrator now exposes estimated context budget, emits staged context alerts, writes checkpoint summaries to memory near threshold, and can auto-compact proactively. Gateway now emits `context_warning` stream events during `agent.send`, serves `system.contextUsage` snapshots, and dashboard usage UI includes context budget visibility. Added config schema support under `compaction.proactive`, mapped runtime wiring in both WS SessionBridge and channel routing paths, and updated protocol/docs/default config examples with focused tests. Follow-up added `/context` command fast-path visibility, TUI parser/help/autocomplete + handler parity for `/context`, dedicated audit events for proactive checkpoint writes and proactive auto-compaction, and operator/docs references for those events.",
"summary": "Implemented proactive context-window management end-to-end: orchestrator now exposes estimated context budget, emits staged context alerts, writes checkpoint summaries to memory near threshold, and can auto-compact proactively. Gateway now emits `context_warning` stream events during `agent.send`, serves `system.contextUsage` snapshots, and dashboard usage UI includes context budget visibility. Added config schema support under `compaction.proactive`, mapped runtime wiring in both WS SessionBridge and channel routing paths, and updated protocol/docs/default config examples with focused tests. Follow-up added `/context` command fast-path visibility, TUI parser/help/autocomplete + handler parity for `/context`, dedicated audit events for proactive checkpoint writes and proactive auto-compaction, configurable `/context` threshold display wired from runtime `compaction.threshold_pct`, and operator/docs references for those events.",
"files_modified": [
"src/context/compaction.ts",
"src/backends/native/prompts.ts",
@@ -3529,6 +3529,7 @@
"src/frontends/tui/commands.ts",
"src/frontends/tui/commands.test.ts",
"src/frontends/tui/minimal.ts",
"src/frontends/tui/minimal.test.ts",
"src/frontends/tui/components/App.tsx",
"src/commands/builtin/index.ts",
"src/commands/types.ts",
@@ -3543,7 +3544,7 @@
"config/default.yaml",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/backends/native/orchestrator.test.ts src/config/schema.test.ts src/gateway/handlers/agent.test.ts src/gateway/handlers/handlers.test.ts src/gateway/protocol.test.ts src/commands/builtin/index.test.ts src/frontends/tui/commands.test.ts + pnpm typecheck passing"
"test_status": "pnpm test:run src/backends/native/orchestrator.test.ts src/config/schema.test.ts src/gateway/handlers/agent.test.ts src/gateway/handlers/handlers.test.ts src/gateway/protocol.test.ts src/commands/builtin/index.test.ts src/frontends/tui/commands.test.ts src/frontends/tui/minimal.test.ts + pnpm typecheck passing"
}
},
"overall_progress": {
+3
View File
@@ -242,6 +242,7 @@ export function registerTuiCommand(program: Command): void {
agent,
hookEngine,
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
onExit: cleanup,
});
} else {
@@ -257,6 +258,7 @@ export function registerTuiCommand(program: Command): void {
pairingManager,
localProviders: config.models.local_providers,
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
currentLocalProvider: config.models.local?.provider,
onTransfer: (target) => {
if (target === 'telegram') {
@@ -290,6 +292,7 @@ export function registerTuiCommand(program: Command): void {
agent,
hookEngine,
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
onExit: cleanup,
});
return;
+3 -1
View File
@@ -50,6 +50,7 @@ export interface AppProps {
agent?: NativeAgent;
hookEngine?: HookEngine;
modelProviderConfigs?: Partial<Record<ModelProvider, ModelConfig>>;
contextThresholdPct?: number;
onExit?: () => void;
}
@@ -62,6 +63,7 @@ export function App({
agent,
hookEngine,
modelProviderConfigs,
contextThresholdPct,
onExit,
}: AppProps): React.ReactElement {
const { exit } = useApp();
@@ -247,7 +249,7 @@ export function App({
const modelName = modelRouter?.getLabel(tier) ?? model;
const window = getContextWindow(modelName);
const usagePct = window > 0 ? (estimated / window) * 100 : 0;
const thresholdPct = 80;
const thresholdPct = contextThresholdPct ?? 80;
const thresholdTokens = Math.floor((thresholdPct / 100) * window);
const remaining = Math.max(0, window - estimated);
const text = [
+2
View File
@@ -17,6 +17,7 @@ export interface FullscreenTuiConfig {
agent?: NativeAgent;
hookEngine?: HookEngine;
modelProviderConfigs?: Partial<Record<ModelProvider, ModelConfig>>;
contextThresholdPct?: number;
onExit?: () => void;
}
@@ -40,6 +41,7 @@ export async function startFullscreenTui(config: FullscreenTuiConfig): Promise<v
agent: config.agent,
hookEngine: config.hookEngine,
modelProviderConfigs: config.modelProviderConfigs,
contextThresholdPct: config.contextThresholdPct,
onExit: config.onExit,
}),
);
+39
View File
@@ -29,6 +29,7 @@ function asAgent(value: unknown): NativeAgent {
function minimalTuiPrivates(value: MinimalTui): {
handleBackendCommand: (provider: string) => Promise<void>;
handleModelCommand: (tier: string, providerModel?: string) => void;
handleContextCommand: () => void;
handleEscapeAction: () => boolean;
prompt: (text: string) => Promise<string>;
rl: {
@@ -43,6 +44,7 @@ function minimalTuiPrivates(value: MinimalTui): {
return value as unknown as {
handleBackendCommand: (provider: string) => Promise<void>;
handleModelCommand: (tier: string, providerModel?: string) => void;
handleContextCommand: () => void;
handleEscapeAction: () => boolean;
prompt: (text: string) => Promise<string>;
rl: {
@@ -174,6 +176,43 @@ describe('MinimalTui backend command', () => {
expect(mockAgent.setModelTier).toHaveBeenCalledWith('local');
});
it('uses configured compaction threshold in /context output', () => {
const mockSession = {
id: 'test',
getHistory: () => [{ role: 'user', content: 'x'.repeat(400) }],
addMessage: vi.fn(),
clear: vi.fn(),
replaceHistory: vi.fn(),
};
const mockRouter: TuiRouterStub = {
getTier: () => 'default' as const,
getAvailableTiers: () => ['default'],
setTier: vi.fn(() => true),
getLabel: () => 'gpt-4o',
getLocalProviderName: () => 'ollama',
setLocalClient: vi.fn(),
chat: vi.fn(),
getClient: vi.fn(),
};
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
try {
const tui = new MinimalTui({
session: asSession(mockSession),
modelClient: asRouter(mockRouter),
modelRouter: asRouter(mockRouter),
systemPrompt: 'test',
contextThresholdPct: 67,
});
minimalTuiPrivates(tui).handleContextCommand();
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('compaction threshold: 67%'));
} finally {
logSpy.mockRestore();
}
});
it('reuses configured provider credentials for /model <tier> <provider/model>', () => {
const prevOpenRouterKey = process.env.OPENROUTER_API_KEY;
delete process.env.OPENROUTER_API_KEY;
+2 -1
View File
@@ -68,6 +68,7 @@ export interface MinimalTuiConfig {
currentLocalProvider?: string;
pairingManager?: PairingManager;
hookEngine?: HookEngine;
contextThresholdPct?: number;
}
export class MinimalTui {
@@ -394,7 +395,7 @@ export class MinimalTui {
const modelName = this.config.modelRouter?.getLabel(tier) ?? 'unknown';
const window = getContextWindow(modelName);
const usagePct = window > 0 ? (estimated / window) * 100 : 0;
const thresholdPct = 80;
const thresholdPct = this.config.contextThresholdPct ?? 80;
const thresholdTokens = Math.floor((thresholdPct / 100) * window);
const remaining = Math.max(0, window - estimated);