feat: add multi-model delegation (Phase 0) and context compaction (Phase 1)

Phase 0 — Multi-Model Delegation:
- AgentOrchestrator wraps NativeAgent with delegate() for stateless
  single-turn calls to any model tier (fast/default/complex/local)
- DelegationConfig maps task types (compaction, classification, etc.)
  to model tiers
- Delegation prompts for compaction, memory extraction, classification,
  and tool summarisation
- Per-tier usage tracking for cost visibility
- Config schema: agents.delegation and agents.primary_tier

Phase 1 — Context Compaction:
- Token estimation (char/4 heuristic) with context window lookup
- shouldCompact() threshold check against context window percentage
- compactHistory() splits old/recent messages, delegates summary to
  fast tier, returns CompactionResult
- Automatic compaction in AgentOrchestrator.process() when configured
- Force-compact via orchestrator.compact() with session persistence
- Session.replaceHistory() with atomic SQLite transaction
- /compact TUI command with feedback on compacted token counts
- Config schema: compaction.enabled, threshold_pct, keep_turns,
  summary_max_tokens

Tests: 385 passing across 50 files (22 new tests in 2 new test files)
This commit is contained in:
William Valentin
2026-02-06 13:17:02 -08:00
parent f7cc87a4bb
commit 306e11bd2e
22 changed files with 1562 additions and 12 deletions
+5
View File
@@ -26,6 +26,10 @@ describe('parseCommand', () => {
expect(parseCommand('/fs')).toEqual({ type: 'fullscreen' });
});
it('parses /compact command', () => {
expect(parseCommand('/compact')).toEqual({ type: 'compact' });
});
it('parses /model command without argument', () => {
expect(parseCommand('/model')).toEqual({ type: 'model' });
});
@@ -64,6 +68,7 @@ describe('getHelpText', () => {
expect(help).toContain('/help');
expect(help).toContain('/model');
expect(help).toContain('/reset');
expect(help).toContain('/compact');
expect(help).toContain('/quit');
});
});
+9
View File
@@ -4,6 +4,7 @@ export type Command =
| { type: 'help' }
| { type: 'status' }
| { type: 'fullscreen' }
| { type: 'compact' }
| { type: 'model'; name?: string }
| { type: 'backend'; provider?: string }
| { type: 'transfer'; target: string }
@@ -38,6 +39,11 @@ export function parseCommand(input: string): Command | null {
return { type: 'fullscreen' };
}
// Compact
if (trimmed === '/compact') {
return { type: 'compact' };
}
// Model (with optional argument)
if (trimmed === '/model') {
return { type: 'model' };
@@ -73,6 +79,7 @@ Commands:
/model [name] Show or switch model (local, default, fast, complex)
/backend [provider] Show or switch local backend (ollama, llamacpp)
/reset, /clear, /new Clear conversation history
/compact Compact conversation history
/status Show session info and token usage
/fullscreen, /fs Switch to fullscreen mode
/transfer <dest> Transfer session to another frontend
@@ -90,6 +97,7 @@ export const SLASH_COMMANDS = [
'/reset',
'/clear',
'/new',
'/compact',
'/status',
'/fullscreen',
'/fs',
@@ -106,6 +114,7 @@ export const COMMAND_TOOLTIPS: Record<string, string> = {
'/reset': 'Clear conversation history',
'/clear': 'Clear conversation history',
'/new': 'Start a new conversation',
'/compact': 'Compact conversation history to save context space',
'/status': 'Show session info and token usage',
'/fullscreen': 'Switch to fullscreen mode',
'/fs': 'Switch to fullscreen mode',
+2
View File
@@ -44,6 +44,7 @@ describe('MinimalTui backend command', () => {
getHistory: () => [],
addMessage: vi.fn(),
clear: vi.fn(),
replaceHistory: vi.fn(),
};
const mockRouter = {
@@ -84,6 +85,7 @@ describe('MinimalTui backend command', () => {
getHistory: () => [],
addMessage: vi.fn(),
clear: vi.fn(),
replaceHistory: vi.fn(),
};
const mockRouter = {