@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
98 lines • 4.17 kB
JavaScript
import React from 'react';
import { isNonInteractiveModeComplete } from '../app/helpers.js';
import { TIMEOUT_EXECUTION_MAX_MS, TIMEOUT_OUTPUT_FLUSH_MS } from '../constants.js';
import { setCurrentMode as setCurrentModeContext } from '../context/mode-context.js';
import { getLogger } from '../utils/logging/index.js';
import { getShutdownManager } from '../utils/shutdown/index.js';
/**
* Handles non-interactive mode logic:
* - Automatically submits prompt when ready
* - Sets auto-accept mode
* - Monitors completion and exits when done
*/
export function useNonInteractiveMode({ nonInteractivePrompt, mcpInitialized, client, appState, setDevelopmentMode, handleMessageSubmit, }) {
const [nonInteractiveSubmitted, setNonInteractiveSubmitted] = React.useState(false);
const [startTime] = React.useState(Date.now());
// Auto-submit prompt when ready
React.useEffect(() => {
if (nonInteractivePrompt &&
mcpInitialized &&
client &&
!nonInteractiveSubmitted) {
setNonInteractiveSubmitted(true);
// Set auto-accept mode for non-interactive execution
// Sync both React state AND global context synchronously
// to prevent race conditions where tools check global context
// before the useEffect in App.tsx has a chance to sync it
setDevelopmentMode('auto-accept');
setCurrentModeContext('auto-accept');
// Submit the prompt
void handleMessageSubmit(nonInteractivePrompt);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
nonInteractivePrompt,
mcpInitialized,
client,
nonInteractiveSubmitted,
handleMessageSubmit,
setDevelopmentMode,
]);
// Exit when processing is complete
React.useEffect(() => {
if (nonInteractivePrompt && nonInteractiveSubmitted) {
const { shouldExit, reason } = isNonInteractiveModeComplete(appState, startTime, TIMEOUT_EXECUTION_MAX_MS);
if (shouldExit) {
const logger = getLogger();
if (reason === 'timeout') {
logger.error('Non-interactive mode timed out');
}
else if (reason === 'error') {
logger.error('Non-interactive mode encountered errors');
}
else if (reason === 'tool-approval') {
// Exit with error code when tool approval is required
// Error message already printed by useChatHandler
}
// Wait a bit to ensure all output is flushed
const code = reason === 'error' || reason === 'tool-approval' ? 1 : 0;
const timer = setTimeout(() => {
void getShutdownManager().gracefulShutdown(code);
}, TIMEOUT_OUTPUT_FLUSH_MS);
return () => clearTimeout(timer);
}
}
}, [nonInteractivePrompt, nonInteractiveSubmitted, appState, startTime]);
// Compute loading message
const nonInteractiveLoadingMessage = React.useMemo(() => {
if (!nonInteractivePrompt) {
return null;
}
// Don't show loading message when conversation is complete (about to exit)
if (appState.isConversationComplete) {
return null;
}
if (!mcpInitialized || !client) {
return 'Waiting for MCP servers...';
}
const pendingToolCallCount = 0; // This would need to be passed if available
if (appState.isToolExecuting ||
appState.isToolConfirmationMode ||
pendingToolCallCount > 0) {
return 'Waiting for tooling...';
}
return 'Waiting for chat to complete...';
}, [
nonInteractivePrompt,
appState.isConversationComplete,
mcpInitialized,
client,
appState.isToolExecuting,
appState.isToolConfirmationMode,
]);
return {
nonInteractiveSubmitted,
nonInteractiveLoadingMessage,
};
}
//# sourceMappingURL=useNonInteractiveMode.js.map