@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
553 lines • 31.3 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Box, Text, useApp } from 'ink';
import Spinner from 'ink-spinner';
import React, { useEffect, useMemo } from 'react';
import { createStaticComponents } from '../app/components/app-container.js';
import { ChatHistory } from '../app/components/chat-history.js';
import { ChatInput } from '../app/components/chat-input.js';
import { ModalSelectors } from '../app/components/modal-selectors.js';
import { FileExplorer } from '../components/file-explorer.js';
import { IdeSelector } from '../components/ide-selector.js';
import { SuccessMessage } from '../components/message-box.js';
import { SchedulerView } from '../components/scheduler-view.js';
import SecurityDisclaimer from '../components/security-disclaimer.js';
import { shouldPromptExtensionInstall, VSCodeExtensionPrompt, } from '../components/vscode-extension-prompt.js';
import WelcomeMessage from '../components/welcome-message.js';
import { updateSelectedTheme } from '../config/preferences.js';
import { getThemeColors } from '../config/themes.js';
import { setCurrentMode as setCurrentModeContext } from '../context/mode-context.js';
import { useChatHandler } from '../hooks/chat-handler/index.js';
import { useAppHandlers } from '../hooks/useAppHandlers.js';
import { useAppInitialization } from '../hooks/useAppInitialization.js';
import { useAppState } from '../hooks/useAppState.js';
import { useContextPercentage } from '../hooks/useContextPercentage.js';
import { useDirectoryTrust } from '../hooks/useDirectoryTrust.js';
import { useModeHandlers } from '../hooks/useModeHandlers.js';
import { useNonInteractiveMode } from '../hooks/useNonInteractiveMode.js';
import { useSchedulerMode } from '../hooks/useSchedulerMode.js';
import { useSessionAutosave } from '../hooks/useSessionAutosave.js';
import { ThemeContext } from '../hooks/useTheme.js';
import { TitleShapeContext, updateTitleShape } from '../hooks/useTitleShape.js';
import { useToolHandler } from '../hooks/useToolHandler.js';
import { UIStateProvider } from '../hooks/useUIState.js';
import { useVSCodeServer } from '../hooks/useVSCodeServer.js';
import { generateCorrelationId, withNewCorrelationContext, } from '../utils/logging/index.js';
import { createPinoLogger } from '../utils/logging/pino-logger.js';
import { setGlobalMessageQueue } from '../utils/message-queue.js';
import { setGlobalQuestionHandler, } from '../utils/question-queue.js';
import { getShutdownManager } from '../utils/shutdown/index.js';
import { displayCompactCountsSummary } from '../utils/tool-result-display.js';
import { isExtensionInstalled } from '../vscode/extension-installer.js';
import { shouldRenderWelcome } from './helpers.js';
export default function App({ vscodeMode = false, vscodePort, nonInteractivePrompt, nonInteractiveMode = false, cliProvider, cliModel, }) {
// Memoize the logger to prevent recreation on every render
const logger = useMemo(() => createPinoLogger(), []);
// Log application startup with key configuration
React.useEffect(() => {
logger.info('Nanocoder application starting', {
vscodeMode,
vscodePort,
nodeEnv: process.env.NODE_ENV || 'development',
platform: process.platform,
pid: process.pid,
});
}, [logger, vscodeMode, vscodePort]);
// Use extracted hooks
const appState = useAppState();
const { exit } = useApp();
const { isTrusted, handleConfirmTrust, isTrustLoading, isTrustedError } = useDirectoryTrust();
// Sync global mode context whenever development mode changes.
// Note: This useEffect serves as a backup synchronization mechanism.
// Primary synchronization happens synchronously at the call sites:
// - useNonInteractiveMode.ts: setCurrentModeContext() called with setDevelopmentMode()
// - useToolHandler.tsx: setCurrentModeContext() called with setDevelopmentMode()
// - useAppHandlers.tsx: setCurrentModeContext() called within handleToggleDevelopmentMode()
// This effect ensures the global context stays in sync even if new code paths
// are added that update React state without updating the global context.
React.useEffect(() => {
setCurrentModeContext(appState.developmentMode);
logger.info('Development mode changed', {
newMode: appState.developmentMode,
previousMode: undefined,
});
}, [appState.developmentMode, logger]);
// VS Code extension installation prompt state
const [showExtensionPrompt, setShowExtensionPrompt] = React.useState(() => vscodeMode && shouldPromptExtensionInstall());
const [extensionPromptComplete, setExtensionPromptComplete] = React.useState(false);
const handleExit = () => {
exit();
void getShutdownManager().gracefulShutdown(0);
};
// VS Code server integration
// Reference to handleMessageSubmit that will be set after appHandlers is created
const handleMessageSubmitRef = React.useRef(null);
const handleVSCodePrompt = React.useCallback((prompt, context) => {
const correlationId = generateCorrelationId();
logger.info('VS Code prompt received', {
promptLength: prompt.length,
hasContext: !!context,
filePath: context?.filePath,
hasSelection: !!context?.selection,
cursorPosition: context?.cursorPosition,
correlationId,
});
// Build the full prompt with code context for the LLM
let fullPrompt = prompt;
if (context?.selection && context?.fileName) {
const lineInfo = context.startLine && context.endLine
? ` (lines ${context.startLine}-${context.endLine})`
: '';
// Format: question + placeholder tag (for display) + hidden code block (for LLM)
// The placeholder tag [@filename] will be highlighted in the UI
// The code block is included for the LLM but won't clutter the display
fullPrompt = `${prompt}\n\n[@${context.fileName}${lineInfo}]<!--vscode-context-->\n\`\`\`\n${context.selection}\n\`\`\`<!--/vscode-context-->`;
}
logger.debug('VS Code enhanced prompt prepared', {
enhancedPromptLength: fullPrompt.length,
correlationId,
});
// Submit the prompt to the chat
if (handleMessageSubmitRef.current) {
handleMessageSubmitRef.current(fullPrompt);
}
else {
logger.warn('VS Code prompt received but handleMessageSubmit not ready', {
correlationId,
});
}
}, [logger]);
const effectiveVscodeEnabled = vscodeMode || appState.isVscodeEnabled;
const vscodeServer = useVSCodeServer({
enabled: effectiveVscodeEnabled,
port: vscodePort,
currentModel: appState.currentModel,
currentProvider: appState.currentProvider,
onPrompt: handleVSCodePrompt,
});
// Create theme context value
const themeContextValue = {
currentTheme: appState.currentTheme,
colors: getThemeColors(appState.currentTheme),
setCurrentTheme: (theme) => {
appState.setCurrentTheme(theme);
updateSelectedTheme(theme);
},
};
// Create title shape context value
const titleShapeContextValue = {
currentTitleShape: appState.currentTitleShape,
setCurrentTitleShape: (shape) => {
appState.setCurrentTitleShape(shape);
updateTitleShape(shape);
},
};
// Initialize global message queue on component mount
React.useEffect(() => {
setGlobalMessageQueue(appState.addToChatQueue);
logger.debug('Global message queue initialized', {
chatQueueFunction: 'addToChatQueue',
});
}, [appState.addToChatQueue, logger]);
// Question handler - ref holds the resolver for the pending question promise
const questionResolverRef = React.useRef(null);
// Initialize global question handler
React.useEffect(() => {
setGlobalQuestionHandler((question) => {
return new Promise(resolve => {
questionResolverRef.current = resolve;
appState.setPendingQuestion(question);
appState.setIsQuestionMode(true);
});
});
}, [appState.setPendingQuestion, appState.setIsQuestionMode, appState]);
// Handle user answering a question
const handleQuestionAnswer = React.useCallback((answer) => {
if (questionResolverRef.current) {
questionResolverRef.current(answer);
questionResolverRef.current = null;
}
appState.setIsQuestionMode(false);
appState.setPendingQuestion(null);
}, [appState.setIsQuestionMode, appState.setPendingQuestion, appState]);
// Log important application state changes
React.useEffect(() => {
if (appState.client) {
logger.info('AI client initialized', {
provider: appState.currentProvider,
model: appState.currentModel,
hasToolManager: !!appState.toolManager,
});
}
}, [
appState.client,
appState.currentProvider,
appState.currentModel,
appState.toolManager,
logger,
]);
React.useEffect(() => {
if (appState.mcpInitialized) {
logger.info('MCP servers initialized', {
serverCount: appState.mcpServersStatus?.length || 0,
status: 'connected',
});
}
}, [appState.mcpInitialized, appState.mcpServersStatus, logger]);
React.useEffect(() => {
if (appState.updateInfo) {
logger.info('Update information available', {
hasUpdate: appState.updateInfo.hasUpdate,
currentVersion: appState.updateInfo.currentVersion,
latestVersion: appState.updateInfo.latestVersion,
});
}
}, [appState.updateInfo, logger]);
// Setup chat handler
const chatHandler = useChatHandler({
client: appState.client,
toolManager: appState.toolManager,
customCommandLoader: appState.customCommandLoader,
messages: appState.messages,
setMessages: appState.updateMessages,
currentProvider: appState.currentProvider,
currentModel: appState.currentModel,
setIsCancelling: appState.setIsCancelling,
addToChatQueue: appState.addToChatQueue,
getNextComponentKey: appState.getNextComponentKey,
abortController: appState.abortController,
setAbortController: appState.setAbortController,
developmentMode: appState.developmentMode,
nonInteractiveMode,
onStartToolConfirmationFlow: (toolCalls, messagesBeforeToolExecution, assistantMsg, systemMessage) => {
appState.setPendingToolCalls(toolCalls);
appState.setCurrentToolIndex(0);
appState.setCompletedToolResults([]);
appState.setCurrentConversationContext({
messagesBeforeToolExecution,
assistantMsg,
systemMessage,
});
appState.setIsToolConfirmationMode(true);
},
onConversationComplete: () => {
appState.setIsConversationComplete(true);
appState.setCompactToolCounts(null);
appState.compactToolCountsRef.current = {};
},
compactToolDisplayRef: appState.compactToolDisplayRef,
onSetCompactToolCounts: appState.setCompactToolCounts,
compactToolCountsRef: appState.compactToolCountsRef,
});
// Track context window usage percentage
useContextPercentage({
currentModel: appState.currentModel,
messages: appState.messages,
tokenizer: appState.tokenizer,
getMessageTokens: appState.getMessageTokens,
toolManager: appState.toolManager,
streamingTokenCount: chatHandler.tokenCount,
contextLimit: appState.contextLimit,
setContextPercentUsed: appState.setContextPercentUsed,
setContextLimit: appState.setContextLimit,
});
// Setup tool handler
const toolHandler = useToolHandler({
pendingToolCalls: appState.pendingToolCalls,
currentToolIndex: appState.currentToolIndex,
completedToolResults: appState.completedToolResults,
currentConversationContext: appState.currentConversationContext,
setPendingToolCalls: appState.setPendingToolCalls,
setCurrentToolIndex: appState.setCurrentToolIndex,
setCompletedToolResults: appState.setCompletedToolResults,
setCurrentConversationContext: appState.setCurrentConversationContext,
setIsToolConfirmationMode: appState.setIsToolConfirmationMode,
setIsToolExecuting: appState.setIsToolExecuting,
setMessages: appState.updateMessages,
addToChatQueue: appState.addToChatQueue,
setLiveComponent: appState.setLiveComponent,
getNextComponentKey: appState.getNextComponentKey,
resetToolConfirmationState: appState.resetToolConfirmationState,
onProcessAssistantResponse: chatHandler.processAssistantResponse,
client: appState.client,
currentProvider: appState.currentProvider,
setDevelopmentMode: appState.setDevelopmentMode,
compactToolDisplay: appState.compactToolDisplay,
});
// Log when application is fully ready
useEffect(() => {
if (appState.mcpInitialized &&
appState.client &&
!appState.isToolExecuting &&
!appState.isToolConfirmationMode &&
appState.activeMode !== 'configWizard' &&
appState.activeMode !== 'mcpWizard' &&
appState.pendingToolCalls.length === 0) {
const correlationId = generateCorrelationId();
withNewCorrelationContext(() => {
logger.info('Application interface ready for user interaction', {
correlationId,
interfaceState: {
developmentMode: appState.developmentMode,
hasPendingToolCalls: appState.pendingToolCalls.length > 0,
clientInitialized: !!appState.client,
mcpServersConnected: appState.mcpInitialized,
inputDisabled: chatHandler.isGenerating || appState.isToolExecuting,
},
});
}, correlationId);
}
}, [
appState.mcpInitialized,
appState.client,
appState.isToolExecuting,
appState.isToolConfirmationMode,
appState.activeMode,
appState.pendingToolCalls.length,
logger,
appState.developmentMode,
chatHandler.isGenerating,
]);
// Setup initialization
const appInitialization = useAppInitialization({
setClient: appState.setClient,
setCurrentModel: appState.setCurrentModel,
setCurrentProvider: appState.setCurrentProvider,
setToolManager: appState.setToolManager,
setCustomCommandLoader: appState.setCustomCommandLoader,
setCustomCommandExecutor: appState.setCustomCommandExecutor,
setCustomCommandCache: appState.setCustomCommandCache,
setStartChat: appState.setStartChat,
setMcpInitialized: appState.setMcpInitialized,
setUpdateInfo: appState.setUpdateInfo,
setMcpServersStatus: appState.setMcpServersStatus,
setLspServersStatus: appState.setLspServersStatus,
setPreferencesLoaded: appState.setPreferencesLoaded,
setCustomCommandsCount: appState.setCustomCommandsCount,
addToChatQueue: appState.addToChatQueue,
getNextComponentKey: appState.getNextComponentKey,
customCommandCache: appState.customCommandCache,
setActiveMode: appState.setActiveMode,
cliProvider,
cliModel,
});
// Setup mode handlers
const modeHandlers = useModeHandlers({
client: appState.client,
currentModel: appState.currentModel,
currentProvider: appState.currentProvider,
setClient: appState.setClient,
setCurrentModel: appState.setCurrentModel,
setCurrentProvider: appState.setCurrentProvider,
setMessages: appState.updateMessages,
setActiveMode: appState.setActiveMode,
setIsSettingsMode: appState.setIsSettingsMode,
addToChatQueue: appState.addToChatQueue,
getNextComponentKey: appState.getNextComponentKey,
reinitializeMCPServers: appInitialization.reinitializeMCPServers,
});
// Scheduler mode enter/exit handlers
const enterSchedulerMode = React.useCallback(() => {
appState.setActiveMode('scheduler');
}, [appState.setActiveMode, appState]);
const exitSchedulerMode = React.useCallback(() => {
appState.setActiveMode(null);
}, [appState.setActiveMode, appState]);
// IDE selection handler
const handleIdeSelect = React.useCallback((ide) => {
appState.setActiveMode(null);
if (ide === 'vscode') {
appState.setIsVscodeEnabled(true);
// Check if extension needs installing
void (async () => {
if (!(await isExtensionInstalled())) {
setShowExtensionPrompt(true);
setExtensionPromptComplete(false);
}
else {
appState.addToChatQueue(_jsx(SuccessMessage, { message: "VS Code integration enabled. Starting server...", hideBox: true }, `ide-vscode-enabled-${appState.getNextComponentKey()}`));
}
})();
}
}, [appState]);
// Show confirmation once VS Code server is ready with its port
const prevVscodePortRef = React.useRef(vscodeServer.actualPort);
React.useEffect(() => {
const prevPort = prevVscodePortRef.current;
prevVscodePortRef.current = vscodeServer.actualPort;
// Only show message when port transitions from null to a value
// and it was triggered by /ide (not the --vscode CLI flag)
if (prevPort === null &&
vscodeServer.actualPort !== null &&
appState.isVscodeEnabled) {
appState.addToChatQueue(_jsx(SuccessMessage, { message: `VS Code server listening on port ${vscodeServer.actualPort}`, hideBox: true }, `ide-vscode-ready-${appState.getNextComponentKey()}`));
}
}, [vscodeServer.actualPort, appState]);
// Setup app handlers
const appHandlers = useAppHandlers({
messages: appState.messages,
currentProvider: appState.currentProvider,
currentModel: appState.currentModel,
currentTheme: appState.currentTheme,
abortController: appState.abortController,
updateInfo: appState.updateInfo,
mcpServersStatus: appState.mcpServersStatus,
lspServersStatus: appState.lspServersStatus,
preferencesLoaded: appState.preferencesLoaded,
customCommandsCount: appState.customCommandsCount,
getNextComponentKey: appState.getNextComponentKey,
customCommandCache: appState.customCommandCache,
customCommandLoader: appState.customCommandLoader,
customCommandExecutor: appState.customCommandExecutor,
updateMessages: appState.updateMessages,
setIsCancelling: appState.setIsCancelling,
setDevelopmentMode: appState.setDevelopmentMode,
setIsConversationComplete: appState.setIsConversationComplete,
setIsToolExecuting: appState.setIsToolExecuting,
setActiveMode: appState.setActiveMode,
setCheckpointLoadData: appState.setCheckpointLoadData,
setShowAllSessions: appState.setShowAllSessions,
setCurrentSessionId: appState.setCurrentSessionId,
setCurrentProvider: appState.setCurrentProvider,
setCurrentModel: appState.setCurrentModel,
addToChatQueue: appState.addToChatQueue,
setLiveComponent: appState.setLiveComponent,
client: appState.client,
getMessageTokens: appState.getMessageTokens,
enterModelSelectionMode: modeHandlers.enterModelSelectionMode,
enterProviderSelectionMode: modeHandlers.enterProviderSelectionMode,
enterModelDatabaseMode: modeHandlers.enterModelDatabaseMode,
enterConfigWizardMode: modeHandlers.enterConfigWizardMode,
enterSettingsMode: modeHandlers.enterSettingsMode,
enterMcpWizardMode: modeHandlers.enterMcpWizardMode,
enterExplorerMode: modeHandlers.enterExplorerMode,
enterIdeSelectionMode: modeHandlers.enterIdeSelectionMode,
enterSchedulerMode,
handleChatMessage: chatHandler.handleChatMessage,
});
// Update the ref so VS Code prompts can be submitted
React.useEffect(() => {
handleMessageSubmitRef.current = appHandlers.handleMessageSubmit;
}, [appHandlers.handleMessageSubmit]);
// Setup non-interactive mode
const { nonInteractiveLoadingMessage } = useNonInteractiveMode({
nonInteractivePrompt,
nonInteractiveMode,
mcpInitialized: appState.mcpInitialized,
client: appState.client,
appState: {
isToolExecuting: appState.isToolExecuting,
isToolConfirmationMode: appState.isToolConfirmationMode,
isConversationComplete: appState.isConversationComplete,
messages: appState.messages,
},
setDevelopmentMode: appState.setDevelopmentMode,
handleMessageSubmit: appHandlers.handleMessageSubmit,
});
// Setup scheduler mode
const schedulerMode = useSchedulerMode({
isSchedulerMode: appState.isSchedulerMode,
mcpInitialized: appState.mcpInitialized,
setDevelopmentMode: appState.setDevelopmentMode,
handleMessageSubmit: appHandlers.handleMessageSubmit,
clearMessages: appHandlers.clearMessages,
isConversationComplete: appState.isConversationComplete,
isToolExecuting: appState.isToolExecuting,
isToolConfirmationMode: appState.isToolConfirmationMode,
messages: appState.messages,
addToChatQueue: appState.addToChatQueue,
});
// Setup session autosave
useSessionAutosave({
messages: appState.messages,
currentProvider: appState.currentProvider,
currentModel: appState.currentModel,
currentSessionId: appState.currentSessionId,
setCurrentSessionId: appState.setCurrentSessionId,
});
const shouldShowWelcome = shouldRenderWelcome(nonInteractiveMode);
// Memoize static components
const staticComponents = React.useMemo(() => createStaticComponents({
shouldShowWelcome,
currentProvider: appState.currentProvider,
currentModel: appState.currentModel,
currentTheme: appState.currentTheme,
updateInfo: appState.updateInfo,
mcpServersStatus: appState.mcpServersStatus,
lspServersStatus: appState.lspServersStatus,
preferencesLoaded: appState.preferencesLoaded,
customCommandsCount: appState.customCommandsCount,
vscodeMode: effectiveVscodeEnabled,
vscodePort: vscodeServer.actualPort,
vscodeRequestedPort: vscodeServer.requestedPort,
}), [
shouldShowWelcome,
appState.currentProvider,
appState.currentModel,
appState.currentTheme,
appState.updateInfo,
appState.mcpServersStatus,
appState.lspServersStatus,
appState.preferencesLoaded,
appState.customCommandsCount,
effectiveVscodeEnabled,
vscodeServer.actualPort,
vscodeServer.requestedPort,
]);
// Handle loading state for directory trust check
if (isTrustLoading) {
logger.debug('Directory trust check in progress');
return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: themeContextValue.colors.secondary, children: [_jsx(Spinner, { type: "dots" }), " Checking directory trust..."] }) }) }));
}
// Handle error state for directory trust
if (isTrustedError) {
logger.error('Directory trust check failed', {
error: isTrustedError,
suggestion: 'restart_application_or_check_permissions',
});
return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { color: themeContextValue.colors.error, children: ["\u26A0\uFE0F Error checking directory trust: ", isTrustedError] }), _jsx(Text, { color: themeContextValue.colors.secondary, children: "Please restart the application or check your permissions." })] }) }));
}
// Show security disclaimer if directory is not trusted
if (!isTrusted) {
logger.info('Directory not trusted, showing security disclaimer');
return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsx(TitleShapeContext.Provider, { value: titleShapeContextValue, children: _jsx(SecurityDisclaimer, { onConfirm: handleConfirmTrust, onExit: handleExit }) }) }));
}
// Directory is trusted - application can proceed
logger.debug('Directory trusted, proceeding with application initialization');
// Show VS Code extension installation prompt if needed
if (showExtensionPrompt && !extensionPromptComplete) {
logger.info('Showing VS Code extension installation prompt', {
vscodeMode,
extensionPromptComplete,
});
return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsx(TitleShapeContext.Provider, { value: titleShapeContextValue, children: _jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(WelcomeMessage, {}), _jsx(VSCodeExtensionPrompt, { onComplete: () => {
logger.info('VS Code extension prompt completed');
setShowExtensionPrompt(false);
setExtensionPromptComplete(true);
}, onSkip: () => {
logger.info('VS Code extension prompt skipped');
setShowExtensionPrompt(false);
setExtensionPromptComplete(true);
} })] }) }) }));
}
// Main application render
return (_jsx(ThemeContext.Provider, { value: themeContextValue, children: _jsx(TitleShapeContext.Provider, { value: titleShapeContextValue, children: _jsx(UIStateProvider, { children: _jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(ChatHistory, { startChat: appState.startChat, staticComponents: staticComponents, queuedComponents: appState.chatComponents, liveComponent: appState.liveComponent }), appState.isExplorerMode && (_jsx(Box, { marginLeft: -1, flexDirection: "column", children: _jsx(FileExplorer, { onClose: modeHandlers.handleExplorerCancel }) })), appState.isIdeSelectionMode && (_jsx(Box, { marginLeft: -1, flexDirection: "column", children: _jsx(IdeSelector, { onSelect: handleIdeSelect, onCancel: modeHandlers.handleIdeSelectionCancel }) })), (appState.activeMode !== null &&
appState.activeMode !== 'explorer' &&
appState.activeMode !== 'ideSelection' &&
appState.activeMode !== 'scheduler') ||
appState.isSettingsMode ? (_jsx(Box, { marginLeft: -1, flexDirection: "column", children: _jsx(ModalSelectors, { activeMode: appState.activeMode, isSettingsMode: appState.isSettingsMode, showAllSessions: appState.showAllSessions, client: appState.client, currentModel: appState.currentModel, currentProvider: appState.currentProvider, checkpointLoadData: appState.checkpointLoadData, onModelSelect: modeHandlers.handleModelSelect, onModelSelectionCancel: modeHandlers.handleModelSelectionCancel, onProviderSelect: modeHandlers.handleProviderSelect, onProviderSelectionCancel: modeHandlers.handleProviderSelectionCancel, onModelDatabaseCancel: modeHandlers.handleModelDatabaseCancel, onConfigWizardComplete: modeHandlers.handleConfigWizardComplete, onConfigWizardCancel: modeHandlers.handleConfigWizardCancel, onMcpWizardComplete: modeHandlers.handleMcpWizardComplete, onMcpWizardCancel: modeHandlers.handleMcpWizardCancel, onSettingsCancel: modeHandlers.handleSettingsCancel, onCheckpointSelect: appHandlers.handleCheckpointSelect, onCheckpointCancel: appHandlers.handleCheckpointCancel, onSessionSelect: sessionId => void appHandlers.handleSessionSelect(sessionId), onSessionCancel: appHandlers.handleSessionCancel }) })) : null, appState.isSchedulerMode && (_jsx(SchedulerView, { activeJobCount: schedulerMode.activeJobCount, queueLength: schedulerMode.queueLength, isProcessing: schedulerMode.isProcessing, currentJobCommand: schedulerMode.currentJobCommand, developmentMode: appState.developmentMode, contextPercentUsed: appState.contextPercentUsed, onExit: exitSchedulerMode })), appState.startChat &&
appState.activeMode === null &&
!appState.isSettingsMode && (_jsx(ChatInput, { isCancelling: appState.isCancelling, isToolExecuting: appState.isToolExecuting, isToolConfirmationMode: appState.isToolConfirmationMode, isQuestionMode: appState.isQuestionMode, pendingToolCalls: appState.pendingToolCalls, currentToolIndex: appState.currentToolIndex, pendingQuestion: appState.pendingQuestion, onQuestionAnswer: handleQuestionAnswer, mcpInitialized: appState.mcpInitialized, client: appState.client, nonInteractivePrompt: nonInteractivePrompt, nonInteractiveLoadingMessage: nonInteractiveLoadingMessage, customCommands: Array.from(appState.customCommandCache.keys()), inputDisabled: chatHandler.isGenerating || appState.isToolExecuting, developmentMode: appState.developmentMode, contextPercentUsed: appState.contextPercentUsed, compactToolCounts: appState.compactToolCounts, compactToolDisplay: appState.compactToolDisplay, onToggleCompactDisplay: () => {
const expanding = appState.compactToolDisplay;
appState.setCompactToolDisplay(!expanding);
// When expanding, flush accumulated counts to static
if (expanding) {
const counts = appState.compactToolCountsRef.current;
if (Object.keys(counts).length > 0) {
displayCompactCountsSummary(counts, appState.addToChatQueue, appState.getNextComponentKey);
appState.compactToolCountsRef.current = {};
appState.setCompactToolCounts(null);
}
}
}, onToolConfirm: toolHandler.handleToolConfirmation, onToolCancel: toolHandler.handleToolConfirmationCancel, onSubmit: appHandlers.handleMessageSubmit, onCancel: appHandlers.handleCancel, onToggleMode: appHandlers.handleToggleDevelopmentMode }))] }) }) }) }));
}
//# sourceMappingURL=App.js.map