fix(tui): show tool activity in fullscreen mode via Ink-compatible callback
Replace process.stdout.write-based onToolUse callback (which corrupts Ink rendering) with a React state-driven approach that shows tool names, args, and completion status in the streaming content area.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { Box, useApp, useInput } from 'ink';
|
||||
import { StatusBar } from './StatusBar.js';
|
||||
import { MessageList } from './MessageList.js';
|
||||
@@ -8,6 +8,34 @@ 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';
|
||||
import type { ToolUseEvent } from '../../../backends/native/agent.js';
|
||||
|
||||
/** Format a tool name like "gmail.list" -> "Gmail: List" */
|
||||
function formatToolName(name: string): string {
|
||||
const parts = name.split('.');
|
||||
return parts.map((p, i) => {
|
||||
const capitalized = p.charAt(0).toUpperCase() + p.slice(1);
|
||||
return i === 0 && parts.length > 1 ? capitalized + ':' : capitalized;
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
/** Format tool args as a compact, readable summary. */
|
||||
function formatToolArgs(args: unknown): string {
|
||||
if (!args || typeof args !== 'object') return '';
|
||||
const entries = Object.entries(args as Record<string, unknown>);
|
||||
if (entries.length === 0) return '';
|
||||
const parts = entries.map(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
const display = value.length > 50 ? value.slice(0, 47) + '...' : value;
|
||||
return `${key}: "${display}"`;
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return `${key}: ${value}`;
|
||||
}
|
||||
return `${key}: ${JSON.stringify(value)}`;
|
||||
});
|
||||
return parts.join(', ');
|
||||
}
|
||||
|
||||
export interface AppProps {
|
||||
session: ManagedSession;
|
||||
@@ -37,6 +65,35 @@ export function App({
|
||||
const [tokenUsage, setTokenUsage] = useState<TokenUsage>({ inputTokens: 0, outputTokens: 0 });
|
||||
const [currentModel, setCurrentModel] = useState(model);
|
||||
const abortRef = useRef(false);
|
||||
const toolLinesRef = useRef<string[]>([]);
|
||||
|
||||
// Set up an Ink-compatible onToolUse callback for the agent.
|
||||
// This replaces the process.stdout.write callback (which corrupts Ink rendering)
|
||||
// with one that updates React state to show tool activity in the streaming area.
|
||||
useEffect(() => {
|
||||
if (!agent) return;
|
||||
|
||||
const handleToolEvent = (event: ToolUseEvent) => {
|
||||
if (event.type === 'start') {
|
||||
const label = formatToolName(event.tool);
|
||||
const argsStr = event.args ? ` (${formatToolArgs(event.args)})` : '';
|
||||
toolLinesRef.current = [...toolLinesRef.current, `> ${label}${argsStr}`];
|
||||
setStreamingContent(toolLinesRef.current.join('\n'));
|
||||
} else if (event.type === 'end' && event.result) {
|
||||
const icon = event.result.success ? 'done' : 'error';
|
||||
const detail = event.result.success
|
||||
? `(${event.result.output.split('\n').length} lines)`
|
||||
: (event.result.error ?? 'unknown error');
|
||||
toolLinesRef.current = [...toolLinesRef.current, ` ${icon} ${detail}`];
|
||||
setStreamingContent(toolLinesRef.current.join('\n'));
|
||||
}
|
||||
};
|
||||
|
||||
agent.setOnToolUse(handleToolEvent);
|
||||
return () => {
|
||||
agent.setOnToolUse(undefined);
|
||||
};
|
||||
}, [agent]);
|
||||
|
||||
useInput((inputChar, key) => {
|
||||
if (key.escape) {
|
||||
@@ -172,6 +229,7 @@ export function App({
|
||||
// Process response
|
||||
setIsStreaming(true);
|
||||
setStreamingContent('');
|
||||
toolLinesRef.current = [];
|
||||
abortRef.current = false;
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user