feat(tui): use configured compaction threshold for /context output
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user