centaurus-cli 2.8.9 → 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 +21 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +302 -68
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/models.d.ts +8 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +29 -0
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts +1 -0
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +13 -1
- package/dist/config/slash-commands.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.js +3 -0
- 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 +0 -3
- 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.map +1 -1
- package/dist/services/api-client.d.ts +26 -40
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +32 -34
- package/dist/services/api-client.js.map +1 -1
- 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 +33 -0
- 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/ui/components/App.d.ts +3 -2
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +127 -44
- 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/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +198 -199
- 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 +153 -39
- 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/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +139 -20
- package/dist/utils/input-classifier.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 -1
package/dist/cli-adapter.js
CHANGED
|
@@ -17,6 +17,8 @@ import { createPlanTool, markTaskCompleteTool, getCurrentPlan, clearPlan, approv
|
|
|
17
17
|
import { webSearchTool, fetchUrlTool } from './tools/web-search.js';
|
|
18
18
|
import { taskCompleteTool } from './tools/task-complete.js';
|
|
19
19
|
import { readBinaryFileTool } from './tools/read-binary-file.js';
|
|
20
|
+
import { createImageTool } from './tools/create-image.js';
|
|
21
|
+
import { backgroundCommandTool } from './tools/background-command.js';
|
|
20
22
|
import { apiClient } from './services/api-client.js';
|
|
21
23
|
import { conversationManager } from './services/conversation-manager.js';
|
|
22
24
|
import { aiServiceClient } from './services/ai-service-client.js';
|
|
@@ -89,6 +91,7 @@ export class CentaurusCLI {
|
|
|
89
91
|
onShowBackgroundTaskPickerCallback;
|
|
90
92
|
onShowBackgroundTaskCancelPickerCallback;
|
|
91
93
|
onBackgroundTaskViewCallback;
|
|
94
|
+
onTokenCountUpdate; // Report actual AI context token count to UI
|
|
92
95
|
constructor() {
|
|
93
96
|
this.configManager = new ConfigManager();
|
|
94
97
|
this.toolRegistry = new ToolRegistry();
|
|
@@ -174,6 +177,9 @@ export class CentaurusCLI {
|
|
|
174
177
|
setOnConnectionStatusUpdate(callback) {
|
|
175
178
|
this.onConnectionStatusUpdate = callback;
|
|
176
179
|
}
|
|
180
|
+
setOnTokenCountUpdate(callback) {
|
|
181
|
+
this.onTokenCountUpdate = callback;
|
|
182
|
+
}
|
|
177
183
|
async initializeMCP() {
|
|
178
184
|
try {
|
|
179
185
|
const mcpConfigManager = new MCPConfigManager();
|
|
@@ -248,9 +254,9 @@ export class CentaurusCLI {
|
|
|
248
254
|
// This prevents caching issues when we update model configs
|
|
249
255
|
this.configManager.set('model', selectedModel.id);
|
|
250
256
|
this.configManager.set('modelName', selectedModel.name);
|
|
251
|
-
// Notify UI of model name change
|
|
257
|
+
// Notify UI of model name change and contextWindow
|
|
252
258
|
if (this.onModelChange) {
|
|
253
|
-
this.onModelChange(selectedModel.name);
|
|
259
|
+
this.onModelChange(selectedModel.name, selectedModel.contextWindow);
|
|
254
260
|
}
|
|
255
261
|
const responseMessage = `Model changed to: ${selectedModel.name}`;
|
|
256
262
|
// Send response back to UI
|
|
@@ -343,6 +349,8 @@ export class CentaurusCLI {
|
|
|
343
349
|
this.toolRegistry.register(fetchUrlTool);
|
|
344
350
|
this.toolRegistry.register(taskCompleteTool);
|
|
345
351
|
this.toolRegistry.register(readBinaryFileTool);
|
|
352
|
+
this.toolRegistry.register(createImageTool);
|
|
353
|
+
this.toolRegistry.register(backgroundCommandTool);
|
|
346
354
|
// Load configuration
|
|
347
355
|
const config = this.configManager.load();
|
|
348
356
|
// Enable backend sync if authenticated
|
|
@@ -404,47 +412,24 @@ Press Enter to continue...
|
|
|
404
412
|
}
|
|
405
413
|
/**
|
|
406
414
|
* Start a new conversation in the backend
|
|
415
|
+
* @deprecated Backend conversation creation is no longer needed since messages are stored locally
|
|
416
|
+
* This function is kept as a no-op for compatibility
|
|
407
417
|
*/
|
|
408
418
|
async ensureConversationStarted() {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Check if conversationManager already has a conversation (e.g., created by App.tsx for image upload)
|
|
413
|
-
// This prevents duplicate conversation creation
|
|
414
|
-
if (conversationManager.getCurrentConversationId()) {
|
|
415
|
-
this.conversationStarted = true;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
try {
|
|
419
|
-
const config = this.configManager.load();
|
|
420
|
-
const title = `Conversation ${new Date().toLocaleString()}`;
|
|
421
|
-
await conversationManager.startNewConversation(title, config.model || 'gemini-2.5-flash', 'google', // Always use Google provider now
|
|
422
|
-
this.cwd);
|
|
423
|
-
this.conversationStarted = true;
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
// Silently continue without backend persistence
|
|
427
|
-
}
|
|
419
|
+
// No-op: Backend conversation creation has been disabled
|
|
420
|
+
// Conversations are now managed locally via local-chat-storage.ts
|
|
421
|
+
return;
|
|
428
422
|
}
|
|
429
423
|
/**
|
|
430
424
|
* Save a message to the backend
|
|
425
|
+
* @deprecated Messages are now stored locally only via saveCurrentChat()
|
|
426
|
+
* This function is kept as a no-op for compatibility
|
|
431
427
|
*/
|
|
432
428
|
async saveMessageToBackend(role, content) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
await this.ensureConversationStarted();
|
|
438
|
-
await conversationManager.addMessage({
|
|
439
|
-
role,
|
|
440
|
-
content,
|
|
441
|
-
contentType: 'text',
|
|
442
|
-
metadata: {},
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
catch (error) {
|
|
446
|
-
// Silently continue without backend persistence
|
|
447
|
-
}
|
|
429
|
+
// No-op: Messages are stored locally only
|
|
430
|
+
// Backend message storage has been disabled to avoid "failed to fetch" errors
|
|
431
|
+
// All conversation history is persisted via local-chat-storage.ts
|
|
432
|
+
return;
|
|
448
433
|
}
|
|
449
434
|
getModel() {
|
|
450
435
|
const config = this.configManager.load();
|
|
@@ -500,6 +485,27 @@ Press Enter to continue...
|
|
|
500
485
|
this.conversationHistory.splice(lastAssistantWithToolCallsIndex);
|
|
501
486
|
}
|
|
502
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Strip thinking blocks from ALL assistant messages in the conversation history.
|
|
490
|
+
*
|
|
491
|
+
* This is called at the START of a new user request to clear thinking from
|
|
492
|
+
* previous tasks. During the current agent loop, thinking is preserved for
|
|
493
|
+
* all turns to help the AI maintain reasoning context.
|
|
494
|
+
*
|
|
495
|
+
* Pattern:
|
|
496
|
+
* - New user request starts → strip ALL thinking from history
|
|
497
|
+
* - During agent loop (multi-turn tool execution) → keep ALL thinking
|
|
498
|
+
* - This allows AI to remember reasoning for current task but not old tasks
|
|
499
|
+
*/
|
|
500
|
+
stripThinkingFromHistory() {
|
|
501
|
+
for (let i = 0; i < this.conversationHistory.length; i++) {
|
|
502
|
+
const msg = this.conversationHistory[i];
|
|
503
|
+
if (msg.role === 'assistant' && msg.thinking) {
|
|
504
|
+
// Remove thinking from the message in place
|
|
505
|
+
delete msg.thinking;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
503
509
|
async handleMessage(message) {
|
|
504
510
|
// Handle command mode - execute commands directly
|
|
505
511
|
if (this.commandMode) {
|
|
@@ -550,13 +556,17 @@ CRITICAL INSTRUCTIONS:
|
|
|
550
556
|
DO NOT use write_to_file, edit_file, or execute_command until the plan is approved.`;
|
|
551
557
|
userMessageContent = planModePrefix;
|
|
552
558
|
}
|
|
559
|
+
// NEW USER REQUEST: Strip thinking from previous tasks
|
|
560
|
+
// This clears thinking from the previous agent loop but thinking will be
|
|
561
|
+
// preserved for all turns within the current agent loop
|
|
562
|
+
this.stripThinkingFromHistory();
|
|
553
563
|
// Add user message to history
|
|
554
564
|
this.conversationHistory.push({
|
|
555
565
|
role: 'user',
|
|
556
566
|
content: userMessageContent,
|
|
557
567
|
});
|
|
558
|
-
//
|
|
559
|
-
|
|
568
|
+
// Messages are stored locally only - no backend persistence needed
|
|
569
|
+
// Local storage is handled by saveCurrentChat() which saves to ~/.centaurus/chats/
|
|
560
570
|
// Start logging session and log user message
|
|
561
571
|
conversationLogger.startSession();
|
|
562
572
|
conversationLogger.logUserMessage(message);
|
|
@@ -606,7 +616,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
606
616
|
let narrationAttempts = 0; // Track how many times AI narrated without executing
|
|
607
617
|
let completionAttempts = 0; // Track how many times AI provided text summary without task_complete
|
|
608
618
|
let thoughtStartTime = null; // Track when thinking started
|
|
609
|
-
let thoughtContent = ''; // Accumulate thought content
|
|
619
|
+
let thoughtContent = ''; // Accumulate thought content during streaming
|
|
620
|
+
let currentTurnThinking = ''; // Persist thinking for the current turn to attach to assistant message
|
|
610
621
|
// ANTI-LOOP: Track duplicate tool calls to detect infinite loops
|
|
611
622
|
const MAX_DUPLICATE_CALLS = 2; // Max times same operation allowed on same target
|
|
612
623
|
const fileWriteTracker = new Map(); // Track writes per file
|
|
@@ -631,7 +642,28 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
631
642
|
// DEBUG: Log message history state before AI call
|
|
632
643
|
const messageStats = {
|
|
633
644
|
totalMessages: messages.length,
|
|
634
|
-
totalCharacters: messages.reduce((sum, m) =>
|
|
645
|
+
totalCharacters: messages.reduce((sum, m) => {
|
|
646
|
+
let len = typeof m.content === 'string' ? m.content.length : 0;
|
|
647
|
+
// Include thinking content (internal reasoning)
|
|
648
|
+
if (m.thinking) {
|
|
649
|
+
len += m.thinking.length;
|
|
650
|
+
}
|
|
651
|
+
// Include tool calls (name + arguments)
|
|
652
|
+
if (m.tool_calls) {
|
|
653
|
+
m.tool_calls.forEach(tc => {
|
|
654
|
+
len += tc.name.length;
|
|
655
|
+
// Arguments are JSON stringified in the payload
|
|
656
|
+
if (tc.arguments) {
|
|
657
|
+
len += JSON.stringify(tc.arguments).length;
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// Include tool_call_id for tool messages
|
|
662
|
+
if (m.role === 'tool' && m.tool_call_id) {
|
|
663
|
+
len += m.tool_call_id.length;
|
|
664
|
+
}
|
|
665
|
+
return sum + len;
|
|
666
|
+
}, 0),
|
|
635
667
|
byRole: {
|
|
636
668
|
system: messages.filter(m => m.role === 'system').length,
|
|
637
669
|
user: messages.filter(m => m.role === 'user').length,
|
|
@@ -647,6 +679,13 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
647
679
|
quickLog(`[${new Date().toISOString()}] [CLI] Assistant messages with tool_calls: ${messageStats.assistantWithToolCalls}\n`);
|
|
648
680
|
}
|
|
649
681
|
catch (e) { }
|
|
682
|
+
// Report actual token count to UI (estimate: 1 token ≈ 4 chars)
|
|
683
|
+
// Include system prompt estimate (~14000 chars based on backend logs)
|
|
684
|
+
const SYSTEM_PROMPT_ESTIMATE = 14000; // Backend injects ~14K char system prompt
|
|
685
|
+
const estimatedTokens = Math.ceil((messageStats.totalCharacters + SYSTEM_PROMPT_ESTIMATE) / 4);
|
|
686
|
+
if (this.onTokenCountUpdate) {
|
|
687
|
+
this.onTokenCountUpdate(estimatedTokens);
|
|
688
|
+
}
|
|
650
689
|
// Stream AI response from backend
|
|
651
690
|
// Backend will inject system prompt automatically with environment context
|
|
652
691
|
for await (const chunk of aiServiceClient.streamChat(selectedModel, messages, tools, environmentContext, mode, selectedModelThinkingConfig, this.currentAbortController.signal)) {
|
|
@@ -687,6 +726,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
687
726
|
if (this.onThoughtCompleteCallback) {
|
|
688
727
|
this.onThoughtCompleteCallback(thinkingDuration);
|
|
689
728
|
}
|
|
729
|
+
// Capture thinking for this turn before clearing
|
|
730
|
+
currentTurnThinking = thoughtContent;
|
|
690
731
|
thoughtStartTime = null;
|
|
691
732
|
thoughtContent = '';
|
|
692
733
|
}
|
|
@@ -719,6 +760,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
719
760
|
if (this.onThoughtCompleteCallback) {
|
|
720
761
|
this.onThoughtCompleteCallback(thinkingDuration);
|
|
721
762
|
}
|
|
763
|
+
// Capture thinking for this turn before clearing
|
|
764
|
+
currentTurnThinking = thoughtContent;
|
|
722
765
|
thoughtStartTime = null;
|
|
723
766
|
thoughtContent = '';
|
|
724
767
|
}
|
|
@@ -747,6 +790,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
747
790
|
if (this.onThoughtCompleteCallback) {
|
|
748
791
|
this.onThoughtCompleteCallback(thinkingDuration);
|
|
749
792
|
}
|
|
793
|
+
// Capture thinking for this turn before clearing
|
|
794
|
+
currentTurnThinking = thoughtContent;
|
|
750
795
|
thoughtStartTime = null;
|
|
751
796
|
thoughtContent = '';
|
|
752
797
|
}
|
|
@@ -845,11 +890,15 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
845
890
|
this.onPlanModeChange(false);
|
|
846
891
|
}
|
|
847
892
|
// Add assistant message with plan tool call to history
|
|
848
|
-
|
|
893
|
+
const planAssistantMsg = {
|
|
849
894
|
role: 'assistant',
|
|
850
895
|
content: '',
|
|
851
896
|
tool_calls: [toolCall],
|
|
852
|
-
}
|
|
897
|
+
};
|
|
898
|
+
if (currentTurnThinking) {
|
|
899
|
+
planAssistantMsg.thinking = currentTurnThinking;
|
|
900
|
+
}
|
|
901
|
+
this.conversationHistory.push(planAssistantMsg);
|
|
853
902
|
// Add plan approval response
|
|
854
903
|
this.conversationHistory.push({
|
|
855
904
|
role: 'tool',
|
|
@@ -885,11 +934,15 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
885
934
|
else {
|
|
886
935
|
// No approval callback - add the tool result to history and wait for user response
|
|
887
936
|
// This ensures the AI doesn't get stuck in a silent loop
|
|
888
|
-
|
|
937
|
+
const planAssistantMsg = {
|
|
889
938
|
role: 'assistant',
|
|
890
939
|
content: '',
|
|
891
940
|
tool_calls: [toolCall],
|
|
892
|
-
}
|
|
941
|
+
};
|
|
942
|
+
if (currentTurnThinking) {
|
|
943
|
+
planAssistantMsg.thinking = currentTurnThinking;
|
|
944
|
+
}
|
|
945
|
+
this.conversationHistory.push(planAssistantMsg);
|
|
893
946
|
this.conversationHistory.push({
|
|
894
947
|
role: 'tool',
|
|
895
948
|
tool_call_id: toolCall.id,
|
|
@@ -909,11 +962,15 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
909
962
|
// Log error and add error result to history so AI knows
|
|
910
963
|
logWarning(`Failed to parse plan: ${parseError?.message || parseError}`);
|
|
911
964
|
// CRITICAL: Add tool result even on parse error to prevent silent loop
|
|
912
|
-
|
|
965
|
+
const errorAssistantMsg = {
|
|
913
966
|
role: 'assistant',
|
|
914
967
|
content: '',
|
|
915
968
|
tool_calls: [toolCall],
|
|
916
|
-
}
|
|
969
|
+
};
|
|
970
|
+
if (currentTurnThinking) {
|
|
971
|
+
errorAssistantMsg.thinking = currentTurnThinking;
|
|
972
|
+
}
|
|
973
|
+
this.conversationHistory.push(errorAssistantMsg);
|
|
917
974
|
this.conversationHistory.push({
|
|
918
975
|
role: 'tool',
|
|
919
976
|
tool_call_id: toolCall.id,
|
|
@@ -926,11 +983,15 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
926
983
|
}
|
|
927
984
|
else {
|
|
928
985
|
// Tool returned non-PLAN_CREATED result - add it to history
|
|
929
|
-
|
|
986
|
+
const resultAssistantMsg = {
|
|
930
987
|
role: 'assistant',
|
|
931
988
|
content: '',
|
|
932
989
|
tool_calls: [toolCall],
|
|
933
|
-
}
|
|
990
|
+
};
|
|
991
|
+
if (currentTurnThinking) {
|
|
992
|
+
resultAssistantMsg.thinking = currentTurnThinking;
|
|
993
|
+
}
|
|
994
|
+
this.conversationHistory.push(resultAssistantMsg);
|
|
934
995
|
this.conversationHistory.push({
|
|
935
996
|
role: 'tool',
|
|
936
997
|
tool_call_id: toolCall.id,
|
|
@@ -959,7 +1020,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
959
1020
|
const mainTaskNum = parseInt(taskNumParts[0], 10) - 1;
|
|
960
1021
|
const task = currentPlanData.steps[mainTaskNum];
|
|
961
1022
|
if (task) {
|
|
962
|
-
this.onTaskCompleted(task, completion.taskNumber, completion.totalCount, completion.completionNote
|
|
1023
|
+
this.onTaskCompleted(task, completion.taskNumber, completion.totalCount, completion.completionNote, completion.taskDescription // Pass the actual task/subtask description
|
|
1024
|
+
);
|
|
963
1025
|
}
|
|
964
1026
|
}
|
|
965
1027
|
// Notify UI about completed task/subtask
|
|
@@ -979,11 +1041,15 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
979
1041
|
result: `Task completed. Moving to Task ${nextPhase.taskNumber}: ${nextPhase.task.description}`,
|
|
980
1042
|
});
|
|
981
1043
|
// Add the tool call and result to history
|
|
982
|
-
|
|
1044
|
+
const nextPhaseAssistantMsg = {
|
|
983
1045
|
role: 'assistant',
|
|
984
1046
|
content: '',
|
|
985
1047
|
tool_calls: [toolCall],
|
|
986
|
-
}
|
|
1048
|
+
};
|
|
1049
|
+
if (currentTurnThinking) {
|
|
1050
|
+
nextPhaseAssistantMsg.thinking = currentTurnThinking;
|
|
1051
|
+
}
|
|
1052
|
+
this.conversationHistory.push(nextPhaseAssistantMsg);
|
|
987
1053
|
this.conversationHistory.push({
|
|
988
1054
|
role: 'tool',
|
|
989
1055
|
tool_call_id: toolCall.id,
|
|
@@ -1216,12 +1282,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1216
1282
|
}
|
|
1217
1283
|
// If user cancelled an operation, stop the agentic loop immediately
|
|
1218
1284
|
if (userCancelledOperation) {
|
|
1219
|
-
// Add assistant message to history
|
|
1220
|
-
|
|
1285
|
+
// Add assistant message to history with thinking if available
|
|
1286
|
+
const cancelledAssistantMsg = {
|
|
1221
1287
|
role: 'assistant',
|
|
1222
1288
|
content: assistantMessage || '',
|
|
1223
1289
|
tool_calls: toolCalls, // Store tool calls for MaaS models
|
|
1224
|
-
}
|
|
1290
|
+
};
|
|
1291
|
+
if (currentTurnThinking) {
|
|
1292
|
+
cancelledAssistantMsg.thinking = currentTurnThinking;
|
|
1293
|
+
}
|
|
1294
|
+
this.conversationHistory.push(cancelledAssistantMsg);
|
|
1225
1295
|
// Add tool results to history
|
|
1226
1296
|
for (const toolResult of toolResults) {
|
|
1227
1297
|
this.conversationHistory.push({
|
|
@@ -1259,11 +1329,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1259
1329
|
const unhandledToolResults = toolResults.filter(tr => !handledToolCallIds.has(tr.tool_call_id));
|
|
1260
1330
|
// Only add assistant message if there are unhandled tool calls
|
|
1261
1331
|
if (unhandledToolCalls.length > 0) {
|
|
1262
|
-
|
|
1332
|
+
const assistantHistoryMsg = {
|
|
1263
1333
|
role: 'assistant',
|
|
1264
1334
|
content: assistantMessage || '',
|
|
1265
1335
|
tool_calls: unhandledToolCalls, // Only include unhandled tool calls
|
|
1266
|
-
}
|
|
1336
|
+
};
|
|
1337
|
+
// Include thinking from this turn if available (Extended Thinking pattern)
|
|
1338
|
+
if (currentTurnThinking) {
|
|
1339
|
+
assistantHistoryMsg.thinking = currentTurnThinking;
|
|
1340
|
+
}
|
|
1341
|
+
this.conversationHistory.push(assistantHistoryMsg);
|
|
1267
1342
|
}
|
|
1268
1343
|
// Add tool results to conversation history as tool messages
|
|
1269
1344
|
// Format: { tool_call_id, name, result: <object or string> }
|
|
@@ -1278,8 +1353,10 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1278
1353
|
});
|
|
1279
1354
|
}
|
|
1280
1355
|
// Rebuild messages array with updated history
|
|
1281
|
-
//
|
|
1356
|
+
// During agent loop: keep ALL thinking for current task
|
|
1357
|
+
// (Thinking from previous tasks was already stripped at request start)
|
|
1282
1358
|
messages = [...this.conversationHistory];
|
|
1359
|
+
// No need to reset currentTurnThinking - keep accumulating for the task
|
|
1283
1360
|
// Re-inject subshell context
|
|
1284
1361
|
messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
|
|
1285
1362
|
continue; // Loop back to AI service
|
|
@@ -1469,8 +1546,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1469
1546
|
role: 'assistant',
|
|
1470
1547
|
content: finalMessage,
|
|
1471
1548
|
});
|
|
1472
|
-
//
|
|
1473
|
-
await this.saveMessageToBackend('assistant', finalMessage);
|
|
1549
|
+
// Messages are stored locally only via saveCurrentChat() below
|
|
1474
1550
|
} // End of while loop
|
|
1475
1551
|
// Auto-save conversation to local storage
|
|
1476
1552
|
this.saveCurrentChat();
|
|
@@ -1511,11 +1587,13 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1511
1587
|
`/init - Analyze project and create/load centaurus.md context file\n` +
|
|
1512
1588
|
`/chat - Manage chat sessions (resume previous chats)\n` +
|
|
1513
1589
|
`/clear - Clear conversation and start a new chat\n` +
|
|
1590
|
+
`/sync - Sync data to/from cloud (upload/restore)\n` +
|
|
1514
1591
|
`/config - View current configuration\n` +
|
|
1515
1592
|
`/model - Select from available Google models\n` +
|
|
1516
1593
|
`/plan - Toggle plan mode for complex implementations\n` +
|
|
1517
1594
|
`/mcp - Manage configured MCP servers and tools\n` +
|
|
1518
1595
|
`/docs - Open Centaurus documentation in browser\n` +
|
|
1596
|
+
`/copy-chat-context - Copy chat as readable text to clipboard\n` +
|
|
1519
1597
|
`/quality - Toggle enhanced quality features (thinking protocol, validation)\n` +
|
|
1520
1598
|
`/autonomous - Toggle autonomous mode (Silent Operator with task_complete)\n` +
|
|
1521
1599
|
`/sign-in - Sign in with Google (if not already signed in)\n` +
|
|
@@ -1938,7 +2016,7 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
1938
2016
|
// Show list of saved chats for resuming (interactive picker)
|
|
1939
2017
|
const chats = localChatStorage.listChats();
|
|
1940
2018
|
if (chats.length === 0) {
|
|
1941
|
-
responseMessage = '
|
|
2019
|
+
responseMessage = 'No saved chats found.\n\nStart a new conversation and it will be automatically saved!';
|
|
1942
2020
|
}
|
|
1943
2021
|
else {
|
|
1944
2022
|
// Format chat list for picker display
|
|
@@ -1948,13 +2026,13 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
1948
2026
|
}
|
|
1949
2027
|
else {
|
|
1950
2028
|
// Fallback: show as text if no picker callback
|
|
1951
|
-
responseMessage = '
|
|
2029
|
+
responseMessage = 'Saved Chats:\n\n' +
|
|
1952
2030
|
chats.slice(0, 10).map((chat, i) => {
|
|
1953
2031
|
const date = new Date(chat.updatedAt).toLocaleDateString();
|
|
1954
2032
|
const time = new Date(chat.updatedAt).toLocaleTimeString();
|
|
1955
|
-
return `${i + 1}. ${chat.title}
|
|
1956
|
-
}).join('
|
|
1957
|
-
(chats.length > 10 ?
|
|
2033
|
+
return `${i + 1}. ${chat.title}\n ${date} ${time} | ${chat.messageCount} messages`;
|
|
2034
|
+
}).join('\n\n') +
|
|
2035
|
+
(chats.length > 10 ? `\n\n...and ${chats.length - 10} more chats` : '');
|
|
1958
2036
|
}
|
|
1959
2037
|
}
|
|
1960
2038
|
}
|
|
@@ -1962,7 +2040,7 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
1962
2040
|
// Show read-only list of saved chats (no selection)
|
|
1963
2041
|
const chats = localChatStorage.listChats();
|
|
1964
2042
|
if (chats.length === 0) {
|
|
1965
|
-
responseMessage = '
|
|
2043
|
+
responseMessage = 'No saved chats found.\n\nStart a new conversation and it will be automatically saved!';
|
|
1966
2044
|
}
|
|
1967
2045
|
else {
|
|
1968
2046
|
// Format chat list for read-only display
|
|
@@ -1987,7 +2065,7 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
1987
2065
|
// Show list of saved chats for deletion
|
|
1988
2066
|
const chats = localChatStorage.listChats();
|
|
1989
2067
|
if (chats.length === 0) {
|
|
1990
|
-
responseMessage = '
|
|
2068
|
+
responseMessage = 'No saved chats to delete.';
|
|
1991
2069
|
}
|
|
1992
2070
|
else {
|
|
1993
2071
|
// Format chat list for delete picker display
|
|
@@ -2007,13 +2085,13 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2007
2085
|
if (this.onRestoreMessagesCallback) {
|
|
2008
2086
|
this.onRestoreMessagesCallback([]);
|
|
2009
2087
|
}
|
|
2010
|
-
responseMessage = '
|
|
2088
|
+
responseMessage = '✅ Started a new chat session!\n\nYour previous conversation has been saved and can be resumed with /chat resume.';
|
|
2011
2089
|
}
|
|
2012
2090
|
else if (chatSubCommand === 'rename') {
|
|
2013
2091
|
// Show list of saved chats for renaming
|
|
2014
2092
|
const chats = localChatStorage.listChats();
|
|
2015
2093
|
if (chats.length === 0) {
|
|
2016
|
-
responseMessage = '
|
|
2094
|
+
responseMessage = 'No saved chats to rename.';
|
|
2017
2095
|
}
|
|
2018
2096
|
else {
|
|
2019
2097
|
// Format chat list for rename picker display
|
|
@@ -2030,6 +2108,30 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2030
2108
|
responseMessage = `Unknown /chat subcommand: ${chatSubCommand}\\n\\nUsage:\\n /chat resume - Resume a previous chat session\\n /chat list - List all saved chats\\n /chat delete - Delete a saved chat\\n /chat new - Start a new chat session\\n /chat rename - Rename a saved chat`;
|
|
2031
2109
|
}
|
|
2032
2110
|
break;
|
|
2111
|
+
case 'copy-chat-context':
|
|
2112
|
+
try {
|
|
2113
|
+
const { formatChatForClipboard } = await import('./utils/chat-formatter.js');
|
|
2114
|
+
const { copyTextToClipboard } = await import('./utils/text-clipboard.js');
|
|
2115
|
+
if (this.uiMessageHistory.length === 0) {
|
|
2116
|
+
responseMessage = 'No messages in current chat to copy.';
|
|
2117
|
+
}
|
|
2118
|
+
else {
|
|
2119
|
+
const formattedChat = formatChatForClipboard(this.uiMessageHistory);
|
|
2120
|
+
const success = await copyTextToClipboard(formattedChat);
|
|
2121
|
+
if (success) {
|
|
2122
|
+
responseMessage = '✅ Chat content copied to clipboard!';
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
responseMessage = '❌ Failed to copy to clipboard.\n\n' +
|
|
2126
|
+
'This might happen if clipboard access is not available in your environment.\n' +
|
|
2127
|
+
'Try running the CLI in a terminal with clipboard access.';
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
catch (error) {
|
|
2132
|
+
responseMessage = `❌ Error copying chat content: ${error.message}`;
|
|
2133
|
+
}
|
|
2134
|
+
break;
|
|
2033
2135
|
case 'exit':
|
|
2034
2136
|
process.exit(0);
|
|
2035
2137
|
break;
|
|
@@ -2147,6 +2249,138 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2147
2249
|
' /background-task cancel - Cancel a running background task';
|
|
2148
2250
|
}
|
|
2149
2251
|
break;
|
|
2252
|
+
case 'sync':
|
|
2253
|
+
// Sync local data to/from cloud
|
|
2254
|
+
if (!apiClient.isAuthenticated()) {
|
|
2255
|
+
responseMessage = '❌ You must be signed in to sync data.\n\nUse /sign-in to authenticate first.';
|
|
2256
|
+
break;
|
|
2257
|
+
}
|
|
2258
|
+
// Parse subcommand
|
|
2259
|
+
const syncSubcommand = args[0]?.toLowerCase();
|
|
2260
|
+
if (!syncSubcommand) {
|
|
2261
|
+
// Show sync help
|
|
2262
|
+
responseMessage = '☁️ Sync Commands:\n\n' +
|
|
2263
|
+
' /sync upload - Upload local data to cloud (overwrites cloud data)\n' +
|
|
2264
|
+
' /sync restore - Download cloud data and restore locally (overwrites local data)\n\n' +
|
|
2265
|
+
'Use these commands to backup or restore your chat history and settings.';
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
if (syncSubcommand === 'upload') {
|
|
2269
|
+
try {
|
|
2270
|
+
responseMessage = '☁️ Uploading data to cloud...';
|
|
2271
|
+
// Send initial response
|
|
2272
|
+
if (this.onDirectMessageCallback) {
|
|
2273
|
+
this.onDirectMessageCallback(responseMessage);
|
|
2274
|
+
}
|
|
2275
|
+
// Gather all local data
|
|
2276
|
+
const config = this.configManager.load();
|
|
2277
|
+
const chats = localChatStorage.listChats();
|
|
2278
|
+
// Load full chat data for each chat
|
|
2279
|
+
const fullChats = chats.map(chatMeta => {
|
|
2280
|
+
const fullChat = localChatStorage.loadChat(chatMeta.id);
|
|
2281
|
+
return fullChat;
|
|
2282
|
+
}).filter(chat => chat !== null);
|
|
2283
|
+
// Create sync data structure
|
|
2284
|
+
const syncData = {
|
|
2285
|
+
version: 1,
|
|
2286
|
+
exportedAt: new Date().toISOString(),
|
|
2287
|
+
config: {
|
|
2288
|
+
model: config.model,
|
|
2289
|
+
modelName: config.modelName,
|
|
2290
|
+
enhancedQuality: config.enhancedQuality,
|
|
2291
|
+
autonomousMode: config.autonomousMode,
|
|
2292
|
+
},
|
|
2293
|
+
chats: fullChats,
|
|
2294
|
+
metadata: {
|
|
2295
|
+
totalChats: fullChats.length,
|
|
2296
|
+
totalMessages: fullChats.reduce((sum, chat) => sum + (chat?.messageCount || 0), 0),
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
// Upload to backend
|
|
2300
|
+
const result = await apiClient.uploadSyncData(syncData);
|
|
2301
|
+
responseMessage = `✅ Data uploaded successfully!\n\n` +
|
|
2302
|
+
`📊 Upload Summary:\n` +
|
|
2303
|
+
` • Chats: ${fullChats.length}\n` +
|
|
2304
|
+
` • Messages: ${syncData.metadata.totalMessages}\n` +
|
|
2305
|
+
` • Version: ${result.version}\n` +
|
|
2306
|
+
` • Updated: ${new Date(result.updatedAt).toLocaleString()}\n\n` +
|
|
2307
|
+
`Your local data is now backed up to the cloud.`;
|
|
2308
|
+
}
|
|
2309
|
+
catch (error) {
|
|
2310
|
+
responseMessage = `❌ Upload failed: ${error.message}\n\nPlease try again later.`;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
else if (syncSubcommand === 'restore') {
|
|
2314
|
+
try {
|
|
2315
|
+
responseMessage = '☁️ Downloading data from cloud...';
|
|
2316
|
+
// Send initial response
|
|
2317
|
+
if (this.onDirectMessageCallback) {
|
|
2318
|
+
this.onDirectMessageCallback(responseMessage);
|
|
2319
|
+
}
|
|
2320
|
+
// Get sync data from backend
|
|
2321
|
+
const cloudData = await apiClient.getSyncData();
|
|
2322
|
+
if (!cloudData) {
|
|
2323
|
+
responseMessage = '❌ No cloud data found.\n\nUse /sync upload first to backup your data.';
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
const syncData = cloudData.syncData;
|
|
2327
|
+
// Validate sync data structure
|
|
2328
|
+
if (!syncData || typeof syncData !== 'object') {
|
|
2329
|
+
responseMessage = '❌ Invalid cloud data format.\n\nPlease try uploading again with /sync upload.';
|
|
2330
|
+
break;
|
|
2331
|
+
}
|
|
2332
|
+
// Restore config if present
|
|
2333
|
+
if (syncData.config) {
|
|
2334
|
+
const currentConfig = this.configManager.load();
|
|
2335
|
+
if (syncData.config.model) {
|
|
2336
|
+
currentConfig.model = syncData.config.model;
|
|
2337
|
+
}
|
|
2338
|
+
if (syncData.config.modelName) {
|
|
2339
|
+
currentConfig.modelName = syncData.config.modelName;
|
|
2340
|
+
}
|
|
2341
|
+
if (typeof syncData.config.enhancedQuality === 'boolean') {
|
|
2342
|
+
currentConfig.enhancedQuality = syncData.config.enhancedQuality;
|
|
2343
|
+
}
|
|
2344
|
+
if (typeof syncData.config.autonomousMode === 'boolean') {
|
|
2345
|
+
currentConfig.autonomousMode = syncData.config.autonomousMode;
|
|
2346
|
+
}
|
|
2347
|
+
this.configManager.save(currentConfig);
|
|
2348
|
+
}
|
|
2349
|
+
// Restore chats if present
|
|
2350
|
+
let restoredChats = 0;
|
|
2351
|
+
let restoredMessages = 0;
|
|
2352
|
+
if (syncData.chats && Array.isArray(syncData.chats)) {
|
|
2353
|
+
for (const chat of syncData.chats) {
|
|
2354
|
+
if (chat && chat.id) {
|
|
2355
|
+
// Import each chat to local storage
|
|
2356
|
+
const success = localChatStorage.importChat(chat);
|
|
2357
|
+
if (success) {
|
|
2358
|
+
restoredChats++;
|
|
2359
|
+
restoredMessages += chat.messageCount || chat.messages?.length || 0;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
responseMessage = `✅ Data restored successfully!\n\n` +
|
|
2365
|
+
`📊 Restore Summary:\n` +
|
|
2366
|
+
` • Chats restored: ${restoredChats}\n` +
|
|
2367
|
+
` • Messages restored: ${restoredMessages}\n` +
|
|
2368
|
+
` • Cloud version: ${cloudData.version}\n` +
|
|
2369
|
+
` • Last updated: ${new Date(cloudData.updatedAt).toLocaleString()}\n\n` +
|
|
2370
|
+
`Your local data has been restored from the cloud.\n` +
|
|
2371
|
+
`Note: Existing local chats with the same ID were overwritten.`;
|
|
2372
|
+
}
|
|
2373
|
+
catch (error) {
|
|
2374
|
+
responseMessage = `❌ Restore failed: ${error.message}\n\nPlease try again later.`;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
else {
|
|
2378
|
+
responseMessage = `❌ Unknown sync subcommand: ${syncSubcommand}\n\n` +
|
|
2379
|
+
'Available subcommands:\n' +
|
|
2380
|
+
' /sync upload - Upload local data to cloud\n' +
|
|
2381
|
+
' /sync restore - Download and restore cloud data locally';
|
|
2382
|
+
}
|
|
2383
|
+
break;
|
|
2150
2384
|
default:
|
|
2151
2385
|
responseMessage = `Unknown command: /${cmd}\nType /help for available commands.`;
|
|
2152
2386
|
}
|