Wire agent.delegate into TUI tool registry

This commit is contained in:
William Valentin
2026-02-17 11:03:55 -08:00
parent e3b6f9df7c
commit 8394086446
4 changed files with 74 additions and 8 deletions
+13
View File
@@ -3754,6 +3754,19 @@
"docs/plans/state.json" "docs/plans/state.json"
], ],
"test_status": "pnpm test:run src/frontends/tui/minimal.test.ts src/models/gemini.test.ts + pnpm typecheck passing" "test_status": "pnpm test:run src/frontends/tui/minimal.test.ts src/models/gemini.test.ts + pnpm typecheck passing"
},
"tui-agent-delegate-registration": {
"status": "completed",
"date": "2026-02-17",
"updated": "2026-02-17",
"summary": "Wired minimal/fullscreen TUI tool registry to register `agents.list` and `agent.delegate` when `agent_configs` exist. Added a TUI delegation bridge that executes delegated single-turn tasks through `ModelRouter` while preserving per-agent tier/system prompt behavior and keeping existing TUI `NativeAgent` flow unchanged.",
"files_modified": [
"src/cli/tui.ts",
"src/tools/builtin/agent-delegate.ts",
"src/tools/builtin/agent-delegate.test.ts",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/tools/builtin/agent-delegate.test.ts + pnpm typecheck passing"
} }
}, },
"overall_progress": { "overall_progress": {
+40 -1
View File
@@ -102,9 +102,25 @@ export function registerTuiCommand(program: Command): void {
setLogLevel(tuiLogLevel); setLogLevel(tuiLogLevel);
const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js'); const { MinimalTui, startFullscreenTui } = await import('../frontends/tui/index.js');
const { NativeAgent } = await import('../backends/index.js'); const { NativeAgent } = await import('../backends/index.js');
const { ToolRegistry, ToolExecutor, ToolPolicy, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager, createGmailTools, createGcalTools, createGdocsTools, createGdriveTools, createGtasksTools } = await import('../tools/index.js'); const {
ToolRegistry,
ToolExecutor,
ToolPolicy,
allBuiltinTools,
createWebSearchTools,
createProcessTools,
ProcessManager,
createGmailTools,
createGcalTools,
createGdocsTools,
createGdriveTools,
createGtasksTools,
createAgentsListTool,
createAgentDelegateTool,
} = await import('../tools/index.js');
const { HookEngine } = await import('../hooks/index.js'); const { HookEngine } = await import('../hooks/index.js');
const { createModelRouter } = await import('../daemon/index.js'); const { createModelRouter } = await import('../daemon/index.js');
const { AgentConfigRegistry } = await import('../agents/index.js');
const dataDir = process.env.FLYNN_DATA_DIR ?? resolve(homedir(), '.local/share/flynn'); const dataDir = process.env.FLYNN_DATA_DIR ?? resolve(homedir(), '.local/share/flynn');
mkdirSync(dataDir, { recursive: true }); mkdirSync(dataDir, { recursive: true });
@@ -194,6 +210,29 @@ export function registerTuiCommand(program: Command): void {
} }
} }
const agentConfigRegistry = new AgentConfigRegistry();
agentConfigRegistry.loadFromConfig(config.agent_configs);
if (agentConfigRegistry.list().length > 0) {
toolRegistry.register(createAgentsListTool(agentConfigRegistry));
toolRegistry.register(createAgentDelegateTool({
registry: agentConfigRegistry,
orchestrator: {
async delegate(request) {
const response = await modelRouter.chat({
messages: [{ role: 'user', content: request.message }],
system: request.systemPrompt,
maxTokens: request.maxTokens,
}, request.tier);
return {
content: response.content,
usage: response.usage,
tier: request.tier,
};
},
},
}));
}
toolRegistry.setPolicy(new ToolPolicy(config.tools)); toolRegistry.setPolicy(new ToolPolicy(config.tools));
const toolExecutor = new ToolExecutor(toolRegistry, hookEngine); const toolExecutor = new ToolExecutor(toolRegistry, hookEngine);
+6 -5
View File
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createAgentDelegateTool } from './agent-delegate.js'; import { createAgentDelegateTool } from './agent-delegate.js';
import type { AgentDelegateDeps } from './agent-delegate.js'; import type { AgentDelegateDeps } from './agent-delegate.js';
import type { AgentConfigRegistry } from '../../agents/registry.js'; import type { AgentConfigRegistry } from '../../agents/registry.js';
import type { AgentOrchestrator } from '../../backends/native/orchestrator.js';
function createMockRegistry(configs: Record<string, { systemPrompt?: string; modelTier?: string }>): AgentConfigRegistry { function createMockRegistry(configs: Record<string, { systemPrompt?: string; modelTier?: string }>): AgentConfigRegistry {
const entries = Object.entries(configs).map(([name, cfg]) => ({ const entries = Object.entries(configs).map(([name, cfg]) => ({
@@ -17,19 +16,21 @@ function createMockRegistry(configs: Record<string, { systemPrompt?: string; mod
} as unknown as AgentConfigRegistry; } as unknown as AgentConfigRegistry;
} }
function createMockOrchestrator(response?: { content: string; usage: { inputTokens: number; outputTokens: number }; tier: string }): AgentOrchestrator { function createMockOrchestrator(
response?: { content: string; usage: { inputTokens: number; outputTokens: number }; tier: 'fast' | 'default' | 'complex' | 'local' },
): AgentDelegateDeps['orchestrator'] {
return { return {
delegate: vi.fn().mockResolvedValue(response ?? { delegate: vi.fn().mockResolvedValue(response ?? {
content: 'Mock agent response', content: 'Mock agent response',
usage: { inputTokens: 100, outputTokens: 50 }, usage: { inputTokens: 100, outputTokens: 50 },
tier: 'default', tier: 'default',
}), }),
} as unknown as AgentOrchestrator; };
} }
describe('agent.delegate tool', () => { describe('agent.delegate tool', () => {
let deps: AgentDelegateDeps; let deps: AgentDelegateDeps;
let mockOrchestrator: AgentOrchestrator; let mockOrchestrator: AgentDelegateDeps['orchestrator'];
beforeEach(() => { beforeEach(() => {
mockOrchestrator = createMockOrchestrator(); mockOrchestrator = createMockOrchestrator();
@@ -127,7 +128,7 @@ describe('agent.delegate tool', () => {
it('handles orchestrator errors gracefully', async () => { it('handles orchestrator errors gracefully', async () => {
const failingOrchestrator = { const failingOrchestrator = {
delegate: vi.fn().mockRejectedValue(new Error('Model provider unavailable')), delegate: vi.fn().mockRejectedValue(new Error('Model provider unavailable')),
} as unknown as AgentOrchestrator; } as AgentDelegateDeps['orchestrator'];
const tool = createAgentDelegateTool({ ...deps, orchestrator: failingOrchestrator }); const tool = createAgentDelegateTool({ ...deps, orchestrator: failingOrchestrator });
const result = await tool.execute({ agent: 'research', task: 'This will fail' }); const result = await tool.execute({ agent: 'research', task: 'This will fail' });
+15 -2
View File
@@ -1,11 +1,24 @@
import type { Tool, ToolResult } from '../types.js'; import type { Tool, ToolResult } from '../types.js';
import type { AgentConfigRegistry } from '../../agents/registry.js'; import type { AgentConfigRegistry } from '../../agents/registry.js';
import type { AgentOrchestrator } from '../../backends/native/orchestrator.js';
import type { ModelTier } from '../../models/router.js'; import type { ModelTier } from '../../models/router.js';
import type { TokenUsage } from '../../models/types.js';
interface AgentDelegateRunner {
delegate(request: {
tier: ModelTier;
systemPrompt: string;
message: string;
maxTokens?: number;
}): Promise<{
content: string;
usage: TokenUsage;
tier: ModelTier;
}>;
}
export interface AgentDelegateDeps { export interface AgentDelegateDeps {
registry: AgentConfigRegistry; registry: AgentConfigRegistry;
orchestrator: AgentOrchestrator; orchestrator: AgentDelegateRunner;
} }
/** /**