From e46e8740a186eff4c4acc57c1b43ea1ce2b10c9c Mon Sep 17 00:00:00 2001 From: William Valentin Date: Tue, 10 Feb 2026 13:21:22 -0800 Subject: [PATCH] fix(tui): enable tool access in fullscreen mode via NativeAgent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fullscreen TUI was calling modelClient directly, bypassing the NativeAgent tool loop entirely. Pass the agent through FullscreenTuiConfig → App and use agent.process() for message handling, which enables the full tool registry and executor. --- src/cli/tui.ts | 2 ++ src/frontends/tui/components/App.tsx | 31 +++++++++++++++++++++------- src/frontends/tui/fullscreen.ts | 3 +++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/cli/tui.ts b/src/cli/tui.ts index 559daf5..063eea7 100644 --- a/src/cli/tui.ts +++ b/src/cli/tui.ts @@ -216,6 +216,7 @@ export function registerTuiCommand(program: Command): void { modelRouter, systemPrompt, model: config.models.default.model, + agent, onExit: cleanup, }); } else { @@ -259,6 +260,7 @@ export function registerTuiCommand(program: Command): void { modelRouter, systemPrompt, model: config.models.default.model, + agent, onExit: cleanup, }); return; diff --git a/src/frontends/tui/components/App.tsx b/src/frontends/tui/components/App.tsx index 6dfb02f..199162f 100644 --- a/src/frontends/tui/components/App.tsx +++ b/src/frontends/tui/components/App.tsx @@ -7,6 +7,7 @@ import { parseCommand, getHelpText, resolveModelAlias, getCommandCompletions } f import type { Message, ModelClient, TokenUsage } from '../../../models/types.js'; import type { ModelRouter } from '../../../models/router.js'; import type { ManagedSession } from '../../../session/index.js'; +import type { NativeAgent } from '../../../backends/native/agent.js'; export interface AppProps { session: ManagedSession; @@ -14,6 +15,7 @@ export interface AppProps { modelRouter?: ModelRouter; systemPrompt: string; model: string; + agent?: NativeAgent; onExit?: () => void; } @@ -23,6 +25,7 @@ export function App({ modelRouter, systemPrompt, model, + agent, onExit, }: AppProps): React.ReactElement { const { exit } = useApp(); @@ -156,19 +159,33 @@ export function App({ if (command.type !== 'message' || isStreaming) return; - // Add user message + // Add user message to UI (and session if no agent — agent adds it internally) const userMessage: Message = { role: 'user', content: command.content }; - const messageWithTimestamp = session.addMessage(userMessage); - setMessages(prev => [...prev, messageWithTimestamp]); + if (!agent) { + const messageWithTimestamp = session.addMessage(userMessage); + setMessages(prev => [...prev, messageWithTimestamp]); + } else { + setMessages(prev => [...prev, { ...userMessage, timestamp: Date.now() }]); + } setScrollOffset(0); // Auto-scroll to bottom - // Stream response + // Process response setIsStreaming(true); setStreamingContent(''); abortRef.current = false; try { - if (modelClient.chatStream) { + if (agent) { + // agent.process() handles session history internally + const response = await agent.process(command.content); + + const usage = agent.getUsage(); + setTokenUsage({ inputTokens: usage.inputTokens, outputTokens: usage.outputTokens }); + + // Sync UI with session history (agent already added messages to session) + setMessages(session.getHistory()); + } else if (modelClient.chatStream) { + // Fallback: direct streaming without tools let fullContent = ''; for await (const event of modelClient.chatStream({ @@ -199,7 +216,7 @@ export function App({ const assistantWithTimestamp = session.addMessage(assistantMessage); setMessages(prev => [...prev, assistantWithTimestamp]); } else { - // Fallback to non-streaming + // Fallback: non-streaming without tools const response = await modelClient.chat({ messages: session.getHistory(), system: systemPrompt, @@ -225,7 +242,7 @@ export function App({ setIsStreaming(false); setStreamingContent(''); } - }, [isStreaming, session, modelClient, modelRouter, systemPrompt, exit, onExit, messages.length, tokenUsage]); + }, [isStreaming, session, agent, modelClient, modelRouter, systemPrompt, exit, onExit, messages.length, tokenUsage]); return ( diff --git a/src/frontends/tui/fullscreen.ts b/src/frontends/tui/fullscreen.ts index a7703a3..0ffa108 100644 --- a/src/frontends/tui/fullscreen.ts +++ b/src/frontends/tui/fullscreen.ts @@ -4,6 +4,7 @@ import { App } from './components/index.js'; import type { ManagedSession } from '../../session/index.js'; import type { ModelClient } from '../../models/types.js'; import type { ModelRouter } from '../../models/router.js'; +import type { NativeAgent } from '../../backends/native/agent.js'; export interface FullscreenTuiConfig { session: ManagedSession; @@ -11,6 +12,7 @@ export interface FullscreenTuiConfig { modelRouter?: ModelRouter; systemPrompt: string; model: string; + agent?: NativeAgent; onExit?: () => void; } @@ -27,6 +29,7 @@ export async function startFullscreenTui(config: FullscreenTuiConfig): Promise