centaurus-cli 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/cli-adapter.d.ts +15 -0
  2. package/dist/cli-adapter.d.ts.map +1 -1
  3. package/dist/cli-adapter.js +380 -122
  4. package/dist/cli-adapter.js.map +1 -1
  5. package/dist/config/mcp-config-manager.d.ts +53 -0
  6. package/dist/config/mcp-config-manager.d.ts.map +1 -0
  7. package/dist/config/mcp-config-manager.js +210 -0
  8. package/dist/config/mcp-config-manager.js.map +1 -0
  9. package/dist/config/slash-commands.d.ts +14 -0
  10. package/dist/config/slash-commands.d.ts.map +1 -0
  11. package/dist/config/slash-commands.js +65 -0
  12. package/dist/config/slash-commands.js.map +1 -0
  13. package/dist/index.js +17 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/mcp-command-handler.d.ts +16 -0
  16. package/dist/mcp/mcp-command-handler.d.ts.map +1 -0
  17. package/dist/mcp/mcp-command-handler.js +196 -0
  18. package/dist/mcp/mcp-command-handler.js.map +1 -0
  19. package/dist/mcp/mcp-server-manager.d.ts +30 -0
  20. package/dist/mcp/mcp-server-manager.d.ts.map +1 -0
  21. package/dist/mcp/mcp-server-manager.js +165 -0
  22. package/dist/mcp/mcp-server-manager.js.map +1 -0
  23. package/dist/mcp/mcp-tool-wrapper.d.ts +12 -0
  24. package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -0
  25. package/dist/mcp/mcp-tool-wrapper.js +57 -0
  26. package/dist/mcp/mcp-tool-wrapper.js.map +1 -0
  27. package/dist/services/ai-service-client.d.ts.map +1 -1
  28. package/dist/services/ai-service-client.js +1 -0
  29. package/dist/services/ai-service-client.js.map +1 -1
  30. package/dist/services/environment-context-injector.d.ts +2 -2
  31. package/dist/services/environment-context-injector.d.ts.map +1 -1
  32. package/dist/services/environment-context-injector.js +4 -4
  33. package/dist/services/environment-context-injector.js.map +1 -1
  34. package/dist/tools/command.d.ts +1 -2
  35. package/dist/tools/command.d.ts.map +1 -1
  36. package/dist/tools/command.js +39 -131
  37. package/dist/tools/command.js.map +1 -1
  38. package/dist/tools/file-ops.d.ts +3 -3
  39. package/dist/tools/file-ops.d.ts.map +1 -1
  40. package/dist/tools/file-ops.js +125 -99
  41. package/dist/tools/file-ops.js.map +1 -1
  42. package/dist/tools/get-diff.d.ts +5 -0
  43. package/dist/tools/get-diff.d.ts.map +1 -1
  44. package/dist/tools/get-diff.js +67 -5
  45. package/dist/tools/get-diff.js.map +1 -1
  46. package/dist/tools/grep-search.d.ts +13 -21
  47. package/dist/tools/grep-search.d.ts.map +1 -1
  48. package/dist/tools/grep-search.js +309 -280
  49. package/dist/tools/grep-search.js.map +1 -1
  50. package/dist/tools/inspect-symbol.d.ts +5 -0
  51. package/dist/tools/inspect-symbol.d.ts.map +1 -1
  52. package/dist/tools/inspect-symbol.js +102 -20
  53. package/dist/tools/inspect-symbol.js.map +1 -1
  54. package/dist/tools/types.d.ts +2 -1
  55. package/dist/tools/types.d.ts.map +1 -1
  56. package/dist/tools/validation.d.ts +8 -10
  57. package/dist/tools/validation.d.ts.map +1 -1
  58. package/dist/tools/validation.js +35 -37
  59. package/dist/tools/validation.js.map +1 -1
  60. package/dist/ui/components/App.d.ts +4 -0
  61. package/dist/ui/components/App.d.ts.map +1 -1
  62. package/dist/ui/components/App.js +182 -70
  63. package/dist/ui/components/App.js.map +1 -1
  64. package/dist/ui/components/AuthWelcomeScreen.d.ts.map +1 -1
  65. package/dist/ui/components/AuthWelcomeScreen.js +1 -9
  66. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  67. package/dist/ui/components/CodeBlock.d.ts.map +1 -1
  68. package/dist/ui/components/CodeBlock.js +24 -13
  69. package/dist/ui/components/CodeBlock.js.map +1 -1
  70. package/dist/ui/components/DiffViewer.d.ts +1 -0
  71. package/dist/ui/components/DiffViewer.d.ts.map +1 -1
  72. package/dist/ui/components/DiffViewer.js +107 -70
  73. package/dist/ui/components/DiffViewer.js.map +1 -1
  74. package/dist/ui/components/FileCreationPreview.d.ts +8 -0
  75. package/dist/ui/components/FileCreationPreview.d.ts.map +1 -0
  76. package/dist/ui/components/FileCreationPreview.js +63 -0
  77. package/dist/ui/components/FileCreationPreview.js.map +1 -0
  78. package/dist/ui/components/InputBox.d.ts.map +1 -1
  79. package/dist/ui/components/InputBox.js +134 -10
  80. package/dist/ui/components/InputBox.js.map +1 -1
  81. package/dist/ui/components/InteractiveShell.d.ts +1 -0
  82. package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
  83. package/dist/ui/components/InteractiveShell.js +30 -8
  84. package/dist/ui/components/InteractiveShell.js.map +1 -1
  85. package/dist/ui/components/MarkdownRenderer.d.ts.map +1 -1
  86. package/dist/ui/components/MarkdownRenderer.js +8 -30
  87. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  88. package/dist/ui/components/SlashCommandAutocomplete.d.ts +11 -0
  89. package/dist/ui/components/SlashCommandAutocomplete.d.ts.map +1 -0
  90. package/dist/ui/components/SlashCommandAutocomplete.js +15 -0
  91. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -0
  92. package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
  93. package/dist/ui/components/ToolExecutionMessage.js +212 -53
  94. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  95. package/dist/ui/components/ToolExecutionStatus.d.ts.map +1 -1
  96. package/dist/ui/components/ToolExecutionStatus.js +28 -1
  97. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  98. package/dist/utils/input-classifier.d.ts.map +1 -1
  99. package/dist/utils/input-classifier.js +3 -1
  100. package/dist/utils/input-classifier.js.map +1 -1
  101. package/dist/utils/shell.d.ts +2 -0
  102. package/dist/utils/shell.d.ts.map +1 -1
  103. package/dist/utils/shell.js +71 -0
  104. package/dist/utils/shell.js.map +1 -1
  105. package/package.json +2 -1
  106. package/prompts/system-prompt-autonomous.md +22 -73
@@ -7,8 +7,13 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
  import { ConfigManager } from './config/manager.js';
9
9
  import { ToolRegistry } from './tools/registry.js';
10
- import { readFileTool, writeFileTool, editFileTool, listDirectoryTool } from './tools/file-ops.js';
11
- import { executeCommandTool, grepSearchTool } from './tools/command.js';
10
+ import { viewFileTool, writeToFileTool, editFileTool, listDirTool } from './tools/file-ops.js';
11
+ import { runCommandTool } from './tools/command.js';
12
+ import { grepSearchTool } from './tools/grep-search.js';
13
+ import { findFilesTool } from './tools/find-files.js';
14
+ import { getDiffTool } from './tools/get-diff.js';
15
+ import { inspectSymbolTool } from './tools/inspect-symbol.js';
16
+ import { exitPlanModeTool, getPlanStatusTool, updatePlanStepTool } from './tools/plan-mode.js';
12
17
  import { webSearchTool, fetchUrlTool } from './tools/web-search.js';
13
18
  import { taskCompleteTool } from './tools/task-complete.js';
14
19
  import { apiClient } from './services/api-client.js';
@@ -23,6 +28,9 @@ import { WSLHandler } from './context/handlers/wsl-handler.js';
23
28
  import { DockerHandler } from './context/handlers/docker-handler.js';
24
29
  import { AIContextInjector } from './services/ai-context-injector.js';
25
30
  import { environmentContextInjector } from './services/environment-context-injector.js';
31
+ import { MCPConfigManager } from './config/mcp-config-manager.js';
32
+ import { MCPServerManager } from './mcp/mcp-server-manager.js';
33
+ import { MCPCommandHandler } from './mcp/mcp-command-handler.js';
26
34
  export class CentaurusCLI {
27
35
  configManager;
28
36
  toolRegistry;
@@ -32,6 +40,7 @@ export class CentaurusCLI {
32
40
  commandMode = false;
33
41
  previousMode = 'execution';
34
42
  onResponseCallback;
43
+ onDirectMessageCallback; // For slash commands - adds directly to history
35
44
  onResponseStreamCallback;
36
45
  onThoughtStreamCallback;
37
46
  onThoughtCompleteCallback;
@@ -52,6 +61,7 @@ export class CentaurusCLI {
52
61
  aiContextInjector;
53
62
  onSubshellContextChange;
54
63
  currentAbortController;
64
+ mcpCommandHandler;
55
65
  constructor() {
56
66
  this.configManager = new ConfigManager();
57
67
  this.toolRegistry = new ToolRegistry();
@@ -70,10 +80,15 @@ export class CentaurusCLI {
70
80
  this.onSubshellContextChange(context);
71
81
  }
72
82
  });
83
+ // Initialize MCP
84
+ this.initializeMCP();
73
85
  }
74
86
  setOnResponseCallback(callback) {
75
87
  this.onResponseCallback = callback;
76
88
  }
89
+ setOnDirectMessageCallback(callback) {
90
+ this.onDirectMessageCallback = callback;
91
+ }
77
92
  setOnResponseStreamCallback(callback) {
78
93
  this.onResponseStreamCallback = callback;
79
94
  }
@@ -120,6 +135,18 @@ export class CentaurusCLI {
120
135
  this.sshHandler.setPasswordRequestCallback(callback);
121
136
  }
122
137
  }
138
+ async initializeMCP() {
139
+ try {
140
+ const mcpConfigManager = new MCPConfigManager();
141
+ const mcpServerManager = new MCPServerManager();
142
+ this.mcpCommandHandler = new MCPCommandHandler(mcpConfigManager, mcpServerManager, this.toolRegistry);
143
+ // Initialize MCP servers and tools
144
+ await this.mcpCommandHandler.initializeMCP();
145
+ }
146
+ catch (error) {
147
+ console.error('Failed to initialize MCP:', error);
148
+ }
149
+ }
123
150
  writeToShellStdin(input) {
124
151
  if (this.currentInteractiveProcess) {
125
152
  try {
@@ -130,6 +157,27 @@ export class CentaurusCLI {
130
157
  }
131
158
  }
132
159
  }
160
+ sendSignalToShell(signal) {
161
+ if (this.currentInteractiveProcess) {
162
+ try {
163
+ this.currentInteractiveProcess.signal(signal);
164
+ }
165
+ catch (error) {
166
+ // Silently ignore - process might have already exited
167
+ }
168
+ }
169
+ }
170
+ killCurrentProcess() {
171
+ if (this.currentInteractiveProcess && this.currentInteractiveProcess.process.pid) {
172
+ try {
173
+ const { killProcessTree } = require('./utils/shell.js');
174
+ killProcessTree(this.currentInteractiveProcess.process.pid, 'SIGKILL');
175
+ }
176
+ catch (error) {
177
+ // Ignore
178
+ }
179
+ }
180
+ }
133
181
  getPlanMode() {
134
182
  return this.planMode;
135
183
  }
@@ -173,14 +221,50 @@ export class CentaurusCLI {
173
221
  }
174
222
  }
175
223
  sshHandler;
224
+ /**
225
+ * Notify UI about tool execution status
226
+ */
227
+ notifyToolStatus(toolName, status, args, result, error) {
228
+ if (this.onToolExecutionUpdate) {
229
+ // Add cwd to arguments for execute_command tool
230
+ const toolArgs = toolName === 'execute_command' && args
231
+ ? { ...args, cwd: this.cwd }
232
+ : args;
233
+ this.onToolExecutionUpdate({
234
+ toolName,
235
+ status,
236
+ result,
237
+ error,
238
+ arguments: toolArgs
239
+ });
240
+ }
241
+ }
242
+ /**
243
+ * Truncate large results for AI consumption
244
+ */
245
+ truncateResult(result, maxLength = 500000) {
246
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
247
+ if (resultStr.length > maxLength) {
248
+ const truncated = resultStr.substring(0, maxLength);
249
+ const linesRemoved = (resultStr.match(/\n/g) || []).length - (truncated.match(/\n/g) || []).length;
250
+ return truncated + `\n\n[... truncated ${resultStr.length - maxLength} characters, ~${linesRemoved} lines removed for brevity ...]`;
251
+ }
252
+ return result;
253
+ }
176
254
  async initialize() {
177
255
  // Register tools
178
- this.toolRegistry.register(readFileTool);
179
- this.toolRegistry.register(writeFileTool);
256
+ this.toolRegistry.register(viewFileTool);
257
+ this.toolRegistry.register(writeToFileTool);
180
258
  this.toolRegistry.register(editFileTool);
181
- this.toolRegistry.register(listDirectoryTool);
182
- this.toolRegistry.register(executeCommandTool);
259
+ this.toolRegistry.register(listDirTool);
260
+ this.toolRegistry.register(runCommandTool);
183
261
  this.toolRegistry.register(grepSearchTool);
262
+ this.toolRegistry.register(findFilesTool);
263
+ this.toolRegistry.register(getDiffTool);
264
+ this.toolRegistry.register(inspectSymbolTool);
265
+ this.toolRegistry.register(exitPlanModeTool);
266
+ this.toolRegistry.register(getPlanStatusTool);
267
+ this.toolRegistry.register(updatePlanStepTool);
184
268
  this.toolRegistry.register(webSearchTool);
185
269
  this.toolRegistry.register(fetchUrlTool);
186
270
  this.toolRegistry.register(taskCompleteTool);
@@ -370,7 +454,8 @@ Press Enter to continue...
370
454
  const currentContext = this.contextManager.getCurrentContext();
371
455
  messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
372
456
  // Build environment context to send to backend
373
- const environmentContext = this.getEnvironmentContext();
457
+ // Initial context - will be updated in loop
458
+ let environmentContext = this.getEnvironmentContext();
374
459
  const mode = this.getMode();
375
460
  let finalAssistantMessage = '';
376
461
  const MAX_TURNS = 500; // Allow up to 500 turns for complex tasks
@@ -378,6 +463,7 @@ Press Enter to continue...
378
463
  const MAX_NARRATION_ATTEMPTS = 3; // Maximum times we'll prompt AI to stop narrating
379
464
  let turnCount = 0;
380
465
  let narrationAttempts = 0; // Track how many times AI narrated without executing
466
+ let completionAttempts = 0; // Track how many times AI provided text summary without task_complete
381
467
  let thoughtStartTime = null; // Track when thinking started
382
468
  let thoughtContent = ''; // Accumulate thought content
383
469
  // Create AbortController for this request
@@ -385,6 +471,14 @@ Press Enter to continue...
385
471
  // Multi-turn tool execution loop
386
472
  while (turnCount < MAX_TURNS) {
387
473
  turnCount++;
474
+ // Refresh environment context to capture any CWD changes from previous turns
475
+ environmentContext = this.getEnvironmentContext();
476
+ // Refresh system prompt with new CWD
477
+ const refreshedSystemPrompt = environmentContextInjector.getEnhancedSystemPrompt(systemPrompt, this.cwd);
478
+ // Update the system message in the messages array
479
+ if (messages.length > 0 && messages[0].role === 'system') {
480
+ messages[0].content = refreshedSystemPrompt;
481
+ }
388
482
  let assistantMessage = '';
389
483
  let toolCalls = [];
390
484
  // Stream AI response from backend
@@ -469,8 +563,8 @@ Press Enter to continue...
469
563
  }
470
564
  break;
471
565
  }
472
- }
473
- // If there are tool calls, execute them and continue loop
566
+ } // End of stream loop
567
+ // If there are tool calls, execute them
474
568
  if (toolCalls.length > 0) {
475
569
  // CRITICAL: AI should ONLY communicate via reason_text and task_complete summary
476
570
  // Any text output alongside tool calls should be suppressed
@@ -483,16 +577,6 @@ Press Enter to continue...
483
577
  // Silently limit tool calls
484
578
  toolCalls = toolCalls.slice(0, MAX_TOOL_CALLS_PER_TURN);
485
579
  }
486
- // Helper function to truncate large results
487
- const truncateResult = (result, maxLength = 500000) => {
488
- const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
489
- if (resultStr.length > maxLength) {
490
- const truncated = resultStr.substring(0, maxLength);
491
- const linesRemoved = (resultStr.match(/\n/g) || []).length - (truncated.match(/\n/g) || []).length;
492
- return truncated + `\n\n[... truncated ${resultStr.length - maxLength} characters, ~${linesRemoved} lines removed for brevity ...]`;
493
- }
494
- return result;
495
- };
496
580
  const toolResults = [];
497
581
  let userCancelledOperation = false;
498
582
  let taskCompleted = false;
@@ -561,7 +645,7 @@ Press Enter to continue...
561
645
  }
562
646
  }
563
647
  // Truncate result before sending to AI (to avoid exceeding message size limits)
564
- const truncatedResult = truncateResult(parsedResult);
648
+ const truncatedResult = this.truncateResult(parsedResult);
565
649
  toolResults.push({
566
650
  tool_call_id: toolCall.id,
567
651
  name: toolCall.name,
@@ -691,107 +775,115 @@ Press Enter to continue...
691
775
  },
692
776
  ...this.conversationHistory
693
777
  ];
694
- // Re-inject subshell context for the next turn
778
+ // Re-inject subshell context
695
779
  messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
696
- // Add delay between turns to prevent rate limiting
697
- await new Promise(resolve => setTimeout(resolve, 500));
698
- // Continue to next turn to let AI process results
699
- continue;
780
+ continue; // Loop back to AI service
700
781
  }
701
- // No tool calls - AI must make tool calls or finish
702
- if (assistantMessage) {
703
- // CRITICAL: AI should NOT output text without tool calls
704
- // The ONLY valid text output is through task_complete summary
705
- // Suppress this text and prompt AI to use tools
706
- // Add assistant message to history (for AI context, but don't show to user)
707
- this.conversationHistory.push({
708
- role: 'assistant',
709
- content: assistantMessage,
710
- });
711
- // Check if this looks like it should have been a task_complete
712
- const looksLikeFinalAnswer = assistantMessage.length < 500 &&
713
- !assistantMessage.includes('?') &&
714
- (assistantMessage.includes('!') || assistantMessage.match(/\./g)?.length || 0 > 1);
715
- const isNarration = /\b(I will|I'll|Let me|Let's|I need to|I'm going to|I should|I can)\b/i.test(assistantMessage);
716
- if (isNarration) {
717
- narrationAttempts++;
718
- // If AI keeps narrating without executing, force completion immediately
719
- if (narrationAttempts >= MAX_NARRATION_ATTEMPTS) {
720
- // Force task completion with error message
721
- finalAssistantMessage = '⚠️ **Task Incomplete**: The AI repeatedly described actions without executing them.\n\n' +
722
- '**What happened**: The AI entered a narration loop, describing what it wanted to do instead of using tool calls.\n\n' +
723
- '**Suggestions**:\n' +
724
- '1. Try rephrasing your request more specifically\n' +
725
- '2. Break the task into smaller, concrete steps\n' +
726
- '3. Provide explicit file paths if known\n' +
727
- '4. Check if the model supports tool calling properly\n\n' +
728
- '**Last message**: ' + assistantMessage;
729
- break;
730
- }
731
- // First narration attempt - give a strong warning with specific guidance
732
- if (narrationAttempts === 1) {
733
- const completionPrompt = '🛑 **CRITICAL ERROR**: You output text without using tools.\n\n' +
734
- '**COMMUNICATION RULE VIOLATION**: You can ONLY communicate through:\n' +
735
- '1. `reason_text` parameter in tool calls\n' +
736
- '2. `summary` parameter in task_complete tool\n\n' +
737
- '**Your text output was HIDDEN from the user.**\n\n' +
738
- '**MANDATORY CORRECTION**:\n' +
739
- '- If you need to DO something: Call the tool with `reason_text`\n' +
740
- '- If you are DONE: Call `task_complete(summary="your message")`\n' +
741
- '- NEVER output plain text - it will be hidden\n\n' +
742
- '**Example for greeting**:\n' +
743
- '```\n' +
744
- '<thought>User said hello, I should greet back</thought>\n' +
745
- '(Call task_complete with summary="Hello! How can I help you today?")\n' +
746
- '```\n\n' +
747
- '**Your NEXT response MUST use tools.**';
748
- this.conversationHistory.push({
749
- role: 'user',
750
- content: completionPrompt,
751
- });
752
- }
753
- else {
754
- // Second narration attempt - final warning before forced completion
755
- const completionPrompt = '🚨 **FINAL WARNING** (Attempt ' + narrationAttempts + '/' + MAX_NARRATION_ATTEMPTS + '): You are STILL narrating instead of executing.\n\n' +
756
- '**This is your LAST chance**:\n' +
757
- '1. Execute a tool call NOW, or\n' +
758
- '2. Call task_complete() to end\n\n' +
759
- 'If you output narration text again, the task will be forcibly terminated.';
760
- this.conversationHistory.push({
761
- role: 'user',
762
- content: completionPrompt,
763
- });
764
- }
782
+ else {
783
+ // No tool calls - check for silent stop or text-only response
784
+ // Case 1: Silent Stop (No tool calls AND no text)
785
+ if (!assistantMessage || !assistantMessage.trim()) {
786
+ // No tool calls and no message - AI stopped silently
787
+ // This usually means the AI thinks it's done but didn't call task_complete
788
+ // Prompt it to either continue or complete
789
+ const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
790
+ '**This is not allowed.** You must either:\n' +
791
+ '1. Execute a tool call if more work is needed, OR\n' +
792
+ '2. Call task_complete() with a summary of what you accomplished\n\n' +
793
+ '**If you have completed the task**, call task_complete() NOW with a comprehensive summary.\n' +
794
+ '**If more work is needed**, execute the next tool call immediately.';
795
+ this.conversationHistory.push({
796
+ role: 'user',
797
+ content: silentStopPrompt,
798
+ });
765
799
  }
800
+ // Case 2: Text-only response (Narration or Summary)
766
801
  else {
767
- // AI output a response without narration - it should finish
768
- // Reset narration counter since this is a valid response
769
- narrationAttempts = 0;
770
- // Check if the message looks like a final answer/summary
771
- const isFinalAnswer = assistantMessage.length > 50 &&
772
- (assistantMessage.includes('found') ||
773
- assistantMessage.includes('identified') ||
774
- assistantMessage.includes('completed') ||
775
- assistantMessage.includes('summary') ||
776
- assistantMessage.includes('result'));
777
- if (isFinalAnswer) {
778
- // This looks like a final answer - prompt to call task_complete
779
- const completionPrompt = ' Your response looks complete. Call task_complete() now to finalize the conversation.\n\n' +
780
- 'If there are more steps needed, execute the next tool immediately.';
781
- this.conversationHistory.push({
782
- role: 'user',
783
- content: completionPrompt,
784
- });
802
+ const isNarration = /\b(I will|I'll|Let me|Let's|I need to|I'm going to|I should|I can)\b/i.test(assistantMessage);
803
+ if (isNarration) {
804
+ narrationAttempts++;
805
+ // If AI keeps narrating without executing, force completion immediately
806
+ if (narrationAttempts >= MAX_NARRATION_ATTEMPTS) {
807
+ // Force task completion with error message
808
+ finalAssistantMessage = '⚠️ **Task Incomplete**: The AI repeatedly described actions without executing them.\n\n' +
809
+ '**What happened**: The AI entered a narration loop, describing what it wanted to do instead of using tool calls.\n\n' +
810
+ '**Suggestions**:\n' +
811
+ '1. Try rephrasing your request more specifically\n' +
812
+ '2. Break the task into smaller, concrete steps\n' +
813
+ '3. Provide explicit file paths if known\n' +
814
+ '4. Check if the model supports tool calling properly\n\n' +
815
+ '**Last message**: ' + assistantMessage;
816
+ break;
817
+ }
818
+ // First narration attempt - give a strong warning with specific guidance
819
+ if (narrationAttempts === 1) {
820
+ const completionPrompt = '🛑 **CRITICAL ERROR**: You output text without using tools.\n\n' +
821
+ '**COMMUNICATION RULE VIOLATION**: You can ONLY communicate through:\n' +
822
+ '1. `reason_text` parameter in tool calls\n' +
823
+ '2. `summary` parameter in task_complete tool\n\n' +
824
+ '**Your text output was HIDDEN from the user.**\n\n' +
825
+ '**MANDATORY CORRECTION**:\n' +
826
+ '- If you need to DO something: Call the tool with `reason_text`\n' +
827
+ '- If you are DONE: Call `task_complete(summary="your message")`\n' +
828
+ '- NEVER output plain text - it will be hidden\n\n' +
829
+ '**Example for greeting**:\n' +
830
+ '```\n' +
831
+ '<thought>User said hello, I should greet back</thought>\n' +
832
+ '(Call task_complete with summary="Hello! How can I help you today?")\n' +
833
+ '```\n\n' +
834
+ '**Your NEXT response MUST use tools.**';
835
+ this.conversationHistory.push({
836
+ role: 'user',
837
+ content: completionPrompt,
838
+ });
839
+ }
840
+ else {
841
+ // Second narration attempt - final warning before forced completion
842
+ const completionPrompt = '🚨 **FINAL WARNING** (Attempt ' + narrationAttempts + '/' + MAX_NARRATION_ATTEMPTS + '): You are STILL narrating instead of executing.\n\n' +
843
+ '**This is your LAST chance**:\n' +
844
+ '1. Execute a tool call NOW, or\n' +
845
+ '2. Call task_complete() to end\n\n' +
846
+ 'If you output narration text again, the task will be forcibly terminated.';
847
+ this.conversationHistory.push({
848
+ role: 'user',
849
+ content: completionPrompt,
850
+ });
851
+ }
785
852
  }
786
853
  else {
787
- // Short message without clear intent - ask for clarification or completion
788
- const completionPrompt = 'Your response is unclear. Either:\n' +
789
- '1. Execute the next tool call if more work is needed, or\n' +
790
- '2. Call task_complete() if the task is done';
791
- this.conversationHistory.push({
792
- role: 'user',
793
- content: completionPrompt,
794
- });
854
+ // AI output a response without narration - it should finish
855
+ // Reset narration counter since this is a valid response
856
+ narrationAttempts = 0;
857
+ // Check if the message looks like a final answer/summary
858
+ // If it has substantial length, assume it's a summary attempt
859
+ const isFinalAnswer = assistantMessage.length > 20;
860
+ if (isFinalAnswer) {
861
+ completionAttempts++;
862
+ // If AI keeps providing text summaries without calling task_complete, accept the text and finish
863
+ // This prevents the infinite loop where the AI keeps summarizing in response to our prompt
864
+ if (completionAttempts > 1) {
865
+ finalAssistantMessage = assistantMessage;
866
+ break;
867
+ }
868
+ // This looks like a final answer - prompt to call task_complete
869
+ const completionPrompt = '✅ **Possible Completion Detected**: You provided a text response but did not call `task_complete`.\n\n' +
870
+ '**To finish the conversation, you MUST call the `task_complete` tool.**\n\n' +
871
+ 'Please call `task_complete` now with your summary as the argument.';
872
+ this.conversationHistory.push({
873
+ role: 'user',
874
+ content: completionPrompt,
875
+ });
876
+ }
877
+ else {
878
+ // Short message without clear intent - ask for clarification or completion
879
+ const completionPrompt = 'Your response is unclear. Either:\n' +
880
+ '1. Execute the next tool call if more work is needed, or\n' +
881
+ '2. Call task_complete() if the task is done';
882
+ this.conversationHistory.push({
883
+ role: 'user',
884
+ content: completionPrompt,
885
+ });
886
+ }
795
887
  }
796
888
  }
797
889
  // Rebuild messages array
@@ -850,10 +942,10 @@ Press Enter to continue...
850
942
  });
851
943
  // Save assistant message to backend
852
944
  await this.saveMessageToBackend('assistant', finalMessage);
853
- }
945
+ } // End of while loop
854
946
  // Parse response for plan mode
855
947
  if (this.planMode) {
856
- const planData = this.parsePlanResponse(finalMessage);
948
+ const planData = this.parsePlanResponse(finalAssistantMessage);
857
949
  if (planData && this.onPlanApprovalRequest) {
858
950
  // Ask user for approval
859
951
  const approved = await this.onPlanApprovalRequest(planData);
@@ -870,8 +962,8 @@ Press Enter to continue...
870
962
  }
871
963
  }
872
964
  // Send response back to UI (only if there's a message)
873
- if (this.onResponseCallback && finalMessage) {
874
- this.onResponseCallback(finalMessage);
965
+ if (this.onResponseCallback && finalAssistantMessage) {
966
+ this.onResponseCallback(finalAssistantMessage);
875
967
  }
876
968
  }
877
969
  catch (error) {
@@ -898,6 +990,7 @@ Press Enter to continue...
898
990
  case 'help':
899
991
  responseMessage = `Available Commands:\n\n` +
900
992
  `/help - Show this help message\n` +
993
+ `/init - Analyze project and create/load centaurus.md context file\n` +
901
994
  `/clear - Clear conversation history\n` +
902
995
  `/config - View current configuration\n` +
903
996
  `/model - Select from available Google models\n` +
@@ -913,6 +1006,163 @@ Press Enter to continue...
913
1006
  `Ctrl+T - Toggle auto-accept mode\n` +
914
1007
  `? - Show keyboard shortcuts help`;
915
1008
  break;
1009
+ case 'init':
1010
+ try {
1011
+ // Define the context file names in priority order
1012
+ const contextFiles = ['centaurus.md', 'gemini.md', 'claude.md'];
1013
+ let foundFile = null;
1014
+ let foundFilePath = null;
1015
+ // Check for existing context files in priority order
1016
+ for (const fileName of contextFiles) {
1017
+ const filePath = path.join(this.cwd, fileName);
1018
+ if (fs.existsSync(filePath)) {
1019
+ foundFile = fileName;
1020
+ foundFilePath = filePath;
1021
+ break;
1022
+ }
1023
+ }
1024
+ if (foundFile) {
1025
+ // Context file exists - read and load it
1026
+ try {
1027
+ const content = fs.readFileSync(foundFilePath, 'utf-8');
1028
+ // Check if content is empty
1029
+ if (!content.trim()) {
1030
+ responseMessage = `⚠️ Found ${foundFile} but it's empty.\n\n` +
1031
+ `Please either:\n` +
1032
+ `1. Delete the file and run /init again to generate new documentation\n` +
1033
+ `2. Add content to the file manually`;
1034
+ break;
1035
+ }
1036
+ // If the found file is not centaurus.md, create a copy
1037
+ if (foundFile !== 'centaurus.md') {
1038
+ const centaurusPath = path.join(this.cwd, 'centaurus.md');
1039
+ // Check if centaurus.md already exists
1040
+ if (fs.existsSync(centaurusPath)) {
1041
+ responseMessage = `✅ Found both ${foundFile} and centaurus.md. Loaded project context from centaurus.md`;
1042
+ }
1043
+ else {
1044
+ // Create a copy as centaurus.md
1045
+ fs.copyFileSync(foundFilePath, centaurusPath);
1046
+ responseMessage = `✅ Found ${foundFile} and created a copy as centaurus.md. Loaded project context.`;
1047
+ }
1048
+ }
1049
+ else {
1050
+ // Already centaurus.md
1051
+ responseMessage = `✅ Loaded project context from centaurus.md`;
1052
+ }
1053
+ // Add the context to conversation history so the AI actually has it
1054
+ this.conversationHistory.push({
1055
+ role: 'user',
1056
+ content: `Here is the project context from ${foundFile}:\n\n${content}`
1057
+ });
1058
+ }
1059
+ catch (readError) {
1060
+ responseMessage = `❌ Error reading ${foundFile}: ${readError.message}\n\n` +
1061
+ `Please check file permissions and try again.`;
1062
+ }
1063
+ }
1064
+ else {
1065
+ // No context file exists - trigger AI analysis
1066
+ responseMessage = `🔍 I will now analyze the contents of this directory and then create a centaurus.md`;
1067
+ // Send initial response
1068
+ if (this.onResponseCallback) {
1069
+ this.onResponseCallback(responseMessage);
1070
+ }
1071
+ // Create a specialized prompt for project analysis
1072
+ const initPrompt = `You are tasked with analyzing the current project directory and creating a comprehensive documentation file called "centaurus.md".
1073
+
1074
+ **Your Task:**
1075
+ 1. Use the list_directory tool to explore the project structure starting from the current directory
1076
+ 2. Use read_file to examine key files such as:
1077
+ - package.json, requirements.txt, or similar dependency files
1078
+ - README files
1079
+ - Configuration files (tsconfig.json, .eslintrc, etc.)
1080
+ - Main entry points (index.js, main.py, App.tsx, etc.)
1081
+ 3. Use grep_search to find important patterns and understand the codebase
1082
+ 4. Create a comprehensive centaurus.md file using the write_file tool
1083
+
1084
+ **The centaurus.md file should include:**
1085
+
1086
+ # [Project Name]
1087
+
1088
+ ## Overview
1089
+ Brief description of what this project does and its purpose
1090
+
1091
+ ## Technology Stack
1092
+ - **Languages**: List programming languages used
1093
+ - **Frameworks**: Main frameworks (React, Express, Django, etc.)
1094
+ - **Key Dependencies**: Important libraries and tools
1095
+ - **Build Tools**: Webpack, Vite, Maven, etc.
1096
+
1097
+ ## Directory Structure
1098
+ \`\`\`
1099
+ /
1100
+ /src - Main source code
1101
+ /tests - Test files
1102
+ /config - Configuration files
1103
+ ...
1104
+ \`\`\`
1105
+
1106
+ Brief explanation of each major directory's purpose
1107
+
1108
+ ## Key Components
1109
+ Describe the main modules, classes, or components:
1110
+ - **Component Name**: What it does, where it's located
1111
+ - **Component Name**: What it does, where it's located
1112
+
1113
+ ## Architecture
1114
+ Explain how the components interact:
1115
+ - Data flow
1116
+ - Communication patterns (REST APIs, GraphQL, event-driven, etc.)
1117
+ - State management
1118
+ - Database architecture (if applicable)
1119
+
1120
+ ## Development Workflow
1121
+ - How to install dependencies
1122
+ - How to run the development server
1123
+ - How to run tests
1124
+ - How to build for production
1125
+
1126
+ ## Important Conventions
1127
+ - Code organization patterns
1128
+ - Naming conventions
1129
+ - File structure patterns
1130
+ - Any special patterns or best practices used
1131
+
1132
+ ## Entry Points
1133
+ - Main files where execution starts
1134
+ - API endpoints (if applicable)
1135
+ - CLI commands (if applicable)
1136
+
1137
+ ## Quick Reference
1138
+ Useful commands, key files to know about, common tasks
1139
+
1140
+ **IMPORTANT INSTRUCTIONS:**
1141
+ - Be thorough but concise
1142
+ - Use clear, professional language
1143
+ - Include code examples where helpful
1144
+ - Create the file in the current working directory: ${this.cwd}
1145
+ - After creating the file, call task_complete with a summary
1146
+
1147
+ **Current working directory**: ${this.cwd}
1148
+
1149
+ Start by listing the directory structure to understand what you're working with.`;
1150
+ // Add to conversation history
1151
+ this.conversationHistory.push({
1152
+ role: 'user',
1153
+ content: initPrompt
1154
+ });
1155
+ // Trigger the AI to process this
1156
+ await this.handleMessage('');
1157
+ // Don't send another response - the AI will respond
1158
+ return;
1159
+ }
1160
+ }
1161
+ catch (error) {
1162
+ responseMessage = `❌ Error executing /init command: ${error.message}\n\n` +
1163
+ `Please try again or check file permissions.`;
1164
+ }
1165
+ break;
916
1166
  case 'sign-in':
917
1167
  // Check if already authenticated
918
1168
  if (apiClient.isAuthenticated()) {
@@ -1134,15 +1384,23 @@ Press Enter to continue...
1134
1384
  }
1135
1385
  }
1136
1386
  break;
1387
+ case 'mcp':
1388
+ if (this.mcpCommandHandler) {
1389
+ responseMessage = await this.mcpCommandHandler.handleCommand(args);
1390
+ }
1391
+ else {
1392
+ responseMessage = '❌ MCP is not initialized. Please restart the CLI.';
1393
+ }
1394
+ break;
1137
1395
  case 'exit':
1138
1396
  process.exit(0);
1139
1397
  break;
1140
1398
  default:
1141
1399
  responseMessage = `Unknown command: /${cmd}\nType /help for available commands.`;
1142
1400
  }
1143
- // Send command response back to UI
1144
- if (responseMessage && this.onResponseCallback) {
1145
- this.onResponseCallback(responseMessage);
1401
+ // Send command response directly to history (not streaming)
1402
+ if (responseMessage && this.onDirectMessageCallback) {
1403
+ this.onDirectMessageCallback(responseMessage);
1146
1404
  }
1147
1405
  }
1148
1406
  /**