centaurus-cli 2.9.1 → 2.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter.d.ts +70 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +349 -156
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +21 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -1
- package/dist/config/mcp-config-manager.js +184 -1
- package/dist/config/mcp-config-manager.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +7 -2
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +4 -3
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/index.js +60 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts +34 -3
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
- package/dist/mcp/mcp-command-handler.js +171 -83
- package/dist/mcp/mcp-command-handler.js.map +1 -1
- package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
- package/dist/mcp/mcp-server-manager.js +9 -23
- package/dist/mcp/mcp-server-manager.js.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.js +42 -5
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
- package/dist/services/api-client.d.ts +9 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +25 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/input-detection-agent.d.ts +40 -0
- package/dist/services/input-detection-agent.d.ts.map +1 -0
- package/dist/services/input-detection-agent.js +213 -0
- package/dist/services/input-detection-agent.js.map +1 -0
- package/dist/services/input-requirement-detector.d.ts +28 -0
- package/dist/services/input-requirement-detector.d.ts.map +1 -0
- package/dist/services/input-requirement-detector.js +203 -0
- package/dist/services/input-requirement-detector.js.map +1 -0
- package/dist/services/monitored-shell-manager.d.ts +120 -0
- package/dist/services/monitored-shell-manager.d.ts.map +1 -0
- package/dist/services/monitored-shell-manager.js +239 -0
- package/dist/services/monitored-shell-manager.js.map +1 -0
- package/dist/services/shell-input-agent.d.ts +89 -0
- package/dist/services/shell-input-agent.d.ts.map +1 -0
- package/dist/services/shell-input-agent.js +361 -0
- package/dist/services/shell-input-agent.js.map +1 -0
- package/dist/services/sub-agent-manager.d.ts +139 -0
- package/dist/services/sub-agent-manager.d.ts.map +1 -0
- package/dist/services/sub-agent-manager.js +517 -0
- package/dist/services/sub-agent-manager.js.map +1 -0
- package/dist/tools/background-command.d.ts.map +1 -1
- package/dist/tools/background-command.js +33 -13
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +64 -1
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +33 -19
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/get-diff.js +1 -1
- package/dist/tools/get-diff.js.map +1 -1
- package/dist/tools/grep-search.d.ts.map +1 -1
- package/dist/tools/grep-search.js +41 -15
- package/dist/tools/grep-search.js.map +1 -1
- package/dist/tools/plan-mode.js +3 -3
- package/dist/tools/plan-mode.js.map +1 -1
- package/dist/tools/registry.js +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sub-agent.d.ts +9 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +232 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/task-complete.d.ts.map +1 -1
- package/dist/tools/task-complete.js +14 -26
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +43 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +560 -94
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/CircularSelectInput.d.ts +24 -0
- package/dist/ui/components/CircularSelectInput.d.ts.map +1 -0
- package/dist/ui/components/CircularSelectInput.js +71 -0
- package/dist/ui/components/CircularSelectInput.js.map +1 -0
- package/dist/ui/components/ErrorBoundary.d.ts +3 -2
- package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/ui/components/ErrorBoundary.js +29 -1
- package/dist/ui/components/ErrorBoundary.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +2 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +23 -3
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +6 -0
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
- package/dist/ui/components/InteractiveShell.js +57 -6
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/MCPAddScreen.d.ts +13 -0
- package/dist/ui/components/MCPAddScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPAddScreen.js +54 -0
- package/dist/ui/components/MCPAddScreen.js.map +1 -0
- package/dist/ui/components/MCPListScreen.d.ts +17 -0
- package/dist/ui/components/MCPListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPListScreen.js +50 -0
- package/dist/ui/components/MCPListScreen.js.map +1 -0
- package/dist/ui/components/MCPServerListScreen.d.ts +16 -0
- package/dist/ui/components/MCPServerListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPServerListScreen.js +59 -0
- package/dist/ui/components/MCPServerListScreen.js.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts +23 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.js +69 -0
- package/dist/ui/components/MonitorModeAIPanel.js.map +1 -0
- package/dist/ui/components/MultiLineInput.d.ts +13 -0
- package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
- package/dist/ui/components/MultiLineInput.js +223 -0
- package/dist/ui/components/MultiLineInput.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +2 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +33 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +226 -12
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.d.ts.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.js +3 -2
- package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
2
|
import { Box, Text, useApp, useInput, Static } from 'ink';
|
|
3
3
|
import SelectInput from 'ink-select-input';
|
|
4
|
+
import { CircularSelectInput } from './CircularSelectInput.js';
|
|
5
|
+
import stripAnsi from 'strip-ansi';
|
|
4
6
|
import TextInput from 'ink-text-input';
|
|
5
7
|
import { quickLog } from '../../utils/conversation-logger.js';
|
|
6
8
|
import { WelcomeBanner } from './WelcomeBanner.js';
|
|
@@ -22,8 +24,15 @@ import { runInteractiveEditor, runWSLEditor, runDockerEditor, runSSHEditor } fro
|
|
|
22
24
|
import { DetailedPlanReviewScreen } from './DetailedPlanReviewScreen.js';
|
|
23
25
|
import { TaskCompletedMessage } from './TaskCompletedMessage.js';
|
|
24
26
|
import { PlanAcceptedMessage } from './PlanAcceptedMessage.js';
|
|
27
|
+
import { MCPAddScreen } from './MCPAddScreen.js';
|
|
28
|
+
import { MCPServerListScreen } from './MCPServerListScreen.js';
|
|
29
|
+
import { MCPListScreen } from './MCPListScreen.js';
|
|
25
30
|
import { processTerminalOutput } from '../../utils/terminal-output.js';
|
|
26
31
|
import { BackgroundTaskManager } from '../../services/background-task-manager.js';
|
|
32
|
+
import { MonitorModeAIPanel } from './MonitorModeAIPanel.js';
|
|
33
|
+
import { detectInputRequirement, extractPromptContext, getInputTypeDescription } from '../../services/input-requirement-detector.js';
|
|
34
|
+
import { ShellInputAgent } from '../../services/shell-input-agent.js';
|
|
35
|
+
import { InputDetectionAgent } from '../../services/input-detection-agent.js';
|
|
27
36
|
import { useConnectivity } from '../../hooks/useConnectivity.js';
|
|
28
37
|
import { apiClient } from '../../services/api-client.js';
|
|
29
38
|
import { conversationManager } from '../../services/conversation-manager.js';
|
|
@@ -137,12 +146,18 @@ const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
|
|
|
137
146
|
React.createElement(Box, { marginTop: 1 },
|
|
138
147
|
React.createElement(Text, { dimColor: true }, "Press Enter to save, ESC to cancel"))));
|
|
139
148
|
};
|
|
140
|
-
export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode, onResponseReceived, onDirectMessage, onResponseStream, onClearStreamedResponse, onThoughtStream, onThoughtComplete, onPickerSetup, onPickerSelection, onToolExecutionUpdate, onToolApprovalRequest, onToolStreamingOutput, onPlanModeChange, onPlanApprovalRequest, onPlanCreated, onTaskCompleted, onCommandModeChange, onToggleCommandMode, onBackgroundModeChange, onToggleBackgroundMode, onBackgroundTaskCountChange, onSetAutoModeSetup, onCwdChange, onModelChange, onSubshellContextChange, onPasswordRequest, onShellInput, onShellSignal, onKillProcess, onInteractiveEditorMode, onConnectionStatusUpdate, onTokenCountUpdate, onChatPickerSetup, onChatPickerSelection, onChatDeletePickerSetup, onChatDeletePickerSelection, onChatListSetup, onChatRenamePickerSetup, onChatRename, onRestoreMessagesSetup, onUIMessageHistoryUpdate, onBackgroundTaskListSetup, onBackgroundTaskSelection, onBackgroundTaskCancelSetup, onBackgroundTaskCancel, onBackgroundTaskViewSetup, onSessionQuotaUpdate }) => {
|
|
149
|
+
export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode, onResponseReceived, onDirectMessage, onResponseStream, onClearStreamedResponse, onThoughtStream, onThoughtComplete, onPickerSetup, onPickerSelection, onToolExecutionUpdate, onToolApprovalRequest, onToolStreamingOutput, onPlanModeChange, onPlanApprovalRequest, onPlanCreated, onTaskCompleted, onCommandModeChange, onToggleCommandMode, onBackgroundModeChange, onToggleBackgroundMode, onBackgroundTaskCountChange, onSubAgentCountChange, onSetAutoModeSetup, onCwdChange, onModelChange, onSubshellContextChange, onPasswordRequest, onShellInput, onShellSignal, onKillProcess, onInteractiveEditorMode, onConnectionStatusUpdate, onTokenCountUpdate, onContextLimitReached, onChatPickerSetup, onChatPickerSelection, onChatDeletePickerSetup, onChatDeletePickerSelection, onChatListSetup, onChatRenamePickerSetup, onChatRename, onRestoreMessagesSetup, onUIMessageHistoryUpdate, onBackgroundTaskListSetup, onBackgroundTaskSelection, onBackgroundTaskCancelSetup, onBackgroundTaskCancel, onBackgroundTaskViewSetup, onSessionQuotaUpdate, onMCPAddScreenSetup, onMCPRemoveScreenSetup, onMCPEnableScreenSetup, onMCPDisableScreenSetup, onMCPListScreenSetup, onMCPAddServer, onMCPRemoveServer, onMCPEnableServer, onMCPDisableServer, onMCPValidateConfig, onPromptAnswered, getMainConversation }) => {
|
|
141
150
|
const { exit } = useApp();
|
|
151
|
+
// Calculate limit for paginated lists (75% of terminal height)
|
|
152
|
+
const listLimit = Math.max(5, Math.floor((process.stdout.rows || 24) * 0.75));
|
|
142
153
|
const autoAcceptRef = React.useRef(false);
|
|
143
154
|
const setAutoModeCallbackRef = React.useRef(null);
|
|
144
155
|
// Connectivity State
|
|
145
156
|
const isConnected = useConnectivity();
|
|
157
|
+
// Agent control mode refs for input detection
|
|
158
|
+
const lastOutputTimeRef = React.useRef(Date.now());
|
|
159
|
+
const inputDetectionPendingRef = React.useRef(false);
|
|
160
|
+
const detectionDebounceRef = React.useRef(null);
|
|
146
161
|
// Helper to clear screen
|
|
147
162
|
const clearScreen = useCallback(() => {
|
|
148
163
|
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
@@ -195,6 +210,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
195
210
|
isAiWorking: false,
|
|
196
211
|
currentTokens: 0,
|
|
197
212
|
maxTokens: getMaxTokensForModel(initialModel || 'gemini-2.5-flash'),
|
|
213
|
+
contextLimitReached: false,
|
|
198
214
|
shellState: undefined,
|
|
199
215
|
isInteractiveEditorMode: false,
|
|
200
216
|
pickerOptions: undefined,
|
|
@@ -203,6 +219,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
203
219
|
passwordRequest: undefined,
|
|
204
220
|
connectionStatus: undefined,
|
|
205
221
|
backgroundTaskCount: 0,
|
|
222
|
+
subAgentCount: 0,
|
|
206
223
|
sessionQuotaExhausted: false,
|
|
207
224
|
sessionQuotaTimeRemaining: '',
|
|
208
225
|
});
|
|
@@ -270,6 +287,20 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
270
287
|
}
|
|
271
288
|
};
|
|
272
289
|
}, [clearScreen]);
|
|
290
|
+
// Handle global keyboard shortcuts
|
|
291
|
+
useInput((input, key) => {
|
|
292
|
+
// Escape key handling for background task screens
|
|
293
|
+
if (key.escape) {
|
|
294
|
+
if (state.screen === 'background-task-list' || state.screen === 'background-task-cancel') {
|
|
295
|
+
clearScreen();
|
|
296
|
+
setState(prev => ({
|
|
297
|
+
...prev,
|
|
298
|
+
screen: 'chat',
|
|
299
|
+
backgroundTasks: undefined // Clear the list
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
273
304
|
// Check for version updates on mount
|
|
274
305
|
React.useEffect(() => {
|
|
275
306
|
checkForUpdates().then(versionInfo => {
|
|
@@ -323,6 +354,17 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
323
354
|
React.useEffect(() => {
|
|
324
355
|
autoAcceptRef.current = state.autoAcceptMode;
|
|
325
356
|
}, [state.autoAcceptMode]);
|
|
357
|
+
// Push shell output updates to InputDetectionAgent when agent control is active
|
|
358
|
+
// This ensures the detection agent always has the latest output for polling
|
|
359
|
+
React.useEffect(() => {
|
|
360
|
+
if (state.shellState?.isAgentControlled && state.shellState?.shellId && state.shellState?.output) {
|
|
361
|
+
InputDetectionAgent.updateOutput(state.shellState.shellId, state.shellState.output);
|
|
362
|
+
}
|
|
363
|
+
}, [state.shellState?.output, state.shellState?.isAgentControlled, state.shellState?.shellId]);
|
|
364
|
+
// Ref to track if we should strictly block incoming AI updates (Zombie Agent Protection)
|
|
365
|
+
// This is set to true when an Agent-Controlled shell exits, essentially killing the "AI Agent"
|
|
366
|
+
// associated with it from the UI perspective. It is reset when the user manually inputs a new message.
|
|
367
|
+
const ignoreIncomingAIUpdatesRef = React.useRef(false);
|
|
326
368
|
// NOTE: Token count is now updated via onTokenCountUpdate callback from cli-adapter
|
|
327
369
|
// which tracks the actual AI conversation history including system prompt.
|
|
328
370
|
// The old UI-based calculation has been removed to avoid overwriting correct values.
|
|
@@ -335,6 +377,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
335
377
|
// Set up callback to receive streaming chunks - only once on mount
|
|
336
378
|
React.useEffect(() => {
|
|
337
379
|
onResponseStream((chunk) => {
|
|
380
|
+
// CIRCUIT BREAKER: Block incoming messages from "Zombie Agents"
|
|
381
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
382
|
+
return;
|
|
338
383
|
isStreamingRef.current = true; // Mark that we're streaming
|
|
339
384
|
// Check if terminal is large enough for streaming
|
|
340
385
|
const dimensions = getTerminalDimensions();
|
|
@@ -438,6 +483,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
438
483
|
// Set up callback to receive thought chunks
|
|
439
484
|
React.useEffect(() => {
|
|
440
485
|
onThoughtStream((thought) => {
|
|
486
|
+
// CIRCUIT BREAKER: Block incoming thoughts from "Zombie Agents"
|
|
487
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
488
|
+
return;
|
|
441
489
|
// Debug logging to file
|
|
442
490
|
try {
|
|
443
491
|
quickLog(`[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
|
|
@@ -529,6 +577,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
529
577
|
// Set up callback for when thinking completes
|
|
530
578
|
React.useEffect(() => {
|
|
531
579
|
onThoughtComplete((durationSeconds) => {
|
|
580
|
+
// CIRCUIT BREAKER: Block incoming thought completion from "Zombie Agents"
|
|
581
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
582
|
+
return;
|
|
532
583
|
try {
|
|
533
584
|
quickLog(`[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
|
|
534
585
|
}
|
|
@@ -565,12 +616,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
565
616
|
});
|
|
566
617
|
});
|
|
567
618
|
}, [onThoughtComplete]);
|
|
619
|
+
// Helper function to check if a message should be skipped (thought-only with no content or tools)
|
|
620
|
+
const shouldSkipMessage = (message) => {
|
|
621
|
+
if (!message)
|
|
622
|
+
return false;
|
|
623
|
+
// Skip if it's an assistant message with only thinking duration and no content or tool execution
|
|
624
|
+
if (message.role === 'assistant' &&
|
|
625
|
+
message.thinkingDuration !== undefined &&
|
|
626
|
+
message.content.trim() === '' &&
|
|
627
|
+
!message.toolExecution) {
|
|
628
|
+
return true;
|
|
629
|
+
}
|
|
630
|
+
return false;
|
|
631
|
+
};
|
|
568
632
|
// Set up callback for direct messages (slash commands) - add directly to history
|
|
569
633
|
React.useEffect(() => {
|
|
570
634
|
onDirectMessage((message) => {
|
|
635
|
+
// CIRCUIT BREAKER: Block incoming direct messages from "Zombie Agents"
|
|
636
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
637
|
+
return;
|
|
571
638
|
setState(prev => {
|
|
572
|
-
// Move current message to history if exists
|
|
573
|
-
const newHistory = prev.currentMessage
|
|
639
|
+
// Move current message to history if exists (skip thought-only messages)
|
|
640
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
574
641
|
? [...prev.messageHistory, prev.currentMessage]
|
|
575
642
|
: prev.messageHistory;
|
|
576
643
|
// Create system message for slash command response
|
|
@@ -594,6 +661,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
594
661
|
// Set up callback to receive complete responses (for slash commands and non-streaming responses)
|
|
595
662
|
React.useEffect(() => {
|
|
596
663
|
onResponseReceived((message) => {
|
|
664
|
+
// CIRCUIT BREAKER: Block incoming responses from "Zombie Agents"
|
|
665
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
666
|
+
return;
|
|
597
667
|
// If we were streaming, move the current message to history immediately
|
|
598
668
|
if (isStreamingRef.current) {
|
|
599
669
|
isStreamingRef.current = false; // Reset for next message
|
|
@@ -702,8 +772,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
702
772
|
timestamp: now,
|
|
703
773
|
shouldStream: false // Command responses should appear instantly
|
|
704
774
|
};
|
|
705
|
-
// If we have a current message that's an assistant message, move it to history
|
|
706
|
-
if (prev.currentMessage && prev.currentMessage.role === 'assistant') {
|
|
775
|
+
// If we have a current message that's an assistant message, move it to history (skip thought-only)
|
|
776
|
+
if (prev.currentMessage && prev.currentMessage.role === 'assistant' && !shouldSkipMessage(prev.currentMessage)) {
|
|
707
777
|
return {
|
|
708
778
|
...prev,
|
|
709
779
|
messageHistory: [...prev.messageHistory, prev.currentMessage],
|
|
@@ -806,6 +876,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
806
876
|
}));
|
|
807
877
|
});
|
|
808
878
|
}, []); // Empty dependency array - only register once
|
|
879
|
+
// Set up callback for sub-agent count change
|
|
880
|
+
React.useEffect(() => {
|
|
881
|
+
onSubAgentCountChange((count) => {
|
|
882
|
+
setState(prev => ({
|
|
883
|
+
...prev,
|
|
884
|
+
subAgentCount: count
|
|
885
|
+
}));
|
|
886
|
+
});
|
|
887
|
+
}, []); // Empty dependency array - only register once
|
|
809
888
|
// Set up callback for setting Auto mode (used after background task starts)
|
|
810
889
|
React.useEffect(() => {
|
|
811
890
|
onSetAutoModeSetup((enabled) => {
|
|
@@ -943,6 +1022,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
943
1022
|
});
|
|
944
1023
|
});
|
|
945
1024
|
}, []); // Empty dependency array - only register once
|
|
1025
|
+
// Set up callback for MCP Add screen
|
|
1026
|
+
React.useEffect(() => {
|
|
1027
|
+
onMCPAddScreenSetup(() => {
|
|
1028
|
+
clearScreen();
|
|
1029
|
+
setState(prev => ({
|
|
1030
|
+
...prev,
|
|
1031
|
+
screen: 'mcp-add',
|
|
1032
|
+
isLoading: false
|
|
1033
|
+
}));
|
|
1034
|
+
});
|
|
1035
|
+
}, []); // Empty dependency array - only register once
|
|
1036
|
+
// Set up callback for MCP Remove screen
|
|
1037
|
+
React.useEffect(() => {
|
|
1038
|
+
onMCPRemoveScreenSetup((servers) => {
|
|
1039
|
+
clearScreen();
|
|
1040
|
+
setState(prev => ({
|
|
1041
|
+
...prev,
|
|
1042
|
+
screen: 'mcp-remove',
|
|
1043
|
+
mcpServers: servers,
|
|
1044
|
+
isLoading: false
|
|
1045
|
+
}));
|
|
1046
|
+
});
|
|
1047
|
+
}, []); // Empty dependency array - only register once
|
|
1048
|
+
// Set up callback for MCP Enable screen
|
|
1049
|
+
React.useEffect(() => {
|
|
1050
|
+
onMCPEnableScreenSetup((servers) => {
|
|
1051
|
+
clearScreen();
|
|
1052
|
+
setState(prev => ({
|
|
1053
|
+
...prev,
|
|
1054
|
+
screen: 'mcp-enable',
|
|
1055
|
+
mcpServers: servers,
|
|
1056
|
+
isLoading: false
|
|
1057
|
+
}));
|
|
1058
|
+
});
|
|
1059
|
+
}, []); // Empty dependency array - only register once
|
|
1060
|
+
// Set up callback for MCP Disable screen
|
|
1061
|
+
React.useEffect(() => {
|
|
1062
|
+
onMCPDisableScreenSetup((servers) => {
|
|
1063
|
+
clearScreen();
|
|
1064
|
+
setState(prev => ({
|
|
1065
|
+
...prev,
|
|
1066
|
+
screen: 'mcp-disable',
|
|
1067
|
+
mcpServers: servers,
|
|
1068
|
+
isLoading: false
|
|
1069
|
+
}));
|
|
1070
|
+
});
|
|
1071
|
+
}, []); // Empty dependency array - only register once
|
|
1072
|
+
// Set up callback for MCP List screen
|
|
1073
|
+
React.useEffect(() => {
|
|
1074
|
+
onMCPListScreenSetup((servers) => {
|
|
1075
|
+
clearScreen();
|
|
1076
|
+
setState(prev => ({
|
|
1077
|
+
...prev,
|
|
1078
|
+
screen: 'mcp-list',
|
|
1079
|
+
mcpListData: servers,
|
|
1080
|
+
isLoading: false
|
|
1081
|
+
}));
|
|
1082
|
+
});
|
|
1083
|
+
}, []); // Empty dependency array - only register once
|
|
946
1084
|
// Sync UI message history to CLI adapter whenever it changes
|
|
947
1085
|
React.useEffect(() => {
|
|
948
1086
|
onUIMessageHistoryUpdate(state.messageHistory);
|
|
@@ -950,6 +1088,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
950
1088
|
// Set up callback for tool execution updates
|
|
951
1089
|
React.useEffect(() => {
|
|
952
1090
|
onToolExecutionUpdate((update) => {
|
|
1091
|
+
// CIRCUIT BREAKER: Block incoming tool updates from "Zombie Agents"
|
|
1092
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
1093
|
+
return;
|
|
953
1094
|
// Special handling for execute_command
|
|
954
1095
|
if (update.toolName === 'execute_command') {
|
|
955
1096
|
try {
|
|
@@ -958,6 +1099,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
958
1099
|
catch (e) { }
|
|
959
1100
|
// STARTING EXECUTION
|
|
960
1101
|
if (update.status === 'executing') {
|
|
1102
|
+
// Special handling for shell_input - DO NOT overwrite shell state
|
|
1103
|
+
// The agent is sending input to an EXISTING shell, so we must preserve the current shell state
|
|
1104
|
+
if (update.arguments?.shell_input) {
|
|
1105
|
+
setState(prev => ({
|
|
1106
|
+
...prev,
|
|
1107
|
+
// Move current message to history if needed
|
|
1108
|
+
messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1109
|
+
? [...prev.messageHistory, prev.currentMessage]
|
|
1110
|
+
: prev.messageHistory,
|
|
1111
|
+
// Update current message to show the tool execution
|
|
1112
|
+
currentMessage: {
|
|
1113
|
+
id: `tool-execute_command-${Date.now()}`,
|
|
1114
|
+
role: 'tool',
|
|
1115
|
+
content: '',
|
|
1116
|
+
timestamp: new Date(),
|
|
1117
|
+
toolExecution: { ...update }
|
|
1118
|
+
},
|
|
1119
|
+
isLoading: false,
|
|
1120
|
+
isAiWorking: true
|
|
1121
|
+
}));
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
961
1124
|
const command = update.arguments?.command || update.arguments?.CommandLine || update.arguments?.commandLine || '';
|
|
962
1125
|
// Start a pending shell with a short delay
|
|
963
1126
|
// This prevents the UI from flickering for very fast commands (like ls)
|
|
@@ -973,6 +1136,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
973
1136
|
setState(prev => ({
|
|
974
1137
|
...prev,
|
|
975
1138
|
shellState: {
|
|
1139
|
+
shellId: `shell-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
976
1140
|
command: cmd,
|
|
977
1141
|
cwd: cwd,
|
|
978
1142
|
output: bufferedOutput,
|
|
@@ -981,10 +1145,11 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
981
1145
|
error: undefined,
|
|
982
1146
|
isFocused: false,
|
|
983
1147
|
isPty: update.arguments?.isPty || false,
|
|
984
|
-
remoteContext: update.arguments?.remoteContext
|
|
1148
|
+
remoteContext: update.arguments?.remoteContext,
|
|
1149
|
+
isAgentStarted: true // Mark as agent-started since it came from execute_command tool
|
|
985
1150
|
},
|
|
986
|
-
// IMPORTANT: Always preserve current message by moving it to history first
|
|
987
|
-
messageHistory: prev.currentMessage
|
|
1151
|
+
// IMPORTANT: Always preserve current message by moving it to history first (skip thought-only)
|
|
1152
|
+
messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
988
1153
|
? [...prev.messageHistory, prev.currentMessage]
|
|
989
1154
|
: prev.messageHistory,
|
|
990
1155
|
currentMessage: {
|
|
@@ -1040,8 +1205,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1040
1205
|
arguments: update.arguments
|
|
1041
1206
|
}
|
|
1042
1207
|
};
|
|
1043
|
-
// Move current message to history if exists
|
|
1044
|
-
const newHistory = prev.currentMessage
|
|
1208
|
+
// Move current message to history if exists (skip thought-only)
|
|
1209
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1045
1210
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1046
1211
|
: prev.messageHistory;
|
|
1047
1212
|
return {
|
|
@@ -1078,12 +1243,44 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1078
1243
|
arguments: update.arguments
|
|
1079
1244
|
}
|
|
1080
1245
|
};
|
|
1246
|
+
// Check if this was just an input string sent to an existing shell
|
|
1247
|
+
const isShellInput = update.arguments?.shell_input;
|
|
1248
|
+
// Reset input detection pending flag so we can detect NEXT prompt
|
|
1249
|
+
if (update.status === 'completed' || update.status === 'error') {
|
|
1250
|
+
inputDetectionPendingRef.current = false;
|
|
1251
|
+
}
|
|
1252
|
+
// Check if we hit a dead shell error during agent control
|
|
1253
|
+
const isDeadShellError = update.status === 'error' && update.error?.includes('No active interactive shell');
|
|
1254
|
+
// STRICT ISOLATION TRIGGER: If agent-controlled shell is done (completed or dead), kill the agent loop
|
|
1255
|
+
// LOGIC SPLIT:
|
|
1256
|
+
// 1. Manual Shell (User started, then Alt+I): Strict Sub-Agent behavior. Kill IMMEDIATELY on exit/completion.
|
|
1257
|
+
// 2. Agent Shell (Agent started via execute_command): Main Agent behavior. Kill only on ERROR. Allow success pass-through.
|
|
1258
|
+
const isAgentStarted = prev.shellState?.isAgentStarted;
|
|
1259
|
+
const shouldKill = isAgentStarted ? isDeadShellError : (update.status === 'completed' || isDeadShellError);
|
|
1260
|
+
if (prev.shellState?.isAgentControlled && shouldKill) {
|
|
1261
|
+
try {
|
|
1262
|
+
quickLog(`[${new Date().toISOString()}] [App] Strict Isolation: Agent Controlled shell exited/died. Engaging circuit breaker. (AgentStarted: ${isAgentStarted})\n`);
|
|
1263
|
+
}
|
|
1264
|
+
catch (e) { }
|
|
1265
|
+
ignoreIncomingAIUpdatesRef.current = true;
|
|
1266
|
+
// Terminate the shell input agent session
|
|
1267
|
+
if (prev.shellState?.shellId) {
|
|
1268
|
+
ShellInputAgent.terminateSession(prev.shellState.shellId);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1081
1271
|
return {
|
|
1082
1272
|
...prev,
|
|
1083
|
-
|
|
1273
|
+
// If it's just shell input, preserve the shell state UNLESS it's a dead shell error
|
|
1274
|
+
shellState: (isShellInput && !isDeadShellError) ? prev.shellState : undefined,
|
|
1084
1275
|
messageHistory: [...prev.messageHistory, shellMessage],
|
|
1085
1276
|
currentMessage: null,
|
|
1086
|
-
|
|
1277
|
+
// Determine if AI should keep working:
|
|
1278
|
+
// - Normal shell input: preserve state (keep thrusting if in chain)
|
|
1279
|
+
// - Agent-started shell completed successfully: preserve state (agent loop continues)
|
|
1280
|
+
// - Dead shell error or manual shell completed: STOP thrusting (reset to false)
|
|
1281
|
+
isAiWorking: (isShellInput && !isDeadShellError) ? prev.isAiWorking
|
|
1282
|
+
: (isAgentStarted && !shouldKill) ? prev.isAiWorking
|
|
1283
|
+
: false
|
|
1087
1284
|
};
|
|
1088
1285
|
});
|
|
1089
1286
|
return;
|
|
@@ -1177,7 +1374,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1177
1374
|
// Carry the thinkingDuration to the tool message, don't add empty message to history
|
|
1178
1375
|
pendingThinkingDuration = prev.currentMessage.thinkingDuration;
|
|
1179
1376
|
try {
|
|
1180
|
-
quickLog(`[${new Date().toISOString()}] [App]
|
|
1377
|
+
quickLog(`[${new Date().toISOString()}] [App] Skipping thought-only message (no content), saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
|
|
1181
1378
|
}
|
|
1182
1379
|
catch (e) { }
|
|
1183
1380
|
}
|
|
@@ -1209,7 +1406,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1209
1406
|
}
|
|
1210
1407
|
catch (e) { }
|
|
1211
1408
|
let newHistory = prev.messageHistory;
|
|
1212
|
-
if (prev.currentMessage) {
|
|
1409
|
+
if (prev.currentMessage && !shouldSkipMessage(prev.currentMessage)) {
|
|
1213
1410
|
newHistory = [...prev.messageHistory, prev.currentMessage];
|
|
1214
1411
|
}
|
|
1215
1412
|
return {
|
|
@@ -1235,8 +1432,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1235
1432
|
timestamp: new Date(),
|
|
1236
1433
|
connectionStatus: status
|
|
1237
1434
|
};
|
|
1238
|
-
// Move current message to history if exists
|
|
1239
|
-
const newHistory = prev.currentMessage
|
|
1435
|
+
// Move current message to history if exists (skip thought-only messages)
|
|
1436
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1240
1437
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1241
1438
|
: prev.messageHistory;
|
|
1242
1439
|
return {
|
|
@@ -1265,8 +1462,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1265
1462
|
currentMessage: null
|
|
1266
1463
|
};
|
|
1267
1464
|
}
|
|
1268
|
-
// Otherwise just add to history
|
|
1269
|
-
const newHistory = prev.currentMessage
|
|
1465
|
+
// Otherwise just add to history (skip thought-only messages)
|
|
1466
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1270
1467
|
? [...prev.messageHistory, prev.currentMessage, finalConnectionMessage]
|
|
1271
1468
|
: [...prev.messageHistory, finalConnectionMessage];
|
|
1272
1469
|
return {
|
|
@@ -1361,14 +1558,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1361
1558
|
pendingShellRef.current.output += update.chunk;
|
|
1362
1559
|
return;
|
|
1363
1560
|
}
|
|
1561
|
+
// Update last output time for stall detection
|
|
1562
|
+
lastOutputTimeRef.current = Date.now();
|
|
1364
1563
|
setState(prev => {
|
|
1365
1564
|
// Update shell state if it's an execute_command
|
|
1366
1565
|
if (update.toolName === 'execute_command' && prev.shellState) {
|
|
1566
|
+
const newOutput = prev.shellState.output + update.chunk;
|
|
1567
|
+
// If agent control is enabled, schedule input detection
|
|
1568
|
+
if (prev.shellState.isAgentControlled && prev.shellState.isRunning && !inputDetectionPendingRef.current) {
|
|
1569
|
+
// Clear previous debounce timer
|
|
1570
|
+
if (detectionDebounceRef.current) {
|
|
1571
|
+
clearTimeout(detectionDebounceRef.current);
|
|
1572
|
+
}
|
|
1573
|
+
// Debounce: wait 500ms after last output before checking for input requirement
|
|
1574
|
+
// This ensures we don't trigger prematurely during fast output
|
|
1575
|
+
detectionDebounceRef.current = setTimeout(() => {
|
|
1576
|
+
const timeSinceLastOutput = Date.now() - lastOutputTimeRef.current;
|
|
1577
|
+
const detection = detectInputRequirement(newOutput, timeSinceLastOutput, 1000);
|
|
1578
|
+
if (detection.requiresInput && detection.confidence !== 'low') {
|
|
1579
|
+
inputDetectionPendingRef.current = true;
|
|
1580
|
+
// Add monitoring message
|
|
1581
|
+
const promptContext = extractPromptContext(newOutput);
|
|
1582
|
+
const inputType = getInputTypeDescription(detection);
|
|
1583
|
+
setState(innerPrev => {
|
|
1584
|
+
if (!innerPrev.shellState)
|
|
1585
|
+
return innerPrev;
|
|
1586
|
+
const newMessage = {
|
|
1587
|
+
id: `msg-${Date.now()}`,
|
|
1588
|
+
type: 'thinking',
|
|
1589
|
+
content: `Detected input prompt: "${promptContext}" (${inputType})`,
|
|
1590
|
+
timestamp: new Date()
|
|
1591
|
+
};
|
|
1592
|
+
return {
|
|
1593
|
+
...innerPrev,
|
|
1594
|
+
shellState: {
|
|
1595
|
+
...innerPrev.shellState,
|
|
1596
|
+
monitoringMessages: [...(innerPrev.shellState.monitoringMessages || []), newMessage]
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
});
|
|
1600
|
+
// Trigger AI re-prompting with shell context via isolated ShellInputAgent
|
|
1601
|
+
const shellId = prev.shellState?.shellId;
|
|
1602
|
+
if (shellId && ShellInputAgent.isSessionActive(shellId)) {
|
|
1603
|
+
ShellInputAgent.handleInputPrompt(shellId, newOutput, promptContext, inputType, detection.confidence);
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
quickLog(`[${new Date().toISOString()}] [Debounce] ShellInputAgent session not active! shellId=${shellId}\n`);
|
|
1607
|
+
}
|
|
1608
|
+
// Reset pending flag after a delay to allow for next detection
|
|
1609
|
+
setTimeout(() => {
|
|
1610
|
+
inputDetectionPendingRef.current = false;
|
|
1611
|
+
}, 3000);
|
|
1612
|
+
}
|
|
1613
|
+
}, 500);
|
|
1614
|
+
}
|
|
1367
1615
|
return {
|
|
1368
1616
|
...prev,
|
|
1369
1617
|
shellState: {
|
|
1370
1618
|
...prev.shellState,
|
|
1371
|
-
output:
|
|
1619
|
+
output: newOutput
|
|
1372
1620
|
}
|
|
1373
1621
|
};
|
|
1374
1622
|
}
|
|
@@ -1390,7 +1638,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1390
1638
|
return prev;
|
|
1391
1639
|
});
|
|
1392
1640
|
});
|
|
1393
|
-
}, []);
|
|
1641
|
+
}, [onMessage]);
|
|
1394
1642
|
// Set up callback to receive plan mode changes
|
|
1395
1643
|
React.useEffect(() => {
|
|
1396
1644
|
onPlanModeChange((planMode) => {
|
|
@@ -1448,6 +1696,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1448
1696
|
}));
|
|
1449
1697
|
});
|
|
1450
1698
|
}, [onTokenCountUpdate]);
|
|
1699
|
+
// Set up callback to receive context limit state from cli-adapter
|
|
1700
|
+
React.useEffect(() => {
|
|
1701
|
+
onContextLimitReached((reached) => {
|
|
1702
|
+
setState(prev => ({
|
|
1703
|
+
...prev,
|
|
1704
|
+
contextLimitReached: reached
|
|
1705
|
+
}));
|
|
1706
|
+
});
|
|
1707
|
+
}, [onContextLimitReached]);
|
|
1451
1708
|
// Set up callback to receive session quota updates from cli-adapter
|
|
1452
1709
|
React.useEffect(() => {
|
|
1453
1710
|
onSessionQuotaUpdate((remaining, canSend, timeRemaining) => {
|
|
@@ -1458,6 +1715,36 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1458
1715
|
}));
|
|
1459
1716
|
});
|
|
1460
1717
|
}, [onSessionQuotaUpdate]);
|
|
1718
|
+
// Set up callback for when AI answers a shell prompt (updates thinking message to show tick)
|
|
1719
|
+
React.useEffect(() => {
|
|
1720
|
+
onPromptAnswered((shellId) => {
|
|
1721
|
+
setState(prev => {
|
|
1722
|
+
if (!prev.shellState || prev.shellState.shellId !== shellId) {
|
|
1723
|
+
return prev;
|
|
1724
|
+
}
|
|
1725
|
+
// Find the last 'thinking' message that isn't already answered and mark it
|
|
1726
|
+
const messages = prev.shellState.monitoringMessages || [];
|
|
1727
|
+
let updated = false;
|
|
1728
|
+
const updatedMessages = [...messages].reverse().map((msg, idx) => {
|
|
1729
|
+
if (!updated && msg.type === 'thinking' && !msg.isAnswered) {
|
|
1730
|
+
updated = true;
|
|
1731
|
+
return { ...msg, isAnswered: true };
|
|
1732
|
+
}
|
|
1733
|
+
return msg;
|
|
1734
|
+
}).reverse();
|
|
1735
|
+
if (!updated) {
|
|
1736
|
+
return prev;
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
...prev,
|
|
1740
|
+
shellState: {
|
|
1741
|
+
...prev.shellState,
|
|
1742
|
+
monitoringMessages: updatedMessages
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
});
|
|
1746
|
+
});
|
|
1747
|
+
}, [onPromptAnswered]);
|
|
1461
1748
|
// Set up callback for plan approval requests
|
|
1462
1749
|
React.useEffect(() => {
|
|
1463
1750
|
onPlanApprovalRequest(async (plan) => {
|
|
@@ -1505,8 +1792,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1505
1792
|
completionNote
|
|
1506
1793
|
}
|
|
1507
1794
|
};
|
|
1508
|
-
// Move current message to history if exists
|
|
1509
|
-
const newHistory = prev.currentMessage
|
|
1795
|
+
// Move current message to history if exists (skip thought-only)
|
|
1796
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1510
1797
|
? [...prev.messageHistory, prev.currentMessage, taskCompletedMessage]
|
|
1511
1798
|
: [...prev.messageHistory, taskCompletedMessage];
|
|
1512
1799
|
return {
|
|
@@ -1667,6 +1954,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1667
1954
|
}));
|
|
1668
1955
|
return;
|
|
1669
1956
|
}
|
|
1957
|
+
// If in an MCP screen, return to chat
|
|
1958
|
+
if (state.screen === 'mcp-add' || state.screen === 'mcp-remove' || state.screen === 'mcp-enable' || state.screen === 'mcp-disable' || state.screen === 'mcp-list') {
|
|
1959
|
+
setState(prev => ({
|
|
1960
|
+
...prev,
|
|
1961
|
+
screen: 'chat',
|
|
1962
|
+
mcpServers: undefined
|
|
1963
|
+
}));
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1670
1966
|
// If AI is working, cancel the operation
|
|
1671
1967
|
if (state.isLoading || state.isAiWorking) {
|
|
1672
1968
|
// Cancel the current AI request
|
|
@@ -1811,8 +2107,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1811
2107
|
// In command mode, add command to history immediately
|
|
1812
2108
|
if (state.commandMode) {
|
|
1813
2109
|
setState(prev => {
|
|
1814
|
-
// Move current message to history if exists
|
|
1815
|
-
const newHistory = prev.currentMessage
|
|
2110
|
+
// Move current message to history if exists (skip thought-only)
|
|
2111
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1816
2112
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1817
2113
|
: prev.messageHistory;
|
|
1818
2114
|
const commandMessage = {
|
|
@@ -1850,7 +2146,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1850
2146
|
// Generate a stable message ID that we can use to update the message later
|
|
1851
2147
|
const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1852
2148
|
setState(prev => {
|
|
1853
|
-
const newHistory = prev.currentMessage
|
|
2149
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1854
2150
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1855
2151
|
: prev.messageHistory;
|
|
1856
2152
|
const userMessage = {
|
|
@@ -1993,8 +2289,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1993
2289
|
content: `❌ Error: ${error.message || 'Unknown error occurred'}`,
|
|
1994
2290
|
timestamp: new Date()
|
|
1995
2291
|
};
|
|
1996
|
-
// Move current to history if exists and add error
|
|
1997
|
-
const newHistory = prev.currentMessage
|
|
2292
|
+
// Move current to history if exists and add error (skip thought-only)
|
|
2293
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1998
2294
|
? [...prev.messageHistory, prev.currentMessage, errorMessage]
|
|
1999
2295
|
: [...prev.messageHistory, errorMessage];
|
|
2000
2296
|
return {
|
|
@@ -2079,7 +2375,55 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2079
2375
|
return;
|
|
2080
2376
|
}
|
|
2081
2377
|
onShellSignal(signal);
|
|
2378
|
+
}, shellId: state.shellState.shellId, isAgentControlled: state.shellState.isAgentControlled, onToggleAgentControl: () => {
|
|
2379
|
+
// Toggle agent control mode
|
|
2380
|
+
const wasAgentControlled = state.shellState?.isAgentControlled;
|
|
2381
|
+
const willBeAgentControlled = !wasAgentControlled;
|
|
2382
|
+
const currentOutput = state.shellState?.output || '';
|
|
2383
|
+
const isShellRunning = state.shellState?.isRunning;
|
|
2384
|
+
// Log the toggle using the imported quickLog
|
|
2385
|
+
quickLog(`[${new Date().toISOString()}] [AgentControl] Toggle: ${wasAgentControlled} -> ${willBeAgentControlled}, output length: ${currentOutput.length}, isRunning: ${isShellRunning}\n`);
|
|
2386
|
+
setState(prev => ({
|
|
2387
|
+
...prev,
|
|
2388
|
+
shellState: prev.shellState ? {
|
|
2389
|
+
...prev.shellState,
|
|
2390
|
+
isAgentControlled: willBeAgentControlled,
|
|
2391
|
+
monitoringMessages: willBeAgentControlled ? [] : prev.shellState.monitoringMessages
|
|
2392
|
+
} : undefined
|
|
2393
|
+
}));
|
|
2394
|
+
// Start or terminate ShellInputAgent session
|
|
2395
|
+
const shellId = state.shellState?.shellId;
|
|
2396
|
+
const shellCommand = state.shellState?.command || '';
|
|
2397
|
+
const shellCwd = state.shellState?.cwd || process.cwd();
|
|
2398
|
+
if (willBeAgentControlled && shellId) {
|
|
2399
|
+
// Start isolated shell input agent session with main conversation context
|
|
2400
|
+
const mainConversation = getMainConversation();
|
|
2401
|
+
ShellInputAgent.startSession(shellId, shellCommand, shellCwd, mainConversation);
|
|
2402
|
+
quickLog(`[${new Date().toISOString()}] [AgentControl] Started ShellInputAgent session for ${shellId}\n`);
|
|
2403
|
+
// Start AI-based input detection polling
|
|
2404
|
+
InputDetectionAgent.startPolling(shellId, currentOutput, // Pass current output directly
|
|
2405
|
+
shellCommand, shellCwd);
|
|
2406
|
+
quickLog(`[${new Date().toISOString()}] [AgentControl] Started AI-based input detection polling for ${shellId}\n`);
|
|
2407
|
+
}
|
|
2408
|
+
else if (!willBeAgentControlled && shellId) {
|
|
2409
|
+
// Stop AI-based input detection polling
|
|
2410
|
+
InputDetectionAgent.stopPolling(shellId);
|
|
2411
|
+
quickLog(`[${new Date().toISOString()}] [AgentControl] Stopped AI-based input detection polling for ${shellId}\n`);
|
|
2412
|
+
// Terminate shell input agent session
|
|
2413
|
+
ShellInputAgent.terminateSession(shellId);
|
|
2414
|
+
quickLog(`[${new Date().toISOString()}] [AgentControl] Terminated ShellInputAgent session for ${shellId}\n`);
|
|
2415
|
+
}
|
|
2082
2416
|
} })),
|
|
2417
|
+
state.shellState?.isAgentControlled && state.shellState?.isFocused && state.shellState?.isRunning && (React.createElement(MonitorModeAIPanel, { messages: [
|
|
2418
|
+
...(state.shellState.monitoringMessages || []),
|
|
2419
|
+
// Inject current assistant message if present (so AI thoughts/responses appear in panel)
|
|
2420
|
+
...(state.currentMessage?.role === 'assistant' && state.currentMessage.content ? [{
|
|
2421
|
+
id: state.currentMessage.id,
|
|
2422
|
+
type: 'text', // Cast to literal type to match MonitorMessage interface
|
|
2423
|
+
content: state.currentMessage.content,
|
|
2424
|
+
timestamp: state.currentMessage.timestamp
|
|
2425
|
+
}] : [])
|
|
2426
|
+
], maxHeight: Math.floor((process.stdout.rows || 24) / 2) - 5, isActive: true })),
|
|
2083
2427
|
state.isAiWorking && !state.shellState && !state.approvalRequest &&
|
|
2084
2428
|
!(state.currentMessage?.toolExecution?.status === 'executing') && (React.createElement(Box, { marginBottom: 1, paddingLeft: 1 },
|
|
2085
2429
|
React.createElement(LoadingIndicator, { key: "loading-indicator" }),
|
|
@@ -2090,10 +2434,12 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2090
2434
|
resolve(approved);
|
|
2091
2435
|
}
|
|
2092
2436
|
} })) : (React.createElement(InputBox, { key: "input-box", onSubmit: (value, clipboardImages) => {
|
|
2437
|
+
// Reset "Zombie Agent" protection on new manual input
|
|
2438
|
+
ignoreIncomingAIUpdatesRef.current = false;
|
|
2093
2439
|
// Clear preserved input on submit
|
|
2094
2440
|
preservedInputTextRef.current = '';
|
|
2095
2441
|
handleSubmit(value, clipboardImages);
|
|
2096
|
-
}, autoAcceptMode: state.autoAcceptMode, model: state.currentModel, planMode: state.planMode, commandMode: state.commandMode, backgroundMode: state.backgroundMode, currentWorkingDirectory: state.currentWorkingDirectory, commandHistory: state.commandHistory, onToggleAutoAccept: handleToggleAutoAccept, onToggleCommandMode: onToggleCommandMode, onToggleBackgroundMode: onToggleBackgroundMode, isActive: true, subshellContext: state.subshellContext, currentTokens: state.currentTokens, maxTokens: state.maxTokens, isShellRunning: state.shellState?.isRunning, backgroundTaskCount: state.backgroundTaskCount, initialValue: preservedInputTextRef.current, onValueChange: handleInputValueChange, onSetAutoModeSetup: (callback) => {
|
|
2442
|
+
}, autoAcceptMode: state.autoAcceptMode, model: state.currentModel, planMode: state.planMode, commandMode: state.commandMode, backgroundMode: state.backgroundMode, currentWorkingDirectory: state.currentWorkingDirectory, commandHistory: state.commandHistory, onToggleAutoAccept: handleToggleAutoAccept, onToggleCommandMode: onToggleCommandMode, onToggleBackgroundMode: onToggleBackgroundMode, isActive: true, subshellContext: state.subshellContext, currentTokens: state.currentTokens, maxTokens: state.maxTokens, contextLimitReached: state.contextLimitReached, isShellRunning: state.shellState?.isRunning, backgroundTaskCount: state.backgroundTaskCount, subAgentCount: state.subAgentCount, initialValue: preservedInputTextRef.current, onValueChange: handleInputValueChange, onSetAutoModeSetup: (callback) => {
|
|
2097
2443
|
setAutoModeCallbackRef.current = callback;
|
|
2098
2444
|
}, sessionQuotaExhausted: state.sessionQuotaExhausted, sessionQuotaTimeRemaining: state.sessionQuotaTimeRemaining }))),
|
|
2099
2445
|
state.showExitWarning && (React.createElement(Box, { marginTop: 1 },
|
|
@@ -2140,7 +2486,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2140
2486
|
state.screen === 'chat-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2141
2487
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDCDA Resume a Previous Chat"),
|
|
2142
2488
|
React.createElement(Box, { marginTop: 1 },
|
|
2143
|
-
React.createElement(
|
|
2489
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2144
2490
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2145
2491
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2146
2492
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2199,7 +2545,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2199
2545
|
state.screen === 'chat-delete-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2200
2546
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDDD1\uFE0F Select a Chat to Delete"),
|
|
2201
2547
|
React.createElement(Box, { marginTop: 1 },
|
|
2202
|
-
React.createElement(
|
|
2548
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2203
2549
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2204
2550
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2205
2551
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2254,35 +2600,42 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2254
2600
|
"\uD83D\uDCDA Saved Chats (",
|
|
2255
2601
|
state.chatPickerChats.length,
|
|
2256
2602
|
")"),
|
|
2257
|
-
React.createElement(Box, { marginTop: 1
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2603
|
+
React.createElement(Box, { marginTop: 1 },
|
|
2604
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2605
|
+
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2606
|
+
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2607
|
+
const isCurrent = chat.id === state.currentChatId;
|
|
2608
|
+
const env = chat.environment || 'local';
|
|
2609
|
+
return {
|
|
2610
|
+
label: `${isCurrent ? '● ' : ' '}${chat.title}`,
|
|
2611
|
+
envLabel: `[${env}]`,
|
|
2612
|
+
dateLabel: `${date} ${time}`,
|
|
2613
|
+
msgLabel: `${chat.messageCount} msgs`,
|
|
2614
|
+
value: chat.id,
|
|
2615
|
+
isCurrent,
|
|
2616
|
+
environment: env
|
|
2617
|
+
};
|
|
2618
|
+
}), itemComponent: ({ isSelected, label, isCurrent, envLabel, dateLabel, msgLabel, environment }) => (React.createElement(Box, null,
|
|
2619
|
+
React.createElement(Text, { color: isSelected ? '#00ccff' : (isCurrent ? '#00cc66' : 'white'), bold: isSelected || isCurrent },
|
|
2620
|
+
isSelected ? '> ' : ' ',
|
|
2621
|
+
label),
|
|
2622
|
+
React.createElement(Text, { color: environment === 'local' ? 'gray' : (environment?.startsWith('ssh') ? '#ff9966' : environment?.startsWith('wsl') ? '#66ff99' : '#66ccff') },
|
|
2623
|
+
" ",
|
|
2624
|
+
envLabel),
|
|
2625
|
+
React.createElement(Text, { color: "gray" },
|
|
2626
|
+
" ",
|
|
2627
|
+
dateLabel),
|
|
2628
|
+
React.createElement(Text, { color: "gray" }, " - "),
|
|
2629
|
+
React.createElement(Text, { color: "gray" }, msgLabel))), onSelect: () => {
|
|
2630
|
+
// Just close the view
|
|
2631
|
+
setState(prev => ({ ...prev, screen: 'chat' }));
|
|
2632
|
+
} })),
|
|
2280
2633
|
React.createElement(Box, { marginTop: 1 },
|
|
2281
2634
|
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2282
2635
|
state.screen === 'chat-rename-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2283
2636
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\u270F\uFE0F Select a Chat to Rename"),
|
|
2284
2637
|
React.createElement(Box, { marginTop: 1 },
|
|
2285
|
-
React.createElement(
|
|
2638
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2286
2639
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2287
2640
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2288
2641
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2398,7 +2751,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2398
2751
|
}));
|
|
2399
2752
|
} }))),
|
|
2400
2753
|
state.screen === 'background-task-list' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#9966ff", paddingX: 1 },
|
|
2401
|
-
React.createElement(Text, { color: "#9966ff", bold: true }, "
|
|
2754
|
+
React.createElement(Text, { color: "#9966ff", bold: true }, "Running Background Tasks"),
|
|
2402
2755
|
React.createElement(Text, { dimColor: true }, "Select a task to view its output in focus mode"),
|
|
2403
2756
|
React.createElement(Box, { marginTop: 1 },
|
|
2404
2757
|
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
@@ -2409,35 +2762,49 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2409
2762
|
cwd: task.cwd,
|
|
2410
2763
|
duration: `${durationSec}s`,
|
|
2411
2764
|
isRunning: task.isRunning,
|
|
2412
|
-
outputPreview: task.outputPreview
|
|
2765
|
+
outputPreview: task.outputPreview // Pass raw preview, sanitize in component
|
|
2413
2766
|
};
|
|
2414
|
-
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) =>
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2767
|
+
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) => {
|
|
2768
|
+
// Calculate max width for wrapping (terminal width - border padding - left padding)
|
|
2769
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
2770
|
+
const maxTextWidth = Math.max(terminalWidth - 10, 40);
|
|
2771
|
+
// Truncate cwd if too long
|
|
2772
|
+
const truncatedCwd = cwd && cwd.length > maxTextWidth
|
|
2773
|
+
? cwd.slice(0, maxTextWidth - 3) + '...'
|
|
2774
|
+
: cwd;
|
|
2775
|
+
// Truncate output preview if too long (accounting for └─ prefix)
|
|
2776
|
+
const maxPreviewWidth = maxTextWidth - 3;
|
|
2777
|
+
// Sanitize: Strip ANSI codes and replace newlines with spaces
|
|
2778
|
+
const cleanPreview = outputPreview ? stripAnsi(outputPreview).replace(/[\r\n]+/g, ' ').trim() : '';
|
|
2779
|
+
const truncatedPreview = cleanPreview.length > maxPreviewWidth
|
|
2780
|
+
? cleanPreview.slice(0, maxPreviewWidth - 3) + '...'
|
|
2781
|
+
: cleanPreview;
|
|
2782
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
2783
|
+
React.createElement(Box, null,
|
|
2784
|
+
React.createElement(Text, { color: isSelected ? '#9966ff' : 'white', bold: isSelected },
|
|
2785
|
+
isSelected ? '> ' : ' ',
|
|
2786
|
+
label),
|
|
2787
|
+
React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
|
|
2788
|
+
" [",
|
|
2789
|
+
isRunning ? 'running' : 'done',
|
|
2790
|
+
"]"),
|
|
2791
|
+
React.createElement(Text, { color: "gray" },
|
|
2792
|
+
" ",
|
|
2793
|
+
duration)),
|
|
2794
|
+
React.createElement(Box, { paddingLeft: 4 },
|
|
2795
|
+
React.createElement(Text, { color: "gray", wrap: "truncate" }, truncatedCwd)),
|
|
2796
|
+
truncatedPreview && (React.createElement(Box, { paddingLeft: 4 },
|
|
2797
|
+
React.createElement(Text, { color: "gray", dimColor: true, wrap: "truncate" },
|
|
2798
|
+
"\u2514\u2500 ",
|
|
2799
|
+
truncatedPreview)))));
|
|
2800
|
+
}, onSelect: (item) => {
|
|
2434
2801
|
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2435
2802
|
onBackgroundTaskSelection(item.value);
|
|
2436
2803
|
} })),
|
|
2437
2804
|
React.createElement(Box, { marginTop: 1 },
|
|
2438
2805
|
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2439
2806
|
state.screen === 'background-task-cancel' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#ff6666", paddingX: 1 },
|
|
2440
|
-
React.createElement(Text, { color: "#ff6666", bold: true }, "
|
|
2807
|
+
React.createElement(Text, { color: "#ff6666", bold: true }, "Cancel a Background Task"),
|
|
2441
2808
|
React.createElement(Text, { dimColor: true }, "Select a task to terminate it"),
|
|
2442
2809
|
React.createElement(Box, { marginTop: 1 },
|
|
2443
2810
|
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
@@ -2449,26 +2816,125 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2449
2816
|
duration: `${durationSec}s`,
|
|
2450
2817
|
isRunning: task.isRunning
|
|
2451
2818
|
};
|
|
2452
|
-
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) =>
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
React.createElement(
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2819
|
+
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) => {
|
|
2820
|
+
// Calculate max width for wrapping (terminal width - border padding - left padding)
|
|
2821
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
2822
|
+
const maxTextWidth = Math.max(terminalWidth - 10, 40);
|
|
2823
|
+
// Truncate cwd if too long
|
|
2824
|
+
const truncatedCwd = cwd && cwd.length > maxTextWidth
|
|
2825
|
+
? cwd.slice(0, maxTextWidth - 3) + '...'
|
|
2826
|
+
: cwd;
|
|
2827
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
2828
|
+
React.createElement(Box, null,
|
|
2829
|
+
React.createElement(Text, { color: isSelected ? '#ff6666' : 'white', bold: isSelected },
|
|
2830
|
+
isSelected ? '> ' : ' ',
|
|
2831
|
+
label),
|
|
2832
|
+
React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
|
|
2833
|
+
" [",
|
|
2834
|
+
isRunning ? 'running' : 'done',
|
|
2835
|
+
"]"),
|
|
2836
|
+
React.createElement(Text, { color: "gray" },
|
|
2837
|
+
" ",
|
|
2838
|
+
duration)),
|
|
2839
|
+
React.createElement(Box, { paddingLeft: 4 },
|
|
2840
|
+
React.createElement(Text, { color: "gray", wrap: "truncate" }, truncatedCwd))));
|
|
2841
|
+
}, onSelect: (item) => {
|
|
2468
2842
|
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2469
2843
|
onBackgroundTaskCancel(item.value);
|
|
2470
2844
|
} })),
|
|
2471
2845
|
React.createElement(Box, { marginTop: 1 },
|
|
2472
|
-
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))))
|
|
2846
|
+
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2847
|
+
state.screen === 'mcp-add' && (React.createElement(MCPAddScreen, { onAdd: (config) => {
|
|
2848
|
+
const result = onMCPAddServer(config);
|
|
2849
|
+
if (result.success) {
|
|
2850
|
+
// Return to chat with success message
|
|
2851
|
+
const successMessage = {
|
|
2852
|
+
id: `mcp-add-${Date.now()}`,
|
|
2853
|
+
role: 'system',
|
|
2854
|
+
content: `✅ MCP server "${config.name}" added successfully!\n\nRun \`/mcp refresh\` to connect to the new server.`,
|
|
2855
|
+
timestamp: new Date()
|
|
2856
|
+
};
|
|
2857
|
+
setState(prev => ({
|
|
2858
|
+
...prev,
|
|
2859
|
+
screen: 'chat',
|
|
2860
|
+
messageHistory: [...prev.messageHistory, successMessage]
|
|
2861
|
+
}));
|
|
2862
|
+
}
|
|
2863
|
+
}, onCancel: () => {
|
|
2864
|
+
setState(prev => ({
|
|
2865
|
+
...prev,
|
|
2866
|
+
screen: 'chat'
|
|
2867
|
+
}));
|
|
2868
|
+
}, validateConfig: onMCPValidateConfig })),
|
|
2869
|
+
state.screen === 'mcp-remove' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "remove", servers: state.mcpServers, onSelect: (serverName) => {
|
|
2870
|
+
onMCPRemoveServer(serverName);
|
|
2871
|
+
const successMessage = {
|
|
2872
|
+
id: `mcp-remove-${Date.now()}`,
|
|
2873
|
+
role: 'system',
|
|
2874
|
+
content: `✅ MCP server "${serverName}" removed from configuration.`,
|
|
2875
|
+
timestamp: new Date()
|
|
2876
|
+
};
|
|
2877
|
+
setState(prev => ({
|
|
2878
|
+
...prev,
|
|
2879
|
+
screen: 'chat',
|
|
2880
|
+
mcpServers: undefined,
|
|
2881
|
+
messageHistory: [...prev.messageHistory, successMessage]
|
|
2882
|
+
}));
|
|
2883
|
+
}, onCancel: () => {
|
|
2884
|
+
setState(prev => ({
|
|
2885
|
+
...prev,
|
|
2886
|
+
screen: 'chat',
|
|
2887
|
+
mcpServers: undefined
|
|
2888
|
+
}));
|
|
2889
|
+
} })),
|
|
2890
|
+
state.screen === 'mcp-enable' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "enable", servers: state.mcpServers, onSelect: (serverName) => {
|
|
2891
|
+
onMCPEnableServer(serverName);
|
|
2892
|
+
const successMessage = {
|
|
2893
|
+
id: `mcp-enable-${Date.now()}`,
|
|
2894
|
+
role: 'system',
|
|
2895
|
+
content: `✅ MCP server "${serverName}" enabled!\n\nRun \`/mcp refresh\` to connect.`,
|
|
2896
|
+
timestamp: new Date()
|
|
2897
|
+
};
|
|
2898
|
+
setState(prev => ({
|
|
2899
|
+
...prev,
|
|
2900
|
+
screen: 'chat',
|
|
2901
|
+
mcpServers: undefined,
|
|
2902
|
+
messageHistory: [...prev.messageHistory, successMessage]
|
|
2903
|
+
}));
|
|
2904
|
+
}, onCancel: () => {
|
|
2905
|
+
setState(prev => ({
|
|
2906
|
+
...prev,
|
|
2907
|
+
screen: 'chat',
|
|
2908
|
+
mcpServers: undefined
|
|
2909
|
+
}));
|
|
2910
|
+
} })),
|
|
2911
|
+
state.screen === 'mcp-disable' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "disable", servers: state.mcpServers, onSelect: (serverName) => {
|
|
2912
|
+
onMCPDisableServer(serverName);
|
|
2913
|
+
const successMessage = {
|
|
2914
|
+
id: `mcp-disable-${Date.now()}`,
|
|
2915
|
+
role: 'system',
|
|
2916
|
+
content: `⏸️ MCP server "${serverName}" disabled. AI will no longer use its tools.`,
|
|
2917
|
+
timestamp: new Date()
|
|
2918
|
+
};
|
|
2919
|
+
setState(prev => ({
|
|
2920
|
+
...prev,
|
|
2921
|
+
screen: 'chat',
|
|
2922
|
+
mcpServers: undefined,
|
|
2923
|
+
messageHistory: [...prev.messageHistory, successMessage]
|
|
2924
|
+
}));
|
|
2925
|
+
}, onCancel: () => {
|
|
2926
|
+
setState(prev => ({
|
|
2927
|
+
...prev,
|
|
2928
|
+
screen: 'chat',
|
|
2929
|
+
mcpServers: undefined
|
|
2930
|
+
}));
|
|
2931
|
+
} })),
|
|
2932
|
+
state.screen === 'mcp-list' && state.mcpListData && (React.createElement(MCPListScreen, { servers: state.mcpListData, onClose: () => {
|
|
2933
|
+
setState(prev => ({
|
|
2934
|
+
...prev,
|
|
2935
|
+
screen: 'chat',
|
|
2936
|
+
mcpListData: undefined
|
|
2937
|
+
}));
|
|
2938
|
+
} }))));
|
|
2473
2939
|
};
|
|
2474
2940
|
//# sourceMappingURL=App.js.map
|