diff --git a/src/frontends/tui/minimal.ts b/src/frontends/tui/minimal.ts index 0d37380..791142e 100644 --- a/src/frontends/tui/minimal.ts +++ b/src/frontends/tui/minimal.ts @@ -2,6 +2,7 @@ import * as readline from 'node:readline'; import type { ManagedSession } from '../../session/index.js'; import type { ModelClient, TokenUsage } from '../../models/types.js'; import type { ModelRouter, ModelTier } from '../../models/router.js'; +import type { NativeAgent } from '../../backends/native/agent.js'; import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions, getCommandTooltip, type Command } from './commands.js'; import { renderMarkdown } from './markdown.js'; import type { ModelConfig } from '../../config/schema.js'; @@ -32,6 +33,7 @@ export interface MinimalTuiConfig { modelClient: ModelClient; modelRouter?: ModelRouter; systemPrompt: string; + agent?: NativeAgent; onFullscreen?: () => void; onTransfer?: (target: string) => void; localProviders?: Record; @@ -285,12 +287,22 @@ export class MinimalTui { } private async handleMessage(content: string): Promise { - this.config.session.addMessage({ role: 'user', content }); - // Print Flynn label before response process.stdout.write(`\n${colors.orange}${colors.bold}Flynn:${colors.reset}\n`); try { + // Use agent if available (supports tool loop) + if (this.config.agent) { + const response = await this.config.agent.process(content); + const rendered = renderMarkdown(response); + console.log(rendered); + console.log(); + return; + } + + // Fallback: direct model client (no tool support) + this.config.session.addMessage({ role: 'user', content }); + // Try streaming if available if (this.config.modelClient.chatStream) { let fullContent = ''; diff --git a/src/tui.ts b/src/tui.ts index 604151e..b7ea742 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -2,17 +2,40 @@ import { loadConfig } from './config/index.js'; import { SessionStore, SessionManager } from './session/index.js'; import { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, ModelRouter } from './models/index.js'; import { MinimalTui, startFullscreenTui } from './frontends/tui/index.js'; +import { NativeAgent } from './backends/index.js'; +import { ToolRegistry, ToolExecutor, allBuiltinTools } from './tools/index.js'; +import { HookEngine } from './hooks/index.js'; import type { Config } from './config/index.js'; import { resolve } from 'path'; import { homedir } from 'os'; -import { existsSync, mkdirSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync } from 'fs'; const CONFIG_PATH = process.env.FLYNN_CONFIG ?? resolve(homedir(), '.config/flynn/config.yaml'); -const SYSTEM_PROMPT = `You are Flynn, a helpful personal AI assistant. You are direct, concise, and helpful. You can help with a variety of tasks including answering questions, providing information, and having conversations. +// ANSI color codes for tool status display +const toolColors = { + reset: '\x1b[0m', + dim: '\x1b[2m', + cyan: '\x1b[36m', + green: '\x1b[32m', + red: '\x1b[31m', +}; -Keep responses focused and avoid unnecessary verbosity. Use markdown formatting when it improves readability.`; +function loadSystemPrompt(): string { + const paths = [ + resolve(process.cwd(), 'SOUL.md'), + resolve(import.meta.dirname, '../SOUL.md'), + ]; + + for (const soulPath of paths) { + if (existsSync(soulPath)) { + return readFileSync(soulPath, 'utf-8'); + } + } + + return 'You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.'; +} function createModelRouter(config: Config): ModelRouter { const models = config.models; @@ -99,10 +122,40 @@ async function main() { const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db')); const sessionManager = new SessionManager(sessionStore); const modelRouter = createModelRouter(config); + const systemPrompt = loadSystemPrompt(); + + // Initialize tool registry and executor + const hookEngine = new HookEngine(config.hooks); + const toolRegistry = new ToolRegistry(); + for (const tool of allBuiltinTools) { + toolRegistry.register(tool); + } + const toolExecutor = new ToolExecutor(toolRegistry, hookEngine); // Get TUI session const session = sessionManager.getSession('tui', 'local'); + // Create agent with tools and tool status display + const agent = new NativeAgent({ + modelClient: modelRouter, + systemPrompt, + session, + toolRegistry, + toolExecutor, + onToolUse: (event) => { + if (event.type === 'start') { + const argsStr = event.args ? ` ${toolColors.dim}${JSON.stringify(event.args)}${toolColors.reset}` : ''; + process.stdout.write(`${toolColors.cyan}> ${event.tool}${toolColors.reset}${argsStr}\n`); + } else if (event.type === 'end' && event.result) { + const icon = event.result.success ? `${toolColors.green}done` : `${toolColors.red}error`; + const detail = event.result.success + ? `${toolColors.dim}(${event.result.output.split('\n').length} lines)${toolColors.reset}` + : `${toolColors.dim}${event.result.error ?? 'unknown error'}${toolColors.reset}`; + process.stdout.write(` ${icon}${toolColors.reset} ${detail}\n`); + } + }, + }); + const cleanup = () => { sessionStore.close(); }; @@ -118,7 +171,7 @@ async function main() { session, modelClient: modelRouter, modelRouter, - systemPrompt: SYSTEM_PROMPT, + systemPrompt: systemPrompt, model: config.models.default.model, onExit: cleanup, }); @@ -130,7 +183,8 @@ async function main() { session, modelClient: modelRouter, modelRouter, - systemPrompt: SYSTEM_PROMPT, + systemPrompt, + agent, localProviders: config.models.local_providers, currentLocalProvider: config.models.local?.provider, onTransfer: (target) => { @@ -156,7 +210,7 @@ async function main() { session, modelClient: modelRouter, modelRouter, - systemPrompt: SYSTEM_PROMPT, + systemPrompt, model: config.models.default.model, onExit: cleanup, });