centaurus-cli 2.9.0 → 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 +78 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +566 -165
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/build-config.d.ts +1 -1
- package/dist/config/build-config.js +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 +5 -3
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/index.js +66 -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/ai-service-client.d.ts +6 -1
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +6 -6
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +20 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +35 -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/session-quota-manager.d.ts +101 -0
- package/dist/services/session-quota-manager.d.ts.map +1 -0
- package/dist/services/session-quota-manager.js +242 -0
- package/dist/services/session-quota-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 +78 -4
- 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.d.ts.map +1 -1
- package/dist/tools/get-diff.js +5 -2
- 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 -32
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +45 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +598 -95
- 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 +4 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +40 -2
- 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 +271 -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/dist/utils/editor-utils.d.ts +3 -3
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +15 -12
- package/dist/utils/editor-utils.js.map +1 -1
- package/dist/utils/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +1 -0
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/terminal-output.d.ts.map +1 -1
- package/dist/utils/terminal-output.js +198 -171
- package/dist/utils/terminal-output.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, 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 }) => {
|
|
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,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
203
219
|
passwordRequest: undefined,
|
|
204
220
|
connectionStatus: undefined,
|
|
205
221
|
backgroundTaskCount: 0,
|
|
222
|
+
subAgentCount: 0,
|
|
223
|
+
sessionQuotaExhausted: false,
|
|
224
|
+
sessionQuotaTimeRemaining: '',
|
|
206
225
|
});
|
|
207
226
|
// Track last terminal width to detect actual width changes
|
|
208
227
|
const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
|
|
@@ -268,6 +287,20 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
268
287
|
}
|
|
269
288
|
};
|
|
270
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
|
+
});
|
|
271
304
|
// Check for version updates on mount
|
|
272
305
|
React.useEffect(() => {
|
|
273
306
|
checkForUpdates().then(versionInfo => {
|
|
@@ -321,6 +354,17 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
321
354
|
React.useEffect(() => {
|
|
322
355
|
autoAcceptRef.current = state.autoAcceptMode;
|
|
323
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);
|
|
324
368
|
// NOTE: Token count is now updated via onTokenCountUpdate callback from cli-adapter
|
|
325
369
|
// which tracks the actual AI conversation history including system prompt.
|
|
326
370
|
// The old UI-based calculation has been removed to avoid overwriting correct values.
|
|
@@ -333,6 +377,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
333
377
|
// Set up callback to receive streaming chunks - only once on mount
|
|
334
378
|
React.useEffect(() => {
|
|
335
379
|
onResponseStream((chunk) => {
|
|
380
|
+
// CIRCUIT BREAKER: Block incoming messages from "Zombie Agents"
|
|
381
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
382
|
+
return;
|
|
336
383
|
isStreamingRef.current = true; // Mark that we're streaming
|
|
337
384
|
// Check if terminal is large enough for streaming
|
|
338
385
|
const dimensions = getTerminalDimensions();
|
|
@@ -406,11 +453,39 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
406
453
|
});
|
|
407
454
|
});
|
|
408
455
|
}, [onResponseStream]);
|
|
456
|
+
// Set up callback to clear streamed response when task_complete has a summary
|
|
457
|
+
// This prevents showing both the streamed text AND the task_complete summary
|
|
458
|
+
React.useEffect(() => {
|
|
459
|
+
onClearStreamedResponse(() => {
|
|
460
|
+
try {
|
|
461
|
+
quickLog(`[${new Date().toISOString()}] [App] onClearStreamedResponse called - clearing current message content\n`);
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
// Ignore logging errors
|
|
465
|
+
}
|
|
466
|
+
setState(prev => {
|
|
467
|
+
// Clear the current message's content (not the whole message, so thoughts and thinking are preserved)
|
|
468
|
+
if (prev.currentMessage && prev.currentMessage.role === 'assistant') {
|
|
469
|
+
return {
|
|
470
|
+
...prev,
|
|
471
|
+
currentMessage: {
|
|
472
|
+
...prev.currentMessage,
|
|
473
|
+
content: '' // Clear the content - task_complete will stream the summary next
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return prev;
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
}, [onClearStreamedResponse]);
|
|
409
481
|
// Ref to accumulate all thought text across multiple chunks
|
|
410
482
|
const thoughtAccumulatorRef = React.useRef('');
|
|
411
483
|
// Set up callback to receive thought chunks
|
|
412
484
|
React.useEffect(() => {
|
|
413
485
|
onThoughtStream((thought) => {
|
|
486
|
+
// CIRCUIT BREAKER: Block incoming thoughts from "Zombie Agents"
|
|
487
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
488
|
+
return;
|
|
414
489
|
// Debug logging to file
|
|
415
490
|
try {
|
|
416
491
|
quickLog(`[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
|
|
@@ -502,6 +577,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
502
577
|
// Set up callback for when thinking completes
|
|
503
578
|
React.useEffect(() => {
|
|
504
579
|
onThoughtComplete((durationSeconds) => {
|
|
580
|
+
// CIRCUIT BREAKER: Block incoming thought completion from "Zombie Agents"
|
|
581
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
582
|
+
return;
|
|
505
583
|
try {
|
|
506
584
|
quickLog(`[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
|
|
507
585
|
}
|
|
@@ -538,12 +616,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
538
616
|
});
|
|
539
617
|
});
|
|
540
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
|
+
};
|
|
541
632
|
// Set up callback for direct messages (slash commands) - add directly to history
|
|
542
633
|
React.useEffect(() => {
|
|
543
634
|
onDirectMessage((message) => {
|
|
635
|
+
// CIRCUIT BREAKER: Block incoming direct messages from "Zombie Agents"
|
|
636
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
637
|
+
return;
|
|
544
638
|
setState(prev => {
|
|
545
|
-
// Move current message to history if exists
|
|
546
|
-
const newHistory = prev.currentMessage
|
|
639
|
+
// Move current message to history if exists (skip thought-only messages)
|
|
640
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
547
641
|
? [...prev.messageHistory, prev.currentMessage]
|
|
548
642
|
: prev.messageHistory;
|
|
549
643
|
// Create system message for slash command response
|
|
@@ -567,6 +661,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
567
661
|
// Set up callback to receive complete responses (for slash commands and non-streaming responses)
|
|
568
662
|
React.useEffect(() => {
|
|
569
663
|
onResponseReceived((message) => {
|
|
664
|
+
// CIRCUIT BREAKER: Block incoming responses from "Zombie Agents"
|
|
665
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
666
|
+
return;
|
|
570
667
|
// If we were streaming, move the current message to history immediately
|
|
571
668
|
if (isStreamingRef.current) {
|
|
572
669
|
isStreamingRef.current = false; // Reset for next message
|
|
@@ -675,8 +772,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
675
772
|
timestamp: now,
|
|
676
773
|
shouldStream: false // Command responses should appear instantly
|
|
677
774
|
};
|
|
678
|
-
// If we have a current message that's an assistant message, move it to history
|
|
679
|
-
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)) {
|
|
680
777
|
return {
|
|
681
778
|
...prev,
|
|
682
779
|
messageHistory: [...prev.messageHistory, prev.currentMessage],
|
|
@@ -779,6 +876,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
779
876
|
}));
|
|
780
877
|
});
|
|
781
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
|
|
782
888
|
// Set up callback for setting Auto mode (used after background task starts)
|
|
783
889
|
React.useEffect(() => {
|
|
784
890
|
onSetAutoModeSetup((enabled) => {
|
|
@@ -916,6 +1022,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
916
1022
|
});
|
|
917
1023
|
});
|
|
918
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
|
|
919
1084
|
// Sync UI message history to CLI adapter whenever it changes
|
|
920
1085
|
React.useEffect(() => {
|
|
921
1086
|
onUIMessageHistoryUpdate(state.messageHistory);
|
|
@@ -923,6 +1088,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
923
1088
|
// Set up callback for tool execution updates
|
|
924
1089
|
React.useEffect(() => {
|
|
925
1090
|
onToolExecutionUpdate((update) => {
|
|
1091
|
+
// CIRCUIT BREAKER: Block incoming tool updates from "Zombie Agents"
|
|
1092
|
+
if (ignoreIncomingAIUpdatesRef.current)
|
|
1093
|
+
return;
|
|
926
1094
|
// Special handling for execute_command
|
|
927
1095
|
if (update.toolName === 'execute_command') {
|
|
928
1096
|
try {
|
|
@@ -931,6 +1099,28 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
931
1099
|
catch (e) { }
|
|
932
1100
|
// STARTING EXECUTION
|
|
933
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
|
+
}
|
|
934
1124
|
const command = update.arguments?.command || update.arguments?.CommandLine || update.arguments?.commandLine || '';
|
|
935
1125
|
// Start a pending shell with a short delay
|
|
936
1126
|
// This prevents the UI from flickering for very fast commands (like ls)
|
|
@@ -946,6 +1136,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
946
1136
|
setState(prev => ({
|
|
947
1137
|
...prev,
|
|
948
1138
|
shellState: {
|
|
1139
|
+
shellId: `shell-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
949
1140
|
command: cmd,
|
|
950
1141
|
cwd: cwd,
|
|
951
1142
|
output: bufferedOutput,
|
|
@@ -954,10 +1145,11 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
954
1145
|
error: undefined,
|
|
955
1146
|
isFocused: false,
|
|
956
1147
|
isPty: update.arguments?.isPty || false,
|
|
957
|
-
remoteContext: update.arguments?.remoteContext
|
|
1148
|
+
remoteContext: update.arguments?.remoteContext,
|
|
1149
|
+
isAgentStarted: true // Mark as agent-started since it came from execute_command tool
|
|
958
1150
|
},
|
|
959
|
-
// IMPORTANT: Always preserve current message by moving it to history first
|
|
960
|
-
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)
|
|
961
1153
|
? [...prev.messageHistory, prev.currentMessage]
|
|
962
1154
|
: prev.messageHistory,
|
|
963
1155
|
currentMessage: {
|
|
@@ -1013,8 +1205,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1013
1205
|
arguments: update.arguments
|
|
1014
1206
|
}
|
|
1015
1207
|
};
|
|
1016
|
-
// Move current message to history if exists
|
|
1017
|
-
const newHistory = prev.currentMessage
|
|
1208
|
+
// Move current message to history if exists (skip thought-only)
|
|
1209
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1018
1210
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1019
1211
|
: prev.messageHistory;
|
|
1020
1212
|
return {
|
|
@@ -1051,12 +1243,44 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1051
1243
|
arguments: update.arguments
|
|
1052
1244
|
}
|
|
1053
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
|
+
}
|
|
1054
1271
|
return {
|
|
1055
1272
|
...prev,
|
|
1056
|
-
|
|
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,
|
|
1057
1275
|
messageHistory: [...prev.messageHistory, shellMessage],
|
|
1058
1276
|
currentMessage: null,
|
|
1059
|
-
|
|
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
|
|
1060
1284
|
};
|
|
1061
1285
|
});
|
|
1062
1286
|
return;
|
|
@@ -1150,7 +1374,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1150
1374
|
// Carry the thinkingDuration to the tool message, don't add empty message to history
|
|
1151
1375
|
pendingThinkingDuration = prev.currentMessage.thinkingDuration;
|
|
1152
1376
|
try {
|
|
1153
|
-
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`);
|
|
1154
1378
|
}
|
|
1155
1379
|
catch (e) { }
|
|
1156
1380
|
}
|
|
@@ -1182,7 +1406,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1182
1406
|
}
|
|
1183
1407
|
catch (e) { }
|
|
1184
1408
|
let newHistory = prev.messageHistory;
|
|
1185
|
-
if (prev.currentMessage) {
|
|
1409
|
+
if (prev.currentMessage && !shouldSkipMessage(prev.currentMessage)) {
|
|
1186
1410
|
newHistory = [...prev.messageHistory, prev.currentMessage];
|
|
1187
1411
|
}
|
|
1188
1412
|
return {
|
|
@@ -1208,8 +1432,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1208
1432
|
timestamp: new Date(),
|
|
1209
1433
|
connectionStatus: status
|
|
1210
1434
|
};
|
|
1211
|
-
// Move current message to history if exists
|
|
1212
|
-
const newHistory = prev.currentMessage
|
|
1435
|
+
// Move current message to history if exists (skip thought-only messages)
|
|
1436
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1213
1437
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1214
1438
|
: prev.messageHistory;
|
|
1215
1439
|
return {
|
|
@@ -1238,8 +1462,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1238
1462
|
currentMessage: null
|
|
1239
1463
|
};
|
|
1240
1464
|
}
|
|
1241
|
-
// Otherwise just add to history
|
|
1242
|
-
const newHistory = prev.currentMessage
|
|
1465
|
+
// Otherwise just add to history (skip thought-only messages)
|
|
1466
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1243
1467
|
? [...prev.messageHistory, prev.currentMessage, finalConnectionMessage]
|
|
1244
1468
|
: [...prev.messageHistory, finalConnectionMessage];
|
|
1245
1469
|
return {
|
|
@@ -1334,14 +1558,65 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1334
1558
|
pendingShellRef.current.output += update.chunk;
|
|
1335
1559
|
return;
|
|
1336
1560
|
}
|
|
1561
|
+
// Update last output time for stall detection
|
|
1562
|
+
lastOutputTimeRef.current = Date.now();
|
|
1337
1563
|
setState(prev => {
|
|
1338
1564
|
// Update shell state if it's an execute_command
|
|
1339
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
|
+
}
|
|
1340
1615
|
return {
|
|
1341
1616
|
...prev,
|
|
1342
1617
|
shellState: {
|
|
1343
1618
|
...prev.shellState,
|
|
1344
|
-
output:
|
|
1619
|
+
output: newOutput
|
|
1345
1620
|
}
|
|
1346
1621
|
};
|
|
1347
1622
|
}
|
|
@@ -1363,7 +1638,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1363
1638
|
return prev;
|
|
1364
1639
|
});
|
|
1365
1640
|
});
|
|
1366
|
-
}, []);
|
|
1641
|
+
}, [onMessage]);
|
|
1367
1642
|
// Set up callback to receive plan mode changes
|
|
1368
1643
|
React.useEffect(() => {
|
|
1369
1644
|
onPlanModeChange((planMode) => {
|
|
@@ -1421,6 +1696,55 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1421
1696
|
}));
|
|
1422
1697
|
});
|
|
1423
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]);
|
|
1708
|
+
// Set up callback to receive session quota updates from cli-adapter
|
|
1709
|
+
React.useEffect(() => {
|
|
1710
|
+
onSessionQuotaUpdate((remaining, canSend, timeRemaining) => {
|
|
1711
|
+
setState(prev => ({
|
|
1712
|
+
...prev,
|
|
1713
|
+
sessionQuotaExhausted: !canSend,
|
|
1714
|
+
sessionQuotaTimeRemaining: timeRemaining
|
|
1715
|
+
}));
|
|
1716
|
+
});
|
|
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]);
|
|
1424
1748
|
// Set up callback for plan approval requests
|
|
1425
1749
|
React.useEffect(() => {
|
|
1426
1750
|
onPlanApprovalRequest(async (plan) => {
|
|
@@ -1468,8 +1792,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1468
1792
|
completionNote
|
|
1469
1793
|
}
|
|
1470
1794
|
};
|
|
1471
|
-
// Move current message to history if exists
|
|
1472
|
-
const newHistory = prev.currentMessage
|
|
1795
|
+
// Move current message to history if exists (skip thought-only)
|
|
1796
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1473
1797
|
? [...prev.messageHistory, prev.currentMessage, taskCompletedMessage]
|
|
1474
1798
|
: [...prev.messageHistory, taskCompletedMessage];
|
|
1475
1799
|
return {
|
|
@@ -1630,6 +1954,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1630
1954
|
}));
|
|
1631
1955
|
return;
|
|
1632
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
|
+
}
|
|
1633
1966
|
// If AI is working, cancel the operation
|
|
1634
1967
|
if (state.isLoading || state.isAiWorking) {
|
|
1635
1968
|
// Cancel the current AI request
|
|
@@ -1774,8 +2107,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1774
2107
|
// In command mode, add command to history immediately
|
|
1775
2108
|
if (state.commandMode) {
|
|
1776
2109
|
setState(prev => {
|
|
1777
|
-
// Move current message to history if exists
|
|
1778
|
-
const newHistory = prev.currentMessage
|
|
2110
|
+
// Move current message to history if exists (skip thought-only)
|
|
2111
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1779
2112
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1780
2113
|
: prev.messageHistory;
|
|
1781
2114
|
const commandMessage = {
|
|
@@ -1813,7 +2146,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1813
2146
|
// Generate a stable message ID that we can use to update the message later
|
|
1814
2147
|
const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1815
2148
|
setState(prev => {
|
|
1816
|
-
const newHistory = prev.currentMessage
|
|
2149
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
1817
2150
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1818
2151
|
: prev.messageHistory;
|
|
1819
2152
|
const userMessage = {
|
|
@@ -1956,8 +2289,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1956
2289
|
content: `❌ Error: ${error.message || 'Unknown error occurred'}`,
|
|
1957
2290
|
timestamp: new Date()
|
|
1958
2291
|
};
|
|
1959
|
-
// Move current to history if exists and add error
|
|
1960
|
-
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)
|
|
1961
2294
|
? [...prev.messageHistory, prev.currentMessage, errorMessage]
|
|
1962
2295
|
: [...prev.messageHistory, errorMessage];
|
|
1963
2296
|
return {
|
|
@@ -2042,7 +2375,55 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2042
2375
|
return;
|
|
2043
2376
|
}
|
|
2044
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
|
+
}
|
|
2045
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 })),
|
|
2046
2427
|
state.isAiWorking && !state.shellState && !state.approvalRequest &&
|
|
2047
2428
|
!(state.currentMessage?.toolExecution?.status === 'executing') && (React.createElement(Box, { marginBottom: 1, paddingLeft: 1 },
|
|
2048
2429
|
React.createElement(LoadingIndicator, { key: "loading-indicator" }),
|
|
@@ -2053,12 +2434,14 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2053
2434
|
resolve(approved);
|
|
2054
2435
|
}
|
|
2055
2436
|
} })) : (React.createElement(InputBox, { key: "input-box", onSubmit: (value, clipboardImages) => {
|
|
2437
|
+
// Reset "Zombie Agent" protection on new manual input
|
|
2438
|
+
ignoreIncomingAIUpdatesRef.current = false;
|
|
2056
2439
|
// Clear preserved input on submit
|
|
2057
2440
|
preservedInputTextRef.current = '';
|
|
2058
2441
|
handleSubmit(value, clipboardImages);
|
|
2059
|
-
}, 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) => {
|
|
2060
2443
|
setAutoModeCallbackRef.current = callback;
|
|
2061
|
-
} }))),
|
|
2444
|
+
}, sessionQuotaExhausted: state.sessionQuotaExhausted, sessionQuotaTimeRemaining: state.sessionQuotaTimeRemaining }))),
|
|
2062
2445
|
state.showExitWarning && (React.createElement(Box, { marginTop: 1 },
|
|
2063
2446
|
React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit"))),
|
|
2064
2447
|
!isConnected && (React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 },
|
|
@@ -2103,7 +2486,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2103
2486
|
state.screen === 'chat-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2104
2487
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDCDA Resume a Previous Chat"),
|
|
2105
2488
|
React.createElement(Box, { marginTop: 1 },
|
|
2106
|
-
React.createElement(
|
|
2489
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2107
2490
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2108
2491
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2109
2492
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2162,7 +2545,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2162
2545
|
state.screen === 'chat-delete-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2163
2546
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\uD83D\uDDD1\uFE0F Select a Chat to Delete"),
|
|
2164
2547
|
React.createElement(Box, { marginTop: 1 },
|
|
2165
|
-
React.createElement(
|
|
2548
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2166
2549
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2167
2550
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2168
2551
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2217,35 +2600,42 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2217
2600
|
"\uD83D\uDCDA Saved Chats (",
|
|
2218
2601
|
state.chatPickerChats.length,
|
|
2219
2602
|
")"),
|
|
2220
|
-
React.createElement(Box, { marginTop: 1
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
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
|
+
} })),
|
|
2243
2633
|
React.createElement(Box, { marginTop: 1 },
|
|
2244
2634
|
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2245
2635
|
state.screen === 'chat-rename-picker' && state.chatPickerChats && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1 },
|
|
2246
2636
|
React.createElement(Text, { color: "#00ccff", bold: true }, "\u270F\uFE0F Select a Chat to Rename"),
|
|
2247
2637
|
React.createElement(Box, { marginTop: 1 },
|
|
2248
|
-
React.createElement(
|
|
2638
|
+
React.createElement(CircularSelectInput, { limit: listLimit, items: state.chatPickerChats.map((chat) => {
|
|
2249
2639
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
2250
2640
|
const time = new Date(chat.updatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
2251
2641
|
const isCurrent = chat.id === state.currentChatId;
|
|
@@ -2361,7 +2751,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2361
2751
|
}));
|
|
2362
2752
|
} }))),
|
|
2363
2753
|
state.screen === 'background-task-list' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#9966ff", paddingX: 1 },
|
|
2364
|
-
React.createElement(Text, { color: "#9966ff", bold: true }, "
|
|
2754
|
+
React.createElement(Text, { color: "#9966ff", bold: true }, "Running Background Tasks"),
|
|
2365
2755
|
React.createElement(Text, { dimColor: true }, "Select a task to view its output in focus mode"),
|
|
2366
2756
|
React.createElement(Box, { marginTop: 1 },
|
|
2367
2757
|
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
@@ -2372,35 +2762,49 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2372
2762
|
cwd: task.cwd,
|
|
2373
2763
|
duration: `${durationSec}s`,
|
|
2374
2764
|
isRunning: task.isRunning,
|
|
2375
|
-
outputPreview: task.outputPreview
|
|
2765
|
+
outputPreview: task.outputPreview // Pass raw preview, sanitize in component
|
|
2376
2766
|
};
|
|
2377
|
-
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) =>
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
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) => {
|
|
2397
2801
|
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2398
2802
|
onBackgroundTaskSelection(item.value);
|
|
2399
2803
|
} })),
|
|
2400
2804
|
React.createElement(Box, { marginTop: 1 },
|
|
2401
2805
|
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2402
2806
|
state.screen === 'background-task-cancel' && state.backgroundTasks && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#ff6666", paddingX: 1 },
|
|
2403
|
-
React.createElement(Text, { color: "#ff6666", bold: true }, "
|
|
2807
|
+
React.createElement(Text, { color: "#ff6666", bold: true }, "Cancel a Background Task"),
|
|
2404
2808
|
React.createElement(Text, { dimColor: true }, "Select a task to terminate it"),
|
|
2405
2809
|
React.createElement(Box, { marginTop: 1 },
|
|
2406
2810
|
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
@@ -2412,26 +2816,125 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2412
2816
|
duration: `${durationSec}s`,
|
|
2413
2817
|
isRunning: task.isRunning
|
|
2414
2818
|
};
|
|
2415
|
-
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) =>
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
React.createElement(
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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) => {
|
|
2431
2842
|
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2432
2843
|
onBackgroundTaskCancel(item.value);
|
|
2433
2844
|
} })),
|
|
2434
2845
|
React.createElement(Box, { marginTop: 1 },
|
|
2435
|
-
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
|
+
} }))));
|
|
2436
2939
|
};
|
|
2437
2940
|
//# sourceMappingURL=App.js.map
|