centaurus-cli 2.9.0 → 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 +78 -0
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +566 -165
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/build-config.d.ts +1 -1
- package/dist/config/build-config.js +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 +5 -3
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/index.js +66 -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/ai-service-client.d.ts +6 -1
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +6 -6
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +20 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +35 -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/session-quota-manager.d.ts +101 -0
- package/dist/services/session-quota-manager.d.ts.map +1 -0
- package/dist/services/session-quota-manager.js +242 -0
- package/dist/services/session-quota-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 +78 -4
- 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.d.ts.map +1 -1
- package/dist/tools/get-diff.js +5 -2
- 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 -32
- package/dist/tools/task-complete.js.map +1 -1
- package/dist/ui/components/App.d.ts +45 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +598 -95
- 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 +4 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +40 -2
- 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 +271 -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/dist/utils/editor-utils.d.ts +3 -3
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +15 -12
- package/dist/utils/editor-utils.js.map +1 -1
- package/dist/utils/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +1 -0
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/terminal-output.d.ts.map +1 -1
- package/dist/utils/terminal-output.js +198 -171
- package/dist/utils/terminal-output.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';
|
|
@@ -38,6 +41,7 @@ import { conversationLogger, quickLog } from './utils/conversation-logger.js';
|
|
|
38
41
|
import { localChatStorage } from './services/local-chat-storage.js';
|
|
39
42
|
import { logWarning } from './utils/logger.js';
|
|
40
43
|
import { BackgroundTaskManager } from './services/background-task-manager.js';
|
|
44
|
+
import { sessionQuotaManager } from './services/session-quota-manager.js';
|
|
41
45
|
export class CentaurusCLI {
|
|
42
46
|
configManager;
|
|
43
47
|
toolRegistry;
|
|
@@ -47,10 +51,12 @@ export class CentaurusCLI {
|
|
|
47
51
|
pendingPlanRequest = null; // Stores original user request during planning phase
|
|
48
52
|
commandMode = false;
|
|
49
53
|
backgroundMode = false; // Background shell mode for running commands in background
|
|
54
|
+
shellIdCounter = 1;
|
|
50
55
|
previousMode = 'execution';
|
|
51
56
|
onResponseCallback;
|
|
52
57
|
onDirectMessageCallback; // For slash commands - adds directly to history
|
|
53
58
|
onResponseStreamCallback;
|
|
59
|
+
onClearStreamedResponse; // Clear streamed text when task_complete has summary
|
|
54
60
|
onThoughtStreamCallback;
|
|
55
61
|
onThoughtCompleteCallback;
|
|
56
62
|
onCommandModeChange;
|
|
@@ -92,6 +98,18 @@ export class CentaurusCLI {
|
|
|
92
98
|
onShowBackgroundTaskCancelPickerCallback;
|
|
93
99
|
onBackgroundTaskViewCallback;
|
|
94
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
|
|
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
|
|
95
113
|
constructor() {
|
|
96
114
|
this.configManager = new ConfigManager();
|
|
97
115
|
this.toolRegistry = new ToolRegistry();
|
|
@@ -112,6 +130,11 @@ export class CentaurusCLI {
|
|
|
112
130
|
});
|
|
113
131
|
// Initialize MCP
|
|
114
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
|
+
});
|
|
115
138
|
}
|
|
116
139
|
setOnResponseCallback(callback) {
|
|
117
140
|
this.onResponseCallback = callback;
|
|
@@ -122,6 +145,9 @@ export class CentaurusCLI {
|
|
|
122
145
|
setOnResponseStreamCallback(callback) {
|
|
123
146
|
this.onResponseStreamCallback = callback;
|
|
124
147
|
}
|
|
148
|
+
setOnClearStreamedResponse(callback) {
|
|
149
|
+
this.onClearStreamedResponse = callback;
|
|
150
|
+
}
|
|
125
151
|
setOnThoughtStreamCallback(callback) {
|
|
126
152
|
this.onThoughtStreamCallback = callback;
|
|
127
153
|
}
|
|
@@ -180,11 +206,173 @@ export class CentaurusCLI {
|
|
|
180
206
|
setOnTokenCountUpdate(callback) {
|
|
181
207
|
this.onTokenCountUpdate = callback;
|
|
182
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
|
+
}
|
|
287
|
+
setOnSessionQuotaUpdate(callback) {
|
|
288
|
+
this.onSessionQuotaUpdate = callback;
|
|
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
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Notify UI about session quota status
|
|
336
|
+
*/
|
|
337
|
+
notifySessionQuotaStatus() {
|
|
338
|
+
if (this.onSessionQuotaUpdate) {
|
|
339
|
+
const remaining = sessionQuotaManager.getRemainingMessages();
|
|
340
|
+
const canSend = sessionQuotaManager.canSendMessage();
|
|
341
|
+
const timeRemaining = sessionQuotaManager.getFormattedTimeRemaining();
|
|
342
|
+
this.onSessionQuotaUpdate(remaining, canSend, timeRemaining);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
183
345
|
async initializeMCP() {
|
|
184
346
|
try {
|
|
185
347
|
const mcpConfigManager = new MCPConfigManager();
|
|
186
348
|
const mcpServerManager = new MCPServerManager();
|
|
187
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
|
+
});
|
|
188
376
|
// Initialize MCP servers and tools
|
|
189
377
|
await this.mcpCommandHandler.initializeMCP();
|
|
190
378
|
}
|
|
@@ -229,12 +417,22 @@ export class CentaurusCLI {
|
|
|
229
417
|
getCommandMode() {
|
|
230
418
|
return this.commandMode;
|
|
231
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
|
+
}
|
|
232
427
|
getCurrentWorkingDirectory() {
|
|
233
428
|
return this.cwd;
|
|
234
429
|
}
|
|
235
430
|
getCurrentSubshellContext() {
|
|
236
431
|
return this.contextManager.getCurrentContext();
|
|
237
432
|
}
|
|
433
|
+
getCurrentInteractiveProcess() {
|
|
434
|
+
return this.currentInteractiveProcess;
|
|
435
|
+
}
|
|
238
436
|
/**
|
|
239
437
|
* Get the current conversation ID for file uploads
|
|
240
438
|
*/
|
|
@@ -351,6 +549,14 @@ export class CentaurusCLI {
|
|
|
351
549
|
this.toolRegistry.register(readBinaryFileTool);
|
|
352
550
|
this.toolRegistry.register(createImageTool);
|
|
353
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
|
+
});
|
|
354
560
|
// Load configuration
|
|
355
561
|
const config = this.configManager.load();
|
|
356
562
|
// Enable backend sync if authenticated
|
|
@@ -367,6 +573,11 @@ export class CentaurusCLI {
|
|
|
367
573
|
const dockerHandler = new DockerHandler();
|
|
368
574
|
this.contextManager.registerHandler('docker', dockerHandler);
|
|
369
575
|
this.commandDetector.registerHandler(dockerHandler);
|
|
576
|
+
// Fetch rate limits configuration from backend (async, non-blocking)
|
|
577
|
+
// Uses cached values if backend is unreachable
|
|
578
|
+
sessionQuotaManager.fetchConfigFromBackend().catch(() => {
|
|
579
|
+
// Silently fall back to cached/default config
|
|
580
|
+
});
|
|
370
581
|
// Note: No need to initialize AI provider - using backend proxy via aiServiceClient
|
|
371
582
|
}
|
|
372
583
|
/**
|
|
@@ -526,6 +737,46 @@ Press Enter to continue...
|
|
|
526
737
|
if (!apiClient.isAuthenticated()) {
|
|
527
738
|
throw new Error('Authentication required. Please sign in to use AI features.');
|
|
528
739
|
}
|
|
740
|
+
// Check session quota before making any AI request
|
|
741
|
+
if (!sessionQuotaManager.canSendMessage()) {
|
|
742
|
+
const timeRemaining = sessionQuotaManager.getFormattedTimeRemaining();
|
|
743
|
+
const message = `\n⚠️ Session quota reached. You have used all ${sessionQuotaManager.getCurrentConfig().maxMessagesPerSession} messages for this session.\n\nYour quota will reset in ${timeRemaining}.\n\nYou can still use:\n • Slash commands (e.g., /help, /session-limits, /exit)\n • Terminal commands (in Command mode)\n\nUse /session-limits to check your quota status.`;
|
|
744
|
+
if (this.onDirectMessageCallback) {
|
|
745
|
+
this.onDirectMessageCallback(message);
|
|
746
|
+
}
|
|
747
|
+
// Notify UI about quota status
|
|
748
|
+
this.notifySessionQuotaStatus();
|
|
749
|
+
return;
|
|
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
|
+
}
|
|
529
780
|
// Cancel any active request when a new message comes in
|
|
530
781
|
// This enables "interrupt and replace" - new message takes priority
|
|
531
782
|
if (this.currentAbortController) {
|
|
@@ -577,6 +828,12 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
577
828
|
contextManager: this.contextManager,
|
|
578
829
|
cliAdapter: this, // Pass CLI adapter reference for interactive process management
|
|
579
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
|
+
}
|
|
580
837
|
if (this.onToolApprovalRequest) {
|
|
581
838
|
return await this.onToolApprovalRequest({ message, risky, preview, operationType, operationDetails });
|
|
582
839
|
}
|
|
@@ -610,14 +867,11 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
610
867
|
const mode = this.getMode();
|
|
611
868
|
let finalAssistantMessage = '';
|
|
612
869
|
const MAX_TURNS = 500; // Allow up to 500 turns for complex tasks
|
|
613
|
-
const MAX_TOOL_CALLS_PER_TURN = 5; // Limit tool calls per turn to prevent overthinking
|
|
614
|
-
const MAX_NARRATION_ATTEMPTS = 3; // Maximum times we'll prompt AI to stop narrating
|
|
615
870
|
let turnCount = 0;
|
|
616
|
-
let narrationAttempts = 0; // Track how many times AI narrated without executing
|
|
617
|
-
let completionAttempts = 0; // Track how many times AI provided text summary without task_complete
|
|
618
871
|
let thoughtStartTime = null; // Track when thinking started
|
|
619
872
|
let thoughtContent = ''; // Accumulate thought content during streaming
|
|
620
873
|
let currentTurnThinking = ''; // Persist thinking for the current turn to attach to assistant message
|
|
874
|
+
let currentTurnThinkingSignature = ''; // Persist thinking signature for Claude extended thinking
|
|
621
875
|
// ANTI-LOOP: Track duplicate tool calls to detect infinite loops
|
|
622
876
|
const MAX_DUPLICATE_CALLS = 2; // Max times same operation allowed on same target
|
|
623
877
|
const fileWriteTracker = new Map(); // Track writes per file
|
|
@@ -634,11 +888,31 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
634
888
|
// Multi-turn tool execution loop
|
|
635
889
|
while (turnCount < MAX_TURNS) {
|
|
636
890
|
turnCount++;
|
|
891
|
+
// Track session quota - each AI call in the agent loop counts as 1 message
|
|
892
|
+
sessionQuotaManager.incrementMessageCount();
|
|
893
|
+
this.notifySessionQuotaStatus();
|
|
894
|
+
// Check if session quota is now exhausted after incrementing
|
|
895
|
+
if (!sessionQuotaManager.canSendMessage() && turnCount > 1) {
|
|
896
|
+
// Quota exhausted mid-loop, stop and inform user
|
|
897
|
+
const timeRemaining = sessionQuotaManager.getFormattedTimeRemaining();
|
|
898
|
+
const quotaMessage = `\n\n⚠️ **Session quota reached** during agent execution.\n\nYou have used all ${sessionQuotaManager.getCurrentConfig().maxMessagesPerSession} messages for this session.\nQuota will reset in ${timeRemaining}.\n\nYour current task may be incomplete. You can resume when your quota resets.\n\nUse /session-limits to check your quota status.`;
|
|
899
|
+
if (this.onResponseCallback) {
|
|
900
|
+
this.onResponseCallback(quotaMessage);
|
|
901
|
+
}
|
|
902
|
+
logWarning('Agent loop stopped due to session quota exhaustion');
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
637
905
|
// Refresh environment context to capture any CWD changes from previous turns
|
|
638
906
|
// This is sent to backend which will inject it into the system prompt
|
|
639
907
|
environmentContext = this.getEnvironmentContext();
|
|
640
908
|
let assistantMessage = '';
|
|
641
909
|
let toolCalls = [];
|
|
910
|
+
// REAL-TIME TOOL EXECUTION: Track execution state and results during streaming
|
|
911
|
+
const inStreamToolResults = []; // Results from tools executed during streaming
|
|
912
|
+
const inStreamHandledIds = new Set(); // IDs of tools already executed in-stream
|
|
913
|
+
let toolsExecutedInStream = false; // Flag to indicate tools were executed during stream
|
|
914
|
+
let pendingTextBuffer = ''; // Buffer for text while tool is executing
|
|
915
|
+
let isToolExecuting = false; // Flag to pause text streaming during tool execution
|
|
642
916
|
// DEBUG: Log message history state before AI call
|
|
643
917
|
const messageStats = {
|
|
644
918
|
totalMessages: messages.length,
|
|
@@ -679,13 +953,11 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
679
953
|
quickLog(`[${new Date().toISOString()}] [CLI] Assistant messages with tool_calls: ${messageStats.assistantWithToolCalls}\n`);
|
|
680
954
|
}
|
|
681
955
|
catch (e) { }
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
this.onTokenCountUpdate(estimatedTokens);
|
|
688
|
-
}
|
|
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
|
+
});
|
|
689
961
|
// Stream AI response from backend
|
|
690
962
|
// Backend will inject system prompt automatically with environment context
|
|
691
963
|
for await (const chunk of aiServiceClient.streamChat(selectedModel, messages, tools, environmentContext, mode, selectedModelThinkingConfig, this.currentAbortController.signal)) {
|
|
@@ -717,6 +989,12 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
717
989
|
}
|
|
718
990
|
continue;
|
|
719
991
|
}
|
|
992
|
+
// Handle thinking_signature chunks (Claude extended thinking)
|
|
993
|
+
if (chunk.type === 'thinking_signature') {
|
|
994
|
+
// Store the signature for this turn - it must be passed back with thinking content
|
|
995
|
+
currentTurnThinkingSignature = chunk.signature;
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
720
998
|
// Handle text chunks
|
|
721
999
|
if (chunk.type === 'text') {
|
|
722
1000
|
// If we were thinking and now got text, finalize the thought
|
|
@@ -738,18 +1016,35 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
738
1016
|
if (filteredContent) {
|
|
739
1017
|
assistantMessage += filteredContent;
|
|
740
1018
|
conversationLogger.logAITextChunk(filteredContent);
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
1019
|
+
// REAL-TIME TOOL EXECUTION: If a tool is executing, accumulate text
|
|
1020
|
+
// This text will be flushed after the tool completes
|
|
1021
|
+
if (isToolExecuting) {
|
|
1022
|
+
pendingTextBuffer += filteredContent;
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
// Normal streaming - send chunk to UI in real-time
|
|
1026
|
+
if (this.onResponseStreamCallback) {
|
|
1027
|
+
this.onResponseStreamCallback(filteredContent);
|
|
1028
|
+
}
|
|
744
1029
|
}
|
|
745
1030
|
}
|
|
746
1031
|
}
|
|
747
1032
|
// Handle tool call chunks
|
|
748
1033
|
if (chunk.type === 'tool_call') {
|
|
749
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
|
+
}
|
|
750
1045
|
// Debug: Log every tool_call chunk received
|
|
751
1046
|
try {
|
|
752
|
-
quickLog(`[${new Date().toISOString()}] [CLI] *** TOOL_CALL CHUNK RECEIVED: ${toolCall?.name || 'unknown'}
|
|
1047
|
+
quickLog(`[${new Date().toISOString()}] [CLI] *** TOOL_CALL CHUNK RECEIVED (REAL-TIME): ${toolCall?.name || 'unknown'}\n`);
|
|
753
1048
|
}
|
|
754
1049
|
catch (e) { }
|
|
755
1050
|
conversationLogger.logToolCall(toolCall?.name || 'unknown', toolCall?.id || 'unknown', toolCall?.arguments || {});
|
|
@@ -766,18 +1061,98 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
766
1061
|
thoughtContent = '';
|
|
767
1062
|
}
|
|
768
1063
|
toolCalls.push(chunk.toolCall);
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1064
|
+
// SPECIAL TOOLS: Skip in-stream execution for tools that need post-stream handling
|
|
1065
|
+
// These tools have special logic (setting flags, clearing state, etc.) that must run post-stream
|
|
1066
|
+
const SPECIAL_TOOLS = ['task_complete', 'create_plan', 'mark_task_complete'];
|
|
1067
|
+
if (SPECIAL_TOOLS.includes(toolCall.name)) {
|
|
1068
|
+
// Just notify UI with pending status, execute in post-stream loop
|
|
1069
|
+
if (this.onToolExecutionUpdate) {
|
|
1070
|
+
this.onToolExecutionUpdate({
|
|
1071
|
+
toolName: toolCall.name,
|
|
1072
|
+
status: 'pending',
|
|
1073
|
+
arguments: toolCall.arguments
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
continue; // Skip to next chunk, handle this tool in post-stream loop
|
|
1077
|
+
}
|
|
1078
|
+
// Mark that we're executing a tool (text will accumulate)
|
|
1079
|
+
isToolExecuting = true;
|
|
1080
|
+
toolsExecutedInStream = true;
|
|
1081
|
+
// REAL-TIME EXECUTION: Execute tool immediately during streaming
|
|
1082
|
+
// This reduces latency by not waiting for the entire stream to finish
|
|
1083
|
+
try {
|
|
1084
|
+
// Extract and display reason_text if present (skip for task_complete and shell_input)
|
|
1085
|
+
const reasonText = toolCall.arguments.reason_text;
|
|
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) {
|
|
1089
|
+
this.onResponseStreamCallback(reasonText + '\n\n');
|
|
1090
|
+
}
|
|
1091
|
+
// Show 'executing' status immediately
|
|
1092
|
+
this.notifyToolStatus(toolCall.name, 'executing', toolCall.arguments);
|
|
1093
|
+
// Log tool execution start
|
|
1094
|
+
conversationLogger.logToolExecutionStart(toolCall.name, toolCall.id);
|
|
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
|
+
}
|
|
1104
|
+
const result = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
1105
|
+
if (result.success) {
|
|
1106
|
+
conversationLogger.logToolResult(toolCall.name, toolCall.id, result.result, true);
|
|
1107
|
+
// Notify UI: tool succeeded
|
|
1108
|
+
this.notifyToolStatus(toolCall.name, 'completed', toolCall.arguments, result.result);
|
|
1109
|
+
// Parse and truncate result for AI
|
|
1110
|
+
let parsedResult = result.result;
|
|
1111
|
+
if (typeof result.result === 'string') {
|
|
1112
|
+
try {
|
|
1113
|
+
parsedResult = JSON.parse(result.result);
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
parsedResult = result.result;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
inStreamToolResults.push({
|
|
1120
|
+
tool_call_id: toolCall.id,
|
|
1121
|
+
name: toolCall.name,
|
|
1122
|
+
result: this.truncateResult(parsedResult),
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
conversationLogger.logToolResult(toolCall.name, toolCall.id, null, false, result.error);
|
|
1127
|
+
// Notify UI: tool failed
|
|
1128
|
+
this.notifyToolStatus(toolCall.name, 'error', toolCall.arguments, undefined, result.error);
|
|
1129
|
+
inStreamToolResults.push({
|
|
1130
|
+
tool_call_id: toolCall.id,
|
|
1131
|
+
name: toolCall.name,
|
|
1132
|
+
result: `Error: ${result.error}`,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
inStreamHandledIds.add(toolCall.id);
|
|
1136
|
+
}
|
|
1137
|
+
catch (error) {
|
|
1138
|
+
conversationLogger.logError(`Tool execution: ${toolCall.name}`, error);
|
|
1139
|
+
this.notifyToolStatus(toolCall.name, 'error', toolCall.arguments, undefined, error.message);
|
|
1140
|
+
inStreamToolResults.push({
|
|
1141
|
+
tool_call_id: toolCall.id,
|
|
1142
|
+
name: toolCall.name,
|
|
1143
|
+
result: `Error: ${error.message}`,
|
|
776
1144
|
});
|
|
1145
|
+
inStreamHandledIds.add(toolCall.id);
|
|
1146
|
+
}
|
|
1147
|
+
// Tool execution complete - flush pending text
|
|
1148
|
+
isToolExecuting = false;
|
|
1149
|
+
if (pendingTextBuffer && this.onResponseStreamCallback) {
|
|
1150
|
+
this.onResponseStreamCallback(pendingTextBuffer);
|
|
1151
|
+
pendingTextBuffer = '';
|
|
777
1152
|
}
|
|
778
|
-
// Debug: Log after
|
|
1153
|
+
// Debug: Log after execution
|
|
779
1154
|
try {
|
|
780
|
-
quickLog(`[${new Date().toISOString()}] [CLI] ***
|
|
1155
|
+
quickLog(`[${new Date().toISOString()}] [CLI] *** TOOL EXECUTED IN-STREAM: ${toolCall?.name || 'unknown'}\n`);
|
|
781
1156
|
}
|
|
782
1157
|
catch (e) { }
|
|
783
1158
|
}
|
|
@@ -807,8 +1182,6 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
807
1182
|
assistantMessageLength: assistantMessage.length,
|
|
808
1183
|
hasToolCalls: toolCalls.length > 0,
|
|
809
1184
|
willContinue: toolCalls.length > 0,
|
|
810
|
-
narrationAttempts,
|
|
811
|
-
completionAttempts,
|
|
812
1185
|
});
|
|
813
1186
|
// If there are tool calls, execute them
|
|
814
1187
|
if (toolCalls.length > 0) {
|
|
@@ -821,40 +1194,48 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
821
1194
|
// Suppress text output - AI should only use reason_text
|
|
822
1195
|
assistantMessage = ''; // Clear ALL text output - AI should only use reason_text
|
|
823
1196
|
}
|
|
824
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
toolCalls = toolCalls.slice(0, MAX_TOOL_CALLS_PER_TURN);
|
|
828
|
-
}
|
|
829
|
-
const toolResults = [];
|
|
830
|
-
const handledToolCallIds = new Set(); // Track tool calls that were handled directly (e.g., create_plan, mark_task_complete)
|
|
1197
|
+
// Tool call limit removed - let AI use as many tools as needed per turn
|
|
1198
|
+
const toolResults = [...inStreamToolResults]; // Start with in-stream results
|
|
1199
|
+
const handledToolCallIds = new Set(); // Only for special tools (create_plan, mark_task_complete)
|
|
831
1200
|
let userCancelledOperation = false;
|
|
832
1201
|
let taskCompleted = false;
|
|
833
1202
|
let taskCompleteSummary = '';
|
|
834
1203
|
for (let i = 0; i < toolCalls.length; i++) {
|
|
1204
|
+
const toolCall = toolCalls[i];
|
|
1205
|
+
// REAL-TIME EXECUTION: Skip tools that were already executed in-stream
|
|
1206
|
+
if (inStreamHandledIds.has(toolCall.id)) {
|
|
1207
|
+
try {
|
|
1208
|
+
quickLog(`[${new Date().toISOString()}] [CLI] *** SKIPPING TOOL (already executed in-stream): ${toolCall.name}\n`);
|
|
1209
|
+
}
|
|
1210
|
+
catch (e) { }
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
835
1213
|
// Debug: Log which tool we're about to execute
|
|
836
1214
|
try {
|
|
837
1215
|
quickLog(`[${new Date().toISOString()}] [CLI] *** ABOUT TO EXECUTE TOOL [${i + 1}/${toolCalls.length}]: ${toolCalls[i].name}\n`);
|
|
838
1216
|
}
|
|
839
1217
|
catch (e) { }
|
|
840
|
-
const toolCall = toolCalls[i];
|
|
841
1218
|
try {
|
|
842
1219
|
// Check if this is task_complete FIRST (before displaying anything)
|
|
843
1220
|
if (toolCall.name === 'task_complete') {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
|
856
1233
|
}
|
|
857
|
-
|
|
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
|
|
858
1239
|
// Execute the tool for proper result handling
|
|
859
1240
|
await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
860
1241
|
// Clear the plan when task is complete
|
|
@@ -898,6 +1279,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
898
1279
|
if (currentTurnThinking) {
|
|
899
1280
|
planAssistantMsg.thinking = currentTurnThinking;
|
|
900
1281
|
}
|
|
1282
|
+
if (currentTurnThinkingSignature) {
|
|
1283
|
+
planAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1284
|
+
}
|
|
901
1285
|
this.conversationHistory.push(planAssistantMsg);
|
|
902
1286
|
// Add plan approval response
|
|
903
1287
|
this.conversationHistory.push({
|
|
@@ -942,6 +1326,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
942
1326
|
if (currentTurnThinking) {
|
|
943
1327
|
planAssistantMsg.thinking = currentTurnThinking;
|
|
944
1328
|
}
|
|
1329
|
+
if (currentTurnThinkingSignature) {
|
|
1330
|
+
planAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1331
|
+
}
|
|
945
1332
|
this.conversationHistory.push(planAssistantMsg);
|
|
946
1333
|
this.conversationHistory.push({
|
|
947
1334
|
role: 'tool',
|
|
@@ -970,6 +1357,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
970
1357
|
if (currentTurnThinking) {
|
|
971
1358
|
errorAssistantMsg.thinking = currentTurnThinking;
|
|
972
1359
|
}
|
|
1360
|
+
if (currentTurnThinkingSignature) {
|
|
1361
|
+
errorAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1362
|
+
}
|
|
973
1363
|
this.conversationHistory.push(errorAssistantMsg);
|
|
974
1364
|
this.conversationHistory.push({
|
|
975
1365
|
role: 'tool',
|
|
@@ -991,6 +1381,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
991
1381
|
if (currentTurnThinking) {
|
|
992
1382
|
resultAssistantMsg.thinking = currentTurnThinking;
|
|
993
1383
|
}
|
|
1384
|
+
if (currentTurnThinkingSignature) {
|
|
1385
|
+
resultAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1386
|
+
}
|
|
994
1387
|
this.conversationHistory.push(resultAssistantMsg);
|
|
995
1388
|
this.conversationHistory.push({
|
|
996
1389
|
role: 'tool',
|
|
@@ -1049,6 +1442,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1049
1442
|
if (currentTurnThinking) {
|
|
1050
1443
|
nextPhaseAssistantMsg.thinking = currentTurnThinking;
|
|
1051
1444
|
}
|
|
1445
|
+
if (currentTurnThinkingSignature) {
|
|
1446
|
+
nextPhaseAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1447
|
+
}
|
|
1052
1448
|
this.conversationHistory.push(nextPhaseAssistantMsg);
|
|
1053
1449
|
this.conversationHistory.push({
|
|
1054
1450
|
role: 'tool',
|
|
@@ -1072,7 +1468,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1072
1468
|
tool_call_id: toolCall.id,
|
|
1073
1469
|
name: toolCall.name,
|
|
1074
1470
|
result: completion.allComplete
|
|
1075
|
-
? 'All tasks completed!
|
|
1471
|
+
? 'All tasks completed! Output your summary of what was accomplished, then call task_complete().'
|
|
1076
1472
|
: completion.nextSubtask
|
|
1077
1473
|
? `Subtask ${completion.taskNumber} completed. Next subtask: ${completion.nextSubtask}`
|
|
1078
1474
|
: completion.nextTask
|
|
@@ -1082,7 +1478,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1082
1478
|
// If all tasks are complete, prompt AI to call task_complete
|
|
1083
1479
|
if (completion.allComplete) {
|
|
1084
1480
|
toolResults[toolResults.length - 1].result =
|
|
1085
|
-
'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().';
|
|
1086
1482
|
}
|
|
1087
1483
|
}
|
|
1088
1484
|
catch (parseError) {
|
|
@@ -1146,12 +1542,14 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1146
1542
|
remoteContext = `docker:${metadata.containerId?.substring(0, 12) || 'container'}`;
|
|
1147
1543
|
}
|
|
1148
1544
|
}
|
|
1149
|
-
// Notify UI: tool
|
|
1545
|
+
// Notify UI: tool executing
|
|
1150
1546
|
if (this.onToolExecutionUpdate) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
+
}
|
|
1155
1553
|
this.onToolExecutionUpdate({
|
|
1156
1554
|
toolName: toolCall.name,
|
|
1157
1555
|
status: 'executing',
|
|
@@ -1161,6 +1559,31 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1161
1559
|
// Log tool execution start
|
|
1162
1560
|
conversationLogger.logToolExecutionStart(toolCall.name, toolCall.id);
|
|
1163
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
|
+
}
|
|
1164
1587
|
const result = await this.toolRegistry.execute(toolCall.name, toolCall.arguments, context);
|
|
1165
1588
|
if (result.success) {
|
|
1166
1589
|
// Log successful tool result
|
|
@@ -1274,6 +1697,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1274
1697
|
}
|
|
1275
1698
|
}
|
|
1276
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
|
+
}
|
|
1277
1710
|
// If task_complete was called, stop the agentic loop immediately
|
|
1278
1711
|
if (taskCompleted) {
|
|
1279
1712
|
// Set the final message: use summary if provided, otherwise use the streamed assistantMessage
|
|
@@ -1291,6 +1724,9 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1291
1724
|
if (currentTurnThinking) {
|
|
1292
1725
|
cancelledAssistantMsg.thinking = currentTurnThinking;
|
|
1293
1726
|
}
|
|
1727
|
+
if (currentTurnThinkingSignature) {
|
|
1728
|
+
cancelledAssistantMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1729
|
+
}
|
|
1294
1730
|
this.conversationHistory.push(cancelledAssistantMsg);
|
|
1295
1731
|
// Add tool results to history
|
|
1296
1732
|
for (const toolResult of toolResults) {
|
|
@@ -1338,6 +1774,16 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1338
1774
|
if (currentTurnThinking) {
|
|
1339
1775
|
assistantHistoryMsg.thinking = currentTurnThinking;
|
|
1340
1776
|
}
|
|
1777
|
+
// Include thinking signature from this turn (required for Claude extended thinking)
|
|
1778
|
+
if (currentTurnThinkingSignature) {
|
|
1779
|
+
assistantHistoryMsg.thinkingSignature = currentTurnThinkingSignature;
|
|
1780
|
+
}
|
|
1781
|
+
// Log signature info for debugging multi-turn flows
|
|
1782
|
+
const geminiSigCount = unhandledToolCalls.filter(tc => !!tc.thoughtSignature).length;
|
|
1783
|
+
try {
|
|
1784
|
+
quickLog(`[${new Date().toISOString()}] [CLI] *** STORING ASSISTANT MSG: ${unhandledToolCalls.length} tool_calls, Gemini signatures: ${geminiSigCount}, Claude thinking: ${!!currentTurnThinking}, Claude sig: ${!!currentTurnThinkingSignature}\n`);
|
|
1785
|
+
}
|
|
1786
|
+
catch (e) { }
|
|
1341
1787
|
this.conversationHistory.push(assistantHistoryMsg);
|
|
1342
1788
|
}
|
|
1343
1789
|
// Add tool results to conversation history as tool messages
|
|
@@ -1375,8 +1821,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1375
1821
|
const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
|
|
1376
1822
|
'**This is not allowed.** You must either:\n' +
|
|
1377
1823
|
'1. Execute a tool call if more work is needed, OR\n' +
|
|
1378
|
-
'2.
|
|
1379
|
-
'**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' +
|
|
1380
1826
|
'**If more work is needed**, execute the next tool call immediately.';
|
|
1381
1827
|
conversationLogger.logSystemPrompt('silent_stop_prompt', silentStopPrompt);
|
|
1382
1828
|
this.conversationHistory.push({
|
|
@@ -1384,113 +1830,17 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1384
1830
|
content: silentStopPrompt,
|
|
1385
1831
|
});
|
|
1386
1832
|
}
|
|
1387
|
-
// Case 2: Text-only response
|
|
1833
|
+
// Case 2: Text-only response - accept it immediately as final
|
|
1388
1834
|
else {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
// If AI keeps narrating without executing, force completion immediately
|
|
1399
|
-
if (narrationAttempts >= MAX_NARRATION_ATTEMPTS) {
|
|
1400
|
-
// Force task completion with error message
|
|
1401
|
-
conversationLogger.logNarrationDetection('narration', {
|
|
1402
|
-
action: 'forced_completion',
|
|
1403
|
-
reason: 'max_narration_attempts_reached',
|
|
1404
|
-
});
|
|
1405
|
-
finalAssistantMessage = '⚠️ **Task Incomplete**: The AI repeatedly described actions without executing them.\n\n' +
|
|
1406
|
-
'**What happened**: The AI entered a narration loop, describing what it wanted to do instead of using tool calls.\n\n' +
|
|
1407
|
-
'**Suggestions**:\n' +
|
|
1408
|
-
'1. Try rephrasing your request more specifically\n' +
|
|
1409
|
-
'2. Break the task into smaller, concrete steps\n' +
|
|
1410
|
-
'3. Provide explicit file paths if known\n' +
|
|
1411
|
-
'4. Check if the model supports tool calling properly\n\n' +
|
|
1412
|
-
'**Last message**: ' + assistantMessage;
|
|
1413
|
-
break;
|
|
1414
|
-
}
|
|
1415
|
-
// First narration attempt - give a strong warning with specific guidance
|
|
1416
|
-
if (narrationAttempts === 1) {
|
|
1417
|
-
const completionPrompt = '🛑 **CRITICAL ERROR**: You output text without using tools.\n\n' +
|
|
1418
|
-
'**COMMUNICATION RULE VIOLATION**: You can ONLY communicate through:\n' +
|
|
1419
|
-
'1. `reason_text` parameter in tool calls\n' +
|
|
1420
|
-
'2. `summary` parameter in task_complete tool\n\n' +
|
|
1421
|
-
'**Your text output was HIDDEN from the user.**\n\n' +
|
|
1422
|
-
'**MANDATORY CORRECTION**:\n' +
|
|
1423
|
-
'- If you need to DO something: Call the tool with `reason_text`\n' +
|
|
1424
|
-
'- If you are DONE: Call `task_complete(summary="your message")`\n' +
|
|
1425
|
-
'- NEVER output plain text - it will be hidden\n\n' +
|
|
1426
|
-
'**Example for greeting**:\n' +
|
|
1427
|
-
'```\n' +
|
|
1428
|
-
'<thought>User said hello, I should greet back</thought>\n' +
|
|
1429
|
-
'(Call task_complete with summary="Hello! How can I help you today?")\n' +
|
|
1430
|
-
'```\n\n' +
|
|
1431
|
-
'**Your NEXT response MUST use tools.**';
|
|
1432
|
-
this.conversationHistory.push({
|
|
1433
|
-
role: 'user',
|
|
1434
|
-
content: completionPrompt,
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
else {
|
|
1438
|
-
// Second narration attempt - final warning before forced completion
|
|
1439
|
-
const completionPrompt = '🚨 **FINAL WARNING** (Attempt ' + narrationAttempts + '/' + MAX_NARRATION_ATTEMPTS + '): You are STILL narrating instead of executing.\n\n' +
|
|
1440
|
-
'**This is your LAST chance**:\n' +
|
|
1441
|
-
'1. Execute a tool call NOW, or\n' +
|
|
1442
|
-
'2. Call task_complete() to end\n\n' +
|
|
1443
|
-
'If you output narration text again, the task will be forcibly terminated.';
|
|
1444
|
-
this.conversationHistory.push({
|
|
1445
|
-
role: 'user',
|
|
1446
|
-
content: completionPrompt,
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
else {
|
|
1451
|
-
// AI output a response without narration - it should finish
|
|
1452
|
-
// Reset narration counter since this is a valid response
|
|
1453
|
-
narrationAttempts = 0;
|
|
1454
|
-
// Check if the message looks like a final answer/summary
|
|
1455
|
-
// If it has substantial length, assume it's a summary attempt
|
|
1456
|
-
const isFinalAnswer = assistantMessage.length > 20;
|
|
1457
|
-
if (isFinalAnswer) {
|
|
1458
|
-
completionAttempts++;
|
|
1459
|
-
conversationLogger.logNarrationDetection('final_answer', {
|
|
1460
|
-
turn: turnCount,
|
|
1461
|
-
completionAttempts,
|
|
1462
|
-
messagePreview: assistantMessage.substring(0, 200),
|
|
1463
|
-
});
|
|
1464
|
-
// If AI keeps providing text summaries without calling task_complete, accept the text and finish
|
|
1465
|
-
// This prevents the infinite loop where the AI keeps summarizing in response to our prompt
|
|
1466
|
-
if (completionAttempts > 1) {
|
|
1467
|
-
conversationLogger.logNarrationDetection('final_answer', {
|
|
1468
|
-
action: 'accepting_text_as_final',
|
|
1469
|
-
reason: 'multiple_completion_attempts',
|
|
1470
|
-
});
|
|
1471
|
-
finalAssistantMessage = assistantMessage;
|
|
1472
|
-
break;
|
|
1473
|
-
}
|
|
1474
|
-
// This looks like a final answer - prompt to call task_complete
|
|
1475
|
-
const completionPrompt = '✅ **Possible Completion Detected**: You provided a text response but did not call `task_complete`.\n\n' +
|
|
1476
|
-
'**To finish the conversation, you MUST call the `task_complete` tool.**\n\n' +
|
|
1477
|
-
'Please call `task_complete` now with your summary as the argument.';
|
|
1478
|
-
this.conversationHistory.push({
|
|
1479
|
-
role: 'user',
|
|
1480
|
-
content: completionPrompt,
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
else {
|
|
1484
|
-
// Short message without clear intent - ask for clarification or completion
|
|
1485
|
-
const completionPrompt = 'Your response is unclear. Either:\n' +
|
|
1486
|
-
'1. Execute the next tool call if more work is needed, or\n' +
|
|
1487
|
-
'2. Call task_complete() if the task is done';
|
|
1488
|
-
this.conversationHistory.push({
|
|
1489
|
-
role: 'user',
|
|
1490
|
-
content: completionPrompt,
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
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;
|
|
1494
1844
|
}
|
|
1495
1845
|
// Rebuild messages array with updated history
|
|
1496
1846
|
// Backend will inject system prompt
|
|
@@ -1506,8 +1856,8 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1506
1856
|
const silentStopPrompt = '⚠️ **SILENT STOP DETECTED**: You ended your turn without any output or tool calls.\n\n' +
|
|
1507
1857
|
'**This is not allowed.** You must either:\n' +
|
|
1508
1858
|
'1. Execute a tool call if more work is needed, OR\n' +
|
|
1509
|
-
'2.
|
|
1510
|
-
'**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' +
|
|
1511
1861
|
'**If more work is needed**, execute the next tool call immediately.';
|
|
1512
1862
|
this.conversationHistory.push({
|
|
1513
1863
|
role: 'user',
|
|
@@ -1594,6 +1944,7 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1594
1944
|
`/mcp - Manage configured MCP servers and tools\n` +
|
|
1595
1945
|
`/docs - Open Centaurus documentation in browser\n` +
|
|
1596
1946
|
`/copy-chat-context - Copy chat as readable text to clipboard\n` +
|
|
1947
|
+
`/session-limits - View session quota usage and limits\n` +
|
|
1597
1948
|
`/quality - Toggle enhanced quality features (thinking protocol, validation)\n` +
|
|
1598
1949
|
`/autonomous - Toggle autonomous mode (Silent Operator with task_complete)\n` +
|
|
1599
1950
|
`/sign-in - Sign in with Google (if not already signed in)\n` +
|
|
@@ -1607,6 +1958,32 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1607
1958
|
`Ctrl+Z - Undo last input change\n` +
|
|
1608
1959
|
`Ctrl+A - Select all text`;
|
|
1609
1960
|
break;
|
|
1961
|
+
case 'session-limits': {
|
|
1962
|
+
const config = sessionQuotaManager.getCurrentConfig();
|
|
1963
|
+
const messagesUsed = sessionQuotaManager.getMessagesUsed();
|
|
1964
|
+
const remaining = sessionQuotaManager.getRemainingMessages();
|
|
1965
|
+
const timeRemaining = sessionQuotaManager.getFormattedTimeRemaining();
|
|
1966
|
+
const maxMessages = config.maxMessagesPerSession;
|
|
1967
|
+
// Calculate percentage used (cap at 100% for display)
|
|
1968
|
+
const percentUsed = maxMessages > 0 ? Math.min(100, Math.round((messagesUsed / maxMessages) * 100)) : 0;
|
|
1969
|
+
// Create a visual progress bar (clamp to valid range)
|
|
1970
|
+
const barLength = 20;
|
|
1971
|
+
const filledLength = Math.min(barLength, Math.max(0, Math.round((messagesUsed / maxMessages) * barLength)));
|
|
1972
|
+
const emptyLength = barLength - filledLength;
|
|
1973
|
+
const progressBar = '█'.repeat(filledLength) + '░'.repeat(emptyLength);
|
|
1974
|
+
// Status message based on quota
|
|
1975
|
+
const quotaStatus = remaining <= 0
|
|
1976
|
+
? '\n\n⚠️ Session quota exhausted! AI requests are blocked until reset.'
|
|
1977
|
+
: '';
|
|
1978
|
+
responseMessage = `📊 Session Limits\n\n` +
|
|
1979
|
+
`Plan: free\n` +
|
|
1980
|
+
`Session Window: ${config.sessionDurationMs / (60 * 60 * 1000)} hours\n\n` +
|
|
1981
|
+
`Messages Used: ${messagesUsed} / ${maxMessages} (${percentUsed}%)\n` +
|
|
1982
|
+
`Messages Left: ${Math.max(0, remaining)}\n` +
|
|
1983
|
+
`Progress: [${progressBar}]\n` +
|
|
1984
|
+
`Time Remaining: ${timeRemaining || 'Session not started'}${quotaStatus}`;
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1610
1987
|
case 'init':
|
|
1611
1988
|
try {
|
|
1612
1989
|
// Define the context file names in priority order
|
|
@@ -1884,7 +2261,7 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
1884
2261
|
'• Work silently without narrating actions\n' +
|
|
1885
2262
|
'• Use Touch-First safety (never guess file paths)\n' +
|
|
1886
2263
|
'• Apply surgical precision to file edits\n' +
|
|
1887
|
-
'•
|
|
2264
|
+
'• Output summary text, then call task_complete() when done\n' +
|
|
1888
2265
|
'• Inject intelligent error recovery hints\n\n' +
|
|
1889
2266
|
'This is the industry-standard autonomous agent mode.'
|
|
1890
2267
|
: '⚠️ Autonomous Mode disabled\n\n' +
|
|
@@ -2826,6 +3203,19 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2826
3203
|
this.onCwdChange(chat.cwd);
|
|
2827
3204
|
}
|
|
2828
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
|
+
});
|
|
2829
3219
|
return true;
|
|
2830
3220
|
}
|
|
2831
3221
|
/**
|
|
@@ -2994,6 +3384,17 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2994
3384
|
this.uiMessageHistory = [];
|
|
2995
3385
|
this.localCwdBeforeRemote = null;
|
|
2996
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
|
+
});
|
|
2997
3398
|
}
|
|
2998
3399
|
/**
|
|
2999
3400
|
* Update UI message history (called from App.tsx via callback)
|