centaurus-cli 2.9.8 → 3.0.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 +7 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +249 -155
- package/dist/cli-adapter.js.map +1 -1
- package/dist/context/handlers/docker-handler.d.ts.map +1 -1
- package/dist/context/handlers/docker-handler.js +2 -2
- package/dist/context/handlers/docker-handler.js.map +1 -1
- package/dist/context/handlers/ssh-handler.d.ts.map +1 -1
- package/dist/context/handlers/ssh-handler.js +3 -0
- package/dist/context/handlers/ssh-handler.js.map +1 -1
- package/dist/context/handlers/wsl-handler.d.ts.map +1 -1
- package/dist/context/handlers/wsl-handler.js +9 -3
- package/dist/context/handlers/wsl-handler.js.map +1 -1
- package/dist/index.js +49 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
- package/dist/mcp/mcp-server-manager.js +17 -3
- package/dist/mcp/mcp-server-manager.js.map +1 -1
- package/dist/services/api-client.d.ts +4 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +27 -18
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/clipboard-service.d.ts +9 -14
- package/dist/services/clipboard-service.d.ts.map +1 -1
- package/dist/services/clipboard-service.js +83 -83
- package/dist/services/clipboard-service.js.map +1 -1
- package/dist/services/conversation-manager.d.ts.map +1 -1
- package/dist/services/conversation-manager.js +3 -2
- package/dist/services/conversation-manager.js.map +1 -1
- package/dist/tools/enter-remote-session.d.ts +35 -0
- package/dist/tools/enter-remote-session.d.ts.map +1 -1
- package/dist/tools/enter-remote-session.js +5 -5
- package/dist/tools/enter-remote-session.js.map +1 -1
- package/dist/tools/read-binary-file.js +6 -6
- package/dist/tools/read-binary-file.js.map +1 -1
- package/dist/ui/components/App.d.ts +2 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +105 -95
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/ClipboardFileAutocomplete.d.ts +10 -0
- package/dist/ui/components/ClipboardFileAutocomplete.d.ts.map +1 -0
- package/dist/ui/components/{ClipboardImageAutocomplete.js → ClipboardFileAutocomplete.js} +14 -12
- package/dist/ui/components/ClipboardFileAutocomplete.js.map +1 -0
- package/dist/ui/components/DetailedPlanReviewScreen.js +1 -1
- package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +2 -2
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +41 -21
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/KeyboardHelp.d.ts.map +1 -1
- package/dist/ui/components/KeyboardHelp.js +2 -0
- package/dist/ui/components/KeyboardHelp.js.map +1 -1
- package/dist/ui/components/MessageDisplay.d.ts +4 -4
- package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/MessageDisplay.js +33 -31
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/PlanAcceptedMessage.js +2 -2
- package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
- package/dist/ui/components/TaskCompletedMessage.js +2 -2
- package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +7 -7
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolResult.js +1 -1
- package/dist/ui/components/ToolResult.js.map +1 -1
- package/dist/utils/context-sanitizer.d.ts +50 -0
- package/dist/utils/context-sanitizer.d.ts.map +1 -0
- package/dist/utils/context-sanitizer.js +119 -0
- package/dist/utils/context-sanitizer.js.map +1 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +12 -1
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-checker.d.ts.map +1 -1
- package/dist/utils/syntax-checker.js +26 -0
- package/dist/utils/syntax-checker.js.map +1 -1
- package/dist/utils/terminal-output.d.ts.map +1 -1
- package/dist/utils/terminal-output.js +10 -8
- package/dist/utils/terminal-output.js.map +1 -1
- package/dist/utils/text-clipboard.d.ts.map +1 -1
- package/dist/utils/text-clipboard.js +21 -8
- package/dist/utils/text-clipboard.js.map +1 -1
- package/package.json +6 -2
- package/dist/config/ConfigManager.d.ts +0 -62
- package/dist/config/ConfigManager.d.ts.map +0 -1
- package/dist/config/ConfigManager.js +0 -234
- package/dist/config/ConfigManager.js.map +0 -1
- package/dist/ui/components/ClipboardImageAutocomplete.d.ts +0 -14
- package/dist/ui/components/ClipboardImageAutocomplete.d.ts.map +0 -1
- package/dist/ui/components/ClipboardImageAutocomplete.js.map +0 -1
|
@@ -8,7 +8,7 @@ import * as path from 'path';
|
|
|
8
8
|
import { quickLog } from '../../utils/conversation-logger.js';
|
|
9
9
|
import { WelcomeBanner } from './WelcomeBanner.js';
|
|
10
10
|
import { InputBox } from './InputBox.js';
|
|
11
|
-
import { MessageDisplay,
|
|
11
|
+
import { MessageDisplay, countFilesInMessage } from './MessageDisplay.js';
|
|
12
12
|
import { StreamingMessageDisplay } from './StreamingMessageDisplay.js';
|
|
13
13
|
import { LoadingIndicator } from './LoadingIndicator.js';
|
|
14
14
|
import { AgentTimer } from './AgentTimer.js';
|
|
@@ -69,21 +69,21 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
69
69
|
const staticItems = React.useMemo(() => {
|
|
70
70
|
return showBanner ? [BANNER_ITEM, ...history] : history;
|
|
71
71
|
}, [history, showBanner]);
|
|
72
|
-
// Calculate cumulative
|
|
73
|
-
const
|
|
72
|
+
// Calculate cumulative file counts for each message (for global numbering)
|
|
73
|
+
const fileCountsUpTo = React.useMemo(() => {
|
|
74
74
|
const counts = [];
|
|
75
75
|
let cumulative = 0;
|
|
76
76
|
for (const msg of history) {
|
|
77
77
|
counts.push(cumulative);
|
|
78
78
|
if (msg.role === 'user') {
|
|
79
|
-
cumulative +=
|
|
79
|
+
cumulative += countFilesInMessage(msg.content);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
return counts;
|
|
83
83
|
}, [history]);
|
|
84
|
-
// Get
|
|
85
|
-
const
|
|
86
|
-
?
|
|
84
|
+
// Get file count for current message (all files in history)
|
|
85
|
+
const totalHistoryFiles = fileCountsUpTo.length > 0
|
|
86
|
+
? fileCountsUpTo[fileCountsUpTo.length - 1] + (history[history.length - 1]?.role === 'user' ? countFilesInMessage(history[history.length - 1].content) : 0)
|
|
87
87
|
: 0;
|
|
88
88
|
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
89
89
|
React.createElement(Static, { items: staticItems }, (item, index) => {
|
|
@@ -94,7 +94,7 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
94
94
|
const msg = item;
|
|
95
95
|
// Calculate history index (account for banner if present)
|
|
96
96
|
const historyIndex = showBanner ? index - 1 : index;
|
|
97
|
-
const
|
|
97
|
+
const fileCountBefore = fileCountsUpTo[historyIndex] || 0;
|
|
98
98
|
if (msg.taskCompletion) {
|
|
99
99
|
return (React.createElement(TaskCompletedMessage, { key: item.id, taskNumber: msg.taskCompletion.taskNumber, totalTasks: msg.taskCompletion.totalTasks, taskDescription: msg.taskCompletion.taskDescription, completionNote: msg.taskCompletion.completionNote }));
|
|
100
100
|
}
|
|
@@ -102,7 +102,7 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
102
102
|
if (msg.planAccepted) {
|
|
103
103
|
return (React.createElement(PlanAcceptedMessage, { key: item.id, planTitle: msg.planAccepted.planTitle, totalTasks: msg.planAccepted.totalTasks, tasks: msg.planAccepted.tasks }));
|
|
104
104
|
}
|
|
105
|
-
return React.createElement(MessageDisplay, { key: item.id, message: item,
|
|
105
|
+
return React.createElement(MessageDisplay, { key: item.id, message: item, fileCountBefore: fileCountBefore });
|
|
106
106
|
}),
|
|
107
107
|
current && !history.some(msg => msg.id === current.id) && (() => {
|
|
108
108
|
// Get terminal dimensions to determine if streaming should be enabled
|
|
@@ -114,7 +114,7 @@ const MessageList = React.memo(({ history, current, showBanner }) => {
|
|
|
114
114
|
return (React.createElement(StreamingMessageDisplay, { key: current.id, message: current, maxLines: dimensions.maxStreamingLines }));
|
|
115
115
|
}
|
|
116
116
|
else {
|
|
117
|
-
return React.createElement(MessageDisplay, { key: current.id, message: current,
|
|
117
|
+
return React.createElement(MessageDisplay, { key: current.id, message: current, fileCountBefore: totalHistoryFiles });
|
|
118
118
|
}
|
|
119
119
|
})()));
|
|
120
120
|
}, (prevProps, nextProps) => {
|
|
@@ -175,7 +175,7 @@ const RenameInputScreen = ({ currentTitle, onRename, onCancel }) => {
|
|
|
175
175
|
React.createElement(Box, { marginTop: 1 },
|
|
176
176
|
React.createElement(Text, { dimColor: true }, "Press Enter to save, ESC to cancel"))));
|
|
177
177
|
};
|
|
178
|
-
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, onWarpifySession, onAiAutoSuggestChange, onWorkflowCreatorSetup, onWorkflowSave, getCheckpoints, onRevertToCheckpointSetup, onSetInputSetup }) => {
|
|
178
|
+
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, onWarpifySession, onAiAutoSuggestChange, onWorkflowCreatorSetup, onWorkflowSave, getCheckpoints, onRevertToCheckpointSetup, onSetInputSetup, onInterruptQueueUpdate, onQueuedMessageDispatched }) => {
|
|
179
179
|
const { exit } = useApp();
|
|
180
180
|
// Calculate limit for paginated lists (75% of terminal height)
|
|
181
181
|
const listLimit = Math.max(5, Math.floor((process.stdout.rows || 24) * 0.75));
|
|
@@ -254,6 +254,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
254
254
|
sessionQuotaExhausted: false,
|
|
255
255
|
sessionQuotaTimeRemaining: '',
|
|
256
256
|
aiAutoSuggest: false,
|
|
257
|
+
interruptQueue: [],
|
|
257
258
|
});
|
|
258
259
|
// Track last terminal width to detect actual width changes
|
|
259
260
|
const lastTerminalWidthRef = React.useRef(process.stdout.columns || 80);
|
|
@@ -405,6 +406,38 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
405
406
|
InputDetectionAgent.updateOutput(state.shellState.shellId, state.shellState.output);
|
|
406
407
|
}
|
|
407
408
|
}, [state.shellState?.output, state.shellState?.isAgentControlled, state.shellState?.shellId]);
|
|
409
|
+
// Handle interrupt queue updates
|
|
410
|
+
React.useEffect(() => {
|
|
411
|
+
onInterruptQueueUpdate((queue) => {
|
|
412
|
+
setState(prev => ({ ...prev, interruptQueue: [...queue] }));
|
|
413
|
+
});
|
|
414
|
+
}, [onInterruptQueueUpdate]);
|
|
415
|
+
// Handle queued message dispatch: add user message to history when it's actually being processed
|
|
416
|
+
// This ensures the correct order: only show the message when it's the AI's next turn, not when queued
|
|
417
|
+
React.useEffect(() => {
|
|
418
|
+
onQueuedMessageDispatched((message) => {
|
|
419
|
+
setState(prev => {
|
|
420
|
+
const userMessage = {
|
|
421
|
+
id: `user-queued-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
422
|
+
role: 'user',
|
|
423
|
+
content: message,
|
|
424
|
+
timestamp: new Date()
|
|
425
|
+
};
|
|
426
|
+
// Move current message to history if present (skip thought-only)
|
|
427
|
+
const newHistory = prev.currentMessage && !shouldSkipMessage(prev.currentMessage)
|
|
428
|
+
? [...prev.messageHistory, prev.currentMessage, userMessage]
|
|
429
|
+
: [...prev.messageHistory, userMessage];
|
|
430
|
+
return {
|
|
431
|
+
...prev,
|
|
432
|
+
messageHistory: newHistory,
|
|
433
|
+
currentMessage: null,
|
|
434
|
+
isLoading: true,
|
|
435
|
+
loadingMessage: 'Processing your request...',
|
|
436
|
+
isAiWorking: true
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
}, [onQueuedMessageDispatched]);
|
|
408
441
|
// Ref to track if we should strictly block incoming AI updates (Zombie Agent Protection)
|
|
409
442
|
// This is set to true when an Agent-Controlled shell exits, essentially killing the "AI Agent"
|
|
410
443
|
// associated with it from the UI perspective. It is reset when the user manually inputs a new message.
|
|
@@ -2268,7 +2301,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2268
2301
|
return;
|
|
2269
2302
|
}
|
|
2270
2303
|
}, { isActive: !state.isInteractiveEditorMode });
|
|
2271
|
-
const handleSubmit = useCallback(async (value,
|
|
2304
|
+
const handleSubmit = useCallback(async (value, clipboardFiles) => {
|
|
2272
2305
|
// Trim the value to remove any leading/trailing whitespace or newlines
|
|
2273
2306
|
const trimmedValue = value.trim();
|
|
2274
2307
|
if (!trimmedValue)
|
|
@@ -2351,69 +2384,37 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2351
2384
|
});
|
|
2352
2385
|
}
|
|
2353
2386
|
else if (!isSlashCommand) {
|
|
2354
|
-
// Only add user message to display if it's not a slash command
|
|
2355
|
-
//
|
|
2356
|
-
//
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
// Clean up UI state: remove any partial/interrupted messages
|
|
2363
|
-
setState(prev => {
|
|
2364
|
-
// Filter out the last user message (the one we're replacing)
|
|
2365
|
-
let cleanedHistory = [...prev.messageHistory];
|
|
2366
|
-
for (let i = cleanedHistory.length - 1; i >= 0; i--) {
|
|
2367
|
-
if (cleanedHistory[i].role === 'user') {
|
|
2368
|
-
cleanedHistory.splice(i, 1);
|
|
2369
|
-
break; // Only remove the most recent user message
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
return {
|
|
2373
|
-
...prev,
|
|
2374
|
-
messageHistory: cleanedHistory,
|
|
2375
|
-
currentMessage: null,
|
|
2376
|
-
isLoading: false,
|
|
2377
|
-
isAiWorking: false,
|
|
2378
|
-
approvalRequest: undefined, // Clear any pending approval
|
|
2379
|
-
};
|
|
2380
|
-
});
|
|
2381
|
-
}
|
|
2382
|
-
// Show user message IMMEDIATELY with image breadcrumbs (uploading state)
|
|
2383
|
-
// Images will be uploaded in background, then AI will be called
|
|
2384
|
-
const hasImagesToUpload = clipboardImages && clipboardImages.length > 0;
|
|
2385
|
-
// Build initial content with placeholder image markers for immediate display
|
|
2387
|
+
// Only add user message to display if it's not a slash command.
|
|
2388
|
+
// The user message is added to messageHistory via the onQueuedMessageDispatched
|
|
2389
|
+
// callback (fired by cli-adapter at the very start of processing, for both direct
|
|
2390
|
+
// messages and queued interrupts). This ensures correct ordering and no duplicates.
|
|
2391
|
+
// Show user message IMMEDIATELY with file breadcrumbs (uploading state)
|
|
2392
|
+
// Files will be uploaded in background, then AI will be called
|
|
2393
|
+
const hasFilesToUpload = clipboardFiles && clipboardFiles.length > 0;
|
|
2394
|
+
// Build initial content with placeholder file markers for immediate display
|
|
2386
2395
|
let initialDisplayContent = trimmedValue;
|
|
2387
|
-
if (
|
|
2396
|
+
if (hasFilesToUpload) {
|
|
2388
2397
|
// Add placeholder markers that will be shown immediately
|
|
2389
|
-
for (let i = 0; i <
|
|
2390
|
-
const
|
|
2398
|
+
for (let i = 0; i < clipboardFiles.length; i++) {
|
|
2399
|
+
const file = clipboardFiles[i];
|
|
2391
2400
|
// Use a placeholder format that MessageDisplay can render as breadcrumb
|
|
2392
|
-
// Format: [
|
|
2393
|
-
|
|
2401
|
+
// Format: [FILE: filename (uploading...)] - will be updated after upload
|
|
2402
|
+
const markerType = file.mimeType?.startsWith('image/') ? 'IMAGE' : 'FILE';
|
|
2403
|
+
initialDisplayContent = `${initialDisplayContent} [${markerType}: ${file.displayName} (gs://uploading)]`;
|
|
2394
2404
|
}
|
|
2395
2405
|
}
|
|
2396
2406
|
// Generate a stable message ID that we can use to update the message later
|
|
2397
2407
|
const userMessageId = `user-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
return {
|
|
2409
|
-
...prev,
|
|
2410
|
-
messageHistory: [...newHistory, userMessage],
|
|
2411
|
-
currentMessage: null,
|
|
2412
|
-
isLoading: true,
|
|
2413
|
-
loadingMessage: hasImagesToUpload ? 'Uploading image(s)...' : 'Processing your request...',
|
|
2414
|
-
isAiWorking: true
|
|
2415
|
-
};
|
|
2416
|
-
});
|
|
2408
|
+
// Update loading state immediately so the UI shows "processing" feedback.
|
|
2409
|
+
// Note: we do NOT add to messageHistory here — that is handled by the
|
|
2410
|
+
// onQueuedMessageDispatched callback which fires synchronously inside
|
|
2411
|
+
// cli-adapter.handleMessage before AI processing begins.
|
|
2412
|
+
setState(prev => ({
|
|
2413
|
+
...prev,
|
|
2414
|
+
isLoading: true,
|
|
2415
|
+
loadingMessage: hasFilesToUpload ? 'Uploading file(s)...' : 'Processing your request...',
|
|
2416
|
+
isAiWorking: true
|
|
2417
|
+
}));
|
|
2417
2418
|
}
|
|
2418
2419
|
else {
|
|
2419
2420
|
// For slash commands, just show loading state
|
|
@@ -2432,12 +2433,12 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2432
2433
|
: [...prev.commandHistory, trimmedValue]
|
|
2433
2434
|
}));
|
|
2434
2435
|
try {
|
|
2435
|
-
// Upload clipboard
|
|
2436
|
-
let
|
|
2437
|
-
// Debug logging for clipboard
|
|
2438
|
-
logDebug(`[CLIPBOARD]
|
|
2436
|
+
// Upload clipboard files if present
|
|
2437
|
+
let messageWithFiles = trimmedValue;
|
|
2438
|
+
// Debug logging for clipboard files
|
|
2439
|
+
logDebug(`[CLIPBOARD] clipboardFiles count: ${clipboardFiles?.length || 0}`);
|
|
2439
2440
|
logDebug(`[CLIPBOARD] currentChatId: ${state.currentChatId || 'null'}`);
|
|
2440
|
-
if (
|
|
2441
|
+
if (clipboardFiles && clipboardFiles.length > 0) {
|
|
2441
2442
|
// Check for existing conversation ID - conversationManager is the single source of truth
|
|
2442
2443
|
// This ensures we use the same conversation that cli-adapter uses
|
|
2443
2444
|
let conversationId = conversationManager.getCurrentConversationId() || state.currentChatId;
|
|
@@ -2446,7 +2447,7 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2446
2447
|
if (!conversationId) {
|
|
2447
2448
|
// No conversation exists anywhere - create one via conversationManager
|
|
2448
2449
|
// This will set the internal state that cli-adapter's ensureConversationStarted checks
|
|
2449
|
-
logDebug(`[CLIPBOARD] No existing conversation - creating new one for
|
|
2450
|
+
logDebug(`[CLIPBOARD] No existing conversation - creating new one for file upload`);
|
|
2450
2451
|
try {
|
|
2451
2452
|
const newConversation = await conversationManager.startNewConversation('New Chat', state.currentModel || 'gemini-2-flash-preview', 'google', undefined // workingDirectory - will use process.cwd()
|
|
2452
2453
|
);
|
|
@@ -2461,38 +2462,40 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2461
2462
|
logDebug(`[CLIPBOARD] Proceeding with upload`);
|
|
2462
2463
|
}
|
|
2463
2464
|
catch (createError) {
|
|
2464
|
-
logError('Failed to create conversation for
|
|
2465
|
+
logError('Failed to create conversation for file upload', createError instanceof Error ? createError : undefined);
|
|
2465
2466
|
// Mark the upload as failed - don't use temp IDs that will fail FK constraint
|
|
2466
|
-
for (let i = 0; i <
|
|
2467
|
-
|
|
2467
|
+
for (let i = 0; i < clipboardFiles.length; i++) {
|
|
2468
|
+
messageWithFiles = messageWithFiles.replace(/#image\b/i, '[FILE UPLOAD FAILED - No conversation]');
|
|
2468
2469
|
}
|
|
2469
2470
|
logDebug(`[CLIPBOARD] Skipping upload due to conversation creation failure`);
|
|
2470
2471
|
}
|
|
2471
2472
|
}
|
|
2472
2473
|
// Only attempt upload if we have a valid conversation ID
|
|
2473
2474
|
if (conversationId && !conversationId.startsWith('temp_')) {
|
|
2474
|
-
logDebug(`[CLIPBOARD] Starting upload for ${
|
|
2475
|
-
// Upload each clipboard
|
|
2476
|
-
const
|
|
2477
|
-
for (const
|
|
2475
|
+
logDebug(`[CLIPBOARD] Starting upload for ${clipboardFiles.length} files with conversationId: ${conversationId}`);
|
|
2476
|
+
// Upload each clipboard file and build the final message content
|
|
2477
|
+
const uploadedFileMarkers = [];
|
|
2478
|
+
for (const file of clipboardFiles) {
|
|
2478
2479
|
try {
|
|
2479
|
-
logDebug(`[CLIPBOARD] Uploading
|
|
2480
|
-
const uploadResult = await apiClient.uploadFile(conversationId,
|
|
2480
|
+
logDebug(`[CLIPBOARD] Uploading file: ${file.displayName}, size: ${file.sizeBytes} bytes`);
|
|
2481
|
+
const uploadResult = await apiClient.uploadFile(conversationId, file.displayName, file.mimeType, file.base64Data);
|
|
2481
2482
|
logDebug(`[CLIPBOARD] Upload success! gcsUri: ${uploadResult.gcsUri || 'none'}`);
|
|
2482
|
-
|
|
2483
|
+
const markerType = file.mimeType?.startsWith('image/') ? 'IMAGE' : 'FILE';
|
|
2484
|
+
// Append file reference info to message (for AI to know a file was attached)
|
|
2483
2485
|
// The backend will use gcsUri for Vertex AI multimodal input
|
|
2484
2486
|
if (uploadResult.gcsUri) {
|
|
2485
|
-
// Build the real
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
logDebug(`[CLIPBOARD] Appended
|
|
2487
|
+
// Build the real file marker with actual GCS URI
|
|
2488
|
+
messageWithFiles = `${messageWithFiles} [${markerType}: ${uploadResult.fileName} (${uploadResult.gcsUri})]`;
|
|
2489
|
+
uploadedFileMarkers.push(`[${markerType}: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
|
|
2490
|
+
logDebug(`[CLIPBOARD] Appended file marker: [${markerType}: ${uploadResult.fileName} (${uploadResult.gcsUri})]`);
|
|
2489
2491
|
}
|
|
2490
2492
|
}
|
|
2491
2493
|
catch (uploadError) {
|
|
2492
|
-
// Log error but continue -
|
|
2493
|
-
|
|
2494
|
+
// Log error but continue - file upload is optional
|
|
2495
|
+
const markerType = file.mimeType?.startsWith('image/') ? 'IMAGE' : 'FILE';
|
|
2496
|
+
logError('Failed to upload clipboard file', uploadError instanceof Error ? uploadError : undefined);
|
|
2494
2497
|
// Add a failed marker so user knows upload failed
|
|
2495
|
-
|
|
2498
|
+
uploadedFileMarkers.push(`[${markerType}: ${file.displayName} (upload failed)]`);
|
|
2496
2499
|
}
|
|
2497
2500
|
}
|
|
2498
2501
|
// Update the user message in history with actual GCS URIs
|
|
@@ -2501,9 +2504,9 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2501
2504
|
const updatedHistory = prev.messageHistory.map(msg => {
|
|
2502
2505
|
// Find the user message we just added (it has placeholder markers)
|
|
2503
2506
|
if (msg.role === 'user' && msg.content.includes('(gs://uploading)')) {
|
|
2504
|
-
// Build new content: original text + real
|
|
2507
|
+
// Build new content: original text + real file markers
|
|
2505
2508
|
let newContent = trimmedValue;
|
|
2506
|
-
for (const marker of
|
|
2509
|
+
for (const marker of uploadedFileMarkers) {
|
|
2507
2510
|
newContent = `${newContent} ${marker}`;
|
|
2508
2511
|
}
|
|
2509
2512
|
return { ...msg, content: newContent };
|
|
@@ -2527,8 +2530,8 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2527
2530
|
return { ...prev, messageHistory: updatedHistory };
|
|
2528
2531
|
});
|
|
2529
2532
|
} // End: if (conversationId && !startsWith temp_)
|
|
2530
|
-
} // End: if (
|
|
2531
|
-
await onMessage(
|
|
2533
|
+
} // End: if (clipboardFiles && clipboardFiles.length > 0)
|
|
2534
|
+
await onMessage(messageWithFiles);
|
|
2532
2535
|
}
|
|
2533
2536
|
catch (error) {
|
|
2534
2537
|
// Add error message
|
|
@@ -2772,6 +2775,13 @@ export const App = ({ onMessage, onCancelRequest, initialModel, initialPlanMode,
|
|
|
2772
2775
|
!(state.currentMessage?.toolExecution?.status === 'executing') && (React.createElement(Box, { marginBottom: 1, paddingLeft: 1 },
|
|
2773
2776
|
React.createElement(LoadingIndicator, { key: "loading-indicator" }),
|
|
2774
2777
|
React.createElement(AgentTimer, { key: "agent-timer" }))),
|
|
2778
|
+
state.interruptQueue && state.interruptQueue.length > 0 && (React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, state.interruptQueue.map((msg, i) => (React.createElement(Text, { key: `interrupt-${i}`, color: "gray", dimColor: true },
|
|
2779
|
+
"[Queued Interrupt ",
|
|
2780
|
+
i + 1,
|
|
2781
|
+
"/",
|
|
2782
|
+
state.interruptQueue.length,
|
|
2783
|
+
"] ",
|
|
2784
|
+
msg))))),
|
|
2775
2785
|
!state.shellState?.isFocused && (state.passwordRequest ? (React.createElement(PasswordPrompt, { message: state.passwordRequest.message, onSubmit: (password) => {
|
|
2776
2786
|
const resolve = state.passwordRequest?.resolve;
|
|
2777
2787
|
if (resolve) {
|