centaurus-cli 2.8.8 → 2.9.0
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 +82 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +622 -94
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/ConfigManager.d.ts.map +1 -1
- package/dist/config/ConfigManager.js +6 -5
- package/dist/config/ConfigManager.js.map +1 -1
- package/dist/config/build-config.d.ts +42 -0
- package/dist/config/build-config.d.ts.map +1 -0
- package/dist/config/build-config.js +44 -0
- package/dist/config/build-config.js.map +1 -0
- package/dist/config/manager.d.ts +2 -2
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +9 -12
- package/dist/config/manager.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +5 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -1
- package/dist/config/mcp-config-manager.js +8 -0
- package/dist/config/mcp-config-manager.js.map +1 -1
- package/dist/config/models.d.ts +48 -42
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +148 -133
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts +2 -0
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +34 -1
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +6 -6
- package/dist/context/context-manager.js.map +1 -1
- package/dist/hooks/useConnectivity.d.ts +2 -0
- package/dist/hooks/useConnectivity.d.ts.map +1 -0
- package/dist/hooks/useConnectivity.js +12 -0
- package/dist/hooks/useConnectivity.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
- package/dist/mcp/mcp-command-handler.js +2 -5
- package/dist/mcp/mcp-command-handler.js.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.js +8 -0
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
- package/dist/services/ai-service-client.d.ts +1 -0
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +8 -6
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +46 -34
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +47 -37
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/background-task-manager.d.ts +114 -0
- package/dist/services/background-task-manager.d.ts.map +1 -0
- package/dist/services/background-task-manager.js +301 -0
- package/dist/services/background-task-manager.js.map +1 -0
- package/dist/services/connectivity-manager.d.ts +18 -0
- package/dist/services/connectivity-manager.d.ts.map +1 -0
- package/dist/services/connectivity-manager.js +72 -0
- package/dist/services/connectivity-manager.js.map +1 -0
- package/dist/services/local-chat-storage.d.ts +5 -0
- package/dist/services/local-chat-storage.d.ts.map +1 -1
- package/dist/services/local-chat-storage.js +38 -4
- package/dist/services/local-chat-storage.js.map +1 -1
- package/dist/tools/background-command.d.ts +11 -0
- package/dist/tools/background-command.d.ts.map +1 -0
- package/dist/tools/background-command.js +162 -0
- package/dist/tools/background-command.js.map +1 -0
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +6 -3
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/create-image.d.ts +10 -0
- package/dist/tools/create-image.d.ts.map +1 -0
- package/dist/tools/create-image.js +189 -0
- package/dist/tools/create-image.js.map +1 -0
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +8 -6
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/components/App.d.ts +34 -2
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +403 -67
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/ContextWindowIndicator.d.ts.map +1 -1
- package/dist/ui/components/ContextWindowIndicator.js +43 -22
- package/dist/ui/components/ContextWindowIndicator.js.map +1 -1
- package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/ui/components/ErrorBoundary.js +2 -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 +289 -207
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/MessageDisplay.js +8 -15
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/SlashCommandAutocomplete.d.ts +2 -0
- package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +1 -1
- package/dist/ui/components/SlashCommandAutocomplete.js +19 -10
- package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +4 -0
- 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 +155 -41
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.js +1 -0
- package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
- package/dist/utils/chat-formatter.d.ts +12 -0
- package/dist/utils/chat-formatter.d.ts.map +1 -0
- package/dist/utils/chat-formatter.js +326 -0
- package/dist/utils/chat-formatter.js.map +1 -0
- package/dist/utils/command-history.d.ts.map +1 -1
- package/dist/utils/command-history.js +2 -1
- package/dist/utils/command-history.js.map +1 -1
- package/dist/utils/conversation-logger.d.ts +15 -0
- package/dist/utils/conversation-logger.d.ts.map +1 -1
- package/dist/utils/conversation-logger.js +56 -2
- package/dist/utils/conversation-logger.js.map +1 -1
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +3 -2
- 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 +140 -20
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +31 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/text-clipboard.d.ts +12 -0
- package/dist/utils/text-clipboard.d.ts.map +1 -0
- package/dist/utils/text-clipboard.js +63 -0
- package/dist/utils/text-clipboard.js.map +1 -0
- package/package.json +1 -2
- package/models-config.json +0 -126
|
@@ -2,7 +2,7 @@ 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
4
|
import TextInput from 'ink-text-input';
|
|
5
|
-
import
|
|
5
|
+
import { quickLog } from '../../utils/conversation-logger.js';
|
|
6
6
|
import { WelcomeBanner } from './WelcomeBanner.js';
|
|
7
7
|
import { InputBox } from './InputBox.js';
|
|
8
8
|
import { MessageDisplay, countImagesInMessage } from './MessageDisplay.js';
|
|
@@ -23,6 +23,8 @@ import { DetailedPlanReviewScreen } from './DetailedPlanReviewScreen.js';
|
|
|
23
23
|
import { TaskCompletedMessage } from './TaskCompletedMessage.js';
|
|
24
24
|
import { PlanAcceptedMessage } from './PlanAcceptedMessage.js';
|
|
25
25
|
import { processTerminalOutput } from '../../utils/terminal-output.js';
|
|
26
|
+
import { BackgroundTaskManager } from '../../services/background-task-manager.js';
|
|
27
|
+
import { useConnectivity } from '../../hooks/useConnectivity.js';
|
|
26
28
|
import { apiClient } from '../../services/api-client.js';
|
|
27
29
|
import { conversationManager } from '../../services/conversation-manager.js';
|
|
28
30
|
import { logDebug, logError } from '../../utils/logger.js';
|
|
@@ -135,29 +137,30 @@ const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
|
|
|
135
137
|
React.createElement(Box, { marginTop: 1 },
|
|
136
138
|
React.createElement(Text, { dimColor: true }, "Press Enter to save, ESC to cancel"))));
|
|
137
139
|
};
|
|
138
|
-
export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode, onResponseReceived, onDirectMessage, onResponseStream, onThoughtStream, onThoughtComplete, onPickerSetup, onPickerSelection, onToolExecutionUpdate, onToolApprovalRequest, onToolStreamingOutput, onPlanModeChange, onPlanApprovalRequest, onPlanCreated, onTaskCompleted, onCommandModeChange, onToggleCommandMode, onCwdChange, onModelChange, onSubshellContextChange, onPasswordRequest, onShellInput, onShellSignal, onKillProcess, onInteractiveEditorMode, onConnectionStatusUpdate, onChatPickerSetup, onChatPickerSelection, onChatDeletePickerSetup, onChatDeletePickerSelection, onChatListSetup, onChatRenamePickerSetup, onChatRename, onRestoreMessagesSetup, onUIMessageHistoryUpdate }) => {
|
|
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 }) => {
|
|
139
141
|
const { exit } = useApp();
|
|
140
142
|
const autoAcceptRef = React.useRef(false);
|
|
143
|
+
const setAutoModeCallbackRef = React.useRef(null);
|
|
144
|
+
// Connectivity State
|
|
145
|
+
const isConnected = useConnectivity();
|
|
141
146
|
// Helper to clear screen
|
|
142
147
|
const clearScreen = useCallback(() => {
|
|
143
148
|
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
144
149
|
}, []);
|
|
145
|
-
// Helper to get max tokens for a model
|
|
150
|
+
// Helper to get max tokens for a model (reads from cached backend config if available)
|
|
146
151
|
const getMaxTokensForModel = useCallback((model) => {
|
|
147
|
-
// Import dynamically to avoid circular dependencies
|
|
148
152
|
try {
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
// Use sync function that reads from cached backend config
|
|
154
|
+
const { getModelContextWindowSync } = require('../../config/models.js');
|
|
155
|
+
return getModelContextWindowSync(model);
|
|
151
156
|
}
|
|
152
157
|
catch (error) {
|
|
153
|
-
// Fallback
|
|
154
|
-
if (model.includes('
|
|
155
|
-
return 2000000;
|
|
156
|
-
if (model.includes('gemini-2.5'))
|
|
157
|
-
return 2000000;
|
|
158
|
-
if (model.includes('kimi'))
|
|
158
|
+
// Fallback defaults if import fails
|
|
159
|
+
if (model.toLowerCase().includes('kimi'))
|
|
159
160
|
return 128000;
|
|
160
|
-
|
|
161
|
+
if (model.toLowerCase().includes('gemini'))
|
|
162
|
+
return 1000000;
|
|
163
|
+
return 1000000;
|
|
161
164
|
}
|
|
162
165
|
}, []);
|
|
163
166
|
// Helper to estimate tokens (rough approximation: 1 token ≈ 4 characters)
|
|
@@ -182,6 +185,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
182
185
|
currentModel: initialModel || 'gemini-2.5-flash',
|
|
183
186
|
planMode: initialPlanMode || false,
|
|
184
187
|
commandMode: false,
|
|
188
|
+
backgroundMode: false,
|
|
185
189
|
currentWorkingDirectory: process.cwd(),
|
|
186
190
|
showExitWarning: false,
|
|
187
191
|
commandHistory: [],
|
|
@@ -198,6 +202,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
198
202
|
planApprovalRequest: undefined,
|
|
199
203
|
passwordRequest: undefined,
|
|
200
204
|
connectionStatus: undefined,
|
|
205
|
+
backgroundTaskCount: 0,
|
|
201
206
|
});
|
|
202
207
|
// Track last terminal width to detect actual width changes
|
|
203
208
|
const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
|
|
@@ -280,24 +285,45 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
280
285
|
}));
|
|
281
286
|
});
|
|
282
287
|
}, []);
|
|
288
|
+
// Fetch models config async and update maxTokens once loaded
|
|
289
|
+
// This ensures correct context window even on initial load
|
|
290
|
+
React.useEffect(() => {
|
|
291
|
+
const loadModelsConfig = async () => {
|
|
292
|
+
try {
|
|
293
|
+
// Import quickLog for debugging
|
|
294
|
+
const { quickLog } = await import('../../utils/conversation-logger.js');
|
|
295
|
+
quickLog(`[${new Date().toISOString()}] [App] loadModelsConfig effect starting, currentModel: ${initialModel}\n`);
|
|
296
|
+
const { fetchModelsConfig, getModelContextWindowSync } = await import('../../config/models.js');
|
|
297
|
+
quickLog(`[${new Date().toISOString()}] [App] fetchModelsConfig imported, calling...\n`);
|
|
298
|
+
const config = await fetchModelsConfig(); // This populates the cache
|
|
299
|
+
quickLog(`[${new Date().toISOString()}] [App] fetchModelsConfig returned, models count: ${config?.models?.length || 0}\n`);
|
|
300
|
+
// Now get maxTokens with the cached config
|
|
301
|
+
const newMaxTokens = getModelContextWindowSync(initialModel || 'gemini-2.5-flash');
|
|
302
|
+
quickLog(`[${new Date().toISOString()}] [App] Got newMaxTokens: ${newMaxTokens} for model "${initialModel}"\n`);
|
|
303
|
+
setState(prev => ({
|
|
304
|
+
...prev,
|
|
305
|
+
maxTokens: newMaxTokens
|
|
306
|
+
}));
|
|
307
|
+
quickLog(`[${new Date().toISOString()}] [App] Updated state.maxTokens to ${newMaxTokens}\n`);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
// Log errors to file
|
|
311
|
+
try {
|
|
312
|
+
const { quickLog } = await import('../../utils/conversation-logger.js');
|
|
313
|
+
quickLog(`[${new Date().toISOString()}] [App] loadModelsConfig ERROR: ${error?.message || error}\n`);
|
|
314
|
+
}
|
|
315
|
+
catch { }
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
loadModelsConfig();
|
|
319
|
+
}, [initialModel]);
|
|
283
320
|
// Keep ref in sync with state
|
|
284
321
|
React.useEffect(() => {
|
|
285
322
|
autoAcceptRef.current = state.autoAcceptMode;
|
|
286
323
|
}, [state.autoAcceptMode]);
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (state.currentMessage) {
|
|
291
|
-
allMessages.push(state.currentMessage);
|
|
292
|
-
}
|
|
293
|
-
const totalTokens = calculateTotalTokens(allMessages);
|
|
294
|
-
if (totalTokens !== state.currentTokens) {
|
|
295
|
-
setState(prev => ({
|
|
296
|
-
...prev,
|
|
297
|
-
currentTokens: totalTokens
|
|
298
|
-
}));
|
|
299
|
-
}
|
|
300
|
-
}, [state.messageHistory, state.currentMessage, calculateTotalTokens]);
|
|
324
|
+
// NOTE: Token count is now updated via onTokenCountUpdate callback from cli-adapter
|
|
325
|
+
// which tracks the actual AI conversation history including system prompt.
|
|
326
|
+
// The old UI-based calculation has been removed to avoid overwriting correct values.
|
|
301
327
|
// Track if we're currently streaming
|
|
302
328
|
const isStreamingRef = React.useRef(false);
|
|
303
329
|
// Buffer content when terminal is too small for streaming display
|
|
@@ -387,7 +413,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
387
413
|
onThoughtStream((thought) => {
|
|
388
414
|
// Debug logging to file
|
|
389
415
|
try {
|
|
390
|
-
|
|
416
|
+
quickLog(`[${new Date().toISOString()}] [App] Received thought chunk (${thought.length} chars): ${thought.substring(0, 100)}\n`);
|
|
391
417
|
}
|
|
392
418
|
catch (e) {
|
|
393
419
|
// Ignore logging errors
|
|
@@ -406,7 +432,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
406
432
|
(prev.currentMessage.thinkingDuration !== undefined && !isThoughtOnlyMessage);
|
|
407
433
|
if (needsNewMessage) {
|
|
408
434
|
try {
|
|
409
|
-
|
|
435
|
+
quickLog(`[${new Date().toISOString()}] [App] Creating new assistant message for thoughts (reason: ${!prev.currentMessage ? 'no current' : prev.currentMessage.role !== 'assistant' ? 'not assistant' : 'has thinkingDuration'})\n`);
|
|
410
436
|
}
|
|
411
437
|
catch (e) {
|
|
412
438
|
// Ignore logging errors
|
|
@@ -445,7 +471,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
445
471
|
// (don't accumulate - the tool will handle displaying)
|
|
446
472
|
if (isThoughtOnlyMessage) {
|
|
447
473
|
try {
|
|
448
|
-
|
|
474
|
+
quickLog(`[${new Date().toISOString()}] [App] Skipping thought chunk - current is thought-only message waiting for tool\n`);
|
|
449
475
|
}
|
|
450
476
|
catch (e) { }
|
|
451
477
|
return prev;
|
|
@@ -457,7 +483,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
457
483
|
const allLines = thoughtAccumulatorRef.current.split('\n').filter(line => line.trim());
|
|
458
484
|
const last3Lines = allLines.slice(-3);
|
|
459
485
|
try {
|
|
460
|
-
|
|
486
|
+
quickLog(`[${new Date().toISOString()}] [App] Accumulated ${allLines.length} total lines, showing last 3: ${JSON.stringify(last3Lines)}\n`);
|
|
461
487
|
}
|
|
462
488
|
catch (e) {
|
|
463
489
|
// Ignore logging errors
|
|
@@ -477,7 +503,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
477
503
|
React.useEffect(() => {
|
|
478
504
|
onThoughtComplete((durationSeconds) => {
|
|
479
505
|
try {
|
|
480
|
-
|
|
506
|
+
quickLog(`[${new Date().toISOString()}] [App] onThoughtComplete called with ${durationSeconds}s\n`);
|
|
481
507
|
}
|
|
482
508
|
catch (e) {
|
|
483
509
|
// Ignore
|
|
@@ -488,7 +514,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
488
514
|
// Only update if we have a current assistant message
|
|
489
515
|
if (!prev.currentMessage || prev.currentMessage.role !== 'assistant') {
|
|
490
516
|
try {
|
|
491
|
-
|
|
517
|
+
quickLog(`[${new Date().toISOString()}] [App] No current assistant message for thought completion\n`);
|
|
492
518
|
}
|
|
493
519
|
catch (e) {
|
|
494
520
|
// Ignore
|
|
@@ -496,7 +522,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
496
522
|
return prev;
|
|
497
523
|
}
|
|
498
524
|
try {
|
|
499
|
-
|
|
525
|
+
quickLog(`[${new Date().toISOString()}] [App] Setting thinkingDuration to ${durationSeconds}s on message ${prev.currentMessage.id}\n`);
|
|
500
526
|
}
|
|
501
527
|
catch (e) {
|
|
502
528
|
// Ignore
|
|
@@ -593,7 +619,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
593
619
|
// (meaning the message was moved to history mid-stream)
|
|
594
620
|
if (lastAssistant.content.length < message.length) {
|
|
595
621
|
try {
|
|
596
|
-
|
|
622
|
+
quickLog(`[${new Date().toISOString()}] [App] onResponseReceived: Updating history assistant message from ${lastAssistant.content.length} chars to ${message.length} chars\n`);
|
|
597
623
|
}
|
|
598
624
|
catch (e) { }
|
|
599
625
|
const updatedHistory = [...prev.messageHistory];
|
|
@@ -735,6 +761,33 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
735
761
|
}));
|
|
736
762
|
});
|
|
737
763
|
}, []); // Empty dependency array - only register once
|
|
764
|
+
// Set up callback for background mode change
|
|
765
|
+
React.useEffect(() => {
|
|
766
|
+
onBackgroundModeChange((backgroundMode) => {
|
|
767
|
+
setState(prev => ({
|
|
768
|
+
...prev,
|
|
769
|
+
backgroundMode
|
|
770
|
+
}));
|
|
771
|
+
});
|
|
772
|
+
}, []); // Empty dependency array - only register once
|
|
773
|
+
// Set up callback for background task count change
|
|
774
|
+
React.useEffect(() => {
|
|
775
|
+
onBackgroundTaskCountChange((count) => {
|
|
776
|
+
setState(prev => ({
|
|
777
|
+
...prev,
|
|
778
|
+
backgroundTaskCount: count
|
|
779
|
+
}));
|
|
780
|
+
});
|
|
781
|
+
}, []); // Empty dependency array - only register once
|
|
782
|
+
// Set up callback for setting Auto mode (used after background task starts)
|
|
783
|
+
React.useEffect(() => {
|
|
784
|
+
onSetAutoModeSetup((enabled) => {
|
|
785
|
+
// Call the InputBox's setAutoMode callback if registered
|
|
786
|
+
if (setAutoModeCallbackRef.current) {
|
|
787
|
+
setAutoModeCallbackRef.current(enabled);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}, []); // Empty dependency array - only register once
|
|
738
791
|
// Set up callback for message restoration (when resuming a chat)
|
|
739
792
|
React.useEffect(() => {
|
|
740
793
|
onRestoreMessagesSetup((restoredMessages) => {
|
|
@@ -748,6 +801,121 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
748
801
|
}));
|
|
749
802
|
});
|
|
750
803
|
}, []); // Empty dependency array - only register once
|
|
804
|
+
// Set up callback for background task list picker
|
|
805
|
+
React.useEffect(() => {
|
|
806
|
+
onBackgroundTaskListSetup((tasks) => {
|
|
807
|
+
clearScreen();
|
|
808
|
+
setState(prev => ({
|
|
809
|
+
...prev,
|
|
810
|
+
screen: 'background-task-list',
|
|
811
|
+
backgroundTasks: tasks,
|
|
812
|
+
isLoading: false
|
|
813
|
+
}));
|
|
814
|
+
});
|
|
815
|
+
}, []); // Empty dependency array - only register once
|
|
816
|
+
// Set up callback for background task cancel picker
|
|
817
|
+
React.useEffect(() => {
|
|
818
|
+
onBackgroundTaskCancelSetup((tasks) => {
|
|
819
|
+
clearScreen();
|
|
820
|
+
setState(prev => ({
|
|
821
|
+
...prev,
|
|
822
|
+
screen: 'background-task-cancel',
|
|
823
|
+
backgroundTasks: tasks,
|
|
824
|
+
isLoading: false
|
|
825
|
+
}));
|
|
826
|
+
});
|
|
827
|
+
}, []); // Empty dependency array - only register once
|
|
828
|
+
// Set up callback for viewing a background task in focus mode (live streaming shell)
|
|
829
|
+
React.useEffect(() => {
|
|
830
|
+
onBackgroundTaskViewSetup((task) => {
|
|
831
|
+
clearScreen();
|
|
832
|
+
// Set up shell state for the background task - this displays in InteractiveShell
|
|
833
|
+
// isFocused: true to start directly in focus mode
|
|
834
|
+
// isBackgroundTask: true to prevent adding to history on exit
|
|
835
|
+
setState(prev => ({
|
|
836
|
+
...prev,
|
|
837
|
+
screen: 'chat',
|
|
838
|
+
shellState: {
|
|
839
|
+
command: `[Background Task] ${task.command}`,
|
|
840
|
+
cwd: task.cwd,
|
|
841
|
+
output: task.output,
|
|
842
|
+
isRunning: task.isRunning,
|
|
843
|
+
exitCode: undefined,
|
|
844
|
+
error: undefined,
|
|
845
|
+
isFocused: true, // Start directly in focus mode
|
|
846
|
+
remoteContext: undefined,
|
|
847
|
+
isBackgroundTask: true, // Flag to prevent history addition
|
|
848
|
+
backgroundTaskId: task.id // Store task ID for input routing
|
|
849
|
+
},
|
|
850
|
+
isLoading: false,
|
|
851
|
+
isAiWorking: false // Don't show AI working for background tasks
|
|
852
|
+
}));
|
|
853
|
+
// Subscribe to output updates - append to shell output
|
|
854
|
+
task.onOutput((chunk) => {
|
|
855
|
+
setState(prev => {
|
|
856
|
+
if (!prev.shellState || !prev.shellState.isBackgroundTask)
|
|
857
|
+
return prev;
|
|
858
|
+
return {
|
|
859
|
+
...prev,
|
|
860
|
+
shellState: {
|
|
861
|
+
...prev.shellState,
|
|
862
|
+
output: prev.shellState.output + chunk
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
// Subscribe to task completion
|
|
868
|
+
task.onComplete((exitCode) => {
|
|
869
|
+
// Debug logging
|
|
870
|
+
try {
|
|
871
|
+
quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete called: exitCode=${exitCode}\n`);
|
|
872
|
+
}
|
|
873
|
+
catch (e) { }
|
|
874
|
+
setState(prev => {
|
|
875
|
+
// If user already exited focus mode, don't do anything
|
|
876
|
+
if (!prev.shellState || !prev.shellState.isBackgroundTask) {
|
|
877
|
+
try {
|
|
878
|
+
quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: shellState not active, ignoring\n`);
|
|
879
|
+
}
|
|
880
|
+
catch (e) { }
|
|
881
|
+
return prev;
|
|
882
|
+
}
|
|
883
|
+
// Task completed - mark as done and update display
|
|
884
|
+
try {
|
|
885
|
+
quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: marking task as complete\n`);
|
|
886
|
+
}
|
|
887
|
+
catch (e) { }
|
|
888
|
+
return {
|
|
889
|
+
...prev,
|
|
890
|
+
shellState: {
|
|
891
|
+
...prev.shellState,
|
|
892
|
+
isRunning: false,
|
|
893
|
+
exitCode
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
});
|
|
897
|
+
// After a brief delay, automatically return to chat
|
|
898
|
+
// This gives the user time to see the final output
|
|
899
|
+
setTimeout(() => {
|
|
900
|
+
try {
|
|
901
|
+
quickLog(`[${new Date().toISOString()}] [App.tsx] task.onComplete: clearing shellState to return to chat\n`);
|
|
902
|
+
}
|
|
903
|
+
catch (e) { }
|
|
904
|
+
clearScreen();
|
|
905
|
+
setState(prev => {
|
|
906
|
+
// Only clear if still showing this background task
|
|
907
|
+
if (prev.shellState?.isBackgroundTask) {
|
|
908
|
+
return {
|
|
909
|
+
...prev,
|
|
910
|
+
shellState: undefined
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
return prev;
|
|
914
|
+
});
|
|
915
|
+
}, 500); // 500ms delay to see final output
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
}, []); // Empty dependency array - only register once
|
|
751
919
|
// Sync UI message history to CLI adapter whenever it changes
|
|
752
920
|
React.useEffect(() => {
|
|
753
921
|
onUIMessageHistoryUpdate(state.messageHistory);
|
|
@@ -758,7 +926,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
758
926
|
// Special handling for execute_command
|
|
759
927
|
if (update.toolName === 'execute_command') {
|
|
760
928
|
try {
|
|
761
|
-
|
|
929
|
+
quickLog(`[${new Date().toISOString()}] [App] execute_command update: ${JSON.stringify(update)}\n`);
|
|
762
930
|
}
|
|
763
931
|
catch (e) { }
|
|
764
932
|
// STARTING EXECUTION
|
|
@@ -897,10 +1065,10 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
897
1065
|
setState(prev => {
|
|
898
1066
|
// Debug logging to trace tool display issues
|
|
899
1067
|
try {
|
|
900
|
-
|
|
901
|
-
|
|
1068
|
+
quickLog(`[${new Date().toISOString()}] [App] onToolExecutionUpdate setState - toolName: ${update.toolName}, status: ${update.status}\n`);
|
|
1069
|
+
quickLog(`[${new Date().toISOString()}] [App] Current state: screen=${prev.screen}, currentMessage.role=${prev.currentMessage?.role}, currentMessage.id=${prev.currentMessage?.id}, historyLen=${prev.messageHistory.length}\n`);
|
|
902
1070
|
if (prev.currentMessage?.thinkingDuration !== undefined) {
|
|
903
|
-
|
|
1071
|
+
quickLog(`[${new Date().toISOString()}] [App] Current message has thinkingDuration: ${prev.currentMessage.thinkingDuration}s\n`);
|
|
904
1072
|
}
|
|
905
1073
|
}
|
|
906
1074
|
catch (e) { }
|
|
@@ -911,7 +1079,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
911
1079
|
// Only track completed tools, skip executing tools (they need approval flow)
|
|
912
1080
|
if (update.status === 'completed' || update.status === 'error') {
|
|
913
1081
|
try {
|
|
914
|
-
|
|
1082
|
+
quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed while on ${prev.screen} screen, adding to history\n`);
|
|
915
1083
|
}
|
|
916
1084
|
catch (e) { }
|
|
917
1085
|
// Add completed tool directly to history
|
|
@@ -945,7 +1113,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
945
1113
|
// Tool completed - move to history with updated status
|
|
946
1114
|
// Note: We now match ANY executing tool, not just same toolName (handles overlapping tools)
|
|
947
1115
|
try {
|
|
948
|
-
|
|
1116
|
+
quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed, moving current tool to history\n`);
|
|
949
1117
|
}
|
|
950
1118
|
catch (e) { }
|
|
951
1119
|
const completedToolMessage = {
|
|
@@ -963,7 +1131,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
963
1131
|
// This ensures thought-only messages stay as currentMessage until executing arrives
|
|
964
1132
|
if (update.status === 'pending') {
|
|
965
1133
|
try {
|
|
966
|
-
|
|
1134
|
+
quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} pending - skipping state modification, waiting for executing\n`);
|
|
967
1135
|
}
|
|
968
1136
|
catch (e) { }
|
|
969
1137
|
return prev; // Return unchanged state
|
|
@@ -982,13 +1150,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
982
1150
|
// Carry the thinkingDuration to the tool message, don't add empty message to history
|
|
983
1151
|
pendingThinkingDuration = prev.currentMessage.thinkingDuration;
|
|
984
1152
|
try {
|
|
985
|
-
|
|
1153
|
+
quickLog(`[${new Date().toISOString()}] [App] Found thought-only message, saving thinkingDuration ${pendingThinkingDuration}s for tool ${update.toolName}\n`);
|
|
986
1154
|
}
|
|
987
1155
|
catch (e) { }
|
|
988
1156
|
}
|
|
989
1157
|
else {
|
|
990
1158
|
try {
|
|
991
|
-
|
|
1159
|
+
quickLog(`[${new Date().toISOString()}] [App] Moving current message (${prev.currentMessage.role}) to history for new tool ${update.toolName}\n`);
|
|
992
1160
|
}
|
|
993
1161
|
catch (e) { }
|
|
994
1162
|
// Move ANY current message to history before showing tool
|
|
@@ -1010,7 +1178,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1010
1178
|
}
|
|
1011
1179
|
// For completed tools without a current executing one (edge case)
|
|
1012
1180
|
try {
|
|
1013
|
-
|
|
1181
|
+
quickLog(`[${new Date().toISOString()}] [App] Tool ${update.toolName} completed without current executing tool, adding to history\n`);
|
|
1014
1182
|
}
|
|
1015
1183
|
catch (e) { }
|
|
1016
1184
|
let newHistory = prev.messageHistory;
|
|
@@ -1225,10 +1393,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1225
1393
|
}, [onCwdChange]);
|
|
1226
1394
|
// Set up callback to receive model changes
|
|
1227
1395
|
React.useEffect(() => {
|
|
1228
|
-
onModelChange((modelName) => {
|
|
1396
|
+
onModelChange((modelName, contextWindow) => {
|
|
1397
|
+
// Update both currentModel AND maxTokens when model changes
|
|
1398
|
+
// contextWindow is passed directly from cli-adapter, no lookup needed
|
|
1229
1399
|
setState(prev => ({
|
|
1230
1400
|
...prev,
|
|
1231
|
-
currentModel: modelName
|
|
1401
|
+
currentModel: modelName,
|
|
1402
|
+
maxTokens: contextWindow
|
|
1232
1403
|
}));
|
|
1233
1404
|
});
|
|
1234
1405
|
}, [onModelChange]);
|
|
@@ -1241,6 +1412,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1241
1412
|
}));
|
|
1242
1413
|
});
|
|
1243
1414
|
}, [onSubshellContextChange]);
|
|
1415
|
+
// Set up callback to receive actual AI context token count from cli-adapter
|
|
1416
|
+
React.useEffect(() => {
|
|
1417
|
+
onTokenCountUpdate((tokens) => {
|
|
1418
|
+
setState(prev => ({
|
|
1419
|
+
...prev,
|
|
1420
|
+
currentTokens: tokens
|
|
1421
|
+
}));
|
|
1422
|
+
});
|
|
1423
|
+
}, [onTokenCountUpdate]);
|
|
1244
1424
|
// Set up callback for plan approval requests
|
|
1245
1425
|
React.useEffect(() => {
|
|
1246
1426
|
onPlanApprovalRequest(async (plan) => {
|
|
@@ -1265,14 +1445,14 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1265
1445
|
// The plan will be displayed via the plan-approval screen
|
|
1266
1446
|
// This callback can be used for additional notifications if needed
|
|
1267
1447
|
try {
|
|
1268
|
-
|
|
1448
|
+
quickLog(`[${new Date().toISOString()}] [App] Plan created: ${plan.title} with ${plan.steps.length} tasks\n`);
|
|
1269
1449
|
}
|
|
1270
1450
|
catch (e) { }
|
|
1271
1451
|
});
|
|
1272
1452
|
}, [onPlanCreated]);
|
|
1273
1453
|
// Set up callback for task completion
|
|
1274
1454
|
React.useEffect(() => {
|
|
1275
|
-
onTaskCompleted((task, taskNumber, totalTasks, completionNote) => {
|
|
1455
|
+
onTaskCompleted((task, taskNumber, totalTasks, completionNote, taskDescription) => {
|
|
1276
1456
|
// Add task completion message to history
|
|
1277
1457
|
setState(prev => {
|
|
1278
1458
|
const taskCompletedMessage = {
|
|
@@ -1283,7 +1463,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1283
1463
|
taskCompletion: {
|
|
1284
1464
|
taskNumber,
|
|
1285
1465
|
totalTasks,
|
|
1286
|
-
taskDescription
|
|
1466
|
+
// Use the passed taskDescription if available (for subtasks), otherwise fall back to task.description
|
|
1467
|
+
taskDescription: taskDescription || task.description,
|
|
1287
1468
|
completionNote
|
|
1288
1469
|
}
|
|
1289
1470
|
};
|
|
@@ -1374,7 +1555,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1374
1555
|
}
|
|
1375
1556
|
else {
|
|
1376
1557
|
// Fallback if no client available
|
|
1377
|
-
|
|
1558
|
+
logError('SSH client not available for editor mode', new Error('No SSH client'));
|
|
1378
1559
|
setState(prev => ({ ...prev, isInteractiveEditorMode: false }));
|
|
1379
1560
|
}
|
|
1380
1561
|
}
|
|
@@ -1615,16 +1796,30 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1615
1796
|
}
|
|
1616
1797
|
else if (!isSlashCommand) {
|
|
1617
1798
|
// Only add user message to display if it's not a slash command
|
|
1618
|
-
//
|
|
1799
|
+
// Show user message IMMEDIATELY with image breadcrumbs (uploading state)
|
|
1800
|
+
// Images will be uploaded in background, then AI will be called
|
|
1801
|
+
const hasImagesToUpload = clipboardImages && clipboardImages.length > 0;
|
|
1802
|
+
// Build initial content with placeholder image markers for immediate display
|
|
1803
|
+
let initialDisplayContent = trimmedValue;
|
|
1804
|
+
if (hasImagesToUpload) {
|
|
1805
|
+
// Add placeholder markers that will be shown immediately
|
|
1806
|
+
for (let i = 0; i < clipboardImages.length; i++) {
|
|
1807
|
+
const image = clipboardImages[i];
|
|
1808
|
+
// Use a placeholder format that MessageDisplay can render as breadcrumb
|
|
1809
|
+
// Format: [IMAGE: filename (uploading...)] - will be updated after upload
|
|
1810
|
+
initialDisplayContent = `${initialDisplayContent} [IMAGE: ${image.displayName} (gs://uploading)]`;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
// Generate a stable message ID that we can use to update the message later
|
|
1814
|
+
const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1619
1815
|
setState(prev => {
|
|
1620
|
-
// Move current message to history if exists
|
|
1621
1816
|
const newHistory = prev.currentMessage
|
|
1622
1817
|
? [...prev.messageHistory, prev.currentMessage]
|
|
1623
1818
|
: prev.messageHistory;
|
|
1624
1819
|
const userMessage = {
|
|
1625
|
-
id:
|
|
1820
|
+
id: userMessageId,
|
|
1626
1821
|
role: 'user',
|
|
1627
|
-
content:
|
|
1822
|
+
content: initialDisplayContent,
|
|
1628
1823
|
timestamp: new Date()
|
|
1629
1824
|
};
|
|
1630
1825
|
return {
|
|
@@ -1632,8 +1827,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1632
1827
|
messageHistory: [...newHistory, userMessage],
|
|
1633
1828
|
currentMessage: null,
|
|
1634
1829
|
isLoading: true,
|
|
1635
|
-
loadingMessage: 'Processing your request...',
|
|
1636
|
-
isAiWorking: true
|
|
1830
|
+
loadingMessage: hasImagesToUpload ? 'Uploading image(s)...' : 'Processing your request...',
|
|
1831
|
+
isAiWorking: true
|
|
1637
1832
|
};
|
|
1638
1833
|
});
|
|
1639
1834
|
}
|
|
@@ -1694,7 +1889,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1694
1889
|
// Only attempt upload if we have a valid conversation ID
|
|
1695
1890
|
if (conversationId && !conversationId.startsWith('temp_')) {
|
|
1696
1891
|
logDebug(`[CLIPBOARD] Starting upload for ${clipboardImages.length} images with conversationId: ${conversationId}`);
|
|
1697
|
-
// Upload each clipboard image
|
|
1892
|
+
// Upload each clipboard image and build the final message content
|
|
1893
|
+
const uploadedImageMarkers = [];
|
|
1698
1894
|
for (const image of clipboardImages) {
|
|
1699
1895
|
try {
|
|
1700
1896
|
logDebug(`[CLIPBOARD] Uploading image: ${image.displayName}, size: ${image.sizeBytes} bytes`);
|
|
@@ -1703,19 +1899,50 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1703
1899
|
// Append image reference info to message (for AI to know an image was attached)
|
|
1704
1900
|
// The backend will use gcsUri for Vertex AI multimodal input
|
|
1705
1901
|
if (uploadResult.gcsUri) {
|
|
1706
|
-
//
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
logDebug(`[CLIPBOARD]
|
|
1902
|
+
// Build the real image marker with actual GCS URI
|
|
1903
|
+
messageWithImages = `${messageWithImages} [IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`;
|
|
1904
|
+
uploadedImageMarkers.push(`[IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
|
|
1905
|
+
logDebug(`[CLIPBOARD] Appended image marker: [IMAGE: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
|
|
1710
1906
|
}
|
|
1711
1907
|
}
|
|
1712
1908
|
catch (uploadError) {
|
|
1713
1909
|
// Log error but continue - image upload is optional
|
|
1714
1910
|
logError('Failed to upload clipboard image', uploadError instanceof Error ? uploadError : undefined);
|
|
1715
|
-
//
|
|
1716
|
-
|
|
1911
|
+
// Add a failed marker so user knows upload failed
|
|
1912
|
+
uploadedImageMarkers.push(`[IMAGE: ${image.displayName} (upload failed)]`);
|
|
1717
1913
|
}
|
|
1718
1914
|
}
|
|
1915
|
+
// Update the user message in history with actual GCS URIs
|
|
1916
|
+
// Replace placeholder markers (gs://uploading) with real ones
|
|
1917
|
+
setState(prev => {
|
|
1918
|
+
const updatedHistory = prev.messageHistory.map(msg => {
|
|
1919
|
+
// Find the user message we just added (it has placeholder markers)
|
|
1920
|
+
if (msg.role === 'user' && msg.content.includes('(gs://uploading)')) {
|
|
1921
|
+
// Build new content: original text + real image markers
|
|
1922
|
+
let newContent = trimmedValue;
|
|
1923
|
+
for (const marker of uploadedImageMarkers) {
|
|
1924
|
+
newContent = `${newContent} ${marker}`;
|
|
1925
|
+
}
|
|
1926
|
+
return { ...msg, content: newContent };
|
|
1927
|
+
}
|
|
1928
|
+
return msg;
|
|
1929
|
+
});
|
|
1930
|
+
return { ...prev, messageHistory: updatedHistory };
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
else {
|
|
1934
|
+
// No valid conversation ID - update message to show upload failed
|
|
1935
|
+
setState(prev => {
|
|
1936
|
+
const updatedHistory = prev.messageHistory.map(msg => {
|
|
1937
|
+
if (msg.role === 'user' && msg.content.includes('(gs://uploading)')) {
|
|
1938
|
+
// Replace uploading placeholders with failed markers
|
|
1939
|
+
const newContent = msg.content.replace(/\(gs:\/\/uploading\)/g, '(upload failed - no conversation)');
|
|
1940
|
+
return { ...msg, content: newContent };
|
|
1941
|
+
}
|
|
1942
|
+
return msg;
|
|
1943
|
+
});
|
|
1944
|
+
return { ...prev, messageHistory: updatedHistory };
|
|
1945
|
+
});
|
|
1719
1946
|
} // End: if (conversationId && !startsWith temp_)
|
|
1720
1947
|
} // End: if (clipboardImages && clipboardImages.length > 0)
|
|
1721
1948
|
await onMessage(messageWithImages);
|
|
@@ -1769,11 +1996,34 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1769
1996
|
state.screen === 'chat' && (React.createElement(React.Fragment, null,
|
|
1770
1997
|
!state.shellState?.isFocused && (React.createElement(MessageList, { history: state.messageHistory, current: state.currentMessage, showBanner: true })),
|
|
1771
1998
|
state.shellState && (getTerminalDimensions().shouldEnableStreaming || !state.shellState.isRunning) && (React.createElement(InteractiveShell, { command: state.shellState.command, cwd: state.shellState.cwd, isRunning: state.shellState.isRunning, output: state.shellState.output, exitCode: state.shellState.exitCode, error: state.shellState.error, isFocused: state.shellState.isFocused, onResize: state.shellState.onResize, remoteContext: state.shellState.remoteContext, onInput: (input) => {
|
|
1999
|
+
// Debug logging
|
|
2000
|
+
try {
|
|
2001
|
+
quickLog(`[${new Date().toISOString()}] [App.tsx onInput] input: ${JSON.stringify(input)}, isBackgroundTask: ${state.shellState?.isBackgroundTask}, backgroundTaskId: ${state.shellState?.backgroundTaskId}\n`);
|
|
2002
|
+
}
|
|
2003
|
+
catch (e) { }
|
|
2004
|
+
// Check if this is a background task
|
|
2005
|
+
if (state.shellState?.isBackgroundTask && state.shellState?.backgroundTaskId) {
|
|
2006
|
+
// Route input to background task
|
|
2007
|
+
BackgroundTaskManager.sendInput(state.shellState.backgroundTaskId, input);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
1772
2010
|
// PTY MODE: Send everything immediately
|
|
1773
2011
|
onShellInput(input);
|
|
1774
2012
|
}, onFocusChange: (focused) => {
|
|
1775
2013
|
// Clear screen when entering/exiting focus mode
|
|
1776
2014
|
clearScreen();
|
|
2015
|
+
// Special handling for background tasks
|
|
2016
|
+
if (!focused && state.shellState?.isBackgroundTask) {
|
|
2017
|
+
// Exiting focus mode for a background task:
|
|
2018
|
+
// Just clear shellState and return to chat, no history message
|
|
2019
|
+
// The task continues running in background
|
|
2020
|
+
setState(prev => ({
|
|
2021
|
+
...prev,
|
|
2022
|
+
shellState: undefined // Clear completely, no preview
|
|
2023
|
+
}));
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
// Normal shell focus change
|
|
1777
2027
|
setState(prev => ({
|
|
1778
2028
|
...prev,
|
|
1779
2029
|
shellState: prev.shellState ? {
|
|
@@ -1782,6 +2032,15 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1782
2032
|
} : undefined
|
|
1783
2033
|
}));
|
|
1784
2034
|
}, onSignal: (signal) => {
|
|
2035
|
+
// Check if this is a background task
|
|
2036
|
+
if (state.shellState?.isBackgroundTask && state.shellState?.backgroundTaskId) {
|
|
2037
|
+
// Route signal to background task
|
|
2038
|
+
const nodeSignal = signal === 'SIGINT' ? 'SIGINT' :
|
|
2039
|
+
signal === 'SIGTERM' ? 'SIGTERM' :
|
|
2040
|
+
signal === 'SIGKILL' ? 'SIGKILL' : 'SIGINT';
|
|
2041
|
+
BackgroundTaskManager.sendSignal(state.shellState.backgroundTaskId, nodeSignal);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1785
2044
|
onShellSignal(signal);
|
|
1786
2045
|
} })),
|
|
1787
2046
|
state.isAiWorking && !state.shellState && !state.approvalRequest &&
|
|
@@ -1797,9 +2056,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
1797
2056
|
// Clear preserved input on submit
|
|
1798
2057
|
preservedInputTextRef.current = '';
|
|
1799
2058
|
handleSubmit(value, clipboardImages);
|
|
1800
|
-
}, autoAcceptMode: state.autoAcceptMode, model: state.currentModel, planMode: state.planMode, commandMode: state.commandMode, currentWorkingDirectory: state.currentWorkingDirectory, commandHistory: state.commandHistory, onToggleAutoAccept: handleToggleAutoAccept, onToggleCommandMode: onToggleCommandMode, isActive: true, subshellContext: state.subshellContext, currentTokens: state.currentTokens, maxTokens: state.maxTokens, isShellRunning: state.shellState?.isRunning, initialValue: preservedInputTextRef.current, onValueChange: handleInputValueChange
|
|
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) => {
|
|
2060
|
+
setAutoModeCallbackRef.current = callback;
|
|
2061
|
+
} }))),
|
|
1801
2062
|
state.showExitWarning && (React.createElement(Box, { marginTop: 1 },
|
|
1802
|
-
React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit")))
|
|
2063
|
+
React.createElement(Text, { color: "#ffaa00", bold: true }, "\u26A0\uFE0F Press Ctrl+C again to exit"))),
|
|
2064
|
+
!isConnected && (React.createElement(Box, { marginTop: 0, marginLeft: state.showExitWarning ? 2 : 0 },
|
|
2065
|
+
React.createElement(Text, { color: "red", bold: true }, "\u26A0\uFE0F No internet connection"))))),
|
|
1803
2066
|
state.screen === 'approval' && state.approvalRequest && (React.createElement(ApprovalSection, { key: `approval-${state.approvalRequest.message}`, approvalRequest: state.approvalRequest, onApprove: (approved) => {
|
|
1804
2067
|
const resolve = state.approvalRequest?.resolve;
|
|
1805
2068
|
if (resolve) {
|
|
@@ -2096,6 +2359,79 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2096
2359
|
...prev,
|
|
2097
2360
|
screen: 'chat'
|
|
2098
2361
|
}));
|
|
2099
|
-
} })))
|
|
2362
|
+
} }))),
|
|
2363
|
+
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 }, "\uD83D\uDD04 Running Background Tasks"),
|
|
2365
|
+
React.createElement(Text, { dimColor: true }, "Select a task to view its output in focus mode"),
|
|
2366
|
+
React.createElement(Box, { marginTop: 1 },
|
|
2367
|
+
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
2368
|
+
const durationSec = Math.round(task.durationMs / 1000);
|
|
2369
|
+
return {
|
|
2370
|
+
label: task.command.length > 50 ? task.command.slice(0, 47) + '...' : task.command,
|
|
2371
|
+
value: task.id,
|
|
2372
|
+
cwd: task.cwd,
|
|
2373
|
+
duration: `${durationSec}s`,
|
|
2374
|
+
isRunning: task.isRunning,
|
|
2375
|
+
outputPreview: task.outputPreview.length > 60 ? task.outputPreview.slice(0, 57) + '...' : task.outputPreview
|
|
2376
|
+
};
|
|
2377
|
+
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning, outputPreview }) => (React.createElement(Box, { flexDirection: "column" },
|
|
2378
|
+
React.createElement(Box, null,
|
|
2379
|
+
React.createElement(Text, { color: isSelected ? '#9966ff' : 'white', bold: isSelected },
|
|
2380
|
+
isSelected ? '> ' : ' ',
|
|
2381
|
+
label),
|
|
2382
|
+
React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
|
|
2383
|
+
" [",
|
|
2384
|
+
isRunning ? 'running' : 'done',
|
|
2385
|
+
"]"),
|
|
2386
|
+
React.createElement(Text, { color: "gray" },
|
|
2387
|
+
" \u23F1\uFE0F ",
|
|
2388
|
+
duration)),
|
|
2389
|
+
React.createElement(Box, { paddingLeft: 4 },
|
|
2390
|
+
React.createElement(Text, { color: "gray" },
|
|
2391
|
+
"\uD83D\uDCC1 ",
|
|
2392
|
+
cwd)),
|
|
2393
|
+
outputPreview && (React.createElement(Box, { paddingLeft: 4 },
|
|
2394
|
+
React.createElement(Text, { color: "gray", dimColor: true },
|
|
2395
|
+
"\u2514\u2500 ",
|
|
2396
|
+
outputPreview))))), onSelect: (item) => {
|
|
2397
|
+
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2398
|
+
onBackgroundTaskSelection(item.value);
|
|
2399
|
+
} })),
|
|
2400
|
+
React.createElement(Box, { marginTop: 1 },
|
|
2401
|
+
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat")))),
|
|
2402
|
+
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 }, "\uD83D\uDED1 Cancel a Background Task"),
|
|
2404
|
+
React.createElement(Text, { dimColor: true }, "Select a task to terminate it"),
|
|
2405
|
+
React.createElement(Box, { marginTop: 1 },
|
|
2406
|
+
React.createElement(SelectInput, { items: state.backgroundTasks.map((task) => {
|
|
2407
|
+
const durationSec = Math.round(task.durationMs / 1000);
|
|
2408
|
+
return {
|
|
2409
|
+
label: task.command.length > 50 ? task.command.slice(0, 47) + '...' : task.command,
|
|
2410
|
+
value: task.id,
|
|
2411
|
+
cwd: task.cwd,
|
|
2412
|
+
duration: `${durationSec}s`,
|
|
2413
|
+
isRunning: task.isRunning
|
|
2414
|
+
};
|
|
2415
|
+
}), itemComponent: ({ isSelected, label, cwd, duration, isRunning }) => (React.createElement(Box, { flexDirection: "column" },
|
|
2416
|
+
React.createElement(Box, null,
|
|
2417
|
+
React.createElement(Text, { color: isSelected ? '#ff6666' : 'white', bold: isSelected },
|
|
2418
|
+
isSelected ? '> ' : ' ',
|
|
2419
|
+
label),
|
|
2420
|
+
React.createElement(Text, { color: isRunning ? '#00cc66' : '#ffaa00' },
|
|
2421
|
+
" [",
|
|
2422
|
+
isRunning ? 'running' : 'done',
|
|
2423
|
+
"]"),
|
|
2424
|
+
React.createElement(Text, { color: "gray" },
|
|
2425
|
+
" \u23F1\uFE0F ",
|
|
2426
|
+
duration)),
|
|
2427
|
+
React.createElement(Box, { paddingLeft: 4 },
|
|
2428
|
+
React.createElement(Text, { color: "gray" },
|
|
2429
|
+
"\uD83D\uDCC1 ",
|
|
2430
|
+
cwd)))), onSelect: (item) => {
|
|
2431
|
+
setState(prev => ({ ...prev, screen: 'chat', isLoading: false }));
|
|
2432
|
+
onBackgroundTaskCancel(item.value);
|
|
2433
|
+
} })),
|
|
2434
|
+
React.createElement(Box, { marginTop: 1 },
|
|
2435
|
+
React.createElement(Text, { dimColor: true }, "Press ESC to return to chat"))))));
|
|
2100
2436
|
};
|
|
2101
2437
|
//# sourceMappingURL=App.js.map
|