centaurus-cli 2.6.2 → 2.7.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 +13 -22
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +383 -240
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/defaultConfig.d.ts +1 -0
- package/dist/config/defaultConfig.d.ts.map +1 -1
- package/dist/config/defaultConfig.js +3 -1
- package/dist/config/defaultConfig.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/services/ai-service-client.d.ts +1 -1
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +7 -2
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +6 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +16 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +77 -25
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +28 -4
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +25 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/task-complete.d.ts +3 -0
- package/dist/tools/task-complete.d.ts.map +1 -0
- package/dist/tools/task-complete.js +48 -0
- package/dist/tools/task-complete.js.map +1 -0
- package/dist/tools/types.d.ts +1 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +16 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/components/AgentTimer.d.ts +7 -0
- package/dist/ui/components/AgentTimer.d.ts.map +1 -0
- package/dist/ui/components/AgentTimer.js +27 -0
- package/dist/ui/components/AgentTimer.js.map +1 -0
- 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 +229 -46
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +586 -130
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +16 -0
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -0
- package/dist/ui/components/InteractiveShell.js +152 -0
- package/dist/ui/components/InteractiveShell.js.map +1 -0
- package/dist/ui/components/LoadingIndicator.js +1 -1
- package/dist/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/ui/components/MarkdownRenderer.js +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +43 -47
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/utils/ansi-encoder.d.ts +2 -0
- package/dist/utils/ansi-encoder.d.ts.map +1 -0
- package/dist/utils/ansi-encoder.js +57 -0
- package/dist/utils/ansi-encoder.js.map +1 -0
- package/dist/utils/command-history.d.ts +14 -0
- package/dist/utils/command-history.d.ts.map +1 -0
- package/dist/utils/command-history.js +140 -0
- package/dist/utils/command-history.js.map +1 -0
- package/dist/utils/input-classifier.d.ts +26 -0
- package/dist/utils/input-classifier.d.ts.map +1 -0
- package/dist/utils/input-classifier.js +154 -0
- package/dist/utils/input-classifier.js.map +1 -0
- package/dist/utils/markdown-parser.d.ts.map +1 -1
- package/dist/utils/markdown-parser.js +6 -5
- package/dist/utils/markdown-parser.js.map +1 -1
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +97 -37
- package/dist/utils/shell.js.map +1 -1
- package/models-config.json +30 -0
- package/package.json +2 -1
- package/prompts/system-prompt-autonomous.md +428 -0
- package/dist/tools/ToolRegistry.d.ts +0 -55
- package/dist/tools/ToolRegistry.d.ts.map +0 -1
- package/dist/tools/ToolRegistry.js +0 -600
- package/dist/tools/ToolRegistry.js.map +0 -1
- package/prompts/system-prompt-enhanced.md +0 -659
- package/prompts/system-prompt.md +0 -206
package/dist/cli-adapter.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname } from 'path';
|
|
5
|
+
import * as shellUtils from './utils/shell.js';
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
8
|
import { ConfigManager } from './config/manager.js';
|
|
@@ -9,6 +10,7 @@ import { ToolRegistry } from './tools/registry.js';
|
|
|
9
10
|
import { readFileTool, writeFileTool, editFileTool, listDirectoryTool } from './tools/file-ops.js';
|
|
10
11
|
import { executeCommandTool, grepSearchTool } from './tools/command.js';
|
|
11
12
|
import { webSearchTool, fetchUrlTool } from './tools/web-search.js';
|
|
13
|
+
import { taskCompleteTool } from './tools/task-complete.js';
|
|
12
14
|
import { apiClient } from './services/api-client.js';
|
|
13
15
|
import { conversationManager } from './services/conversation-manager.js';
|
|
14
16
|
import { aiServiceClient } from './services/ai-service-client.js';
|
|
@@ -43,11 +45,13 @@ export class CentaurusCLI {
|
|
|
43
45
|
onPlanModeChange;
|
|
44
46
|
onPlanApprovalRequest;
|
|
45
47
|
onPasswordRequest;
|
|
48
|
+
currentInteractiveProcess;
|
|
46
49
|
conversationStarted = false;
|
|
47
50
|
contextManager;
|
|
48
51
|
commandDetector;
|
|
49
52
|
aiContextInjector;
|
|
50
53
|
onSubshellContextChange;
|
|
54
|
+
currentAbortController;
|
|
51
55
|
constructor() {
|
|
52
56
|
this.configManager = new ConfigManager();
|
|
53
57
|
this.toolRegistry = new ToolRegistry();
|
|
@@ -116,6 +120,16 @@ export class CentaurusCLI {
|
|
|
116
120
|
this.sshHandler.setPasswordRequestCallback(callback);
|
|
117
121
|
}
|
|
118
122
|
}
|
|
123
|
+
writeToShellStdin(input) {
|
|
124
|
+
if (this.currentInteractiveProcess) {
|
|
125
|
+
try {
|
|
126
|
+
this.currentInteractiveProcess.write(input);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Silently ignore - process might have already exited
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
119
133
|
getPlanMode() {
|
|
120
134
|
return this.planMode;
|
|
121
135
|
}
|
|
@@ -169,6 +183,7 @@ export class CentaurusCLI {
|
|
|
169
183
|
this.toolRegistry.register(grepSearchTool);
|
|
170
184
|
this.toolRegistry.register(webSearchTool);
|
|
171
185
|
this.toolRegistry.register(fetchUrlTool);
|
|
186
|
+
this.toolRegistry.register(taskCompleteTool);
|
|
172
187
|
// Load configuration
|
|
173
188
|
const config = this.configManager.load();
|
|
174
189
|
// Enable backend sync if authenticated
|
|
@@ -243,8 +258,7 @@ Press Enter to continue...
|
|
|
243
258
|
this.conversationStarted = true;
|
|
244
259
|
}
|
|
245
260
|
catch (error) {
|
|
246
|
-
|
|
247
|
-
// Continue without backend persistence
|
|
261
|
+
// Silently continue without backend persistence
|
|
248
262
|
}
|
|
249
263
|
}
|
|
250
264
|
/**
|
|
@@ -264,8 +278,7 @@ Press Enter to continue...
|
|
|
264
278
|
});
|
|
265
279
|
}
|
|
266
280
|
catch (error) {
|
|
267
|
-
|
|
268
|
-
// Continue without backend persistence
|
|
281
|
+
// Silently continue without backend persistence
|
|
269
282
|
}
|
|
270
283
|
}
|
|
271
284
|
getModel() {
|
|
@@ -273,6 +286,15 @@ Press Enter to continue...
|
|
|
273
286
|
// Return the stored model name, or fall back to model ID
|
|
274
287
|
return config.modelName || config.model || 'gemini-2.5-flash';
|
|
275
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Cancel the current AI request
|
|
291
|
+
*/
|
|
292
|
+
cancelCurrentRequest() {
|
|
293
|
+
if (this.currentAbortController) {
|
|
294
|
+
this.currentAbortController.abort();
|
|
295
|
+
this.currentAbortController = undefined;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
276
298
|
async handleMessage(message) {
|
|
277
299
|
// Handle command mode - execute commands directly
|
|
278
300
|
if (this.commandMode) {
|
|
@@ -300,6 +322,7 @@ Press Enter to continue...
|
|
|
300
322
|
const context = {
|
|
301
323
|
cwd: this.cwd,
|
|
302
324
|
contextManager: this.contextManager,
|
|
325
|
+
cliAdapter: this, // Pass CLI adapter reference for interactive process management
|
|
303
326
|
requireApproval: async (message, risky, preview, operationType, operationDetails) => {
|
|
304
327
|
if (this.onToolApprovalRequest) {
|
|
305
328
|
return await this.onToolApprovalRequest({ message, risky, preview, operationType, operationDetails });
|
|
@@ -322,16 +345,15 @@ Press Enter to continue...
|
|
|
322
345
|
const selectedModelConfig = ALL_MODELS.find(m => m.id === selectedModelId && m.name === config.modelName);
|
|
323
346
|
const selectedModel = selectedModelId;
|
|
324
347
|
const selectedModelThinkingConfig = selectedModelConfig?.thinkingConfig;
|
|
325
|
-
// Create messages array and inject
|
|
326
|
-
// Always use the
|
|
327
|
-
const systemPromptPath = path.join(__dirname, '..', 'prompts', 'system-prompt-
|
|
348
|
+
// Create messages array and inject system prompt with environment context
|
|
349
|
+
// Always use the autonomous prompt (optimized for agentic code generation)
|
|
350
|
+
const systemPromptPath = path.join(__dirname, '..', 'prompts', 'system-prompt-autonomous.md');
|
|
328
351
|
let systemPrompt = '';
|
|
329
352
|
try {
|
|
330
353
|
systemPrompt = fs.readFileSync(systemPromptPath, 'utf-8');
|
|
331
354
|
}
|
|
332
355
|
catch (error) {
|
|
333
356
|
// Fallback to basic prompt if file not found
|
|
334
|
-
console.warn('Failed to load system-prompt-enhanced.md, using fallback prompt');
|
|
335
357
|
systemPrompt = 'You are Centaurus, a Senior Site Reliability Engineer (SRE) and Full-Stack Architect embedded in a CLI.';
|
|
336
358
|
}
|
|
337
359
|
// Enhance system prompt with environment context
|
|
@@ -351,12 +373,15 @@ Press Enter to continue...
|
|
|
351
373
|
const environmentContext = this.getEnvironmentContext();
|
|
352
374
|
const mode = this.getMode();
|
|
353
375
|
let finalAssistantMessage = '';
|
|
354
|
-
const MAX_TURNS =
|
|
376
|
+
const MAX_TURNS = 500; // Allow up to 500 turns for complex tasks
|
|
355
377
|
const MAX_TOOL_CALLS_PER_TURN = 5; // Limit tool calls per turn to prevent overthinking
|
|
378
|
+
const MAX_NARRATION_ATTEMPTS = 3; // Maximum times we'll prompt AI to stop narrating
|
|
356
379
|
let turnCount = 0;
|
|
357
|
-
let
|
|
380
|
+
let narrationAttempts = 0; // Track how many times AI narrated without executing
|
|
358
381
|
let thoughtStartTime = null; // Track when thinking started
|
|
359
382
|
let thoughtContent = ''; // Accumulate thought content
|
|
383
|
+
// Create AbortController for this request
|
|
384
|
+
this.currentAbortController = new AbortController();
|
|
360
385
|
// Multi-turn tool execution loop
|
|
361
386
|
while (turnCount < MAX_TURNS) {
|
|
362
387
|
turnCount++;
|
|
@@ -364,7 +389,7 @@ Press Enter to continue...
|
|
|
364
389
|
let toolCalls = [];
|
|
365
390
|
// Stream AI response from backend
|
|
366
391
|
// Backend will inject system prompt automatically with environment context
|
|
367
|
-
for await (const chunk of aiServiceClient.streamChat(selectedModel, messages, tools, environmentContext, mode, selectedModelThinkingConfig)) {
|
|
392
|
+
for await (const chunk of aiServiceClient.streamChat(selectedModel, messages, tools, environmentContext, mode, selectedModelThinkingConfig, this.currentAbortController.signal)) {
|
|
368
393
|
// Handle error chunks
|
|
369
394
|
if (chunk.type === 'error') {
|
|
370
395
|
throw new Error(chunk.message);
|
|
@@ -398,28 +423,15 @@ Press Enter to continue...
|
|
|
398
423
|
thoughtStartTime = null;
|
|
399
424
|
thoughtContent = '';
|
|
400
425
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (cleanBuffer) {
|
|
411
|
-
this.onResponseStreamCallback(cleanBuffer);
|
|
412
|
-
}
|
|
413
|
-
streamBuffer = '';
|
|
414
|
-
}
|
|
415
|
-
else if (streamBuffer.length > 20) {
|
|
416
|
-
// If buffer is getting long without marker, flush it
|
|
417
|
-
// Keep last 20 chars in case marker is split
|
|
418
|
-
const toFlush = streamBuffer.slice(0, -20);
|
|
419
|
-
if (toFlush) {
|
|
420
|
-
this.onResponseStreamCallback(toFlush);
|
|
421
|
-
}
|
|
422
|
-
streamBuffer = streamBuffer.slice(-20);
|
|
426
|
+
// Filter out <TASK_COMPLETE> markers (AI shouldn't output these)
|
|
427
|
+
let filteredContent = chunk.content;
|
|
428
|
+
filteredContent = filteredContent.replace(/<\/?TASK_COMPLETE>/gi, '');
|
|
429
|
+
filteredContent = filteredContent.trim();
|
|
430
|
+
if (filteredContent) {
|
|
431
|
+
assistantMessage += filteredContent;
|
|
432
|
+
// Send chunk to UI in real-time
|
|
433
|
+
if (this.onResponseStreamCallback) {
|
|
434
|
+
this.onResponseStreamCallback(filteredContent);
|
|
423
435
|
}
|
|
424
436
|
}
|
|
425
437
|
}
|
|
@@ -455,28 +467,20 @@ Press Enter to continue...
|
|
|
455
467
|
else {
|
|
456
468
|
fs.appendFileSync('cli_frontend_logs.txt', `[${new Date().toISOString()}] [CLI] Stream done but no thinking was in progress\n`);
|
|
457
469
|
}
|
|
458
|
-
// Flush any remaining buffer
|
|
459
|
-
if (streamBuffer && this.onResponseStreamCallback) {
|
|
460
|
-
const cleanBuffer = streamBuffer.replace(/<TASK_COMPLETE>/g, '');
|
|
461
|
-
if (cleanBuffer) {
|
|
462
|
-
this.onResponseStreamCallback(cleanBuffer);
|
|
463
|
-
}
|
|
464
|
-
streamBuffer = '';
|
|
465
|
-
}
|
|
466
470
|
break;
|
|
467
471
|
}
|
|
468
472
|
}
|
|
469
|
-
// Three-tier completion detection
|
|
470
|
-
// Tier 1: Check for explicit completion marker
|
|
471
|
-
if (this.hasCompletionMarker(assistantMessage)) {
|
|
472
|
-
finalAssistantMessage = this.removeCompletionMarker(assistantMessage);
|
|
473
|
-
break;
|
|
474
|
-
}
|
|
475
473
|
// If there are tool calls, execute them and continue loop
|
|
476
474
|
if (toolCalls.length > 0) {
|
|
475
|
+
// CRITICAL: AI should ONLY communicate via reason_text and task_complete summary
|
|
476
|
+
// Any text output alongside tool calls should be suppressed
|
|
477
|
+
if (assistantMessage && assistantMessage.trim()) {
|
|
478
|
+
// Suppress text output - AI should only use reason_text
|
|
479
|
+
assistantMessage = ''; // Clear ALL text output - AI should only use reason_text
|
|
480
|
+
}
|
|
477
481
|
// Limit tool calls per turn to prevent overthinking
|
|
478
482
|
if (toolCalls.length > MAX_TOOL_CALLS_PER_TURN) {
|
|
479
|
-
|
|
483
|
+
// Silently limit tool calls
|
|
480
484
|
toolCalls = toolCalls.slice(0, MAX_TOOL_CALLS_PER_TURN);
|
|
481
485
|
}
|
|
482
486
|
// Helper function to truncate large results
|
|
@@ -491,20 +495,58 @@ Press Enter to continue...
|
|
|
491
495
|
};
|
|
492
496
|
const toolResults = [];
|
|
493
497
|
let userCancelledOperation = false;
|
|
498
|
+
let taskCompleted = false;
|
|
499
|
+
let taskCompleteSummary = '';
|
|
494
500
|
for (let i = 0; i < toolCalls.length; i++) {
|
|
495
501
|
const toolCall = toolCalls[i];
|
|
496
502
|
try {
|
|
503
|
+
// Check if this is task_complete FIRST (before displaying anything)
|
|
504
|
+
if (toolCall.name === 'task_complete') {
|
|
505
|
+
taskCompleted = true;
|
|
506
|
+
taskCompleteSummary = toolCall.arguments.summary || '';
|
|
507
|
+
// CRITICAL: Suppress any text that came before task_complete in this turn
|
|
508
|
+
// The AI should ONLY communicate through the task_complete summary
|
|
509
|
+
assistantMessage = '';
|
|
510
|
+
// Stream the summary to UI so it's visible
|
|
511
|
+
if (taskCompleteSummary && this.onResponseStreamCallback) {
|
|
512
|
+
this.onResponseStreamCallback(taskCompleteSummary);
|
|
513
|
+
}
|
|
514
|
+
// Execute the tool for proper result handling
|
|
515
|
+
await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
516
|
+
// Stop processing remaining tools
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
// Extract and display reason_text if present (but skip for task_complete)
|
|
520
|
+
const reasonText = toolCall.arguments.reason_text;
|
|
521
|
+
if (reasonText && this.onResponseStreamCallback) {
|
|
522
|
+
this.onResponseStreamCallback(reasonText + '\n\n');
|
|
523
|
+
}
|
|
524
|
+
// Notify UI: tool is executing (send before execution starts)
|
|
525
|
+
if (this.onToolExecutionUpdate) {
|
|
526
|
+
// Add cwd to arguments for execute_command tool
|
|
527
|
+
const toolArgs = toolCall.name === 'execute_command'
|
|
528
|
+
? { ...toolCall.arguments, cwd: this.cwd }
|
|
529
|
+
: toolCall.arguments;
|
|
530
|
+
this.onToolExecutionUpdate({
|
|
531
|
+
toolName: toolCall.name,
|
|
532
|
+
status: 'executing',
|
|
533
|
+
arguments: toolArgs
|
|
534
|
+
});
|
|
535
|
+
}
|
|
497
536
|
// Execute the tool (it will request approval if needed)
|
|
498
|
-
// Don't send 'executing' status here - it causes duplication with approval UI
|
|
499
537
|
const result = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
500
538
|
if (result.success) {
|
|
501
539
|
// Notify UI: tool succeeded (send full result to UI)
|
|
502
540
|
if (this.onToolExecutionUpdate) {
|
|
541
|
+
// Add cwd to arguments for execute_command tool
|
|
542
|
+
const toolArgs = toolCall.name === 'execute_command'
|
|
543
|
+
? { ...toolCall.arguments, cwd: this.cwd }
|
|
544
|
+
: toolCall.arguments;
|
|
503
545
|
this.onToolExecutionUpdate({
|
|
504
546
|
toolName: toolCall.name,
|
|
505
547
|
status: 'completed',
|
|
506
548
|
result: result.result,
|
|
507
|
-
arguments:
|
|
549
|
+
arguments: toolArgs
|
|
508
550
|
});
|
|
509
551
|
}
|
|
510
552
|
// Parse result if it's a string (avoid double-stringification)
|
|
@@ -533,11 +575,15 @@ Press Enter to continue...
|
|
|
533
575
|
}
|
|
534
576
|
// Notify UI: tool failed
|
|
535
577
|
if (this.onToolExecutionUpdate) {
|
|
578
|
+
// Add cwd to arguments for execute_command tool
|
|
579
|
+
const toolArgs = toolCall.name === 'execute_command'
|
|
580
|
+
? { ...toolCall.arguments, cwd: this.cwd }
|
|
581
|
+
: toolCall.arguments;
|
|
536
582
|
this.onToolExecutionUpdate({
|
|
537
583
|
toolName: toolCall.name,
|
|
538
584
|
status: 'error',
|
|
539
585
|
error: result.error,
|
|
540
|
-
arguments:
|
|
586
|
+
arguments: toolArgs
|
|
541
587
|
});
|
|
542
588
|
}
|
|
543
589
|
toolResults.push({
|
|
@@ -558,11 +604,15 @@ Press Enter to continue...
|
|
|
558
604
|
}
|
|
559
605
|
// Notify UI: tool failed
|
|
560
606
|
if (this.onToolExecutionUpdate) {
|
|
607
|
+
// Add cwd to arguments for execute_command tool
|
|
608
|
+
const toolArgs = toolCall.name === 'execute_command'
|
|
609
|
+
? { ...toolCall.arguments, cwd: this.cwd }
|
|
610
|
+
: toolCall.arguments;
|
|
561
611
|
this.onToolExecutionUpdate({
|
|
562
612
|
toolName: toolCall.name,
|
|
563
613
|
status: 'error',
|
|
564
614
|
error: error.message,
|
|
565
|
-
arguments:
|
|
615
|
+
arguments: toolArgs
|
|
566
616
|
});
|
|
567
617
|
}
|
|
568
618
|
toolResults.push({
|
|
@@ -576,6 +626,12 @@ Press Enter to continue...
|
|
|
576
626
|
}
|
|
577
627
|
}
|
|
578
628
|
}
|
|
629
|
+
// If task_complete was called, stop the agentic loop immediately
|
|
630
|
+
if (taskCompleted) {
|
|
631
|
+
// Set the final message to the summary from task_complete
|
|
632
|
+
finalAssistantMessage = taskCompleteSummary;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
579
635
|
// If user cancelled an operation, stop the agentic loop immediately
|
|
580
636
|
if (userCancelledOperation) {
|
|
581
637
|
// Add assistant message to history
|
|
@@ -600,6 +656,11 @@ Press Enter to continue...
|
|
|
600
656
|
finalAssistantMessage = 'Operation cancelled by user. The task was not completed.';
|
|
601
657
|
break;
|
|
602
658
|
}
|
|
659
|
+
// Send assistant message to UI first (if there's text)
|
|
660
|
+
if (assistantMessage && this.onResponseStreamCallback) {
|
|
661
|
+
// Send the message that came with the tool calls
|
|
662
|
+
this.onResponseStreamCallback(assistantMessage);
|
|
663
|
+
}
|
|
603
664
|
// Add assistant message with tool calls to conversation history
|
|
604
665
|
this.conversationHistory.push({
|
|
605
666
|
role: 'assistant',
|
|
@@ -614,7 +675,7 @@ Press Enter to continue...
|
|
|
614
675
|
tool_call_id: toolResult.tool_call_id,
|
|
615
676
|
name: toolResult.name,
|
|
616
677
|
result: toolResult.result,
|
|
617
|
-
system_instruction: 'IMPORTANT: This tool executed successfully. If there are MORE steps needed to complete the task, continue executing the necessary tools. If the task is fully complete,
|
|
678
|
+
system_instruction: 'IMPORTANT: This tool executed successfully. If there are MORE steps needed to complete the task, continue executing the necessary tools. If the task is fully complete, call the task_complete tool with a comprehensive summary.'
|
|
618
679
|
};
|
|
619
680
|
this.conversationHistory.push({
|
|
620
681
|
role: 'tool',
|
|
@@ -622,8 +683,14 @@ Press Enter to continue...
|
|
|
622
683
|
});
|
|
623
684
|
}
|
|
624
685
|
// Rebuild messages array with updated history
|
|
625
|
-
//
|
|
626
|
-
messages = [
|
|
686
|
+
// IMPORTANT: Re-add system prompt for each turn
|
|
687
|
+
messages = [
|
|
688
|
+
{
|
|
689
|
+
role: 'system',
|
|
690
|
+
content: enhancedSystemPrompt,
|
|
691
|
+
},
|
|
692
|
+
...this.conversationHistory
|
|
693
|
+
];
|
|
627
694
|
// Re-inject subshell context for the next turn
|
|
628
695
|
messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
|
|
629
696
|
// Add delay between turns to prevent rate limiting
|
|
@@ -631,63 +698,139 @@ Press Enter to continue...
|
|
|
631
698
|
// Continue to next turn to let AI process results
|
|
632
699
|
continue;
|
|
633
700
|
}
|
|
634
|
-
// No tool calls -
|
|
701
|
+
// No tool calls - AI must make tool calls or finish
|
|
635
702
|
if (assistantMessage) {
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
+
}
|
|
662
765
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
//
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
766
|
+
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
|
+
});
|
|
785
|
+
}
|
|
786
|
+
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
|
+
});
|
|
795
|
+
}
|
|
684
796
|
}
|
|
685
|
-
//
|
|
686
|
-
|
|
797
|
+
// Rebuild messages array
|
|
798
|
+
messages = [...this.conversationHistory];
|
|
799
|
+
// Re-inject subshell context
|
|
800
|
+
messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
|
|
801
|
+
// Add delay before prompting
|
|
802
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
803
|
+
// Continue loop to get AI's response
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
// No tool calls and no message - AI stopped silently
|
|
807
|
+
// This usually means the AI thinks it's done but didn't call task_complete
|
|
808
|
+
// Prompt it to either continue or complete
|
|
809
|
+
const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
|
|
810
|
+
'**This is not allowed.** You must either:\n' +
|
|
811
|
+
'1. Execute a tool call if more work is needed, OR\n' +
|
|
812
|
+
'2. Call task_complete() with a summary of what you accomplished\n\n' +
|
|
813
|
+
'**If you have completed the task**, call task_complete() NOW with a comprehensive summary.\n' +
|
|
814
|
+
'**If more work is needed**, execute the next tool call immediately.';
|
|
815
|
+
this.conversationHistory.push({
|
|
816
|
+
role: 'user',
|
|
817
|
+
content: silentStopPrompt,
|
|
818
|
+
});
|
|
819
|
+
// Rebuild messages array
|
|
820
|
+
messages = [...this.conversationHistory];
|
|
821
|
+
// Re-inject subshell context
|
|
822
|
+
messages = this.aiContextInjector.injectSubshellContext(messages, currentContext);
|
|
823
|
+
// Add delay before prompting
|
|
824
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
825
|
+
// Increment turn count and continue (give AI one more chance)
|
|
826
|
+
turnCount++;
|
|
827
|
+
if (turnCount >= MAX_TURNS) {
|
|
828
|
+
// If we've hit max turns, force completion
|
|
829
|
+
finalAssistantMessage = '⚠️ **Task Incomplete**: The AI stopped responding after multiple prompts.\n\n' +
|
|
830
|
+
'The task may be partially complete. Please review the work done and provide more specific instructions if needed.';
|
|
687
831
|
break;
|
|
688
832
|
}
|
|
689
|
-
|
|
690
|
-
break;
|
|
833
|
+
continue;
|
|
691
834
|
}
|
|
692
835
|
// Check if max turns was reached
|
|
693
836
|
if (turnCount >= MAX_TURNS) {
|
|
@@ -696,15 +839,18 @@ Press Enter to continue...
|
|
|
696
839
|
finalAssistantMessage = (finalAssistantMessage || 'Task processing limit reached.') + warningMessage;
|
|
697
840
|
}
|
|
698
841
|
// Send final message to user (without tool execution logs)
|
|
699
|
-
//
|
|
700
|
-
const finalMessage =
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
842
|
+
// If finalAssistantMessage is empty (task_complete with no summary), don't show anything
|
|
843
|
+
const finalMessage = finalAssistantMessage;
|
|
844
|
+
// Only save and display if there's a message
|
|
845
|
+
if (finalMessage) {
|
|
846
|
+
// Save to conversation history
|
|
847
|
+
this.conversationHistory.push({
|
|
848
|
+
role: 'assistant',
|
|
849
|
+
content: finalMessage,
|
|
850
|
+
});
|
|
851
|
+
// Save assistant message to backend
|
|
852
|
+
await this.saveMessageToBackend('assistant', finalMessage);
|
|
853
|
+
}
|
|
708
854
|
// Parse response for plan mode
|
|
709
855
|
if (this.planMode) {
|
|
710
856
|
const planData = this.parsePlanResponse(finalMessage);
|
|
@@ -723,14 +869,25 @@ Press Enter to continue...
|
|
|
723
869
|
}
|
|
724
870
|
}
|
|
725
871
|
}
|
|
726
|
-
// Send response back to UI
|
|
727
|
-
if (this.onResponseCallback) {
|
|
872
|
+
// Send response back to UI (only if there's a message)
|
|
873
|
+
if (this.onResponseCallback && finalMessage) {
|
|
728
874
|
this.onResponseCallback(finalMessage);
|
|
729
875
|
}
|
|
730
876
|
}
|
|
731
877
|
catch (error) {
|
|
878
|
+
// Check if this was an abort/cancellation
|
|
879
|
+
if (error.name === 'AbortError' || error.message?.includes('aborted')) {
|
|
880
|
+
if (this.onResponseCallback) {
|
|
881
|
+
this.onResponseCallback('⚠️ Request cancelled by user.');
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
732
885
|
throw new Error(`AI Error: ${error.message}`);
|
|
733
886
|
}
|
|
887
|
+
finally {
|
|
888
|
+
// Clean up abort controller
|
|
889
|
+
this.currentAbortController = undefined;
|
|
890
|
+
}
|
|
734
891
|
}
|
|
735
892
|
async handleSlashCommand(command) {
|
|
736
893
|
const parts = command.slice(1).split(' ');
|
|
@@ -746,6 +903,7 @@ Press Enter to continue...
|
|
|
746
903
|
`/model - Select from available Google models\n` +
|
|
747
904
|
`/plan - Toggle plan mode for complex implementations\n` +
|
|
748
905
|
`/quality - Toggle enhanced quality features (thinking protocol, validation)\n` +
|
|
906
|
+
`/autonomous - Toggle autonomous mode (Silent Operator with task_complete)\n` +
|
|
749
907
|
`/sign-in - Sign in with Google (if not already signed in)\n` +
|
|
750
908
|
`/logout - Sign out, clear session, and exit CLI\n` +
|
|
751
909
|
`/exit - Exit the application\n\n` +
|
|
@@ -864,6 +1022,26 @@ Press Enter to continue...
|
|
|
864
1022
|
'• Advanced validation\n' +
|
|
865
1023
|
'• Enhanced tool descriptions';
|
|
866
1024
|
break;
|
|
1025
|
+
case 'autonomous':
|
|
1026
|
+
// Toggle autonomous mode (Silent Operator)
|
|
1027
|
+
const currentAutonomous = this.configManager.get('autonomousMode');
|
|
1028
|
+
const newAutonomous = !currentAutonomous; // Toggle (default is false)
|
|
1029
|
+
this.configManager.set('autonomousMode', newAutonomous);
|
|
1030
|
+
responseMessage = newAutonomous
|
|
1031
|
+
? '✅ Autonomous Mode enabled (Silent Operator)\n\n' +
|
|
1032
|
+
'The AI will now:\n' +
|
|
1033
|
+
'• Work silently without narrating actions\n' +
|
|
1034
|
+
'• Use Touch-First safety (never guess file paths)\n' +
|
|
1035
|
+
'• Apply surgical precision to file edits\n' +
|
|
1036
|
+
'• Call task_complete when done with comprehensive summary\n' +
|
|
1037
|
+
'• Inject intelligent error recovery hints\n\n' +
|
|
1038
|
+
'This is the industry-standard autonomous agent mode.'
|
|
1039
|
+
: '⚠️ Autonomous Mode disabled\n\n' +
|
|
1040
|
+
'The AI will use the standard enhanced prompt with:\n' +
|
|
1041
|
+
'• Verbose communication after each action\n' +
|
|
1042
|
+
'• Standard tool descriptions\n' +
|
|
1043
|
+
'• Manual completion detection';
|
|
1044
|
+
break;
|
|
867
1045
|
case 'clear':
|
|
868
1046
|
this.conversationHistory = [];
|
|
869
1047
|
this.conversationStarted = false;
|
|
@@ -891,11 +1069,11 @@ Press Enter to continue...
|
|
|
891
1069
|
responseMessage = `❌ Failed to update configuration: ${error.message}`;
|
|
892
1070
|
}
|
|
893
1071
|
}
|
|
894
|
-
else if (configKey === 'enhancedQuality') {
|
|
1072
|
+
else if (configKey === 'enhancedQuality' || configKey === 'autonomousMode') {
|
|
895
1073
|
// Parse boolean value
|
|
896
1074
|
const boolValue = configValue.toLowerCase() === 'true' || configValue === '1';
|
|
897
1075
|
try {
|
|
898
|
-
this.configManager.set(
|
|
1076
|
+
this.configManager.set(configKey, boolValue);
|
|
899
1077
|
responseMessage = `✅ Configuration updated: ${configKey} = ${boolValue}`;
|
|
900
1078
|
}
|
|
901
1079
|
catch (error) {
|
|
@@ -903,7 +1081,7 @@ Press Enter to continue...
|
|
|
903
1081
|
}
|
|
904
1082
|
}
|
|
905
1083
|
else {
|
|
906
|
-
responseMessage = `❌ Error: Unknown config key: ${configKey}\nValid keys: model, enhancedQuality`;
|
|
1084
|
+
responseMessage = `❌ Error: Unknown config key: ${configKey}\nValid keys: model, enhancedQuality, autonomousMode`;
|
|
907
1085
|
}
|
|
908
1086
|
}
|
|
909
1087
|
else {
|
|
@@ -916,6 +1094,7 @@ Press Enter to continue...
|
|
|
916
1094
|
`Version: ${currentVersion}\n` +
|
|
917
1095
|
`Model: ${config.model || 'gemini-2.5-flash (default)'}\n` +
|
|
918
1096
|
`Enhanced Quality: ${config.enhancedQuality !== false ? '✅ Enabled' : '❌ Disabled'}\n` +
|
|
1097
|
+
`Autonomous Mode: ${config.autonomousMode === true ? '✅ Enabled' : '❌ Disabled'}\n` +
|
|
919
1098
|
`Authentication: ${apiClient.isAuthenticated() ? '✅ Signed in' : '❌ Not signed in'}`;
|
|
920
1099
|
}
|
|
921
1100
|
break;
|
|
@@ -994,107 +1173,6 @@ Press Enter to continue...
|
|
|
994
1173
|
return 'plan';
|
|
995
1174
|
return 'default';
|
|
996
1175
|
}
|
|
997
|
-
/**
|
|
998
|
-
* Check if response contains completion marker
|
|
999
|
-
* Returns true if the <TASK_COMPLETE> marker is found in the text
|
|
1000
|
-
*/
|
|
1001
|
-
hasCompletionMarker(text) {
|
|
1002
|
-
return text.includes('<TASK_COMPLETE>');
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Remove completion marker from text
|
|
1006
|
-
* Strips the <TASK_COMPLETE> marker from display text
|
|
1007
|
-
*/
|
|
1008
|
-
removeCompletionMarker(text) {
|
|
1009
|
-
return text.replace(/<TASK_COMPLETE>/g, '').trim();
|
|
1010
|
-
}
|
|
1011
|
-
/**
|
|
1012
|
-
* Check if response contains strong completion phrases
|
|
1013
|
-
* Returns true if the text contains phrases that strongly indicate task completion
|
|
1014
|
-
* Used as fallback when completion marker is not present
|
|
1015
|
-
*/
|
|
1016
|
-
hasStrongCompletionPhrase(text) {
|
|
1017
|
-
const strongCompletionPhrases = [
|
|
1018
|
-
'task complete',
|
|
1019
|
-
'task is complete',
|
|
1020
|
-
'tasks complete',
|
|
1021
|
-
'tasks are complete',
|
|
1022
|
-
'all done',
|
|
1023
|
-
'everything is done',
|
|
1024
|
-
'everything is ready',
|
|
1025
|
-
'everything is set up',
|
|
1026
|
-
'everything is complete',
|
|
1027
|
-
'all set',
|
|
1028
|
-
'all finished',
|
|
1029
|
-
'finished everything',
|
|
1030
|
-
'completed everything',
|
|
1031
|
-
'completed all',
|
|
1032
|
-
'done with everything',
|
|
1033
|
-
'implementation complete',
|
|
1034
|
-
'implementation is complete',
|
|
1035
|
-
'setup complete',
|
|
1036
|
-
'setup is complete',
|
|
1037
|
-
'all tasks completed',
|
|
1038
|
-
'all steps completed',
|
|
1039
|
-
'work is complete',
|
|
1040
|
-
'work complete',
|
|
1041
|
-
'fully implemented',
|
|
1042
|
-
'fully complete',
|
|
1043
|
-
'ready to use',
|
|
1044
|
-
'ready to go',
|
|
1045
|
-
'successfully completed',
|
|
1046
|
-
'completed successfully'
|
|
1047
|
-
];
|
|
1048
|
-
const lowerText = text.toLowerCase();
|
|
1049
|
-
return strongCompletionPhrases.some(phrase => lowerText.includes(phrase));
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Check if response seems incomplete
|
|
1053
|
-
* Returns true if the text contains phrases that suggest the AI stopped prematurely
|
|
1054
|
-
* Only triggers within first 15 turns to prevent infinite loops
|
|
1055
|
-
*/
|
|
1056
|
-
seemsIncomplete(text, turnCount) {
|
|
1057
|
-
// Only check for incomplete responses within first 15 turns
|
|
1058
|
-
if (turnCount > 15) {
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1061
|
-
const incompletePhrases = [
|
|
1062
|
-
'i created',
|
|
1063
|
-
'i\'ve created',
|
|
1064
|
-
'created the',
|
|
1065
|
-
'i made',
|
|
1066
|
-
'i\'ve made',
|
|
1067
|
-
'i wrote',
|
|
1068
|
-
'i\'ve written',
|
|
1069
|
-
'next i',
|
|
1070
|
-
'now i',
|
|
1071
|
-
'i will',
|
|
1072
|
-
'i\'ll',
|
|
1073
|
-
'let me',
|
|
1074
|
-
'i should',
|
|
1075
|
-
'first',
|
|
1076
|
-
'step 1',
|
|
1077
|
-
'step 2',
|
|
1078
|
-
'step 3',
|
|
1079
|
-
'i\'ll create',
|
|
1080
|
-
'i\'ll make',
|
|
1081
|
-
'i\'ll write',
|
|
1082
|
-
'i\'ll add',
|
|
1083
|
-
'i\'ll implement',
|
|
1084
|
-
'i need to',
|
|
1085
|
-
'i\'ll need to',
|
|
1086
|
-
'next step',
|
|
1087
|
-
'following step',
|
|
1088
|
-
'then i',
|
|
1089
|
-
'after that',
|
|
1090
|
-
'i\'m creating',
|
|
1091
|
-
'i\'m making',
|
|
1092
|
-
'i\'m writing',
|
|
1093
|
-
'i\'m adding'
|
|
1094
|
-
];
|
|
1095
|
-
const lowerText = text.toLowerCase();
|
|
1096
|
-
return incompletePhrases.some(phrase => lowerText.includes(phrase));
|
|
1097
|
-
}
|
|
1098
1176
|
getPlanModeInstructions() {
|
|
1099
1177
|
return `\n\n## PLAN MODE ACTIVE\n\nYou are currently in PLAN MODE. In this mode, you should:\n\n1. **Explore the current directory** using list_directory and read_file tools to understand the codebase structure\n2. **Research the topic** using the web_search tool to gather best practices and implementation approaches\n3. **Create a detailed implementation plan** with ordered tasks\n\nWhen you've completed your planning, you MUST format your response in this EXACT format:\n\n<tasks>\n1. First task description\n2. Second task description\n3. Third task description\n...\n</tasks>\n\n<question>\nShall I proceed with implementing this plan?\n</question>\n\nIMPORTANT:\n- The <tasks> section must contain a numbered list of tasks in order\n- The <question> section must contain a yes/no question asking if you should proceed\n- Do NOT execute any implementation tasks in plan mode - only create the plan\n- Use tools to explore and research, but do not modify any files`;
|
|
1100
1178
|
}
|
|
@@ -1146,6 +1224,13 @@ Press Enter to continue...
|
|
|
1146
1224
|
}
|
|
1147
1225
|
}
|
|
1148
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Set the current interactive process
|
|
1229
|
+
* This is called by the execute_command tool when starting an interactive command
|
|
1230
|
+
*/
|
|
1231
|
+
setCurrentInteractiveProcess(process) {
|
|
1232
|
+
this.currentInteractiveProcess = process;
|
|
1233
|
+
}
|
|
1149
1234
|
/**
|
|
1150
1235
|
* Handle command execution in command mode
|
|
1151
1236
|
*/
|
|
@@ -1242,26 +1327,84 @@ Press Enter to continue...
|
|
|
1242
1327
|
return;
|
|
1243
1328
|
}
|
|
1244
1329
|
}
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
if (result.stderr && result.stderr.trim()) {
|
|
1253
|
-
if (output)
|
|
1254
|
-
output += '\n';
|
|
1255
|
-
output += result.stderr;
|
|
1330
|
+
// Notify UI that command is executing
|
|
1331
|
+
if (this.onToolExecutionUpdate) {
|
|
1332
|
+
this.onToolExecutionUpdate({
|
|
1333
|
+
toolName: 'execute_command',
|
|
1334
|
+
status: 'executing',
|
|
1335
|
+
arguments: { command, cwd: this.cwd }
|
|
1336
|
+
});
|
|
1256
1337
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
output = '
|
|
1338
|
+
const currentContext = this.contextManager.getCurrentContext();
|
|
1339
|
+
// Execute with streaming support for local commands
|
|
1340
|
+
if (currentContext.type === 'local') {
|
|
1341
|
+
// Use interactive execution to support stdin
|
|
1342
|
+
let output = '';
|
|
1343
|
+
await new Promise((resolve) => {
|
|
1344
|
+
this.currentInteractiveProcess = shellUtils.executeCommandInteractive(command, this.cwd, (data) => {
|
|
1345
|
+
// PTY-style: single merged stream
|
|
1346
|
+
output += data;
|
|
1347
|
+
if (this.onToolStreamingOutput) {
|
|
1348
|
+
this.onToolStreamingOutput({ toolName: 'execute_command', chunk: data, type: 'stdout' });
|
|
1349
|
+
}
|
|
1350
|
+
}, (exitCode) => {
|
|
1351
|
+
// Notify UI of completion
|
|
1352
|
+
if (this.onToolExecutionUpdate) {
|
|
1353
|
+
if (exitCode !== 0) {
|
|
1354
|
+
this.onToolExecutionUpdate({
|
|
1355
|
+
toolName: 'execute_command',
|
|
1356
|
+
status: 'error',
|
|
1357
|
+
result: output,
|
|
1358
|
+
error: `Exit Code: ${exitCode}`,
|
|
1359
|
+
arguments: { command, cwd: this.cwd }
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
this.onToolExecutionUpdate({
|
|
1364
|
+
toolName: 'execute_command',
|
|
1365
|
+
status: 'completed',
|
|
1366
|
+
result: output || 'Command executed successfully',
|
|
1367
|
+
arguments: { command, cwd: this.cwd }
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
this.currentInteractiveProcess = undefined;
|
|
1372
|
+
resolve();
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1262
1375
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1376
|
+
else {
|
|
1377
|
+
// Subshell execution (no streaming yet)
|
|
1378
|
+
const result = await this.contextManager.executeCommand(command);
|
|
1379
|
+
let output = '';
|
|
1380
|
+
if (result.stdout && result.stdout.trim()) {
|
|
1381
|
+
output += result.stdout;
|
|
1382
|
+
}
|
|
1383
|
+
if (result.stderr && result.stderr.trim()) {
|
|
1384
|
+
if (output)
|
|
1385
|
+
output += '\n';
|
|
1386
|
+
output += result.stderr;
|
|
1387
|
+
}
|
|
1388
|
+
// Notify UI of completion
|
|
1389
|
+
if (this.onToolExecutionUpdate) {
|
|
1390
|
+
if (result.exitCode !== 0) {
|
|
1391
|
+
this.onToolExecutionUpdate({
|
|
1392
|
+
toolName: 'execute_command',
|
|
1393
|
+
status: 'error',
|
|
1394
|
+
result: output,
|
|
1395
|
+
error: `Exit Code: ${result.exitCode}`,
|
|
1396
|
+
arguments: { command, cwd: this.cwd }
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
this.onToolExecutionUpdate({
|
|
1401
|
+
toolName: 'execute_command',
|
|
1402
|
+
status: 'completed',
|
|
1403
|
+
result: output || 'Command executed successfully',
|
|
1404
|
+
arguments: { command, cwd: this.cwd }
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1265
1408
|
}
|
|
1266
1409
|
}
|
|
1267
1410
|
catch (error) {
|