Unify TUI slash commands and harden tool inventory responses

This commit is contained in:
William Valentin
2026-02-21 12:39:27 -08:00
parent e9cb1d7c1a
commit b09bfc8373
16 changed files with 505 additions and 21 deletions
+88 -15
View File
@@ -1,5 +1,5 @@
import type { Command } from 'commander';
import type { Config, ModelConfig, ModelProvider } from '../config/index.js';
import type { Config, CouncilsConfig, ModelConfig, ModelProvider } from '../config/index.js';
import { loadConfigSafe, getConfigPath } from './shared.js';
import { existsSync, mkdirSync, readFileSync } from 'fs';
import { resolve } from 'path';
@@ -110,12 +110,14 @@ export function registerTuiCommand(program: Command): void {
createGtasksTools,
createAgentsListTool,
createAgentDelegateTool,
createCouncilRunTool,
} = await import('../tools/index.js');
const { HookEngine } = await import('../hooks/index.js');
const { Lifecycle } = await import('../daemon/lifecycle.js');
const { initTools } = await import('../daemon/tools.js');
const { createModelRouter } = await import('../daemon/index.js');
const { AgentConfigRegistry } = await import('../agents/index.js');
const { loadCouncilScaffoldSafe } = await import('../councils/scaffold.js');
const dataDir = process.env.FLYNN_DATA_DIR ?? resolve(homedir(), '.local/share/flynn');
mkdirSync(dataDir, { recursive: true });
@@ -182,25 +184,39 @@ export function registerTuiCommand(program: Command): void {
const agentConfigRegistry = new AgentConfigRegistry();
agentConfigRegistry.loadFromConfig(config.agent_configs);
const delegateRunner = {
async delegate(request: {
tier: 'fast' | 'default' | 'complex' | 'local';
systemPrompt: string;
message: string;
maxTokens?: number;
}) {
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,
};
},
};
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,
};
},
},
orchestrator: delegateRunner,
}));
if (config.councils?.enabled) {
toolRegistry.register(createCouncilRunTool({
registry: agentConfigRegistry,
orchestrator: delegateRunner,
config: config.councils as CouncilsConfig,
scaffold: loadCouncilScaffoldSafe(config.councils.scaffold_path),
}));
}
}
const session = sessionManager.getSession('tui', 'local');
@@ -296,6 +312,54 @@ export function registerTuiCommand(program: Command): void {
return `Unknown transfer target: ${target}. Supported targets: tui, telegram`;
};
const listAvailableTools = (): string => {
const names = toolRegistry.filteredList().map((tool) => tool.name).sort();
return [
`Available tools (${names.length}):`,
...names.map((name) => `- ${name}`),
].join('\n');
};
const delegateToResearchAgent = async (task: string): Promise<string> => {
const message = task.trim();
if (!message) {
return 'Usage: /research <question or task>';
}
const agentConfig = agentConfigRegistry.get('research');
if (!agentConfig) {
return 'Agent "research" not found. Configure agent_configs.research first.';
}
const tier = agentConfig.modelTier ?? 'default';
const systemPrompt = agentConfig.systemPrompt
?? 'You are a research sub-agent. Produce concise, source-grounded findings.';
const result = await delegateRunner.delegate({
tier,
systemPrompt,
message,
maxTokens: 4096,
});
return `[Agent: research | Tier: ${result.tier} | Tokens: ${result.usage.inputTokens}+${result.usage.outputTokens}]\n\n${result.content}`;
};
const runCouncilTask = async (task: string): Promise<string> => {
const message = task.trim();
if (!message) {
return 'Usage: /council <question or task>';
}
if (!config.councils?.enabled) {
return 'Councils are disabled. Set councils.enabled: true in config.';
}
const tool = toolRegistry.get('council.run');
if (!tool) {
return 'Council tool is not registered. Verify councils config and restart Flynn.';
}
const result = await tool.execute({ task: message });
if (!result.success) {
return `Council run failed: ${result.error ?? 'unknown error'}`;
}
return result.output;
};
if (opts.fullscreen) {
await startFullscreenTui({
session,
@@ -311,6 +375,9 @@ export function registerTuiCommand(program: Command): void {
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
onTransfer: transferSessionToTarget,
onTools: listAvailableTools,
onResearch: delegateToResearchAgent,
onCouncil: runCouncilTask,
onExit: () => {
void cleanup();
},
@@ -326,6 +393,9 @@ export function registerTuiCommand(program: Command): void {
agent,
hookEngine,
pairingManager,
onTools: listAvailableTools,
onResearch: delegateToResearchAgent,
onCouncil: runCouncilTask,
localProviders: config.models.local_providers,
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
@@ -357,6 +427,9 @@ export function registerTuiCommand(program: Command): void {
modelProviderConfigs,
contextThresholdPct: config.compaction.threshold_pct,
onTransfer: transferSessionToTarget,
onTools: listAvailableTools,
onResearch: delegateToResearchAgent,
onCouncil: runCouncilTask,
onExit: () => {
void cleanup();
},