centaurus-cli 2.9.1 → 2.9.2
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 +70 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +349 -156
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/mcp-config-manager.d.ts +21 -0
- package/dist/config/mcp-config-manager.d.ts.map +1 -1
- package/dist/config/mcp-config-manager.js +184 -1
- package/dist/config/mcp-config-manager.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +7 -2
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +4 -3
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/index.js +60 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-command-handler.d.ts +34 -3
- package/dist/mcp/mcp-command-handler.d.ts.map +1 -1
- package/dist/mcp/mcp-command-handler.js +171 -83
- package/dist/mcp/mcp-command-handler.js.map +1 -1
- package/dist/mcp/mcp-server-manager.d.ts.map +1 -1
- package/dist/mcp/mcp-server-manager.js +9 -23
- package/dist/mcp/mcp-server-manager.js.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.d.ts.map +1 -1
- package/dist/mcp/mcp-tool-wrapper.js +42 -5
- package/dist/mcp/mcp-tool-wrapper.js.map +1 -1
- package/dist/services/api-client.d.ts +9 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +25 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/input-detection-agent.d.ts +40 -0
- package/dist/services/input-detection-agent.d.ts.map +1 -0
- package/dist/services/input-detection-agent.js +213 -0
- package/dist/services/input-detection-agent.js.map +1 -0
- package/dist/services/input-requirement-detector.d.ts +28 -0
- package/dist/services/input-requirement-detector.d.ts.map +1 -0
- package/dist/services/input-requirement-detector.js +203 -0
- package/dist/services/input-requirement-detector.js.map +1 -0
- package/dist/services/monitored-shell-manager.d.ts +120 -0
- package/dist/services/monitored-shell-manager.d.ts.map +1 -0
- package/dist/services/monitored-shell-manager.js +239 -0
- package/dist/services/monitored-shell-manager.js.map +1 -0
- package/dist/services/shell-input-agent.d.ts +89 -0
- package/dist/services/shell-input-agent.d.ts.map +1 -0
- package/dist/services/shell-input-agent.js +361 -0
- package/dist/services/shell-input-agent.js.map +1 -0
- package/dist/services/sub-agent-manager.d.ts +139 -0
- package/dist/services/sub-agent-manager.d.ts.map +1 -0
- package/dist/services/sub-agent-manager.js +517 -0
- package/dist/services/sub-agent-manager.js.map +1 -0
- package/dist/tools/background-command.d.ts.map +1 -1
- package/dist/tools/background-command.js +33 -13
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +64 -1
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +33 -19
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/get-diff.js +1 -1
- package/dist/tools/get-diff.js.map +1 -1
- package/dist/tools/grep-search.d.ts.map +1 -1
- package/dist/tools/grep-search.js +41 -15
- package/dist/tools/grep-search.js.map +1 -1
- package/dist/tools/plan-mode.js +3 -3
- package/dist/tools/plan-mode.js.map +1 -1
- package/dist/tools/registry.js +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sub-agent.d.ts +9 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +232 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/task-complete.d.ts.map +1 -1
- package/dist/tools/task-complete.js +14 -26
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +43 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +560 -94
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/CircularSelectInput.d.ts +24 -0
- package/dist/ui/components/CircularSelectInput.d.ts.map +1 -0
- package/dist/ui/components/CircularSelectInput.js +71 -0
- package/dist/ui/components/CircularSelectInput.js.map +1 -0
- package/dist/ui/components/ErrorBoundary.d.ts +3 -2
- package/dist/ui/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/ui/components/ErrorBoundary.js +29 -1
- package/dist/ui/components/ErrorBoundary.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +2 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +23 -3
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +6 -0
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
- package/dist/ui/components/InteractiveShell.js +57 -6
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/MCPAddScreen.d.ts +13 -0
- package/dist/ui/components/MCPAddScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPAddScreen.js +54 -0
- package/dist/ui/components/MCPAddScreen.js.map +1 -0
- package/dist/ui/components/MCPListScreen.d.ts +17 -0
- package/dist/ui/components/MCPListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPListScreen.js +50 -0
- package/dist/ui/components/MCPListScreen.js.map +1 -0
- package/dist/ui/components/MCPServerListScreen.d.ts +16 -0
- package/dist/ui/components/MCPServerListScreen.d.ts.map +1 -0
- package/dist/ui/components/MCPServerListScreen.js +59 -0
- package/dist/ui/components/MCPServerListScreen.js.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts +23 -0
- package/dist/ui/components/MonitorModeAIPanel.d.ts.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.js +69 -0
- package/dist/ui/components/MonitorModeAIPanel.js.map +1 -0
- package/dist/ui/components/MultiLineInput.d.ts +13 -0
- package/dist/ui/components/MultiLineInput.d.ts.map +1 -0
- package/dist/ui/components/MultiLineInput.js +223 -0
- package/dist/ui/components/MultiLineInput.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +2 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +33 -2
- 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 +226 -12
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.d.ts.map +1 -1
- package/dist/ui/components/VersionUpdatePrompt.js +3 -2
- package/dist/ui/components/VersionUpdatePrompt.js.map +1 -1
- package/package.json +2 -1
package/dist/cli-adapter.js
CHANGED
|
@@ -19,6 +19,9 @@ import { taskCompleteTool } from './tools/task-complete.js';
|
|
|
19
19
|
import { readBinaryFileTool } from './tools/read-binary-file.js';
|
|
20
20
|
import { createImageTool } from './tools/create-image.js';
|
|
21
21
|
import { backgroundCommandTool } from './tools/background-command.js';
|
|
22
|
+
import { subAgentTool } from './tools/sub-agent.js';
|
|
23
|
+
import { SubAgentManager } from './services/sub-agent-manager.js';
|
|
24
|
+
import { ShellInputAgent } from './services/shell-input-agent.js';
|
|
22
25
|
import { apiClient } from './services/api-client.js';
|
|
23
26
|
import { conversationManager } from './services/conversation-manager.js';
|
|
24
27
|
import { aiServiceClient } from './services/ai-service-client.js';
|
|
@@ -48,6 +51,7 @@ export class CentaurusCLI {
|
|
|
48
51
|
pendingPlanRequest = null; // Stores original user request during planning phase
|
|
49
52
|
commandMode = false;
|
|
50
53
|
backgroundMode = false; // Background shell mode for running commands in background
|
|
54
|
+
shellIdCounter = 1;
|
|
51
55
|
previousMode = 'execution';
|
|
52
56
|
onResponseCallback;
|
|
53
57
|
onDirectMessageCallback; // For slash commands - adds directly to history
|
|
@@ -94,7 +98,18 @@ export class CentaurusCLI {
|
|
|
94
98
|
onShowBackgroundTaskCancelPickerCallback;
|
|
95
99
|
onBackgroundTaskViewCallback;
|
|
96
100
|
onTokenCountUpdate; // Report actual AI context token count to UI
|
|
101
|
+
currentTokenCount = 0; // Track current token count for context limit checking
|
|
102
|
+
contextLimitReached = false; // Track if context limit has been reached
|
|
103
|
+
onContextLimitReached; // Notify UI about context limit state
|
|
97
104
|
onSessionQuotaUpdate;
|
|
105
|
+
// MCP screen callbacks
|
|
106
|
+
onShowMCPAddScreen;
|
|
107
|
+
onShowMCPRemoveScreen;
|
|
108
|
+
onShowMCPEnableScreen;
|
|
109
|
+
onShowMCPDisableScreen;
|
|
110
|
+
onShowMCPListScreen;
|
|
111
|
+
onSubAgentCountChange; // Callback for sub-agent count changes
|
|
112
|
+
onPromptAnswered; // Callback when AI answers a shell prompt
|
|
98
113
|
constructor() {
|
|
99
114
|
this.configManager = new ConfigManager();
|
|
100
115
|
this.toolRegistry = new ToolRegistry();
|
|
@@ -115,6 +130,11 @@ export class CentaurusCLI {
|
|
|
115
130
|
});
|
|
116
131
|
// Initialize MCP
|
|
117
132
|
this.initializeMCP();
|
|
133
|
+
// Initialize ShellInputAgent with tool registry and wire shell input callback
|
|
134
|
+
ShellInputAgent.initialize(this.toolRegistry);
|
|
135
|
+
ShellInputAgent.setOnShellInput((shellId, input) => {
|
|
136
|
+
this.writeToShellStdin(input);
|
|
137
|
+
});
|
|
118
138
|
}
|
|
119
139
|
setOnResponseCallback(callback) {
|
|
120
140
|
this.onResponseCallback = callback;
|
|
@@ -186,9 +206,131 @@ export class CentaurusCLI {
|
|
|
186
206
|
setOnTokenCountUpdate(callback) {
|
|
187
207
|
this.onTokenCountUpdate = callback;
|
|
188
208
|
}
|
|
209
|
+
setOnContextLimitReached(callback) {
|
|
210
|
+
this.onContextLimitReached = callback;
|
|
211
|
+
}
|
|
212
|
+
setOnSubAgentCountChange(callback) {
|
|
213
|
+
this.onSubAgentCountChange = callback;
|
|
214
|
+
}
|
|
215
|
+
setOnPromptAnswered(callback) {
|
|
216
|
+
this.onPromptAnswered = callback;
|
|
217
|
+
// Wire this callback to ShellInputAgent
|
|
218
|
+
ShellInputAgent.setOnPromptAnswered(callback);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Calculate and update token count based on current conversation history
|
|
222
|
+
* This ensures UI is always in sync with the actual AI context
|
|
223
|
+
* Uses backend's accurate token counting API (Vertex AI countTokens)
|
|
224
|
+
*/
|
|
225
|
+
async updateTokenCount() {
|
|
226
|
+
if (!this.onTokenCountUpdate)
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
// Get current model
|
|
230
|
+
const currentModel = this.configManager.get('modelName') || 'gemini-2.5-flash';
|
|
231
|
+
// Prepare messages for token counting
|
|
232
|
+
// Backend will automatically include system prompt when counting
|
|
233
|
+
// We just send the conversation history
|
|
234
|
+
const messagesForCounting = [...this.conversationHistory];
|
|
235
|
+
// Call backend API for accurate token counting
|
|
236
|
+
const tokenCount = await apiClient.countTokens(currentModel, messagesForCounting);
|
|
237
|
+
// Store locally for context limit checking
|
|
238
|
+
this.currentTokenCount = tokenCount;
|
|
239
|
+
// Update UI with accurate count
|
|
240
|
+
this.onTokenCountUpdate(tokenCount);
|
|
241
|
+
quickLog(`[${new Date().toISOString()}] [updateTokenCount] Accurate count: ${tokenCount} tokens for ${messagesForCounting.length} messages\n`);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
// Fallback to character-based estimation if API fails
|
|
245
|
+
const SYSTEM_PROMPT_ESTIMATE = 14000; // Backend injects ~14K char system prompt
|
|
246
|
+
// Calculate total characters from conversation history
|
|
247
|
+
let totalCharacters = 0;
|
|
248
|
+
for (const msg of this.conversationHistory) {
|
|
249
|
+
// Content
|
|
250
|
+
if (typeof msg.content === 'string') {
|
|
251
|
+
totalCharacters += msg.content.length;
|
|
252
|
+
}
|
|
253
|
+
// Thinking content
|
|
254
|
+
if (msg.thinking) {
|
|
255
|
+
totalCharacters += msg.thinking.length;
|
|
256
|
+
}
|
|
257
|
+
// Tool calls
|
|
258
|
+
if (msg.tool_calls) {
|
|
259
|
+
for (const tc of msg.tool_calls) {
|
|
260
|
+
totalCharacters += tc.name.length;
|
|
261
|
+
if (tc.arguments) {
|
|
262
|
+
totalCharacters += JSON.stringify(tc.arguments).length;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Tool call ID
|
|
267
|
+
if (msg.role === 'tool' && msg.tool_call_id) {
|
|
268
|
+
totalCharacters += msg.tool_call_id.length;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Estimate tokens (1 token ≈ 4 chars)
|
|
272
|
+
// Only include system prompt estimate if there's conversation history
|
|
273
|
+
const systemPromptChars = this.conversationHistory.length > 0 ? SYSTEM_PROMPT_ESTIMATE : 0;
|
|
274
|
+
const estimatedTokens = Math.ceil((totalCharacters + systemPromptChars) / 4);
|
|
275
|
+
// Store locally for context limit checking
|
|
276
|
+
this.currentTokenCount = estimatedTokens;
|
|
277
|
+
this.onTokenCountUpdate(estimatedTokens);
|
|
278
|
+
quickLog(`[${new Date().toISOString()}] [updateTokenCount] Fallback estimate: ${estimatedTokens} tokens (API error: ${error})\n`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get current token count for context limit checking
|
|
283
|
+
*/
|
|
284
|
+
getCurrentTokenCount() {
|
|
285
|
+
return this.currentTokenCount;
|
|
286
|
+
}
|
|
189
287
|
setOnSessionQuotaUpdate(callback) {
|
|
190
288
|
this.onSessionQuotaUpdate = callback;
|
|
191
289
|
}
|
|
290
|
+
// MCP screen callback setters
|
|
291
|
+
setOnMCPAddScreenSetup(callback) {
|
|
292
|
+
this.onShowMCPAddScreen = callback;
|
|
293
|
+
}
|
|
294
|
+
setOnMCPRemoveScreenSetup(callback) {
|
|
295
|
+
this.onShowMCPRemoveScreen = callback;
|
|
296
|
+
}
|
|
297
|
+
setOnMCPEnableScreenSetup(callback) {
|
|
298
|
+
this.onShowMCPEnableScreen = callback;
|
|
299
|
+
}
|
|
300
|
+
setOnMCPDisableScreenSetup(callback) {
|
|
301
|
+
this.onShowMCPDisableScreen = callback;
|
|
302
|
+
}
|
|
303
|
+
setOnMCPListScreenSetup(callback) {
|
|
304
|
+
this.onShowMCPListScreen = callback;
|
|
305
|
+
}
|
|
306
|
+
// MCP server operation methods (called from UI)
|
|
307
|
+
mcpAddServer(config) {
|
|
308
|
+
if (this.mcpCommandHandler) {
|
|
309
|
+
return this.mcpCommandHandler.addServer(config);
|
|
310
|
+
}
|
|
311
|
+
return { success: false, error: 'MCP not initialized' };
|
|
312
|
+
}
|
|
313
|
+
mcpRemoveServer(name) {
|
|
314
|
+
if (this.mcpCommandHandler) {
|
|
315
|
+
this.mcpCommandHandler.removeServer(name);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
mcpEnableServer(name) {
|
|
319
|
+
if (this.mcpCommandHandler) {
|
|
320
|
+
this.mcpCommandHandler.enableServer(name);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
mcpDisableServer(name) {
|
|
324
|
+
if (this.mcpCommandHandler) {
|
|
325
|
+
this.mcpCommandHandler.disableServer(name);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
mcpValidateConfig(jsonString) {
|
|
329
|
+
if (this.mcpCommandHandler) {
|
|
330
|
+
return this.mcpCommandHandler.validateServerConfig(jsonString);
|
|
331
|
+
}
|
|
332
|
+
return { valid: false, error: 'MCP not initialized' };
|
|
333
|
+
}
|
|
192
334
|
/**
|
|
193
335
|
* Notify UI about session quota status
|
|
194
336
|
*/
|
|
@@ -205,6 +347,32 @@ export class CentaurusCLI {
|
|
|
205
347
|
const mcpConfigManager = new MCPConfigManager();
|
|
206
348
|
const mcpServerManager = new MCPServerManager();
|
|
207
349
|
this.mcpCommandHandler = new MCPCommandHandler(mcpConfigManager, mcpServerManager, this.toolRegistry);
|
|
350
|
+
// Wire MCP screen callbacks
|
|
351
|
+
this.mcpCommandHandler.setOnShowMCPAddScreen(() => {
|
|
352
|
+
if (this.onShowMCPAddScreen) {
|
|
353
|
+
this.onShowMCPAddScreen();
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
this.mcpCommandHandler.setOnShowMCPRemoveScreen((servers) => {
|
|
357
|
+
if (this.onShowMCPRemoveScreen) {
|
|
358
|
+
this.onShowMCPRemoveScreen(servers);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
this.mcpCommandHandler.setOnShowMCPEnableScreen((servers) => {
|
|
362
|
+
if (this.onShowMCPEnableScreen) {
|
|
363
|
+
this.onShowMCPEnableScreen(servers);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
this.mcpCommandHandler.setOnShowMCPDisableScreen((servers) => {
|
|
367
|
+
if (this.onShowMCPDisableScreen) {
|
|
368
|
+
this.onShowMCPDisableScreen(servers);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
this.mcpCommandHandler.setOnShowMCPListScreen((servers) => {
|
|
372
|
+
if (this.onShowMCPListScreen) {
|
|
373
|
+
this.onShowMCPListScreen(servers);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
208
376
|
// Initialize MCP servers and tools
|
|
209
377
|
await this.mcpCommandHandler.initializeMCP();
|
|
210
378
|
}
|
|
@@ -249,12 +417,22 @@ export class CentaurusCLI {
|
|
|
249
417
|
getCommandMode() {
|
|
250
418
|
return this.commandMode;
|
|
251
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Get current conversation history for shell input agent context
|
|
422
|
+
* Returns a copy to prevent modification
|
|
423
|
+
*/
|
|
424
|
+
getConversationHistory() {
|
|
425
|
+
return [...this.conversationHistory];
|
|
426
|
+
}
|
|
252
427
|
getCurrentWorkingDirectory() {
|
|
253
428
|
return this.cwd;
|
|
254
429
|
}
|
|
255
430
|
getCurrentSubshellContext() {
|
|
256
431
|
return this.contextManager.getCurrentContext();
|
|
257
432
|
}
|
|
433
|
+
getCurrentInteractiveProcess() {
|
|
434
|
+
return this.currentInteractiveProcess;
|
|
435
|
+
}
|
|
258
436
|
/**
|
|
259
437
|
* Get the current conversation ID for file uploads
|
|
260
438
|
*/
|
|
@@ -371,6 +549,14 @@ export class CentaurusCLI {
|
|
|
371
549
|
this.toolRegistry.register(readBinaryFileTool);
|
|
372
550
|
this.toolRegistry.register(createImageTool);
|
|
373
551
|
this.toolRegistry.register(backgroundCommandTool);
|
|
552
|
+
this.toolRegistry.register(subAgentTool);
|
|
553
|
+
// Initialize SubAgentManager with tool registry
|
|
554
|
+
SubAgentManager.initialize(this.toolRegistry);
|
|
555
|
+
SubAgentManager.setOnSubAgentCountChange((count) => {
|
|
556
|
+
if (this.onSubAgentCountChange) {
|
|
557
|
+
this.onSubAgentCountChange(count);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
374
560
|
// Load configuration
|
|
375
561
|
const config = this.configManager.load();
|
|
376
562
|
// Enable backend sync if authenticated
|
|
@@ -562,6 +748,35 @@ Press Enter to continue...
|
|
|
562
748
|
this.notifySessionQuotaStatus();
|
|
563
749
|
return;
|
|
564
750
|
}
|
|
751
|
+
// Check context window limit before accepting new messages
|
|
752
|
+
// Get current model's context window
|
|
753
|
+
const currentModel = this.configManager.get('modelName') || 'gemini-2.5-flash';
|
|
754
|
+
const { getModelContextWindowSync } = await import('./config/models.js');
|
|
755
|
+
const maxTokens = getModelContextWindowSync(currentModel);
|
|
756
|
+
// Calculate current token usage percentage
|
|
757
|
+
// We need to estimate tokens for the new message too
|
|
758
|
+
const newMessageChars = message.length;
|
|
759
|
+
const estimatedNewMessageTokens = Math.ceil(newMessageChars / 4);
|
|
760
|
+
// Get current token count from state (updated by updateTokenCount)
|
|
761
|
+
const currentTokens = this.getCurrentTokenCount();
|
|
762
|
+
const projectedTokens = currentTokens + estimatedNewMessageTokens;
|
|
763
|
+
const usagePercent = (projectedTokens / maxTokens) * 100;
|
|
764
|
+
// Block new messages if context is ≥80% full
|
|
765
|
+
if (usagePercent >= 80) {
|
|
766
|
+
// Set context limit reached state
|
|
767
|
+
if (!this.contextLimitReached) {
|
|
768
|
+
this.contextLimitReached = true;
|
|
769
|
+
if (this.onContextLimitReached) {
|
|
770
|
+
this.onContextLimitReached(true);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const contextLimitMessage = `\n⚠️ Context limit reached (${usagePercent.toFixed(1)}% of ${maxTokens.toLocaleString()} tokens used).\n\nYour conversation has grown too large for the AI to process effectively.\n\nPlease start a new chat to continue:\n • Use /new to start a fresh conversation\n • Or use /chat to switch to a different chat\n\nYour current conversation has been saved and you can return to it later.\n\nYou can still use:\n • Slash commands (e.g., /help, /new, /chat)\n • Terminal commands (in Command mode)`;
|
|
774
|
+
if (this.onDirectMessageCallback) {
|
|
775
|
+
this.onDirectMessageCallback(contextLimitMessage);
|
|
776
|
+
}
|
|
777
|
+
quickLog(`[${new Date().toISOString()}] [handleMessage] Context limit reached: ${usagePercent.toFixed(1)}% (${projectedTokens}/${maxTokens} tokens)\n`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
565
780
|
// Cancel any active request when a new message comes in
|
|
566
781
|
// This enables "interrupt and replace" - new message takes priority
|
|
567
782
|
if (this.currentAbortController) {
|
|
@@ -613,6 +828,12 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
613
828
|
contextManager: this.contextManager,
|
|
614
829
|
cliAdapter: this, // Pass CLI adapter reference for interactive process management
|
|
615
830
|
requireApproval: async (message, risky, preview, operationType, operationDetails) => {
|
|
831
|
+
// Special bypass for shell input to running processes:
|
|
832
|
+
// If the AI is sending input to an existing shell (via shell_input), we bypass the separate approval step.
|
|
833
|
+
// The user already implicitly approved the interaction by running the command in agent control mode.
|
|
834
|
+
if (operationType === 'execute_command' && operationDetails?.shell_input) {
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
616
837
|
if (this.onToolApprovalRequest) {
|
|
617
838
|
return await this.onToolApprovalRequest({ message, risky, preview, operationType, operationDetails });
|
|
618
839
|
}
|
|
@@ -646,11 +867,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
646
867
|
const mode = this.getMode();
|
|
647
868
|
let finalAssistantMessage = '';
|
|
648
869
|
const MAX_TURNS = 500; // Allow up to 500 turns for complex tasks
|
|
649
|
-
const MAX_TOOL_CALLS_PER_TURN = 5; // Limit tool calls per turn to prevent overthinking
|
|
650
|
-
const MAX_NARRATION_ATTEMPTS = 3; // Maximum times we'll prompt AI to stop narrating
|
|
651
870
|
let turnCount = 0;
|
|
652
|
-
let narrationAttempts = 0; // Track how many times AI narrated without executing
|
|
653
|
-
let completionAttempts = 0; // Track how many times AI provided text summary without task_complete
|
|
654
871
|
let thoughtStartTime = null; // Track when thinking started
|
|
655
872
|
let thoughtContent = ''; // Accumulate thought content during streaming
|
|
656
873
|
let currentTurnThinking = ''; // Persist thinking for the current turn to attach to assistant message
|
|
@@ -736,13 +953,11 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
736
953
|
quickLog(`[${new Date().toISOString()}] [CLI] Assistant messages with tool_calls: ${messageStats.assistantWithToolCalls}\n`);
|
|
737
954
|
}
|
|
738
955
|
catch (e) { }
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
this.onTokenCountUpdate(estimatedTokens);
|
|
745
|
-
}
|
|
956
|
+
// Update token count using accurate API
|
|
957
|
+
// This will use backend's Vertex AI countTokens for precision
|
|
958
|
+
this.updateTokenCount().catch(err => {
|
|
959
|
+
quickLog(`[${new Date().toISOString()}] [CLI] Failed to update token count: ${err}\n`);
|
|
960
|
+
});
|
|
746
961
|
// Stream AI response from backend
|
|
747
962
|
// Backend will inject system prompt automatically with environment context
|
|
748
963
|
for await (const chunk of aiServiceClient.streamChat(selectedModel, messages, tools, environmentContext, mode, selectedModelThinkingConfig, this.currentAbortController.signal)) {
|
|
@@ -817,6 +1032,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
817
1032
|
// Handle tool call chunks
|
|
818
1033
|
if (chunk.type === 'tool_call') {
|
|
819
1034
|
const toolCall = chunk.toolCall;
|
|
1035
|
+
// Kiro/Claude compatibility: Parse string arguments early so they are objects throughout the pipeline
|
|
1036
|
+
// This ensures logging, UI updates, and tool execution all see the parsed object
|
|
1037
|
+
if (toolCall.arguments && typeof toolCall.arguments === 'string') {
|
|
1038
|
+
try {
|
|
1039
|
+
toolCall.arguments = JSON.parse(toolCall.arguments);
|
|
1040
|
+
}
|
|
1041
|
+
catch (e) {
|
|
1042
|
+
// Ignore parsing error, will be handled by downstream logic
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
820
1045
|
// Debug: Log every tool_call chunk received
|
|
821
1046
|
try {
|
|
822
1047
|
quickLog(`[${new Date().toISOString()}] [CLI] *** TOOL_CALL CHUNK RECEIVED (REAL-TIME): ${toolCall?.name || 'unknown'}\n`);
|
|
@@ -856,9 +1081,11 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
856
1081
|
// REAL-TIME EXECUTION: Execute tool immediately during streaming
|
|
857
1082
|
// This reduces latency by not waiting for the entire stream to finish
|
|
858
1083
|
try {
|
|
859
|
-
// Extract and display reason_text if present (skip for task_complete)
|
|
1084
|
+
// Extract and display reason_text if present (skip for task_complete and shell_input)
|
|
860
1085
|
const reasonText = toolCall.arguments.reason_text;
|
|
861
|
-
|
|
1086
|
+
// Don't show reason text for shell inputs (hidden from history per user request)
|
|
1087
|
+
const isShellInput = toolCall.name === 'execute_command' && toolCall.arguments.shell_input;
|
|
1088
|
+
if (reasonText && !isShellInput && this.onResponseStreamCallback) {
|
|
862
1089
|
this.onResponseStreamCallback(reasonText + '\n\n');
|
|
863
1090
|
}
|
|
864
1091
|
// Show 'executing' status immediately
|
|
@@ -866,6 +1093,14 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
866
1093
|
// Log tool execution start
|
|
867
1094
|
conversationLogger.logToolExecutionStart(toolCall.name, toolCall.id);
|
|
868
1095
|
// Execute the tool (it will request approval if needed via requireApproval callback)
|
|
1096
|
+
// SPECIAL: Intercept sub_agent spawn to enforce approval
|
|
1097
|
+
if (toolCall.name === 'sub_agent' && toolCall.arguments?.action === 'spawn') {
|
|
1098
|
+
const approved = await context.requireApproval(`Spawn Sub-Agent`, true, // risky
|
|
1099
|
+
undefined, 'execute_command', { command: `spawn sub-agent` });
|
|
1100
|
+
if (!approved) {
|
|
1101
|
+
throw new Error('User rejected sub-agent spawn request');
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
869
1104
|
const result = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
870
1105
|
if (result.success) {
|
|
871
1106
|
conversationLogger.logToolResult(toolCall.name, toolCall.id, result.result, true);
|
|
@@ -947,8 +1182,6 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
947
1182
|
assistantMessageLength: assistantMessage.length,
|
|
948
1183
|
hasToolCalls: toolCalls.length > 0,
|
|
949
1184
|
willContinue: toolCalls.length > 0,
|
|
950
|
-
narrationAttempts,
|
|
951
|
-
completionAttempts,
|
|
952
1185
|
});
|
|
953
1186
|
// If there are tool calls, execute them
|
|
954
1187
|
if (toolCalls.length > 0) {
|
|
@@ -961,11 +1194,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
961
1194
|
// Suppress text output - AI should only use reason_text
|
|
962
1195
|
assistantMessage = ''; // Clear ALL text output - AI should only use reason_text
|
|
963
1196
|
}
|
|
964
|
-
//
|
|
965
|
-
if (toolCalls.length > MAX_TOOL_CALLS_PER_TURN) {
|
|
966
|
-
// Silently limit tool calls
|
|
967
|
-
toolCalls = toolCalls.slice(0, MAX_TOOL_CALLS_PER_TURN);
|
|
968
|
-
}
|
|
1197
|
+
// Tool call limit removed - let AI use as many tools as needed per turn
|
|
969
1198
|
const toolResults = [...inStreamToolResults]; // Start with in-stream results
|
|
970
1199
|
const handledToolCallIds = new Set(); // Only for special tools (create_plan, mark_task_complete)
|
|
971
1200
|
let userCancelledOperation = false;
|
|
@@ -989,25 +1218,24 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
989
1218
|
try {
|
|
990
1219
|
// Check if this is task_complete FIRST (before displaying anything)
|
|
991
1220
|
if (toolCall.name === 'task_complete') {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
// Stream the summary to UI so it's visible
|
|
1006
|
-
if (this.onResponseStreamCallback) {
|
|
1007
|
-
this.onResponseStreamCallback(taskCompleteSummary);
|
|
1008
|
-
}
|
|
1221
|
+
// SUBAGENT BLOCKING: Check if any sub-agents are still running
|
|
1222
|
+
const runningSubAgents = SubAgentManager.getRunningSubAgents();
|
|
1223
|
+
if (runningSubAgents.length > 0) {
|
|
1224
|
+
// Block task_complete and provide feedback
|
|
1225
|
+
const agentIds = runningSubAgents.map(a => a.id).join(', ');
|
|
1226
|
+
toolResults.push({
|
|
1227
|
+
tool_call_id: toolCall.id,
|
|
1228
|
+
name: toolCall.name,
|
|
1229
|
+
result: `Cannot complete task: ${runningSubAgents.length} sub-agent(s) still running. IDs: ${agentIds}. Check their status periodically with sub_agent(action="status", agent_id="...") and wait for completion before calling task_complete.`,
|
|
1230
|
+
});
|
|
1231
|
+
handledToolCallIds.add(toolCall.id);
|
|
1232
|
+
continue; // Skip task_complete execution, keep loop running
|
|
1009
1233
|
}
|
|
1010
|
-
|
|
1234
|
+
taskCompleted = true;
|
|
1235
|
+
conversationLogger.logTaskComplete('');
|
|
1236
|
+
// task_complete no longer has a summary parameter
|
|
1237
|
+
// The AI streams all response text BEFORE calling task_complete()
|
|
1238
|
+
// So we just preserve whatever assistantMessage was already streamed
|
|
1011
1239
|
// Execute the tool for proper result handling
|
|
1012
1240
|
await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
1013
1241
|
// Clear the plan when task is complete
|
|
@@ -1240,7 +1468,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1240
1468
|
tool_call_id: toolCall.id,
|
|
1241
1469
|
name: toolCall.name,
|
|
1242
1470
|
result: completion.allComplete
|
|
1243
|
-
? 'All tasks completed!
|
|
1471
|
+
? 'All tasks completed! Output your summary of what was accomplished, then call task_complete().'
|
|
1244
1472
|
: completion.nextSubtask
|
|
1245
1473
|
? `Subtask ${completion.taskNumber} completed. Next subtask: ${completion.nextSubtask}`
|
|
1246
1474
|
: completion.nextTask
|
|
@@ -1250,7 +1478,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1250
1478
|
// If all tasks are complete, prompt AI to call task_complete
|
|
1251
1479
|
if (completion.allComplete) {
|
|
1252
1480
|
toolResults[toolResults.length - 1].result =
|
|
1253
|
-
'All tasks in the plan are now completed!
|
|
1481
|
+
'All tasks in the plan are now completed! Output your summary of what was accomplished, then call task_complete().';
|
|
1254
1482
|
}
|
|
1255
1483
|
}
|
|
1256
1484
|
catch (parseError) {
|
|
@@ -1314,12 +1542,14 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1314
1542
|
remoteContext = `docker:${metadata.containerId?.substring(0, 12) || 'container'}`;
|
|
1315
1543
|
}
|
|
1316
1544
|
}
|
|
1317
|
-
// Notify UI: tool
|
|
1545
|
+
// Notify UI: tool executing
|
|
1318
1546
|
if (this.onToolExecutionUpdate) {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1547
|
+
let toolArgs = { ...toolCall.arguments, remoteContext };
|
|
1548
|
+
// Special handling for execute_command
|
|
1549
|
+
if (toolCall.name === 'execute_command') {
|
|
1550
|
+
// Add effective CWD
|
|
1551
|
+
toolArgs.cwd = effectiveCwd;
|
|
1552
|
+
}
|
|
1323
1553
|
this.onToolExecutionUpdate({
|
|
1324
1554
|
toolName: toolCall.name,
|
|
1325
1555
|
status: 'executing',
|
|
@@ -1329,6 +1559,31 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1329
1559
|
// Log tool execution start
|
|
1330
1560
|
conversationLogger.logToolExecutionStart(toolCall.name, toolCall.id);
|
|
1331
1561
|
// Execute the tool (it will request approval if needed)
|
|
1562
|
+
// SPECIAL: Intercept sub_agent spawn to enforce approval
|
|
1563
|
+
if (toolCall.name === 'sub_agent' && toolCall.arguments?.action === 'spawn') {
|
|
1564
|
+
const approved = await context.requireApproval(`Spawn Sub-Agent`, true, // risky
|
|
1565
|
+
undefined, 'execute_command', { command: `spawn sub-agent` });
|
|
1566
|
+
if (!approved) {
|
|
1567
|
+
// User rejected - log result as error and skip execution
|
|
1568
|
+
conversationLogger.logToolResult(toolCall.name, toolCall.id, null, false, 'User rejected');
|
|
1569
|
+
// Notify UI: tool failed
|
|
1570
|
+
if (this.onToolExecutionUpdate) {
|
|
1571
|
+
this.onToolExecutionUpdate({
|
|
1572
|
+
toolName: toolCall.name,
|
|
1573
|
+
status: 'error',
|
|
1574
|
+
error: 'User rejected',
|
|
1575
|
+
arguments: toolCall.arguments
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
toolResults.push({
|
|
1579
|
+
tool_call_id: toolCall.id,
|
|
1580
|
+
name: toolCall.name,
|
|
1581
|
+
result: 'User rejected sub-agent spawn request',
|
|
1582
|
+
error: 'User rejected'
|
|
1583
|
+
});
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1332
1587
|
const result = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
1333
1588
|
if (result.success) {
|
|
1334
1589
|
// Log successful tool result
|
|
@@ -1442,6 +1697,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1442
1697
|
}
|
|
1443
1698
|
}
|
|
1444
1699
|
}
|
|
1700
|
+
// STOP AGENT LOOP if shell_input was provided
|
|
1701
|
+
// Interactive shell input implies handing control back to the shell/user
|
|
1702
|
+
const hasShellInput = toolCalls.some(tc => tc.name === 'execute_command' && tc.arguments && tc.arguments.shell_input);
|
|
1703
|
+
if (hasShellInput) {
|
|
1704
|
+
try {
|
|
1705
|
+
quickLog(`[${new Date().toISOString()}] [CLI] Input sent to shell. Stopping agent loop to await output.\n`);
|
|
1706
|
+
}
|
|
1707
|
+
catch (e) { }
|
|
1708
|
+
taskCompleted = true;
|
|
1709
|
+
}
|
|
1445
1710
|
// If task_complete was called, stop the agentic loop immediately
|
|
1446
1711
|
if (taskCompleted) {
|
|
1447
1712
|
// Set the final message: use summary if provided, otherwise use the streamed assistantMessage
|
|
@@ -1556,8 +1821,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1556
1821
|
const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
|
|
1557
1822
|
'**This is not allowed.** You must either:\n' +
|
|
1558
1823
|
'1. Execute a tool call if more work is needed, OR\n' +
|
|
1559
|
-
'2.
|
|
1560
|
-
'**If you have completed the task**,
|
|
1824
|
+
'2. Output your response text, then call task_complete()\n\n' +
|
|
1825
|
+
'**If you have completed the task**, output your summary now, then call task_complete().\n' +
|
|
1561
1826
|
'**If more work is needed**, execute the next tool call immediately.';
|
|
1562
1827
|
conversationLogger.logSystemPrompt('silent_stop_prompt', silentStopPrompt);
|
|
1563
1828
|
this.conversationHistory.push({
|
|
@@ -1565,113 +1830,17 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1565
1830
|
content: silentStopPrompt,
|
|
1566
1831
|
});
|
|
1567
1832
|
}
|
|
1568
|
-
// Case 2: Text-only response
|
|
1833
|
+
// Case 2: Text-only response - accept it immediately as final
|
|
1569
1834
|
else {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
// If AI keeps narrating without executing, force completion immediately
|
|
1580
|
-
if (narrationAttempts >= MAX_NARRATION_ATTEMPTS) {
|
|
1581
|
-
// Force task completion with error message
|
|
1582
|
-
conversationLogger.logNarrationDetection('narration', {
|
|
1583
|
-
action: 'forced_completion',
|
|
1584
|
-
reason: 'max_narration_attempts_reached',
|
|
1585
|
-
});
|
|
1586
|
-
finalAssistantMessage = '⚠️ **Task Incomplete**: The AI repeatedly described actions without executing them.\n\n' +
|
|
1587
|
-
'**What happened**: The AI entered a narration loop, describing what it wanted to do instead of using tool calls.\n\n' +
|
|
1588
|
-
'**Suggestions**:\n' +
|
|
1589
|
-
'1. Try rephrasing your request more specifically\n' +
|
|
1590
|
-
'2. Break the task into smaller, concrete steps\n' +
|
|
1591
|
-
'3. Provide explicit file paths if known\n' +
|
|
1592
|
-
'4. Check if the model supports tool calling properly\n\n' +
|
|
1593
|
-
'**Last message**: ' + assistantMessage;
|
|
1594
|
-
break;
|
|
1595
|
-
}
|
|
1596
|
-
// First narration attempt - give a strong warning with specific guidance
|
|
1597
|
-
if (narrationAttempts === 1) {
|
|
1598
|
-
const completionPrompt = '🛑 **CRITICAL ERROR**: You output text without using tools.\n\n' +
|
|
1599
|
-
'**COMMUNICATION RULE VIOLATION**: You can ONLY communicate through:\n' +
|
|
1600
|
-
'1. `reason_text` parameter in tool calls\n' +
|
|
1601
|
-
'2. `summary` parameter in task_complete tool\n\n' +
|
|
1602
|
-
'**Your text output was HIDDEN from the user.**\n\n' +
|
|
1603
|
-
'**MANDATORY CORRECTION**:\n' +
|
|
1604
|
-
'- If you need to DO something: Call the tool with `reason_text`\n' +
|
|
1605
|
-
'- If you are DONE: Call `task_complete(summary="your message")`\n' +
|
|
1606
|
-
'- NEVER output plain text - it will be hidden\n\n' +
|
|
1607
|
-
'**Example for greeting**:\n' +
|
|
1608
|
-
'```\n' +
|
|
1609
|
-
'<thought>User said hello, I should greet back</thought>\n' +
|
|
1610
|
-
'(Call task_complete with summary="Hello! How can I help you today?")\n' +
|
|
1611
|
-
'```\n\n' +
|
|
1612
|
-
'**Your NEXT response MUST use tools.**';
|
|
1613
|
-
this.conversationHistory.push({
|
|
1614
|
-
role: 'user',
|
|
1615
|
-
content: completionPrompt,
|
|
1616
|
-
});
|
|
1617
|
-
}
|
|
1618
|
-
else {
|
|
1619
|
-
// Second narration attempt - final warning before forced completion
|
|
1620
|
-
const completionPrompt = '🚨 **FINAL WARNING** (Attempt ' + narrationAttempts + '/' + MAX_NARRATION_ATTEMPTS + '): You are STILL narrating instead of executing.\n\n' +
|
|
1621
|
-
'**This is your LAST chance**:\n' +
|
|
1622
|
-
'1. Execute a tool call NOW, or\n' +
|
|
1623
|
-
'2. Call task_complete() to end\n\n' +
|
|
1624
|
-
'If you output narration text again, the task will be forcibly terminated.';
|
|
1625
|
-
this.conversationHistory.push({
|
|
1626
|
-
role: 'user',
|
|
1627
|
-
content: completionPrompt,
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
else {
|
|
1632
|
-
// AI output a response without narration - it should finish
|
|
1633
|
-
// Reset narration counter since this is a valid response
|
|
1634
|
-
narrationAttempts = 0;
|
|
1635
|
-
// Check if the message looks like a final answer/summary
|
|
1636
|
-
// If it has substantial length, assume it's a summary attempt
|
|
1637
|
-
const isFinalAnswer = assistantMessage.length > 20;
|
|
1638
|
-
if (isFinalAnswer) {
|
|
1639
|
-
completionAttempts++;
|
|
1640
|
-
conversationLogger.logNarrationDetection('final_answer', {
|
|
1641
|
-
turn: turnCount,
|
|
1642
|
-
completionAttempts,
|
|
1643
|
-
messagePreview: assistantMessage.substring(0, 200),
|
|
1644
|
-
});
|
|
1645
|
-
// If AI keeps providing text summaries without calling task_complete, accept the text and finish
|
|
1646
|
-
// This prevents the infinite loop where the AI keeps summarizing in response to our prompt
|
|
1647
|
-
if (completionAttempts > 1) {
|
|
1648
|
-
conversationLogger.logNarrationDetection('final_answer', {
|
|
1649
|
-
action: 'accepting_text_as_final',
|
|
1650
|
-
reason: 'multiple_completion_attempts',
|
|
1651
|
-
});
|
|
1652
|
-
finalAssistantMessage = assistantMessage;
|
|
1653
|
-
break;
|
|
1654
|
-
}
|
|
1655
|
-
// This looks like a final answer - prompt to call task_complete
|
|
1656
|
-
const completionPrompt = '✅ **Possible Completion Detected**: You provided a text response but did not call `task_complete`.\n\n' +
|
|
1657
|
-
'**To finish the conversation, you MUST call the `task_complete` tool.**\n\n' +
|
|
1658
|
-
'Please call `task_complete` now with your summary as the argument.';
|
|
1659
|
-
this.conversationHistory.push({
|
|
1660
|
-
role: 'user',
|
|
1661
|
-
content: completionPrompt,
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
|
-
else {
|
|
1665
|
-
// Short message without clear intent - ask for clarification or completion
|
|
1666
|
-
const completionPrompt = 'Your response is unclear. Either:\n' +
|
|
1667
|
-
'1. Execute the next tool call if more work is needed, or\n' +
|
|
1668
|
-
'2. Call task_complete() if the task is done';
|
|
1669
|
-
this.conversationHistory.push({
|
|
1670
|
-
role: 'user',
|
|
1671
|
-
content: completionPrompt,
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1835
|
+
// Log that we're accepting this as a final answer
|
|
1836
|
+
conversationLogger.logNarrationDetection('final_answer', {
|
|
1837
|
+
turn: turnCount,
|
|
1838
|
+
messagePreview: assistantMessage.substring(0, 200),
|
|
1839
|
+
action: 'accepting_immediately',
|
|
1840
|
+
});
|
|
1841
|
+
// Accept the text as the final message and break
|
|
1842
|
+
finalAssistantMessage = assistantMessage;
|
|
1843
|
+
break;
|
|
1675
1844
|
}
|
|
1676
1845
|
// Rebuild messages array with updated history
|
|
1677
1846
|
// Backend will inject system prompt
|
|
@@ -1687,8 +1856,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1687
1856
|
const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
|
|
1688
1857
|
'**This is not allowed.** You must either:\n' +
|
|
1689
1858
|
'1. Execute a tool call if more work is needed, OR\n' +
|
|
1690
|
-
'2.
|
|
1691
|
-
'**If you have completed the task**,
|
|
1859
|
+
'2. Output your response text, then call task_complete()\n\n' +
|
|
1860
|
+
'**If you have completed the task**, output your summary now, then call task_complete().\n' +
|
|
1692
1861
|
'**If more work is needed**, execute the next tool call immediately.';
|
|
1693
1862
|
this.conversationHistory.push({
|
|
1694
1863
|
role: 'user',
|
|
@@ -2092,7 +2261,7 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2092
2261
|
'• Work silently without narrating actions\n' +
|
|
2093
2262
|
'• Use Touch-First safety (never guess file paths)\n' +
|
|
2094
2263
|
'• Apply surgical precision to file edits\n' +
|
|
2095
|
-
'•
|
|
2264
|
+
'• Output summary text, then call task_complete() when done\n' +
|
|
2096
2265
|
'• Inject intelligent error recovery hints\n\n' +
|
|
2097
2266
|
'This is the industry-standard autonomous agent mode.'
|
|
2098
2267
|
: '⚠️ Autonomous Mode disabled\n\n' +
|
|
@@ -3034,6 +3203,19 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
3034
3203
|
this.onCwdChange(chat.cwd);
|
|
3035
3204
|
}
|
|
3036
3205
|
}
|
|
3206
|
+
// Reset context limit state when loading a chat
|
|
3207
|
+
// We'll recalculate it based on the loaded conversation
|
|
3208
|
+
if (this.contextLimitReached) {
|
|
3209
|
+
this.contextLimitReached = false;
|
|
3210
|
+
if (this.onContextLimitReached) {
|
|
3211
|
+
this.onContextLimitReached(false);
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
// Update token count to reflect loaded conversation
|
|
3215
|
+
// This will also check if the loaded chat is near the limit
|
|
3216
|
+
this.updateTokenCount().catch(err => {
|
|
3217
|
+
quickLog(`[${new Date().toISOString()}] [loadChatFromPicker] Failed to update token count: ${err}\n`);
|
|
3218
|
+
});
|
|
3037
3219
|
return true;
|
|
3038
3220
|
}
|
|
3039
3221
|
/**
|
|
@@ -3202,6 +3384,17 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
3202
3384
|
this.uiMessageHistory = [];
|
|
3203
3385
|
this.localCwdBeforeRemote = null;
|
|
3204
3386
|
this.lastConnectionCommand = null;
|
|
3387
|
+
// Reset context limit state
|
|
3388
|
+
if (this.contextLimitReached) {
|
|
3389
|
+
this.contextLimitReached = false;
|
|
3390
|
+
if (this.onContextLimitReached) {
|
|
3391
|
+
this.onContextLimitReached(false);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
// Update token count to reflect empty conversation
|
|
3395
|
+
this.updateTokenCount().catch(err => {
|
|
3396
|
+
quickLog(`[${new Date().toISOString()}] [startNewChat] Failed to update token count: ${err}\n`);
|
|
3397
|
+
});
|
|
3205
3398
|
}
|
|
3206
3399
|
/**
|
|
3207
3400
|
* Update UI message history (called from App.tsx via callback)
|