Wire agent.delegate into TUI tool registry
This commit is contained in:
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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' });
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user