centaurus-cli 2.9.1 → 2.9.3

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.
Files changed (156) hide show
  1. package/dist/cli-adapter.d.ts +76 -3
  2. package/dist/cli-adapter.d.ts.map +1 -1
  3. package/dist/cli-adapter.js +593 -230
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/config/mcp-config-manager.d.ts +21 -0
  6. package/dist/config/mcp-config-manager.d.ts.map +1 -1
  7. package/dist/config/mcp-config-manager.js +184 -1
  8. package/dist/config/mcp-config-manager.js.map +1 -1
  9. package/dist/config/models.d.ts +1 -0
  10. package/dist/config/models.d.ts.map +1 -1
  11. package/dist/config/models.js +9 -2
  12. package/dist/config/models.js.map +1 -1
  13. package/dist/config/slash-commands.d.ts +3 -0
  14. package/dist/config/slash-commands.d.ts.map +1 -1
  15. package/dist/config/slash-commands.js +39 -4
  16. package/dist/config/slash-commands.js.map +1 -1
  17. package/dist/config/types.d.ts +2 -0
  18. package/dist/config/types.d.ts.map +1 -1
  19. package/dist/config/types.js +1 -0
  20. package/dist/config/types.js.map +1 -1
  21. package/dist/index.js +60 -11
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/mcp-command-handler.d.ts +34 -3
  24. package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
  25. package/dist/mcp/mcp-command-handler.js +171 -83
  26. package/dist/mcp/mcp-command-handler.js.map +1 -1
  27. package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
  28. package/dist/mcp/mcp-server-manager.js +9 -23
  29. package/dist/mcp/mcp-server-manager.js.map +1 -1
  30. package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
  31. package/dist/mcp/mcp-tool-wrapper.js +42 -5
  32. package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
  33. package/dist/services/ai-autocomplete-agent.d.ts +39 -0
  34. package/dist/services/ai-autocomplete-agent.d.ts.map +1 -0
  35. package/dist/services/ai-autocomplete-agent.js +189 -0
  36. package/dist/services/ai-autocomplete-agent.js.map +1 -0
  37. package/dist/services/ai-service-client.d.ts +25 -0
  38. package/dist/services/ai-service-client.d.ts.map +1 -1
  39. package/dist/services/ai-service-client.js +162 -1
  40. package/dist/services/ai-service-client.js.map +1 -1
  41. package/dist/services/api-client.d.ts +9 -0
  42. package/dist/services/api-client.d.ts.map +1 -1
  43. package/dist/services/api-client.js +25 -0
  44. package/dist/services/api-client.js.map +1 -1
  45. package/dist/services/auth-handler.js +1 -1
  46. package/dist/services/auth-handler.js.map +1 -1
  47. package/dist/services/input-detection-agent.d.ts +40 -0
  48. package/dist/services/input-detection-agent.d.ts.map +1 -0
  49. package/dist/services/input-detection-agent.js +213 -0
  50. package/dist/services/input-detection-agent.js.map +1 -0
  51. package/dist/services/input-requirement-detector.d.ts +28 -0
  52. package/dist/services/input-requirement-detector.d.ts.map +1 -0
  53. package/dist/services/input-requirement-detector.js +203 -0
  54. package/dist/services/input-requirement-detector.js.map +1 -0
  55. package/dist/services/local-chat-storage.d.ts +21 -0
  56. package/dist/services/local-chat-storage.d.ts.map +1 -1
  57. package/dist/services/local-chat-storage.js +138 -43
  58. package/dist/services/local-chat-storage.js.map +1 -1
  59. package/dist/services/monitored-shell-manager.d.ts +120 -0
  60. package/dist/services/monitored-shell-manager.d.ts.map +1 -0
  61. package/dist/services/monitored-shell-manager.js +239 -0
  62. package/dist/services/monitored-shell-manager.js.map +1 -0
  63. package/dist/services/ollama-service.d.ts +197 -0
  64. package/dist/services/ollama-service.d.ts.map +1 -0
  65. package/dist/services/ollama-service.js +324 -0
  66. package/dist/services/ollama-service.js.map +1 -0
  67. package/dist/services/shell-input-agent.d.ts +89 -0
  68. package/dist/services/shell-input-agent.d.ts.map +1 -0
  69. package/dist/services/shell-input-agent.js +361 -0
  70. package/dist/services/shell-input-agent.js.map +1 -0
  71. package/dist/services/sub-agent-manager.d.ts +139 -0
  72. package/dist/services/sub-agent-manager.d.ts.map +1 -0
  73. package/dist/services/sub-agent-manager.js +517 -0
  74. package/dist/services/sub-agent-manager.js.map +1 -0
  75. package/dist/tools/background-command.d.ts.map +1 -1
  76. package/dist/tools/background-command.js +33 -13
  77. package/dist/tools/background-command.js.map +1 -1
  78. package/dist/tools/command.d.ts.map +1 -1
  79. package/dist/tools/command.js +64 -1
  80. package/dist/tools/command.js.map +1 -1
  81. package/dist/tools/file-ops.d.ts.map +1 -1
  82. package/dist/tools/file-ops.js +33 -19
  83. package/dist/tools/file-ops.js.map +1 -1
  84. package/dist/tools/get-diff.js +1 -1
  85. package/dist/tools/get-diff.js.map +1 -1
  86. package/dist/tools/grep-search.d.ts.map +1 -1
  87. package/dist/tools/grep-search.js +41 -15
  88. package/dist/tools/grep-search.js.map +1 -1
  89. package/dist/tools/plan-mode.js +3 -3
  90. package/dist/tools/plan-mode.js.map +1 -1
  91. package/dist/tools/registry.js +1 -1
  92. package/dist/tools/registry.js.map +1 -1
  93. package/dist/tools/sub-agent.d.ts +9 -0
  94. package/dist/tools/sub-agent.d.ts.map +1 -0
  95. package/dist/tools/sub-agent.js +232 -0
  96. package/dist/tools/sub-agent.js.map +1 -0
  97. package/dist/tools/task-complete.d.ts.map +1 -1
  98. package/dist/tools/task-complete.js +14 -26
  99. package/dist/tools/task-complete.js.map +1 -1
  100. package/dist/ui/components/App.d.ts +45 -2
  101. package/dist/ui/components/App.d.ts.map +1 -1
  102. package/dist/ui/components/App.js +605 -96
  103. package/dist/ui/components/App.js.map +1 -1
  104. package/dist/ui/components/CircularSelectInput.d.ts +24 -0
  105. package/dist/ui/components/CircularSelectInput.d.ts.map +1 -0
  106. package/dist/ui/components/CircularSelectInput.js +71 -0
  107. package/dist/ui/components/CircularSelectInput.js.map +1 -0
  108. package/dist/ui/components/ErrorBoundary.d.ts +3 -2
  109. package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
  110. package/dist/ui/components/ErrorBoundary.js +29 -1
  111. package/dist/ui/components/ErrorBoundary.js.map +1 -1
  112. package/dist/ui/components/InputBox.d.ts +4 -0
  113. package/dist/ui/components/InputBox.d.ts.map +1 -1
  114. package/dist/ui/components/InputBox.js +343 -21
  115. package/dist/ui/components/InputBox.js.map +1 -1
  116. package/dist/ui/components/InteractiveShell.d.ts +6 -0
  117. package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
  118. package/dist/ui/components/InteractiveShell.js +57 -6
  119. package/dist/ui/components/InteractiveShell.js.map +1 -1
  120. package/dist/ui/components/MCPAddScreen.d.ts +13 -0
  121. package/dist/ui/components/MCPAddScreen.d.ts.map +1 -0
  122. package/dist/ui/components/MCPAddScreen.js +54 -0
  123. package/dist/ui/components/MCPAddScreen.js.map +1 -0
  124. package/dist/ui/components/MCPListScreen.d.ts +17 -0
  125. package/dist/ui/components/MCPListScreen.d.ts.map +1 -0
  126. package/dist/ui/components/MCPListScreen.js +50 -0
  127. package/dist/ui/components/MCPListScreen.js.map +1 -0
  128. package/dist/ui/components/MCPServerListScreen.d.ts +16 -0
  129. package/dist/ui/components/MCPServerListScreen.d.ts.map +1 -0
  130. package/dist/ui/components/MCPServerListScreen.js +59 -0
  131. package/dist/ui/components/MCPServerListScreen.js.map +1 -0
  132. package/dist/ui/components/MonitorModeAIPanel.d.ts +23 -0
  133. package/dist/ui/components/MonitorModeAIPanel.d.ts.map +1 -0
  134. package/dist/ui/components/MonitorModeAIPanel.js +69 -0
  135. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -0
  136. package/dist/ui/components/MultiLineInput.d.ts +13 -0
  137. package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
  138. package/dist/ui/components/MultiLineInput.js +289 -0
  139. package/dist/ui/components/MultiLineInput.js.map +1 -0
  140. package/dist/ui/components/StatusBar.d.ts +2 -0
  141. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  142. package/dist/ui/components/StatusBar.js +33 -2
  143. package/dist/ui/components/StatusBar.js.map +1 -1
  144. package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
  145. package/dist/ui/components/ToolExecutionMessage.js +231 -13
  146. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  147. package/dist/ui/components/VersionUpdatePrompt.d.ts.map +1 -1
  148. package/dist/ui/components/VersionUpdatePrompt.js +3 -2
  149. package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
  150. package/dist/utils/command-history.d.ts +12 -2
  151. package/dist/utils/command-history.d.ts.map +1 -1
  152. package/dist/utils/command-history.js +57 -13
  153. package/dist/utils/command-history.js.map +1 -1
  154. package/dist/utils/input-classifier.js +1 -1
  155. package/dist/utils/input-classifier.js.map +1 -1
  156. package/package.json +2 -1
@@ -1,6 +1,8 @@
1
- import React, { useState, useCallback } from 'react';
1
+ import React, { useState, useCallback, useMemo } 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,13 +24,21 @@ 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';
30
39
  import { logDebug, logError } from '../../utils/logger.js';
31
40
  import { getTerminalDimensions } from '../../hooks/useTerminalDimensions.js';
41
+ import { ConfigManager } from '../../config/manager.js';
32
42
  // Banner item with stable timestamp - created once outside component
33
43
  const BANNER_ITEM = { id: '__banner__', role: '__banner__', content: '', timestamp: new Date(0) };
34
44
  const MessageList = React.memo(({ history, current, showBanner }) => {
@@ -137,12 +147,18 @@ const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
137
147
  React.createElement(Box, { marginTop: 1 },
138
148
  React.createElement(Text, { dimColor: true }, "Press Enter to save, ESC to cancel"))));
139
149
  };
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 }) => {
150
+ 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
151
  const { exit } = useApp();
152
+ // Calculate limit for paginated lists (75% of terminal height)
153
+ const listLimit = Math.max(5, Math.floor((process.stdout.rows || 24) * 0.75));
142
154
  const autoAcceptRef = React.useRef(false);
143
155
  const setAutoModeCallbackRef = React.useRef(null);
144
156
  // Connectivity State
145
157
  const isConnected = useConnectivity();
158
+ // Agent control mode refs for input detection
159
+ const lastOutputTimeRef = React.useRef(Date.now());
160
+ const inputDetectionPendingRef = React.useRef(false);
161
+ const detectionDebounceRef = React.useRef(null);
146
162
  // Helper to clear screen
147
163
  const clearScreen = useCallback(() => {
148
164
  process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
@@ -195,6 +211,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
195
211
  isAiWorking: false,
196
212
  currentTokens: 0,
197
213
  maxTokens: getMaxTokensForModel(initialModel || 'gemini-2.5-flash'),
214
+ contextLimitReached: false,
198
215
  shellState: undefined,
199
216
  isInteractiveEditorMode: false,
200
217
  pickerOptions: undefined,
@@ -203,8 +220,10 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
203
220
  passwordRequest: undefined,
204
221
  connectionStatus: undefined,
205
222
  backgroundTaskCount: 0,
223
+ subAgentCount: 0,
206
224
  sessionQuotaExhausted: false,
207
225
  sessionQuotaTimeRemaining: '',
226
+ aiAutoSuggest: false,
208
227
  });
209
228
  // Track last terminal width to detect actual width changes
210
229
  const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
@@ -270,6 +289,20 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
270
289
  }
271
290
  };
272
291
  }, [clearScreen]);
292
+ // Handle global keyboard shortcuts
293
+ useInput((input, key) => {
294
+ // Escape key handling for background task screens
295
+ if (key.escape) {
296
+ if (state.screen === 'background-task-list' || state.screen === 'background-task-cancel') {
297
+ clearScreen();
298
+ setState(prev => ({
299
+ ...prev,
300
+ screen: 'chat',
301
+ backgroundTasks: undefined // Clear the list
302
+ }));
303
+ }
304
+ }
305
+ });
273
306
  // Check for version updates on mount
274
307
  React.useEffect(() => {
275
308
  checkForUpdates().then(versionInfo => {
@@ -292,6 +325,14 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
292
325
  React.useEffect(() => {
293
326
  const loadModelsConfig = async () => {
294
327
  try {
328
+ // Initialize ConfigManager to load general settings
329
+ const configManager = new ConfigManager();
330
+ const generalConfig = configManager.load();
331
+ setState(prev => ({
332
+ ...prev,
333
+ aiAutoSuggest: generalConfig.aiAutoSuggest === true
334
+ }));
335
+ // Import quickLog for debugging
295
336
  // Import quickLog for debugging
296
337
  const { quickLog } = await import('../../utils/conversation-logger.js');
297
338
  quickLog(`[${new Date().toISOString()}] [App] loadModelsConfig effect starting, currentModel: ${initialModel}\n`);
@@ -323,6 +364,17 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
323
364
  React.useEffect(() => {
324
365
  autoAcceptRef.current = state.autoAcceptMode;
325
366
  }, [state.autoAcceptMode]);
367
+ // Push shell output updates to InputDetectionAgent when agent control is active
368
+ // This ensures the detection agent always has the latest output for polling
369
+ React.useEffect(() => {
370
+ if (state.shellState?.isAgentControlled && state.shellState?.shellId && state.shellState?.output) {
371
+ InputDetectionAgent.updateOutput(state.shellState.shellId, state.shellState.output);
372
+ }
373
+ }, [state.shellState?.output, state.shellState?.isAgentControlled, state.shellState?.shellId]);
374
+ // Ref to track if we should strictly block incoming AI updates (Zombie Agent Protection)
375
+ // This is set to true when an Agent-Controlled shell exits, essentially killing the "AI Agent"
376
+ // associated with it from the UI perspective. It is reset when the user manually inputs a new message.
377
+ const ignoreIncomingAIUpdatesRef = React.useRef(false);
326
378
  // NOTE: Token count is now updated via onTokenCountUpdate callback from cli-adapter
327
379
  // which tracks the actual AI conversation history including system prompt.
328
380
  // The old UI-based calculation has been removed to avoid overwriting correct values.
@@ -335,6 +387,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
335
387
  // Set up callback to receive streaming chunks - only once on mount
336
388
  React.useEffect(() => {
337
389
  onResponseStream((chunk) => {
390
+ // CIRCUIT BREAKER: Block incoming messages from "Zombie Agents"
391
+ if (ignoreIncomingAIUpdatesRef.current)
392
+ return;
338
393
  isStreamingRef.current = true; // Mark that we're streaming
339
394
  // Check if terminal is large enough for streaming
340
395
  const dimensions = getTerminalDimensions();
@@ -438,6 +493,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
438
493
  // Set up callback to receive thought chunks
439
494
  React.useEffect(() => {
440
495
  onThoughtStream((thought) => {
496
+ // CIRCUIT BREAKER: Block incoming thoughts from "Zombie Agents"
497
+ if (ignoreIncomingAIUpdatesRef.current)
498
+ return;
441
499
  // Debug logging to file
442
500
  try {
443
501
  quickLog(`[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
@@ -529,6 +587,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
529
587
  // Set up callback for when thinking completes
530
588
  React.useEffect(() => {
531
589
  onThoughtComplete((durationSeconds) => {
590
+ // CIRCUIT BREAKER: Block incoming thought completion from "Zombie Agents"
591
+ if (ignoreIncomingAIUpdatesRef.current)
592
+ return;
532
593
  try {
533
594
  quickLog(`[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
534
595
  }
@@ -565,12 +626,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
565
626
  });
566
627
  });
567
628
  }, [onThoughtComplete]);
629
+ // Helper function to check if a message should be skipped (thought-only with no content or tools)
630
+ const shouldSkipMessage = (message) => {
631
+ if (!message)
632
+ return false;
633
+ // Skip if it's an assistant message with only thinking duration and no content or tool execution
634
+ if (message.role === 'assistant' &&
635
+ message.thinkingDuration !== undefined &&
636
+ message.content.trim() === '' &&
637
+ !message.toolExecution) {
638
+ return true;
639
+ }
640
+ return false;
641
+ };
568
642
  // Set up callback for direct messages (slash commands) - add directly to history
569
643
  React.useEffect(() => {
570
644
  onDirectMessage((message) => {
645
+ // CIRCUIT BREAKER: Block incoming direct messages from "Zombie Agents"
646
+ if (ignoreIncomingAIUpdatesRef.current)
647
+ return;
571
648
  setState(prev => {
572
- // Move current message to history if exists
573
- const newHistory = prev.currentMessage
649
+ // Move current message to history if exists (skip thought-only messages)
650
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
574
651
  ? [...prev.messageHistory, prev.currentMessage]
575
652
  : prev.messageHistory;
576
653
  // Create system message for slash command response
@@ -594,6 +671,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
594
671
  // Set up callback to receive complete responses (for slash commands and non-streaming responses)
595
672
  React.useEffect(() => {
596
673
  onResponseReceived((message) => {
674
+ // CIRCUIT BREAKER: Block incoming responses from "Zombie Agents"
675
+ if (ignoreIncomingAIUpdatesRef.current)
676
+ return;
597
677
  // If we were streaming, move the current message to history immediately
598
678
  if (isStreamingRef.current) {
599
679
  isStreamingRef.current = false; // Reset for next message
@@ -702,8 +782,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
702
782
  timestamp: now,
703
783
  shouldStream: false // Command responses should appear instantly
704
784
  };
705
- // If we have a current message that's an assistant message, move it to history
706
- if (prev.currentMessage && prev.currentMessage.role === 'assistant') {
785
+ // If we have a current message that's an assistant message, move it to history (skip thought-only)
786
+ if (prev.currentMessage && prev.currentMessage.role === 'assistant' && !shouldSkipMessage(prev.currentMessage)) {
707
787
  return {
708
788
  ...prev,
709
789
  messageHistory: [...prev.messageHistory, prev.currentMessage],
@@ -806,6 +886,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
806
886
  }));
807
887
  });
808
888
  }, []); // Empty dependency array - only register once
889
+ // Set up callback for sub-agent count change
890
+ React.useEffect(() => {
891
+ onSubAgentCountChange((count) => {
892
+ setState(prev => ({
893
+ ...prev,
894
+ subAgentCount: count
895
+ }));
896
+ });
897
+ }, []); // Empty dependency array - only register once
809
898
  // Set up callback for setting Auto mode (used after background task starts)
810
899
  React.useEffect(() => {
811
900
  onSetAutoModeSetup((enabled) => {
@@ -943,6 +1032,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
943
1032
  });
944
1033
  });
945
1034
  }, []); // Empty dependency array - only register once
1035
+ // Set up callback for MCP Add screen
1036
+ React.useEffect(() => {
1037
+ onMCPAddScreenSetup(() => {
1038
+ clearScreen();
1039
+ setState(prev => ({
1040
+ ...prev,
1041
+ screen: 'mcp-add',
1042
+ isLoading: false
1043
+ }));
1044
+ });
1045
+ }, []); // Empty dependency array - only register once
1046
+ // Set up callback for MCP Remove screen
1047
+ React.useEffect(() => {
1048
+ onMCPRemoveScreenSetup((servers) => {
1049
+ clearScreen();
1050
+ setState(prev => ({
1051
+ ...prev,
1052
+ screen: 'mcp-remove',
1053
+ mcpServers: servers,
1054
+ isLoading: false
1055
+ }));
1056
+ });
1057
+ }, []); // Empty dependency array - only register once
1058
+ // Set up callback for MCP Enable screen
1059
+ React.useEffect(() => {
1060
+ onMCPEnableScreenSetup((servers) => {
1061
+ clearScreen();
1062
+ setState(prev => ({
1063
+ ...prev,
1064
+ screen: 'mcp-enable',
1065
+ mcpServers: servers,
1066
+ isLoading: false
1067
+ }));
1068
+ });
1069
+ }, []); // Empty dependency array - only register once
1070
+ // Set up callback for MCP Disable screen
1071
+ React.useEffect(() => {
1072
+ onMCPDisableScreenSetup((servers) => {
1073
+ clearScreen();
1074
+ setState(prev => ({
1075
+ ...prev,
1076
+ screen: 'mcp-disable',
1077
+ mcpServers: servers,
1078
+ isLoading: false
1079
+ }));
1080
+ });
1081
+ }, []); // Empty dependency array - only register once
1082
+ // Set up callback for MCP List screen
1083
+ React.useEffect(() => {
1084
+ onMCPListScreenSetup((servers) => {
1085
+ clearScreen();
1086
+ setState(prev => ({
1087
+ ...prev,
1088
+ screen: 'mcp-list',
1089
+ mcpListData: servers,
1090
+ isLoading: false
1091
+ }));
1092
+ });
1093
+ }, []); // Empty dependency array - only register once
946
1094
  // Sync UI message history to CLI adapter whenever it changes
947
1095
  React.useEffect(() => {
948
1096
  onUIMessageHistoryUpdate(state.messageHistory);
@@ -950,6 +1098,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
950
1098
  // Set up callback for tool execution updates
951
1099
  React.useEffect(() => {
952
1100
  onToolExecutionUpdate((update) => {
1101
+ // CIRCUIT BREAKER: Block incoming tool updates from "Zombie Agents"
1102
+ if (ignoreIncomingAIUpdatesRef.current)
1103
+ return;
953
1104
  // Special handling for execute_command
954
1105
  if (update.toolName === 'execute_command') {
955
1106
  try {
@@ -958,6 +1109,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
958
1109
  catch (e) { }
959
1110
  // STARTING EXECUTION
960
1111
  if (update.status === 'executing') {
1112
+ // Special handling for shell_input - DO NOT overwrite shell state
1113
+ // The agent is sending input to an EXISTING shell, so we must preserve the current shell state
1114
+ if (update.arguments?.shell_input) {
1115
+ setState(prev => ({
1116
+ ...prev,
1117
+ // Move current message to history if needed
1118
+ messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1119
+ ? [...prev.messageHistory, prev.currentMessage]
1120
+ : prev.messageHistory,
1121
+ // Update current message to show the tool execution
1122
+ currentMessage: {
1123
+ id: `tool-execute_command-${Date.now()}`,
1124
+ role: 'tool',
1125
+ content: '',
1126
+ timestamp: new Date(),
1127
+ toolExecution: { ...update }
1128
+ },
1129
+ isLoading: false,
1130
+ isAiWorking: true
1131
+ }));
1132
+ return;
1133
+ }
961
1134
  const command = update.arguments?.command || update.arguments?.CommandLine || update.arguments?.commandLine || '';
962
1135
  // Start a pending shell with a short delay
963
1136
  // This prevents the UI from flickering for very fast commands (like ls)
@@ -973,6 +1146,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
973
1146
  setState(prev => ({
974
1147
  ...prev,
975
1148
  shellState: {
1149
+ shellId: `shell-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
976
1150
  command: cmd,
977
1151
  cwd: cwd,
978
1152
  output: bufferedOutput,
@@ -981,10 +1155,11 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
981
1155
  error: undefined,
982
1156
  isFocused: false,
983
1157
  isPty: update.arguments?.isPty || false,
984
- remoteContext: update.arguments?.remoteContext
1158
+ remoteContext: update.arguments?.remoteContext,
1159
+ isAgentStarted: true // Mark as agent-started since it came from execute_command tool
985
1160
  },
986
- // IMPORTANT: Always preserve current message by moving it to history first
987
- messageHistory: prev.currentMessage
1161
+ // IMPORTANT: Always preserve current message by moving it to history first (skip thought-only)
1162
+ messageHistory: prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
988
1163
  ? [...prev.messageHistory, prev.currentMessage]
989
1164
  : prev.messageHistory,
990
1165
  currentMessage: {
@@ -1040,8 +1215,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1040
1215
  arguments: update.arguments
1041
1216
  }
1042
1217
  };
1043
- // Move current message to history if exists
1044
- const newHistory = prev.currentMessage
1218
+ // Move current message to history if exists (skip thought-only)
1219
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1045
1220
  ? [...prev.messageHistory, prev.currentMessage]
1046
1221
  : prev.messageHistory;
1047
1222
  return {
@@ -1078,12 +1253,44 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1078
1253
  arguments: update.arguments
1079
1254
  }
1080
1255
  };
1256
+ // Check if this was just an input string sent to an existing shell
1257
+ const isShellInput = update.arguments?.shell_input;
1258
+ // Reset input detection pending flag so we can detect NEXT prompt
1259
+ if (update.status === 'completed' || update.status === 'error') {
1260
+ inputDetectionPendingRef.current = false;
1261
+ }
1262
+ // Check if we hit a dead shell error during agent control
1263
+ const isDeadShellError = update.status === 'error' && update.error?.includes('No active interactive shell');
1264
+ // STRICT ISOLATION TRIGGER: If agent-controlled shell is done (completed or dead), kill the agent loop
1265
+ // LOGIC SPLIT:
1266
+ // 1. Manual Shell (User started, then Alt+I): Strict Sub-Agent behavior. Kill IMMEDIATELY on exit/completion.
1267
+ // 2. Agent Shell (Agent started via execute_command): Main Agent behavior. Kill only on ERROR. Allow success pass-through.
1268
+ const isAgentStarted = prev.shellState?.isAgentStarted;
1269
+ const shouldKill = isAgentStarted ? isDeadShellError : (update.status === 'completed' || isDeadShellError);
1270
+ if (prev.shellState?.isAgentControlled && shouldKill) {
1271
+ try {
1272
+ quickLog(`[${new Date().toISOString()}] [App] Strict Isolation: Agent Controlled shell exited/died. Engaging circuit breaker. (AgentStarted: ${isAgentStarted})\n`);
1273
+ }
1274
+ catch (e) { }
1275
+ ignoreIncomingAIUpdatesRef.current = true;
1276
+ // Terminate the shell input agent session
1277
+ if (prev.shellState?.shellId) {
1278
+ ShellInputAgent.terminateSession(prev.shellState.shellId);
1279
+ }
1280
+ }
1081
1281
  return {
1082
1282
  ...prev,
1083
- shellState: undefined,
1283
+ // If it's just shell input, preserve the shell state UNLESS it's a dead shell error
1284
+ shellState: (isShellInput && !isDeadShellError) ? prev.shellState : undefined,
1084
1285
  messageHistory: [...prev.messageHistory, shellMessage],
1085
1286
  currentMessage: null,
1086
- isAiWorking: prev.isAiWorking // Preserve existing state
1287
+ // Determine if AI should keep working:
1288
+ // - Normal shell input: preserve state (keep thrusting if in chain)
1289
+ // - Agent-started shell completed successfully: preserve state (agent loop continues)
1290
+ // - Dead shell error or manual shell completed: STOP thrusting (reset to false)
1291
+ isAiWorking: (isShellInput && !isDeadShellError) ? prev.isAiWorking
1292
+ : (isAgentStarted && !shouldKill) ? prev.isAiWorking
1293
+ : false
1087
1294
  };
1088
1295
  });
1089
1296
  return;
@@ -1177,7 +1384,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1177
1384
  // Carry the thinkingDuration to the tool message, don't add empty message to history
1178
1385
  pendingThinkingDuration = prev.currentMessage.thinkingDuration;
1179
1386
  try {
1180
- quickLog(`[${new Date().toISOString()}] [App] Found thought-only message, saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
1387
+ quickLog(`[${new Date().toISOString()}] [App] Skipping thought-only message (no content), saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
1181
1388
  }
1182
1389
  catch (e) { }
1183
1390
  }
@@ -1209,7 +1416,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1209
1416
  }
1210
1417
  catch (e) { }
1211
1418
  let newHistory = prev.messageHistory;
1212
- if (prev.currentMessage) {
1419
+ if (prev.currentMessage && !shouldSkipMessage(prev.currentMessage)) {
1213
1420
  newHistory = [...prev.messageHistory, prev.currentMessage];
1214
1421
  }
1215
1422
  return {
@@ -1235,8 +1442,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1235
1442
  timestamp: new Date(),
1236
1443
  connectionStatus: status
1237
1444
  };
1238
- // Move current message to history if exists
1239
- const newHistory = prev.currentMessage
1445
+ // Move current message to history if exists (skip thought-only messages)
1446
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1240
1447
  ? [...prev.messageHistory, prev.currentMessage]
1241
1448
  : prev.messageHistory;
1242
1449
  return {
@@ -1265,8 +1472,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1265
1472
  currentMessage: null
1266
1473
  };
1267
1474
  }
1268
- // Otherwise just add to history
1269
- const newHistory = prev.currentMessage
1475
+ // Otherwise just add to history (skip thought-only messages)
1476
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1270
1477
  ? [...prev.messageHistory, prev.currentMessage, finalConnectionMessage]
1271
1478
  : [...prev.messageHistory, finalConnectionMessage];
1272
1479
  return {
@@ -1361,14 +1568,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1361
1568
  pendingShellRef.current.output += update.chunk;
1362
1569
  return;
1363
1570
  }
1571
+ // Update last output time for stall detection
1572
+ lastOutputTimeRef.current = Date.now();
1364
1573
  setState(prev => {
1365
1574
  // Update shell state if it's an execute_command
1366
1575
  if (update.toolName === 'execute_command' && prev.shellState) {
1576
+ const newOutput = prev.shellState.output + update.chunk;
1577
+ // If agent control is enabled, schedule input detection
1578
+ if (prev.shellState.isAgentControlled && prev.shellState.isRunning && !inputDetectionPendingRef.current) {
1579
+ // Clear previous debounce timer
1580
+ if (detectionDebounceRef.current) {
1581
+ clearTimeout(detectionDebounceRef.current);
1582
+ }
1583
+ // Debounce: wait 500ms after last output before checking for input requirement
1584
+ // This ensures we don't trigger prematurely during fast output
1585
+ detectionDebounceRef.current = setTimeout(() => {
1586
+ const timeSinceLastOutput = Date.now() - lastOutputTimeRef.current;
1587
+ const detection = detectInputRequirement(newOutput, timeSinceLastOutput, 1000);
1588
+ if (detection.requiresInput && detection.confidence !== 'low') {
1589
+ inputDetectionPendingRef.current = true;
1590
+ // Add monitoring message
1591
+ const promptContext = extractPromptContext(newOutput);
1592
+ const inputType = getInputTypeDescription(detection);
1593
+ setState(innerPrev => {
1594
+ if (!innerPrev.shellState)
1595
+ return innerPrev;
1596
+ const newMessage = {
1597
+ id: `msg-${Date.now()}`,
1598
+ type: 'thinking',
1599
+ content: `Detected input prompt: "${promptContext}" (${inputType})`,
1600
+ timestamp: new Date()
1601
+ };
1602
+ return {
1603
+ ...innerPrev,
1604
+ shellState: {
1605
+ ...innerPrev.shellState,
1606
+ monitoringMessages: [...(innerPrev.shellState.monitoringMessages || []), newMessage]
1607
+ }
1608
+ };
1609
+ });
1610
+ // Trigger AI re-prompting with shell context via isolated ShellInputAgent
1611
+ const shellId = prev.shellState?.shellId;
1612
+ if (shellId && ShellInputAgent.isSessionActive(shellId)) {
1613
+ ShellInputAgent.handleInputPrompt(shellId, newOutput, promptContext, inputType, detection.confidence);
1614
+ }
1615
+ else {
1616
+ quickLog(`[${new Date().toISOString()}] [Debounce] ShellInputAgent session not active! shellId=${shellId}\n`);
1617
+ }
1618
+ // Reset pending flag after a delay to allow for next detection
1619
+ setTimeout(() => {
1620
+ inputDetectionPendingRef.current = false;
1621
+ }, 3000);
1622
+ }
1623
+ }, 500);
1624
+ }
1367
1625
  return {
1368
1626
  ...prev,
1369
1627
  shellState: {
1370
1628
  ...prev.shellState,
1371
- output: prev.shellState.output + update.chunk
1629
+ output: newOutput
1372
1630
  }
1373
1631
  };
1374
1632
  }
@@ -1390,7 +1648,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1390
1648
  return prev;
1391
1649
  });
1392
1650
  });
1393
- }, []);
1651
+ }, [onMessage]);
1394
1652
  // Set up callback to receive plan mode changes
1395
1653
  React.useEffect(() => {
1396
1654
  onPlanModeChange((planMode) => {
@@ -1448,6 +1706,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1448
1706
  }));
1449
1707
  });
1450
1708
  }, [onTokenCountUpdate]);
1709
+ // Set up callback to receive context limit state from cli-adapter
1710
+ React.useEffect(() => {
1711
+ onContextLimitReached((reached) => {
1712
+ setState(prev => ({
1713
+ ...prev,
1714
+ contextLimitReached: reached
1715
+ }));
1716
+ });
1717
+ }, [onContextLimitReached]);
1451
1718
  // Set up callback to receive session quota updates from cli-adapter
1452
1719
  React.useEffect(() => {
1453
1720
  onSessionQuotaUpdate((remaining, canSend, timeRemaining) => {
@@ -1458,6 +1725,36 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1458
1725
  }));
1459
1726
  });
1460
1727
  }, [onSessionQuotaUpdate]);
1728
+ // Set up callback for when AI answers a shell prompt (updates thinking message to show tick)
1729
+ React.useEffect(() => {
1730
+ onPromptAnswered((shellId) => {
1731
+ setState(prev => {
1732
+ if (!prev.shellState || prev.shellState.shellId !== shellId) {
1733
+ return prev;
1734
+ }
1735
+ // Find the last 'thinking' message that isn't already answered and mark it
1736
+ const messages = prev.shellState.monitoringMessages || [];
1737
+ let updated = false;
1738
+ const updatedMessages = [...messages].reverse().map((msg, idx) => {
1739
+ if (!updated && msg.type === 'thinking' && !msg.isAnswered) {
1740
+ updated = true;
1741
+ return { ...msg, isAnswered: true };
1742
+ }
1743
+ return msg;
1744
+ }).reverse();
1745
+ if (!updated) {
1746
+ return prev;
1747
+ }
1748
+ return {
1749
+ ...prev,
1750
+ shellState: {
1751
+ ...prev.shellState,
1752
+ monitoringMessages: updatedMessages
1753
+ }
1754
+ };
1755
+ });
1756
+ });
1757
+ }, [onPromptAnswered]);
1461
1758
  // Set up callback for plan approval requests
1462
1759
  React.useEffect(() => {
1463
1760
  onPlanApprovalRequest(async (plan) => {
@@ -1505,8 +1802,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1505
1802
  completionNote
1506
1803
  }
1507
1804
  };
1508
- // Move current message to history if exists
1509
- const newHistory = prev.currentMessage
1805
+ // Move current message to history if exists (skip thought-only)
1806
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1510
1807
  ? [...prev.messageHistory, prev.currentMessage, taskCompletedMessage]
1511
1808
  : [...prev.messageHistory, taskCompletedMessage];
1512
1809
  return {
@@ -1667,6 +1964,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1667
1964
  }));
1668
1965
  return;
1669
1966
  }
1967
+ // If in an MCP screen, return to chat
1968
+ if (state.screen === 'mcp-add' || state.screen === 'mcp-remove' || state.screen === 'mcp-enable' || state.screen === 'mcp-disable' || state.screen === 'mcp-list') {
1969
+ setState(prev => ({
1970
+ ...prev,
1971
+ screen: 'chat',
1972
+ mcpServers: undefined
1973
+ }));
1974
+ return;
1975
+ }
1670
1976
  // If AI is working, cancel the operation
1671
1977
  if (state.isLoading || state.isAiWorking) {
1672
1978
  // Cancel the current AI request
@@ -1811,8 +2117,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1811
2117
  // In command mode, add command to history immediately
1812
2118
  if (state.commandMode) {
1813
2119
  setState(prev => {
1814
- // Move current message to history if exists
1815
- const newHistory = prev.currentMessage
2120
+ // Move current message to history if exists (skip thought-only)
2121
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1816
2122
  ? [...prev.messageHistory, prev.currentMessage]
1817
2123
  : prev.messageHistory;
1818
2124
  const commandMessage = {
@@ -1833,6 +2139,33 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1833
2139
  }
1834
2140
  else if (!isSlashCommand) {
1835
2141
  // Only add user message to display if it's not a slash command
2142
+ // INTERRUPT HANDLING: If AI is currently working, cancel and clean up first
2143
+ // This allows the new message to replace the interrupted turn
2144
+ if (state.isAiWorking || state.isLoading) {
2145
+ // Cancel current request (cli-adapter will clean up its history)
2146
+ onCancelRequest();
2147
+ // Small delay to allow abort to propagate before new request starts
2148
+ await new Promise(resolve => setTimeout(resolve, 50));
2149
+ // Clean up UI state: remove any partial/interrupted messages
2150
+ setState(prev => {
2151
+ // Filter out the last user message (the one we're replacing)
2152
+ let cleanedHistory = [...prev.messageHistory];
2153
+ for (let i = cleanedHistory.length - 1; i >= 0; i--) {
2154
+ if (cleanedHistory[i].role === 'user') {
2155
+ cleanedHistory.splice(i, 1);
2156
+ break; // Only remove the most recent user message
2157
+ }
2158
+ }
2159
+ return {
2160
+ ...prev,
2161
+ messageHistory: cleanedHistory,
2162
+ currentMessage: null,
2163
+ isLoading: false,
2164
+ isAiWorking: false,
2165
+ approvalRequest: undefined, // Clear any pending approval
2166
+ };
2167
+ });
2168
+ }
1836
2169
  // Show user message IMMEDIATELY with image breadcrumbs (uploading state)
1837
2170
  // Images will be uploaded in background, then AI will be called
1838
2171
  const hasImagesToUpload = clipboardImages && clipboardImages.length > 0;
@@ -1850,7 +2183,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1850
2183
  // Generate a stable message ID that we can use to update the message later
1851
2184
  const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1852
2185
  setState(prev => {
1853
- const newHistory = prev.currentMessage
2186
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1854
2187
  ? [...prev.messageHistory, prev.currentMessage]
1855
2188
  : prev.messageHistory;
1856
2189
  const userMessage = {
@@ -1993,8 +2326,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
1993
2326
  content: `❌ Error: ${error.message || 'Unknown error occurred'}`,
1994
2327
  timestamp: new Date()
1995
2328
  };
1996
- // Move current to history if exists and add error
1997
- const newHistory = prev.currentMessage
2329
+ // Move current to history if exists and add error (skip thought-only)
2330
+ const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
1998
2331
  ? [...prev.messageHistory, prev.currentMessage, errorMessage]
1999
2332
  : [...prev.messageHistory, errorMessage];
2000
2333
  return {
@@ -2024,6 +2357,12 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2024
2357
  const handleInputValueChange = useCallback((value) => {
2025
2358
  preservedInputTextRef.current = value;
2026
2359
  }, []);
2360
+ // Memoize session commands to prevent unnecessary re-renders in InputBox
2361
+ const sessionCommands = useMemo(() => {
2362
+ return state.messageHistory
2363
+ .filter(m => m.role === 'user')
2364
+ .map(m => m.content);
2365
+ }, [state.messageHistory]);
2027
2366
  // If in interactive editor mode, render minimal UI to keep Ink mounted
2028
2367
  // but don't render any visible content - the editor has the screen
2029
2368
  if (state.isInteractiveEditorMode) {
@@ -2079,7 +2418,55 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2079
2418
  return;
2080
2419
  }
2081
2420
  onShellSignal(signal);
2421
+ }, shellId: state.shellState.shellId, isAgentControlled: state.shellState.isAgentControlled, onToggleAgentControl: () => {
2422
+ // Toggle agent control mode
2423
+ const wasAgentControlled = state.shellState?.isAgentControlled;
2424
+ const willBeAgentControlled = !wasAgentControlled;
2425
+ const currentOutput = state.shellState?.output || '';
2426
+ const isShellRunning = state.shellState?.isRunning;
2427
+ // Log the toggle using the imported quickLog
2428
+ quickLog(`[${new Date().toISOString()}] [AgentControl] Toggle: ${wasAgentControlled} -> ${willBeAgentControlled}, output length: ${currentOutput.length}, isRunning: ${isShellRunning}\n`);
2429
+ setState(prev => ({
2430
+ ...prev,
2431
+ shellState: prev.shellState ? {
2432
+ ...prev.shellState,
2433
+ isAgentControlled: willBeAgentControlled,
2434
+ monitoringMessages: willBeAgentControlled ? [] : prev.shellState.monitoringMessages
2435
+ } : undefined
2436
+ }));
2437
+ // Start or terminate ShellInputAgent session
2438
+ const shellId = state.shellState?.shellId;
2439
+ const shellCommand = state.shellState?.command || '';
2440
+ const shellCwd = state.shellState?.cwd || process.cwd();
2441
+ if (willBeAgentControlled && shellId) {
2442
+ // Start isolated shell input agent session with main conversation context
2443
+ const mainConversation = getMainConversation();
2444
+ ShellInputAgent.startSession(shellId, shellCommand, shellCwd, mainConversation);
2445
+ quickLog(`[${new Date().toISOString()}] [AgentControl] Started ShellInputAgent session for ${shellId}\n`);
2446
+ // Start AI-based input detection polling
2447
+ InputDetectionAgent.startPolling(shellId, currentOutput, // Pass current output directly
2448
+ shellCommand, shellCwd);
2449
+ quickLog(`[${new Date().toISOString()}] [AgentControl] Started AI-based input detection polling for ${shellId}\n`);
2450
+ }
2451
+ else if (!willBeAgentControlled && shellId) {
2452
+ // Stop AI-based input detection polling
2453
+ InputDetectionAgent.stopPolling(shellId);
2454
+ quickLog(`[${new Date().toISOString()}] [AgentControl] Stopped AI-based input detection polling for ${shellId}\n`);
2455
+ // Terminate shell input agent session
2456
+ ShellInputAgent.terminateSession(shellId);
2457
+ quickLog(`[${new Date().toISOString()}] [AgentControl] Terminated ShellInputAgent session for ${shellId}\n`);
2458
+ }
2082
2459
  } })),
2460
+ state.shellState?.isAgentControlled && state.shellState?.isFocused && state.shellState?.isRunning && (React.createElement(MonitorModeAIPanel, { messages: [
2461
+ ...(state.shellState.monitoringMessages || []),
2462
+ // Inject current assistant message if present (so AI thoughts/responses appear in panel)
2463
+ ...(state.currentMessage?.role === 'assistant' && state.currentMessage.content ? [{
2464
+ id: state.currentMessage.id,
2465
+ type: 'text', // Cast to literal type to match MonitorMessage interface
2466
+ content: state.currentMessage.content,
2467
+ timestamp: state.currentMessage.timestamp
2468
+ }] : [])
2469
+ ], maxHeight: Math.floor((process.stdout.rows || 24) / 2) - 5, isActive: true })),
2083
2470
  state.isAiWorking && !state.shellState && !state.approvalRequest &&
2084
2471
  !(state.currentMessage?.toolExecution?.status === 'executing') && (React.createElement(Box, { marginBottom: 1, paddingLeft: 1 },
2085
2472
  React.createElement(LoadingIndicator, { key: "loading-indicator" }),
@@ -2090,12 +2477,14 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2090
2477
  resolve(approved);
2091
2478
  }
2092
2479
  } })) : (React.createElement(InputBox, { key: "input-box", onSubmit: (value, clipboardImages) => {
2480
+ // Reset "Zombie Agent" protection on new manual input
2481
+ ignoreIncomingAIUpdatesRef.current = false;
2093
2482
  // Clear preserved input on submit
2094
2483
  preservedInputTextRef.current = '';
2095
2484
  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) => {
2485
+ }, 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
2486
  setAutoModeCallbackRef.current = callback;
2098
- }, sessionQuotaExhausted: state.sessionQuotaExhausted, sessionQuotaTimeRemaining: state.sessionQuotaTimeRemaining }))),
2487
+ }, sessionQuotaExhausted: state.sessionQuotaExhausted, sessionQuotaTimeRemaining: state.sessionQuotaTimeRemaining, aiAutoSuggestEnabled: state.aiAutoSuggest, sessionCommands: sessionCommands }))),
2099
2488
  state.showExitWarning && (React.createElement(Box, { marginTop: 1 },
2100
2489
  React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit"))),
2101
2490
  !isConnected && (React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 },
@@ -2140,7 +2529,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2140
2529
  state.screen === 'chat-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
2141
2530
  React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDCDA Resume a Previous Chat"),
2142
2531
  React.createElement(Box, { marginTop: 1 },
2143
- React.createElement(SelectInput, { items: state.chatPickerChats.map((chat) => {
2532
+ React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
2144
2533
  const date = new Date(chat.updatedAt).toLocaleDateString();
2145
2534
  const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2146
2535
  const isCurrent = chat.id === state.currentChatId;
@@ -2199,7 +2588,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2199
2588
  state.screen === 'chat-delete-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
2200
2589
  React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDDD1\uFE0F Select a Chat to Delete"),
2201
2590
  React.createElement(Box, { marginTop: 1 },
2202
- React.createElement(SelectInput, { items: state.chatPickerChats.map((chat) => {
2591
+ React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
2203
2592
  const date = new Date(chat.updatedAt).toLocaleDateString();
2204
2593
  const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2205
2594
  const isCurrent = chat.id === state.currentChatId;
@@ -2254,35 +2643,42 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2254
2643
  "\uD83D\uDCDA Saved Chats (",
2255
2644
  state.chatPickerChats.length,
2256
2645
  ")"),
2257
- React.createElement(Box, { marginTop: 1, flexDirection: "column" }, state.chatPickerChats.map((chat) => {
2258
- const date = new Date(chat.updatedAt).toLocaleDateString();
2259
- const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2260
- const isCurrent = chat.id === state.currentChatId;
2261
- const env = chat.environment || 'local';
2262
- return (React.createElement(Box, { key: chat.id },
2263
- React.createElement(Text, { color: isCurrent ? '#00cc66' : 'white', bold: isCurrent },
2264
- isCurrent ? '● ' : ' ',
2265
- chat.title),
2266
- React.createElement(Text, { color: env === 'local' ? 'gray' : (env.startsWith('ssh') ? '#ff9966' : env.startsWith('wsl') ? '#66ff99' : '#66ccff') },
2267
- " [",
2268
- env,
2269
- "]"),
2270
- React.createElement(Text, { color: "gray" },
2271
- " ",
2272
- date,
2273
- " ",
2274
- time),
2275
- React.createElement(Text, { color: "gray" }, " - "),
2276
- React.createElement(Text, { color: "gray" },
2277
- chat.messageCount,
2278
- " msgs")));
2279
- })),
2646
+ React.createElement(Box, { marginTop: 1 },
2647
+ React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
2648
+ const date = new Date(chat.updatedAt).toLocaleDateString();
2649
+ const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2650
+ const isCurrent = chat.id === state.currentChatId;
2651
+ const env = chat.environment || 'local';
2652
+ return {
2653
+ label: `${isCurrent ? '● ' : ' '}${chat.title}`,
2654
+ envLabel: `[${env}]`,
2655
+ dateLabel: `${date} ${time}`,
2656
+ msgLabel: `${chat.messageCount} msgs`,
2657
+ value: chat.id,
2658
+ isCurrent,
2659
+ environment: env
2660
+ };
2661
+ }), itemComponent: ({ isSelected, label, isCurrent, envLabel, dateLabel, msgLabel, environment }) => (React.createElement(Box, null,
2662
+ React.createElement(Text, { color: isSelected ? '#00ccff' : (isCurrent ? '#00cc66' : 'white'), bold: isSelected || isCurrent },
2663
+ isSelected ? '> ' : ' ',
2664
+ label),
2665
+ React.createElement(Text, { color: environment === 'local' ? 'gray' : (environment?.startsWith('ssh') ? '#ff9966' : environment?.startsWith('wsl') ? '#66ff99' : '#66ccff') },
2666
+ " ",
2667
+ envLabel),
2668
+ React.createElement(Text, { color: "gray" },
2669
+ " ",
2670
+ dateLabel),
2671
+ React.createElement(Text, { color: "gray" }, " - "),
2672
+ React.createElement(Text, { color: "gray" }, msgLabel))), onSelect: () => {
2673
+ // Just close the view
2674
+ setState(prev => ({ ...prev, screen: 'chat' }));
2675
+ } })),
2280
2676
  React.createElement(Box, { marginTop: 1 },
2281
2677
  React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
2282
2678
  state.screen === 'chat-rename-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
2283
2679
  React.createElement(Text, { color: "#00ccff", bold: true }, "\u270F\uFE0F Select a Chat to Rename"),
2284
2680
  React.createElement(Box, { marginTop: 1 },
2285
- React.createElement(SelectInput, { items: state.chatPickerChats.map((chat) => {
2681
+ React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
2286
2682
  const date = new Date(chat.updatedAt).toLocaleDateString();
2287
2683
  const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
2288
2684
  const isCurrent = chat.id === state.currentChatId;
@@ -2398,7 +2794,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2398
2794
  }));
2399
2795
  } }))),
2400
2796
  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 }, "\uD83D\uDD04 Running Background Tasks"),
2797
+ React.createElement(Text, { color: "#9966ff", bold: true }, "Running Background Tasks"),
2402
2798
  React.createElement(Text, { dimColor: true }, "Select a task to view its output in focus mode"),
2403
2799
  React.createElement(Box, { marginTop: 1 },
2404
2800
  React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
@@ -2409,35 +2805,49 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2409
2805
  cwd: task.cwd,
2410
2806
  duration: `${durationSec}s`,
2411
2807
  isRunning: task.isRunning,
2412
- outputPreview: task.outputPreview.length > 60 ? task.outputPreview.slice(0, 57) + '...' : task.outputPreview
2808
+ outputPreview: task.outputPreview // Pass raw preview, sanitize in component
2413
2809
  };
2414
- }), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) => (React.createElement(Box, { flexDirection: "column" },
2415
- React.createElement(Box, null,
2416
- React.createElement(Text, { color: isSelected ? '#9966ff' : 'white', bold: isSelected },
2417
- isSelected ? '> ' : ' ',
2418
- label),
2419
- React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2420
- " [",
2421
- isRunning ? 'running' : 'done',
2422
- "]"),
2423
- React.createElement(Text, { color: "gray" },
2424
- " \u23F1\uFE0F ",
2425
- duration)),
2426
- React.createElement(Box, { paddingLeft: 4 },
2427
- React.createElement(Text, { color: "gray" },
2428
- "\uD83D\uDCC1 ",
2429
- cwd)),
2430
- outputPreview && (React.createElement(Box, { paddingLeft: 4 },
2431
- React.createElement(Text, { color: "gray", dimColor: true },
2432
- "\u2514\u2500 ",
2433
- outputPreview))))), onSelect: (item) => {
2810
+ }), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) => {
2811
+ // Calculate max width for wrapping (terminal width - border padding - left padding)
2812
+ const terminalWidth = process.stdout.columns || 80;
2813
+ const maxTextWidth = Math.max(terminalWidth - 10, 40);
2814
+ // Truncate cwd if too long
2815
+ const truncatedCwd = cwd && cwd.length > maxTextWidth
2816
+ ? cwd.slice(0, maxTextWidth - 3) + '...'
2817
+ : cwd;
2818
+ // Truncate output preview if too long (accounting for └─ prefix)
2819
+ const maxPreviewWidth = maxTextWidth - 3;
2820
+ // Sanitize: Strip ANSI codes and replace newlines with spaces
2821
+ const cleanPreview = outputPreview ? stripAnsi(outputPreview).replace(/[\r\n]+/g, ' ').trim() : '';
2822
+ const truncatedPreview = cleanPreview.length > maxPreviewWidth
2823
+ ? cleanPreview.slice(0, maxPreviewWidth - 3) + '...'
2824
+ : cleanPreview;
2825
+ return (React.createElement(Box, { flexDirection: "column" },
2826
+ React.createElement(Box, null,
2827
+ React.createElement(Text, { color: isSelected ? '#9966ff' : 'white', bold: isSelected },
2828
+ isSelected ? '> ' : ' ',
2829
+ label),
2830
+ React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2831
+ " [",
2832
+ isRunning ? 'running' : 'done',
2833
+ "]"),
2834
+ React.createElement(Text, { color: "gray" },
2835
+ " ",
2836
+ duration)),
2837
+ React.createElement(Box, { paddingLeft: 4 },
2838
+ React.createElement(Text, { color: "gray", wrap: "truncate" }, truncatedCwd)),
2839
+ truncatedPreview && (React.createElement(Box, { paddingLeft: 4 },
2840
+ React.createElement(Text, { color: "gray", dimColor: true, wrap: "truncate" },
2841
+ "\u2514\u2500 ",
2842
+ truncatedPreview)))));
2843
+ }, onSelect: (item) => {
2434
2844
  setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
2435
2845
  onBackgroundTaskSelection(item.value);
2436
2846
  } })),
2437
2847
  React.createElement(Box, { marginTop: 1 },
2438
2848
  React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
2439
2849
  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 }, "\uD83D\uDED1 Cancel a Background Task"),
2850
+ React.createElement(Text, { color: "#ff6666", bold: true }, "Cancel a Background Task"),
2441
2851
  React.createElement(Text, { dimColor: true }, "Select a task to terminate it"),
2442
2852
  React.createElement(Box, { marginTop: 1 },
2443
2853
  React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
@@ -2449,26 +2859,125 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
2449
2859
  duration: `${durationSec}s`,
2450
2860
  isRunning: task.isRunning
2451
2861
  };
2452
- }), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) => (React.createElement(Box, { flexDirection: "column" },
2453
- React.createElement(Box, null,
2454
- React.createElement(Text, { color: isSelected ? '#ff6666' : 'white', bold: isSelected },
2455
- isSelected ? '> ' : ' ',
2456
- label),
2457
- React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2458
- " [",
2459
- isRunning ? 'running' : 'done',
2460
- "]"),
2461
- React.createElement(Text, { color: "gray" },
2462
- " \u23F1\uFE0F ",
2463
- duration)),
2464
- React.createElement(Box, { paddingLeft: 4 },
2465
- React.createElement(Text, { color: "gray" },
2466
- "\uD83D\uDCC1 ",
2467
- cwd)))), onSelect: (item) => {
2862
+ }), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) => {
2863
+ // Calculate max width for wrapping (terminal width - border padding - left padding)
2864
+ const terminalWidth = process.stdout.columns || 80;
2865
+ const maxTextWidth = Math.max(terminalWidth - 10, 40);
2866
+ // Truncate cwd if too long
2867
+ const truncatedCwd = cwd && cwd.length > maxTextWidth
2868
+ ? cwd.slice(0, maxTextWidth - 3) + '...'
2869
+ : cwd;
2870
+ return (React.createElement(Box, { flexDirection: "column" },
2871
+ React.createElement(Box, null,
2872
+ React.createElement(Text, { color: isSelected ? '#ff6666' : 'white', bold: isSelected },
2873
+ isSelected ? '> ' : ' ',
2874
+ label),
2875
+ React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
2876
+ " [",
2877
+ isRunning ? 'running' : 'done',
2878
+ "]"),
2879
+ React.createElement(Text, { color: "gray" },
2880
+ " ",
2881
+ duration)),
2882
+ React.createElement(Box, { paddingLeft: 4 },
2883
+ React.createElement(Text, { color: "gray", wrap: "truncate" }, truncatedCwd))));
2884
+ }, onSelect: (item) => {
2468
2885
  setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
2469
2886
  onBackgroundTaskCancel(item.value);
2470
2887
  } })),
2471
2888
  React.createElement(Box, { marginTop: 1 },
2472
- React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))))));
2889
+ React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
2890
+ state.screen === 'mcp-add' && (React.createElement(MCPAddScreen, { onAdd: (config) => {
2891
+ const result = onMCPAddServer(config);
2892
+ if (result.success) {
2893
+ // Return to chat with success message
2894
+ const successMessage = {
2895
+ id: `mcp-add-${Date.now()}`,
2896
+ role: 'system',
2897
+ content: `✅ MCP server "${config.name}" added successfully!\n\nRun \`/mcp refresh\` to connect to the new server.`,
2898
+ timestamp: new Date()
2899
+ };
2900
+ setState(prev => ({
2901
+ ...prev,
2902
+ screen: 'chat',
2903
+ messageHistory: [...prev.messageHistory, successMessage]
2904
+ }));
2905
+ }
2906
+ }, onCancel: () => {
2907
+ setState(prev => ({
2908
+ ...prev,
2909
+ screen: 'chat'
2910
+ }));
2911
+ }, validateConfig: onMCPValidateConfig })),
2912
+ state.screen === 'mcp-remove' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "remove", servers: state.mcpServers, onSelect: (serverName) => {
2913
+ onMCPRemoveServer(serverName);
2914
+ const successMessage = {
2915
+ id: `mcp-remove-${Date.now()}`,
2916
+ role: 'system',
2917
+ content: `✅ MCP server "${serverName}" removed from configuration.`,
2918
+ timestamp: new Date()
2919
+ };
2920
+ setState(prev => ({
2921
+ ...prev,
2922
+ screen: 'chat',
2923
+ mcpServers: undefined,
2924
+ messageHistory: [...prev.messageHistory, successMessage]
2925
+ }));
2926
+ }, onCancel: () => {
2927
+ setState(prev => ({
2928
+ ...prev,
2929
+ screen: 'chat',
2930
+ mcpServers: undefined
2931
+ }));
2932
+ } })),
2933
+ state.screen === 'mcp-enable' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "enable", servers: state.mcpServers, onSelect: (serverName) => {
2934
+ onMCPEnableServer(serverName);
2935
+ const successMessage = {
2936
+ id: `mcp-enable-${Date.now()}`,
2937
+ role: 'system',
2938
+ content: `✅ MCP server "${serverName}" enabled!\n\nRun \`/mcp refresh\` to connect.`,
2939
+ timestamp: new Date()
2940
+ };
2941
+ setState(prev => ({
2942
+ ...prev,
2943
+ screen: 'chat',
2944
+ mcpServers: undefined,
2945
+ messageHistory: [...prev.messageHistory, successMessage]
2946
+ }));
2947
+ }, onCancel: () => {
2948
+ setState(prev => ({
2949
+ ...prev,
2950
+ screen: 'chat',
2951
+ mcpServers: undefined
2952
+ }));
2953
+ } })),
2954
+ state.screen === 'mcp-disable' && state.mcpServers && (React.createElement(MCPServerListScreen, { mode: "disable", servers: state.mcpServers, onSelect: (serverName) => {
2955
+ onMCPDisableServer(serverName);
2956
+ const successMessage = {
2957
+ id: `mcp-disable-${Date.now()}`,
2958
+ role: 'system',
2959
+ content: `⏸️ MCP server "${serverName}" disabled. AI will no longer use its tools.`,
2960
+ timestamp: new Date()
2961
+ };
2962
+ setState(prev => ({
2963
+ ...prev,
2964
+ screen: 'chat',
2965
+ mcpServers: undefined,
2966
+ messageHistory: [...prev.messageHistory, successMessage]
2967
+ }));
2968
+ }, onCancel: () => {
2969
+ setState(prev => ({
2970
+ ...prev,
2971
+ screen: 'chat',
2972
+ mcpServers: undefined
2973
+ }));
2974
+ } })),
2975
+ state.screen === 'mcp-list' && state.mcpListData && (React.createElement(MCPListScreen, { servers: state.mcpListData, onClose: () => {
2976
+ setState(prev => ({
2977
+ ...prev,
2978
+ screen: 'chat',
2979
+ mcpListData: undefined
2980
+ }));
2981
+ } }))));
2473
2982
  };
2474
2983
  //# sourceMappingURL=App.js.map