@xagent-ai/cli 1.2.0 → 1.2.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/README.md +1 -1
- package/README_CN.md +1 -1
- package/dist/agents.js +164 -164
- package/dist/agents.js.map +1 -1
- package/dist/ai-client.d.ts +4 -6
- package/dist/ai-client.d.ts.map +1 -1
- package/dist/ai-client.js +137 -115
- package/dist/ai-client.js.map +1 -1
- package/dist/auth.js +4 -4
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +184 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts.map +1 -1
- package/dist/context-compressor.js +65 -81
- package/dist/context-compressor.js.map +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +5 -31
- package/dist/conversation.js.map +1 -1
- package/dist/memory.d.ts +5 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +77 -37
- package/dist/memory.js.map +1 -1
- package/dist/remote-ai-client.d.ts +1 -8
- package/dist/remote-ai-client.d.ts.map +1 -1
- package/dist/remote-ai-client.js +55 -65
- package/dist/remote-ai-client.js.map +1 -1
- package/dist/retry.d.ts +35 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +166 -0
- package/dist/retry.js.map +1 -0
- package/dist/session.d.ts +0 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +243 -312
- package/dist/session.js.map +1 -1
- package/dist/slash-commands.d.ts +1 -0
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +91 -9
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +18 -17
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.d.ts.map +1 -1
- package/dist/system-prompt-generator.js +149 -139
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/theme.d.ts +48 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +254 -0
- package/dist/theme.js.map +1 -1
- package/dist/tools/edit-diff.d.ts +32 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +185 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +11 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +129 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools.d.ts +19 -5
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +979 -631
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +6 -31
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/agents.ts +504 -504
- package/src/ai-client.ts +1559 -1458
- package/src/auth.ts +4 -4
- package/src/cli.ts +195 -1
- package/src/config.ts +3 -3
- package/src/memory.ts +55 -14
- package/src/remote-ai-client.ts +663 -683
- package/src/retry.ts +217 -0
- package/src/session.ts +1736 -1840
- package/src/slash-commands.ts +98 -9
- package/src/smart-approval.ts +626 -625
- package/src/system-prompt-generator.ts +853 -843
- package/src/theme.ts +284 -0
- package/src/tools.ts +390 -70
package/src/session.ts
CHANGED
|
@@ -1,1840 +1,1736 @@
|
|
|
1
|
-
import readline from 'readline';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import https from 'https';
|
|
4
|
-
import axios from 'axios';
|
|
5
|
-
import crypto from 'crypto';
|
|
6
|
-
import ora from 'ora';
|
|
7
|
-
import inquirer from 'inquirer';
|
|
8
|
-
import { createRequire } from 'module';
|
|
9
|
-
import { dirname, join } from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
|
|
12
|
-
const require = createRequire(import.meta.url);
|
|
13
|
-
const packageJson = require('../package.json');
|
|
14
|
-
import { ExecutionMode, ChatMessage, ToolCall, AuthType } from './types.js';
|
|
15
|
-
import { AIClient, Message, detectThinkingKeywords, getThinkingTokens } from './ai-client.js';
|
|
16
|
-
import { RemoteAIClient, TokenInvalidError } from './remote-ai-client.js';
|
|
17
|
-
import { getConfigManager, ConfigManager } from './config.js';
|
|
18
|
-
import { AuthService, selectAuthType } from './auth.js';
|
|
19
|
-
import { getToolRegistry } from './tools.js';
|
|
20
|
-
import { getAgentManager, DEFAULT_AGENTS, AgentManager } from './agents.js';
|
|
21
|
-
import { getMemoryManager, MemoryManager } from './memory.js';
|
|
22
|
-
import { getMCPManager, MCPManager } from './mcp.js';
|
|
23
|
-
import { getCheckpointManager, CheckpointManager } from './checkpoint.js';
|
|
24
|
-
import { getConversationManager, ConversationManager } from './conversation.js';
|
|
25
|
-
import { getSessionManager, SessionManager } from './session-manager.js';
|
|
26
|
-
import { SlashCommandHandler, parseInput, detectImageInput } from './slash-commands.js';
|
|
27
|
-
import { SystemPromptGenerator } from './system-prompt-generator.js';
|
|
28
|
-
import { theme, icons, colors, styleHelpers, renderMarkdown } from './theme.js';
|
|
29
|
-
import { getCancellationManager, CancellationManager } from './cancellation.js';
|
|
30
|
-
import { getContextCompressor, ContextCompressor, CompressionResult } from './context-compressor.js';
|
|
31
|
-
import { Logger, LogLevel, getLogger } from './logger.js';
|
|
32
|
-
|
|
33
|
-
const logger = getLogger();
|
|
34
|
-
|
|
35
|
-
export class InteractiveSession {
|
|
36
|
-
private conversationManager: ConversationManager;
|
|
37
|
-
private sessionManager: SessionManager;
|
|
38
|
-
private contextCompressor: ContextCompressor;
|
|
39
|
-
private aiClient: AIClient | null = null;
|
|
40
|
-
private remoteAIClient: RemoteAIClient | null = null;
|
|
41
|
-
private conversation: ChatMessage[] = [];
|
|
42
|
-
private toolCalls: ToolCall[] = [];
|
|
43
|
-
private executionMode: ExecutionMode;
|
|
44
|
-
private slashCommandHandler: SlashCommandHandler;
|
|
45
|
-
private configManager: ConfigManager;
|
|
46
|
-
private agentManager: AgentManager;
|
|
47
|
-
private memoryManager: MemoryManager;
|
|
48
|
-
private mcpManager: MCPManager;
|
|
49
|
-
private checkpointManager: CheckpointManager;
|
|
50
|
-
private currentAgent: any = null;
|
|
51
|
-
private rl: readline.Interface;
|
|
52
|
-
private cancellationManager: CancellationManager;
|
|
53
|
-
private indentLevel: number;
|
|
54
|
-
private indentString: string;
|
|
55
|
-
private remoteConversationId: string | null = null;
|
|
56
|
-
private currentTaskId: string | null = null;
|
|
57
|
-
private taskCompleted: boolean = false;
|
|
58
|
-
private isFirstApiCall: boolean = true;
|
|
59
|
-
|
|
60
|
-
constructor(indentLevel: number = 0) {
|
|
61
|
-
|
|
62
|
-
this.rl = readline.createInterface({
|
|
63
|
-
|
|
64
|
-
input: process.stdin,
|
|
65
|
-
|
|
66
|
-
output: process.stdout
|
|
67
|
-
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.configManager = getConfigManager(process.cwd());
|
|
73
|
-
|
|
74
|
-
this.agentManager = getAgentManager(process.cwd());
|
|
75
|
-
|
|
76
|
-
this.memoryManager = getMemoryManager(process.cwd());
|
|
77
|
-
|
|
78
|
-
this.mcpManager = getMCPManager();
|
|
79
|
-
|
|
80
|
-
this.checkpointManager = getCheckpointManager(process.cwd());
|
|
81
|
-
|
|
82
|
-
this.conversationManager = getConversationManager();
|
|
83
|
-
|
|
84
|
-
this.sessionManager = getSessionManager(process.cwd());
|
|
85
|
-
|
|
86
|
-
this.slashCommandHandler = new SlashCommandHandler();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Register MCP update callback, update system prompt
|
|
101
|
-
|
|
102
|
-
this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
|
|
103
|
-
|
|
104
|
-
await this.updateSystemPrompt();
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.executionMode = ExecutionMode.DEFAULT;
|
|
111
|
-
|
|
112
|
-
this.cancellationManager = getCancellationManager();
|
|
113
|
-
|
|
114
|
-
this.indentLevel = indentLevel;
|
|
115
|
-
|
|
116
|
-
this.indentString = ' '.repeat(indentLevel);
|
|
117
|
-
|
|
118
|
-
this.contextCompressor = getContextCompressor();
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private getIndent(): string {
|
|
123
|
-
return this.indentString;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
setAIClient(aiClient: AIClient): void {
|
|
127
|
-
this.aiClient = aiClient;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
setExecutionMode(mode: ExecutionMode): void {
|
|
131
|
-
this.executionMode = mode;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Update system prompt to reflect MCP changes (called after add/remove MCP)
|
|
136
|
-
*/
|
|
137
|
-
async updateSystemPrompt(): Promise<void> {
|
|
138
|
-
const toolRegistry = getToolRegistry();
|
|
139
|
-
const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
|
|
140
|
-
|
|
141
|
-
// Use the current agent's original system prompt as base
|
|
142
|
-
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
|
|
143
|
-
const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
144
|
-
|
|
145
|
-
// Replace old system prompt with new one
|
|
146
|
-
this.conversation = this.conversation.filter(msg => msg.role !== 'system');
|
|
147
|
-
this.conversation.unshift({
|
|
148
|
-
role: 'system',
|
|
149
|
-
content: newSystemPrompt,
|
|
150
|
-
timestamp: Date.now()
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Sync to slashCommandHandler
|
|
154
|
-
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
setAgent(agent: any): void {
|
|
158
|
-
this.currentAgent = agent;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async start(): Promise<void> {
|
|
162
|
-
// Set this session as the singleton for access from other modules
|
|
163
|
-
setSingletonSession(this);
|
|
164
|
-
|
|
165
|
-
// Initialize taskId for GUI operations
|
|
166
|
-
this.currentTaskId = crypto.randomUUID();
|
|
167
|
-
|
|
168
|
-
const separator = icons.separator.repeat(60);
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
|
|
171
|
-
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
172
|
-
console.log(colors.gradient('║') + ' '.repeat(13) + '🤖 ' + colors.gradient('XAGENT CLI') + ' '.repeat(32) + colors.gradient(' ║'));
|
|
173
|
-
console.log(colors.gradient('║') + ' '.repeat(16) + colors.textMuted(`v${packageJson.version}`) + ' '.repeat(36) + colors.gradient(' ║'));
|
|
174
|
-
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
175
|
-
console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
|
|
176
|
-
console.log(colors.textMuted(' AI-powered command-line assistant'));
|
|
177
|
-
console.log('');
|
|
178
|
-
|
|
179
|
-
await this.initialize();
|
|
180
|
-
this.showWelcomeMessage();
|
|
181
|
-
|
|
182
|
-
// Track if an operation is in progress
|
|
183
|
-
(this as any)._isOperationInProgress = false;
|
|
184
|
-
|
|
185
|
-
// Listen for ESC cancellation - only cancel operations, don't exit the program
|
|
186
|
-
const cancelHandler = () => {
|
|
187
|
-
if ((this as any)._isOperationInProgress) {
|
|
188
|
-
// An operation is running, let it be cancelled
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// No operation running, ignore ESC or show a message
|
|
192
|
-
};
|
|
193
|
-
this.cancellationManager.on('cancelled', cancelHandler);
|
|
194
|
-
|
|
195
|
-
this.promptLoop();
|
|
196
|
-
|
|
197
|
-
// Keep the promise pending until shutdown
|
|
198
|
-
return new Promise((resolve) => {
|
|
199
|
-
(this as any)._shutdownResolver = resolve;
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private async initialize(): Promise<void> {
|
|
204
|
-
logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
// Custom spinner for initialization (like Thinking...)
|
|
208
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
209
|
-
let frameIndex = 0;
|
|
210
|
-
const validatingText = colors.textMuted('Validating authentication...');
|
|
211
|
-
|
|
212
|
-
const spinnerInterval = setInterval(() => {
|
|
213
|
-
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${validatingText}`);
|
|
214
|
-
frameIndex = (frameIndex + 1) % frames.length;
|
|
215
|
-
}, 120);
|
|
216
|
-
logger.debug('[SESSION] 调用 configManager.load()...');
|
|
217
|
-
await this.configManager.load();
|
|
218
|
-
|
|
219
|
-
logger.debug('[SESSION] Config loaded');
|
|
220
|
-
let authConfig = this.configManager.getAuthConfig();
|
|
221
|
-
let selectedAuthType = this.configManager.get('selectedAuthType');
|
|
222
|
-
|
|
223
|
-
logger.debug('[SESSION] authConfig.apiKey exists:', String(!!authConfig.apiKey));
|
|
224
|
-
logger.debug('[SESSION] selectedAuthType (initial):', String(selectedAuthType));
|
|
225
|
-
logger.debug('[SESSION] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
226
|
-
logger.debug('[SESSION] AuthType.OPENAI_COMPATIBLE:', String(AuthType.OPENAI_COMPATIBLE));
|
|
227
|
-
logger.debug('[SESSION] Will validate OAuth:', String(!!(authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT)));
|
|
228
|
-
|
|
229
|
-
// Only validate OAuth tokens, skip validation for third-party API keys
|
|
230
|
-
if (authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
231
|
-
clearInterval(spinnerInterval);
|
|
232
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear the line
|
|
233
|
-
|
|
234
|
-
const baseUrl = authConfig.xagentApiBaseUrl || 'https://
|
|
235
|
-
let isValid = await this.validateToken(baseUrl, authConfig.apiKey);
|
|
236
|
-
|
|
237
|
-
// Try refresh token if validation failed
|
|
238
|
-
if (!isValid && authConfig.refreshToken) {
|
|
239
|
-
const refreshingText = colors.textMuted('Refreshing authentication...');
|
|
240
|
-
frameIndex = 0;
|
|
241
|
-
const refreshInterval = setInterval(() => {
|
|
242
|
-
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${refreshingText}`);
|
|
243
|
-
frameIndex = (frameIndex + 1) % frames.length;
|
|
244
|
-
}, 120);
|
|
245
|
-
|
|
246
|
-
const newToken = await this.refreshToken(baseUrl, authConfig.refreshToken);
|
|
247
|
-
clearInterval(refreshInterval);
|
|
248
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
249
|
-
|
|
250
|
-
if (newToken) {
|
|
251
|
-
// Save new token and persist
|
|
252
|
-
await this.configManager.set('apiKey', newToken);
|
|
253
|
-
await this.configManager.save('global');
|
|
254
|
-
authConfig.apiKey = newToken;
|
|
255
|
-
isValid = true;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!isValid) {
|
|
260
|
-
console.log('');
|
|
261
|
-
console.log(colors.warning('Your xAgent session has expired or is not configured'));
|
|
262
|
-
console.log(colors.info('Please select an authentication method to continue.'));
|
|
263
|
-
console.log('');
|
|
264
|
-
|
|
265
|
-
// Clear invalid credentials and persist
|
|
266
|
-
// Note: Do NOT overwrite selectedAuthType - let user re-select their preferred auth method
|
|
267
|
-
await this.configManager.set('apiKey', '');
|
|
268
|
-
await this.configManager.set('refreshToken', '');
|
|
269
|
-
await this.configManager.save('global');
|
|
270
|
-
|
|
271
|
-
await this.configManager.load();
|
|
272
|
-
authConfig = this.configManager.getAuthConfig();
|
|
273
|
-
|
|
274
|
-
await this.setupAuthentication();
|
|
275
|
-
authConfig = this.configManager.getAuthConfig();
|
|
276
|
-
|
|
277
|
-
// Recreate readline interface after inquirer
|
|
278
|
-
this.rl.close();
|
|
279
|
-
this.rl = readline.createInterface({
|
|
280
|
-
input: process.stdin,
|
|
281
|
-
output: process.stdout
|
|
282
|
-
});
|
|
283
|
-
this.rl.on('close', () => {
|
|
284
|
-
// readline closed
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
} else if (!authConfig.apiKey) {
|
|
288
|
-
// No API key configured, need to set up authentication
|
|
289
|
-
clearInterval(spinnerInterval);
|
|
290
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
291
|
-
await this.setupAuthentication();
|
|
292
|
-
authConfig = this.configManager.getAuthConfig();
|
|
293
|
-
selectedAuthType = this.configManager.get('selectedAuthType');
|
|
294
|
-
logger.debug('[SESSION] selectedAuthType (after setup):', String(selectedAuthType));
|
|
295
|
-
|
|
296
|
-
// Recreate readline interface after inquirer
|
|
297
|
-
this.rl.close();
|
|
298
|
-
this.rl = readline.createInterface({
|
|
299
|
-
input: process.stdin,
|
|
300
|
-
output: process.stdout
|
|
301
|
-
});
|
|
302
|
-
this.rl.on('close', () => {
|
|
303
|
-
// readline closed
|
|
304
|
-
});
|
|
305
|
-
} else {
|
|
306
|
-
clearInterval(spinnerInterval);
|
|
307
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
308
|
-
}
|
|
309
|
-
// For OPENAI_COMPATIBLE with API key, skip validation and proceed directly
|
|
310
|
-
|
|
311
|
-
this.aiClient = new AIClient(authConfig);
|
|
312
|
-
this.contextCompressor.setAIClient(this.aiClient);
|
|
313
|
-
|
|
314
|
-
// Initialize remote AI client for OAuth XAGENT mode
|
|
315
|
-
logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
|
|
316
|
-
logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
|
|
317
|
-
if (selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
318
|
-
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://
|
|
319
|
-
// In OAuth XAGENT mode, we still pass apiKey (can be empty or used for other purposes)
|
|
320
|
-
this.remoteAIClient = new RemoteAIClient(authConfig.apiKey || '', webBaseUrl, authConfig.showAIDebugInfo);
|
|
321
|
-
logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
|
|
322
|
-
} else {
|
|
323
|
-
logger.debug('[DEBUG Initialize] RemoteAIClient NOT created (not OAuth XAGENT mode)');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
327
|
-
|
|
328
|
-
await this.agentManager.loadAgents();
|
|
329
|
-
await this.memoryManager.loadMemory();
|
|
330
|
-
await this.conversationManager.initialize();
|
|
331
|
-
await this.sessionManager.initialize();
|
|
332
|
-
|
|
333
|
-
// Create a new conversation and session for this interactive session
|
|
334
|
-
const conversation = await this.conversationManager.createConversation();
|
|
335
|
-
await this.sessionManager.createSession(
|
|
336
|
-
conversation.id,
|
|
337
|
-
this.currentAgent?.name || 'general-purpose',
|
|
338
|
-
this.executionMode
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// Sync conversation history to slashCommandHandler
|
|
342
|
-
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
343
|
-
|
|
344
|
-
const mcpServers = this.configManager.getMcpServers();
|
|
345
|
-
Object.entries(mcpServers).forEach(([name, config]) => {
|
|
346
|
-
console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
|
|
347
|
-
this.mcpManager.registerServer(name, config);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// Eagerly connect to MCP servers to get tool definitions
|
|
351
|
-
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
352
|
-
try {
|
|
353
|
-
console.log(`${colors.info(`${icons.brain} Connecting to ${Object.keys(mcpServers).length} MCP server(s)...`)}`);
|
|
354
|
-
await this.mcpManager.connectAllServers();
|
|
355
|
-
const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s: any) => s.isServerConnected()).length;
|
|
356
|
-
const mcpTools = this.mcpManager.getToolDefinitions();
|
|
357
|
-
console.log(`${colors.success(`✓ ${connectedCount}/${Object.keys(mcpServers).length} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
|
|
358
|
-
|
|
359
|
-
// Register MCP tools with the tool registry (hide MCP origin from LLM)
|
|
360
|
-
const toolRegistry = getToolRegistry();
|
|
361
|
-
const allMcpTools = this.mcpManager.getAllTools();
|
|
362
|
-
toolRegistry.registerMCPTools(allMcpTools);
|
|
363
|
-
} catch (error: any) {
|
|
364
|
-
console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const checkpointingConfig = this.configManager.getCheckpointingConfig();
|
|
369
|
-
if (checkpointingConfig.enabled) {
|
|
370
|
-
this.checkpointManager = getCheckpointManager(
|
|
371
|
-
process.cwd(),
|
|
372
|
-
checkpointingConfig.enabled,
|
|
373
|
-
checkpointingConfig.maxCheckpoints
|
|
374
|
-
);
|
|
375
|
-
await this.checkpointManager.initialize();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
this.currentAgent = this.agentManager.getAgent('general-purpose');
|
|
379
|
-
|
|
380
|
-
console.log(colors.success('✔ Initialization complete'));
|
|
381
|
-
} catch (error: any) {
|
|
382
|
-
const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
|
|
383
|
-
spinner.fail(colors.error(`Initialization failed: ${error.message}`));
|
|
384
|
-
throw error;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Validate token with the backend
|
|
390
|
-
* Returns true if token is valid, false otherwise
|
|
391
|
-
*/
|
|
392
|
-
private async validateToken(baseUrl: string, apiKey: string): Promise<boolean> {
|
|
393
|
-
logger.debug('[SESSION] validateToken called with baseUrl:', baseUrl);
|
|
394
|
-
logger.debug('[SESSION] apiKey exists:', apiKey ? 'yes' : 'no');
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
// For OAuth XAGENT auth, use /api/auth/me endpoint
|
|
398
|
-
const url = `${baseUrl}/api/auth/me`;
|
|
399
|
-
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
400
|
-
|
|
401
|
-
logger.debug('[SESSION] Sending validation request to:', url);
|
|
402
|
-
|
|
403
|
-
const response = await axios.get(url, {
|
|
404
|
-
headers: {
|
|
405
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
406
|
-
'Content-Type': 'application/json'
|
|
407
|
-
},
|
|
408
|
-
httpsAgent,
|
|
409
|
-
timeout: 10000
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
logger.debug('[SESSION] Validation response status:', String(response.status));
|
|
413
|
-
return response.status === 200;
|
|
414
|
-
} catch (error: any) {
|
|
415
|
-
// Network error - log details but still consider token may be invalid
|
|
416
|
-
logger.debug('[SESSION] Error:', error.message);
|
|
417
|
-
if (error.response) {
|
|
418
|
-
logger.debug('[SESSION] Response status:', error.response.status);
|
|
419
|
-
}
|
|
420
|
-
// For network errors, we still return false to trigger re-authentication
|
|
421
|
-
// This ensures security but the user can retry
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private async refreshToken(baseUrl: string, refreshToken: string): Promise<string | null> {
|
|
427
|
-
try {
|
|
428
|
-
const url = `${baseUrl}/api/auth/refresh`;
|
|
429
|
-
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
430
|
-
|
|
431
|
-
const response = await axios.post(url, { refreshToken }, {
|
|
432
|
-
httpsAgent,
|
|
433
|
-
timeout: 10000
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
if (response.status === 200) {
|
|
437
|
-
const data = response.data as { token?: string; refreshToken?: string };
|
|
438
|
-
return data.token || null;
|
|
439
|
-
} else {
|
|
440
|
-
return null;
|
|
441
|
-
}
|
|
442
|
-
} catch (error: any) {
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private async setupAuthentication(): Promise<void> {
|
|
448
|
-
const separator = icons.separator.repeat(40);
|
|
449
|
-
console.log('');
|
|
450
|
-
console.log(colors.primaryBright(`${icons.lock} Setup Authentication`));
|
|
451
|
-
console.log(colors.border(separator));
|
|
452
|
-
console.log('');
|
|
453
|
-
|
|
454
|
-
const authType = await selectAuthType();
|
|
455
|
-
this.configManager.set('selectedAuthType', authType);
|
|
456
|
-
|
|
457
|
-
const authService = new AuthService({
|
|
458
|
-
type: authType,
|
|
459
|
-
apiKey: '',
|
|
460
|
-
baseUrl: '',
|
|
461
|
-
modelName: ''
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
const success = await authService.authenticate();
|
|
465
|
-
|
|
466
|
-
if (!success) {
|
|
467
|
-
console.log('');
|
|
468
|
-
console.log(colors.error('Authentication failed. Exiting...'));
|
|
469
|
-
console.log('');
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const authConfig = authService.getAuthConfig();
|
|
474
|
-
|
|
475
|
-
// VLM configuration is optional - only show for non-OAuth (local) mode
|
|
476
|
-
// Remote mode uses backend VLM configuration
|
|
477
|
-
if (authType !== AuthType.OAUTH_XAGENT) {
|
|
478
|
-
console.log('');
|
|
479
|
-
console.log(colors.info(`${icons.info} VLM configuration is optional.`));
|
|
480
|
-
console.log(colors.info(`You can configure it later using the /vlm command if needed.`));
|
|
481
|
-
console.log('');
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Save LLM config only, skip VLM for now
|
|
485
|
-
await this.configManager.setAuthConfig(authConfig);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private showWelcomeMessage(): void {
|
|
489
|
-
const language = this.configManager.getLanguage();
|
|
490
|
-
const separator = icons.separator.repeat(40);
|
|
491
|
-
|
|
492
|
-
console.log('');
|
|
493
|
-
console.log(colors.border(separator));
|
|
494
|
-
|
|
495
|
-
if (language === 'zh') {
|
|
496
|
-
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
497
|
-
console.log(colors.textMuted('Type /help to see available commands')); } else {
|
|
498
|
-
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
499
|
-
console.log(colors.textMuted('Type /help to see available commands'));
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
console.log(colors.border(separator));
|
|
503
|
-
console.log('');
|
|
504
|
-
|
|
505
|
-
this.showExecutionMode();
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
private showExecutionMode(): void {
|
|
509
|
-
const modeConfig = {
|
|
510
|
-
[ExecutionMode.YOLO]: {
|
|
511
|
-
color: colors.error,
|
|
512
|
-
icon: icons.fire,
|
|
513
|
-
description: 'Execute commands without confirmation'
|
|
514
|
-
},
|
|
515
|
-
[ExecutionMode.ACCEPT_EDITS]: {
|
|
516
|
-
color: colors.warning,
|
|
517
|
-
icon: icons.check,
|
|
518
|
-
description: 'Accept all edits automatically'
|
|
519
|
-
},
|
|
520
|
-
[ExecutionMode.PLAN]: {
|
|
521
|
-
color: colors.info,
|
|
522
|
-
icon: icons.brain,
|
|
523
|
-
description: 'Plan before executing'
|
|
524
|
-
},
|
|
525
|
-
[ExecutionMode.DEFAULT]: {
|
|
526
|
-
color: colors.success,
|
|
527
|
-
icon: icons.bolt,
|
|
528
|
-
description: 'Safe execution with confirmations'
|
|
529
|
-
},
|
|
530
|
-
[ExecutionMode.SMART]: {
|
|
531
|
-
color: colors.primaryBright,
|
|
532
|
-
icon: icons.sparkles,
|
|
533
|
-
description: 'Smart approval with intelligent security checks'
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const config = modeConfig[this.executionMode];
|
|
538
|
-
const modeName = this.executionMode;
|
|
539
|
-
|
|
540
|
-
console.log(colors.textMuted(`${icons.info} Current Mode:`));
|
|
541
|
-
console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
|
|
542
|
-
console.log(` ${colors.textDim(` ${config.description}`)}`);
|
|
543
|
-
console.log('');
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
private async promptLoop(): Promise<void> {
|
|
547
|
-
// Check if we're shutting down
|
|
548
|
-
if ((this as any)._isShuttingDown) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Recreate readline interface for input
|
|
553
|
-
if (this.rl) {
|
|
554
|
-
this.rl.close();
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Enable raw mode BEFORE emitKeypressEvents for better ESC detection
|
|
558
|
-
if (process.stdin.isTTY) {
|
|
559
|
-
process.stdin.setRawMode(true);
|
|
560
|
-
}
|
|
561
|
-
process.stdin.resume();
|
|
562
|
-
readline.emitKeypressEvents(process.stdin);
|
|
563
|
-
|
|
564
|
-
this.rl = readline.createInterface({
|
|
565
|
-
input: process.stdin,
|
|
566
|
-
output: process.stdout
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
const prompt = `${colors.primaryBright('❯')} `;
|
|
570
|
-
this.rl.question(prompt, async (input: string) => {
|
|
571
|
-
if ((this as any)._isShuttingDown) {
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
await this.handleInput(input);
|
|
577
|
-
} catch (err: any) {
|
|
578
|
-
console.log(colors.error(`Error: ${err.message}`));
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
this.promptLoop();
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
private async handleInput(input: string): Promise<void> {
|
|
586
|
-
const trimmedInput = input.trim();
|
|
587
|
-
|
|
588
|
-
if (!trimmedInput) {
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
if (trimmedInput.startsWith('/')) {
|
|
593
|
-
const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
|
|
594
|
-
if (handled) {
|
|
595
|
-
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
596
|
-
// Sync conversation history to slashCommandHandler
|
|
597
|
-
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
598
|
-
}
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (trimmedInput.startsWith('$')) {
|
|
603
|
-
await this.handleSubAgentCommand(trimmedInput);
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
await this.processUserMessage(trimmedInput);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
private async handleSubAgentCommand(input: string): Promise<void> {
|
|
611
|
-
const [agentType, ...taskParts] = input.slice(1).split(' ');
|
|
612
|
-
const task = taskParts.join(' ');
|
|
613
|
-
|
|
614
|
-
const agent = this.agentManager.getAgent(agentType);
|
|
615
|
-
|
|
616
|
-
if (!agent) {
|
|
617
|
-
console.log('');
|
|
618
|
-
console.log(colors.warning(`Agent not found: ${agentType}`));
|
|
619
|
-
console.log(colors.textMuted('Use /agents list to see available agents'));
|
|
620
|
-
console.log('');
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
console.log('');
|
|
625
|
-
console.log(colors.primaryBright(`${icons.robot} Using agent: ${agent.name || agent.agentType}`));
|
|
626
|
-
console.log(colors.border(icons.separator.repeat(40)));
|
|
627
|
-
console.log('');
|
|
628
|
-
|
|
629
|
-
this.currentAgent = agent;
|
|
630
|
-
await this.processUserMessage(task, agent);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
public async processUserMessage(message: string, agent?: any): Promise<void> {
|
|
634
|
-
const inputs = parseInput(message);
|
|
635
|
-
const textInput = inputs.find(i => i.type === 'text');
|
|
636
|
-
const fileInputs = inputs.filter(i => i.type === 'file');
|
|
637
|
-
const commandInput = inputs.find(i => i.type === 'command');
|
|
638
|
-
|
|
639
|
-
if (commandInput) {
|
|
640
|
-
await this.executeShellCommand(commandInput.content);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
let userContent = textInput?.content || '';
|
|
645
|
-
|
|
646
|
-
if (fileInputs.length > 0) {
|
|
647
|
-
const toolRegistry = getToolRegistry();
|
|
648
|
-
for (const fileInput of fileInputs) {
|
|
649
|
-
try {
|
|
650
|
-
const content = await toolRegistry.execute('Read', { filePath: fileInput.content }, this.executionMode);
|
|
651
|
-
userContent += `\n\n--- File: ${fileInput.content} ---\n${content}`;
|
|
652
|
-
} catch (error: any) {
|
|
653
|
-
console.log(chalk.yellow(`Warning: Failed to read file ${fileInput.content}: ${error.message}`));
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Record input to session manager
|
|
659
|
-
const sessionInput = {
|
|
660
|
-
type: 'text' as const,
|
|
661
|
-
content: userContent,
|
|
662
|
-
rawInput: message,
|
|
663
|
-
timestamp: Date.now()
|
|
664
|
-
};
|
|
665
|
-
await this.sessionManager.addInput(sessionInput);
|
|
666
|
-
|
|
667
|
-
// Calculate thinking tokens based on config and user input
|
|
668
|
-
const thinkingConfig = this.configManager.getThinkingConfig();
|
|
669
|
-
let thinkingTokens = 0;
|
|
670
|
-
|
|
671
|
-
if (thinkingConfig.enabled) {
|
|
672
|
-
// If thinking mode is enabled, detect keywords and calculate tokens
|
|
673
|
-
const thinkingMode = detectThinkingKeywords(userContent);
|
|
674
|
-
thinkingTokens = getThinkingTokens(thinkingMode);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const userMessage: ChatMessage = {
|
|
678
|
-
role: 'user',
|
|
679
|
-
content: userContent,
|
|
680
|
-
timestamp: Date.now()
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
// Save last user message for recovery after compression
|
|
684
|
-
const lastUserMessage = userMessage;
|
|
685
|
-
|
|
686
|
-
this.conversation.push(userMessage);
|
|
687
|
-
await this.conversationManager.addMessage(userMessage);
|
|
688
|
-
|
|
689
|
-
// Check if context compression is needed
|
|
690
|
-
await this.checkAndCompressContext(lastUserMessage);
|
|
691
|
-
|
|
692
|
-
// Use remote AI client if available (OAuth XAGENT mode)
|
|
693
|
-
const currentSelectedAuthType = this.configManager.get('selectedAuthType');
|
|
694
|
-
logger.debug('[DEBUG processUserMessage] remoteAIClient exists:', !!this.remoteAIClient ? 'true' : 'false');
|
|
695
|
-
logger.debug('[DEBUG processUserMessage] selectedAuthType:', String(currentSelectedAuthType));
|
|
696
|
-
logger.debug('[DEBUG processUserMessage] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
697
|
-
|
|
698
|
-
if (this.remoteAIClient) {
|
|
699
|
-
logger.debug('[DEBUG processUserMessage] Using generateRemoteResponse');
|
|
700
|
-
await this.generateRemoteResponse(thinkingTokens);
|
|
701
|
-
} else {
|
|
702
|
-
logger.debug('[DEBUG processUserMessage] Using generateResponse (local mode)');
|
|
703
|
-
await this.generateResponse(thinkingTokens);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
private displayThinkingContent(reasoningContent: string): void {
|
|
708
|
-
const indent = this.getIndent();
|
|
709
|
-
const thinkingConfig = this.configManager.getThinkingConfig();
|
|
710
|
-
const displayMode = thinkingConfig.displayMode || 'compact';
|
|
711
|
-
|
|
712
|
-
const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length);
|
|
713
|
-
|
|
714
|
-
console.log('');
|
|
715
|
-
console.log(`${indent}${colors.border(separator)}`);
|
|
716
|
-
|
|
717
|
-
switch (displayMode) {
|
|
718
|
-
case 'full':
|
|
719
|
-
// Full display, using small font and gray color
|
|
720
|
-
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
|
|
721
|
-
console.log('');
|
|
722
|
-
console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
|
|
723
|
-
break;
|
|
724
|
-
|
|
725
|
-
case 'compact':
|
|
726
|
-
// Compact display, truncate partial content
|
|
727
|
-
const maxLength = 500;
|
|
728
|
-
const truncatedContent = reasoningContent.length > maxLength
|
|
729
|
-
? reasoningContent.substring(0, maxLength) + '... (truncated)'
|
|
730
|
-
: reasoningContent;
|
|
731
|
-
|
|
732
|
-
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
|
|
733
|
-
console.log('');
|
|
734
|
-
console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
|
|
735
|
-
console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
|
|
736
|
-
break;
|
|
737
|
-
|
|
738
|
-
case 'indicator':
|
|
739
|
-
// Show indicator only
|
|
740
|
-
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking process completed`)}`);
|
|
741
|
-
console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars of reasoning]`)}`);
|
|
742
|
-
break;
|
|
743
|
-
|
|
744
|
-
default:
|
|
745
|
-
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking:`)}`);
|
|
746
|
-
console.log('');
|
|
747
|
-
console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
console.log(`${indent}${colors.border(separator)}`);
|
|
751
|
-
console.log('');
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Check and compress conversation context
|
|
756
|
-
*/
|
|
757
|
-
private async checkAndCompressContext(lastUserMessage?: ChatMessage): Promise<void> {
|
|
758
|
-
const compressionConfig = this.configManager.getContextCompressionConfig();
|
|
759
|
-
|
|
760
|
-
if (!compressionConfig.enabled) {
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const { needsCompression, reason } = this.contextCompressor.needsCompression(
|
|
765
|
-
this.conversation,
|
|
766
|
-
compressionConfig
|
|
767
|
-
);
|
|
768
|
-
|
|
769
|
-
if (needsCompression) {
|
|
770
|
-
const indent = this.getIndent();
|
|
771
|
-
console.log('');
|
|
772
|
-
console.log(`${indent}${colors.warning(`${icons.brain} Context compression triggered: ${reason}`)}`);
|
|
773
|
-
|
|
774
|
-
const toolRegistry = getToolRegistry();
|
|
775
|
-
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
|
|
776
|
-
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
777
|
-
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
778
|
-
|
|
779
|
-
const result: CompressionResult = await this.contextCompressor.compressContext(
|
|
780
|
-
this.conversation,
|
|
781
|
-
enhancedSystemPrompt,
|
|
782
|
-
compressionConfig
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
if (result.wasCompressed) {
|
|
786
|
-
this.conversation = result.compressedMessages;
|
|
787
|
-
// console.log(`${indent}${colors.success(`✓ Compressed ${result.originalMessageCount} messages to ${result.compressedMessageCount} messages`)}`);
|
|
788
|
-
console.log(`${indent}${colors.textMuted(`✓ Size: ${result.originalSize} → ${result.compressedSize} chars (${Math.round((1 - result.compressedSize / result.originalSize) * 100)}% reduction)`)}`);
|
|
789
|
-
|
|
790
|
-
// Display compressed summary content
|
|
791
|
-
const summaryMessage = result.compressedMessages.find(m => m.role === 'assistant');
|
|
792
|
-
if (summaryMessage && summaryMessage.content) {
|
|
793
|
-
const maxPreviewLength = 800;
|
|
794
|
-
let summaryContent = summaryMessage.content;
|
|
795
|
-
const isTruncated = summaryContent.length > maxPreviewLength;
|
|
796
|
-
|
|
797
|
-
if (isTruncated) {
|
|
798
|
-
summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
console.log('');
|
|
802
|
-
console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
|
|
803
|
-
const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
|
|
804
|
-
console.log(`${indent}${colors.border(separator)}`);
|
|
805
|
-
const renderedSummary = renderMarkdown(summaryContent, (process.stdout.columns || 80) - indent.length * 4);
|
|
806
|
-
console.log(`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`);
|
|
807
|
-
if (isTruncated) {
|
|
808
|
-
console.log(`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`);
|
|
809
|
-
}
|
|
810
|
-
console.log(`${indent}${colors.border(separator)}`);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// Restore user messages after compression, ensuring user message exists for API calls
|
|
814
|
-
if (lastUserMessage) {
|
|
815
|
-
this.conversation.push(lastUserMessage);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Sync compressed conversation history to slashCommandHandler
|
|
819
|
-
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
private async executeShellCommand(command: string): Promise<void> {
|
|
825
|
-
const indent = this.getIndent();
|
|
826
|
-
console.log('');
|
|
827
|
-
console.log(`${indent}${colors.textMuted(`${icons.code} Executing:`)}`);
|
|
828
|
-
console.log(`${indent}${colors.codeText(` $ ${command}`)}`);
|
|
829
|
-
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
830
|
-
console.log('');
|
|
831
|
-
|
|
832
|
-
const toolRegistry = getToolRegistry();
|
|
833
|
-
|
|
834
|
-
try {
|
|
835
|
-
const result = await toolRegistry.execute('Bash', { command }, this.executionMode);
|
|
836
|
-
|
|
837
|
-
if (result.stdout) {
|
|
838
|
-
console.log(`${indent}${result.stdout.replace(/^/gm, indent)}`);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (result.stderr) {
|
|
842
|
-
console.log(`${indent}${colors.warning(result.stderr.replace(/^/gm, indent))}`);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
const toolCall: ToolCall = {
|
|
846
|
-
tool: 'Bash',
|
|
847
|
-
params: { command },
|
|
848
|
-
result,
|
|
849
|
-
timestamp: Date.now()
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
this.toolCalls.push(toolCall);
|
|
853
|
-
|
|
854
|
-
// Record command execution to session manager
|
|
855
|
-
await this.sessionManager.addInput({
|
|
856
|
-
type: 'command',
|
|
857
|
-
content: command,
|
|
858
|
-
rawInput: command,
|
|
859
|
-
timestamp: Date.now()
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
await this.sessionManager.addOutput({
|
|
863
|
-
role: 'tool',
|
|
864
|
-
content: JSON.stringify(result),
|
|
865
|
-
toolName: 'Bash',
|
|
866
|
-
toolParams: { command },
|
|
867
|
-
toolResult: result,
|
|
868
|
-
timestamp: Date.now()
|
|
869
|
-
});
|
|
870
|
-
} catch (error: any) {
|
|
871
|
-
console.log(`${indent}${colors.error(`Command execution failed: ${error.message}`)}`);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
/**
|
|
876
|
-
* Create unified LLM Caller
|
|
877
|
-
* Implement transparency: caller doesn't need to care about remote vs local mode
|
|
878
|
-
*/
|
|
879
|
-
private createLLMCaller(taskId: string, status: 'begin' | 'continue') {
|
|
880
|
-
// Remote mode: use RemoteAIClient
|
|
881
|
-
if (this.remoteAIClient) {
|
|
882
|
-
return this.createRemoteCaller(taskId, status);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Local mode: use AIClient
|
|
886
|
-
if (!this.aiClient) {
|
|
887
|
-
throw new Error('AI client not initialized');
|
|
888
|
-
}
|
|
889
|
-
return this.createLocalCaller();
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Create remote mode LLM caller
|
|
894
|
-
*/
|
|
895
|
-
private createRemoteCaller(taskId: string, status: 'begin' | 'continue') {
|
|
896
|
-
const client = this.remoteAIClient!;
|
|
897
|
-
return {
|
|
898
|
-
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
899
|
-
client.chatCompletion(messages, { ...options, taskId, status }),
|
|
900
|
-
isRemote: true
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
/**
|
|
905
|
-
* Create local mode LLM caller
|
|
906
|
-
*/
|
|
907
|
-
private createLocalCaller() {
|
|
908
|
-
const client = this.aiClient!;
|
|
909
|
-
return {
|
|
910
|
-
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
911
|
-
client.chatCompletion(messages as any, options),
|
|
912
|
-
isRemote: false
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
private async generateResponse(thinkingTokens: number = 0): Promise<void> {
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
this.currentTaskId
|
|
920
|
-
this.
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
)
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
//
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
//
|
|
1101
|
-
(this
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
//
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
//
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
console.log(
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
console.log(
|
|
1243
|
-
console.log(colors.
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
//
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
//
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
console.log(
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
})
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
'
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
//
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
//
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
// // Content preview
|
|
1738
|
-
// const contentStr = typeof message.content === 'string'
|
|
1739
|
-
// ? message.content.substring(0, 100)
|
|
1740
|
-
// : JSON.stringify(message.content).substring(0, 100);
|
|
1741
|
-
// console.log(colors.border(`${boxChar.vertical}`) + ' 📝 CONTENT: ' +
|
|
1742
|
-
// colors.textDim(contentStr + '...') + ' '.repeat(Math.max(0, 40 - contentStr.length)) + colors.border(boxChar.vertical));
|
|
1743
|
-
// }
|
|
1744
|
-
//
|
|
1745
|
-
// console.log(colors.border(
|
|
1746
|
-
// `${boxChar.bottomLeft}${boxChar.horizontal.repeat(58)}${boxChar.bottomRight}`
|
|
1747
|
-
// ));
|
|
1748
|
-
// }
|
|
1749
|
-
|
|
1750
|
-
shutdown(): void {
|
|
1751
|
-
this.rl.close();
|
|
1752
|
-
this.cancellationManager.cleanup();
|
|
1753
|
-
this.mcpManager.disconnectAllServers();
|
|
1754
|
-
|
|
1755
|
-
// End the current session
|
|
1756
|
-
this.sessionManager.completeCurrentSession();
|
|
1757
|
-
|
|
1758
|
-
const separator = icons.separator.repeat(40);
|
|
1759
|
-
console.log('');
|
|
1760
|
-
console.log(colors.border(separator));
|
|
1761
|
-
console.log(colors.primaryBright(`${icons.sparkles} Goodbye!`));
|
|
1762
|
-
console.log(colors.border(separator));
|
|
1763
|
-
console.log('');
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
/**
|
|
1767
|
-
* Get the RemoteAIClient instance
|
|
1768
|
-
* Used by tools.ts to access the remote AI client for GUI operations
|
|
1769
|
-
*/
|
|
1770
|
-
getRemoteAIClient(): RemoteAIClient | null {
|
|
1771
|
-
return this.remoteAIClient;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
/**
|
|
1775
|
-
* Get the current taskId for this user interaction
|
|
1776
|
-
* Used by GUI operations to track the same task
|
|
1777
|
-
*/
|
|
1778
|
-
getTaskId(): string | null {
|
|
1779
|
-
return this.currentTaskId;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
export async function startInteractiveSession(): Promise<void> {
|
|
1784
|
-
const session = new InteractiveSession();
|
|
1785
|
-
|
|
1786
|
-
// Flag to control shutdown
|
|
1787
|
-
(session as any)._isShuttingDown = false;
|
|
1788
|
-
|
|
1789
|
-
// Also listen for raw Ctrl+C on stdin (works in Windows PowerShell)
|
|
1790
|
-
process.stdin.on('data', (chunk: Buffer) => {
|
|
1791
|
-
const str = chunk.toString();
|
|
1792
|
-
// Ctrl+C is character 0x03 or string '\u0003'
|
|
1793
|
-
if (str === '\u0003' || str.charCodeAt(0) === 3) {
|
|
1794
|
-
if (!(session as any)._isShuttingDown) {
|
|
1795
|
-
(session as any)._isShuttingDown = true;
|
|
1796
|
-
|
|
1797
|
-
// Print goodbye immediately
|
|
1798
|
-
const separator = icons.separator.repeat(40);
|
|
1799
|
-
process.stdout.write('\n' + colors.border(separator) + '\n');
|
|
1800
|
-
process.stdout.write(colors.primaryBright(`${icons.sparkles} Goodbye!`) + '\n');
|
|
1801
|
-
process.stdout.write(colors.border(separator) + '\n\n');
|
|
1802
|
-
|
|
1803
|
-
// Force exit
|
|
1804
|
-
process.exit(0);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
});
|
|
1808
|
-
|
|
1809
|
-
process.on('SIGINT', () => {
|
|
1810
|
-
if ((session as any)._isShuttingDown) {
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1813
|
-
(session as any)._isShuttingDown = true;
|
|
1814
|
-
|
|
1815
|
-
// Remove all SIGINT listeners to prevent re-entry
|
|
1816
|
-
process.removeAllListeners('SIGINT');
|
|
1817
|
-
|
|
1818
|
-
// Print goodbye immediately
|
|
1819
|
-
const separator = icons.separator.repeat(40);
|
|
1820
|
-
process.stdout.write('\n' + colors.border(separator) + '\n');
|
|
1821
|
-
process.stdout.write(colors.primaryBright(`${icons.sparkles} Goodbye!`) + '\n');
|
|
1822
|
-
process.stdout.write(colors.border(separator) + '\n\n');
|
|
1823
|
-
|
|
1824
|
-
// Force exit
|
|
1825
|
-
process.exit(0);
|
|
1826
|
-
});
|
|
1827
|
-
|
|
1828
|
-
await session.start();
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
// Singleton session instance for access from other modules
|
|
1832
|
-
let singletonSession: InteractiveSession | null = null;
|
|
1833
|
-
|
|
1834
|
-
export function setSingletonSession(session: InteractiveSession): void {
|
|
1835
|
-
singletonSession = session;
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
export function getSingletonSession(): InteractiveSession | null {
|
|
1839
|
-
return singletonSession;
|
|
1840
|
-
}
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const packageJson = require('../package.json');
|
|
14
|
+
import { ExecutionMode, ChatMessage, ToolCall, AuthType } from './types.js';
|
|
15
|
+
import { AIClient, Message, detectThinkingKeywords, getThinkingTokens } from './ai-client.js';
|
|
16
|
+
import { RemoteAIClient, TokenInvalidError } from './remote-ai-client.js';
|
|
17
|
+
import { getConfigManager, ConfigManager } from './config.js';
|
|
18
|
+
import { AuthService, selectAuthType } from './auth.js';
|
|
19
|
+
import { getToolRegistry } from './tools.js';
|
|
20
|
+
import { getAgentManager, DEFAULT_AGENTS, AgentManager } from './agents.js';
|
|
21
|
+
import { getMemoryManager, MemoryManager } from './memory.js';
|
|
22
|
+
import { getMCPManager, MCPManager } from './mcp.js';
|
|
23
|
+
import { getCheckpointManager, CheckpointManager } from './checkpoint.js';
|
|
24
|
+
import { getConversationManager, ConversationManager } from './conversation.js';
|
|
25
|
+
import { getSessionManager, SessionManager } from './session-manager.js';
|
|
26
|
+
import { SlashCommandHandler, parseInput, detectImageInput } from './slash-commands.js';
|
|
27
|
+
import { SystemPromptGenerator } from './system-prompt-generator.js';
|
|
28
|
+
import { theme, icons, colors, styleHelpers, renderMarkdown, renderDiff, renderLines, TERMINAL_BG } from './theme.js';
|
|
29
|
+
import { getCancellationManager, CancellationManager } from './cancellation.js';
|
|
30
|
+
import { getContextCompressor, ContextCompressor, CompressionResult } from './context-compressor.js';
|
|
31
|
+
import { Logger, LogLevel, getLogger } from './logger.js';
|
|
32
|
+
|
|
33
|
+
const logger = getLogger();
|
|
34
|
+
|
|
35
|
+
export class InteractiveSession {
|
|
36
|
+
private conversationManager: ConversationManager;
|
|
37
|
+
private sessionManager: SessionManager;
|
|
38
|
+
private contextCompressor: ContextCompressor;
|
|
39
|
+
private aiClient: AIClient | null = null;
|
|
40
|
+
private remoteAIClient: RemoteAIClient | null = null;
|
|
41
|
+
private conversation: ChatMessage[] = [];
|
|
42
|
+
private toolCalls: ToolCall[] = [];
|
|
43
|
+
private executionMode: ExecutionMode;
|
|
44
|
+
private slashCommandHandler: SlashCommandHandler;
|
|
45
|
+
private configManager: ConfigManager;
|
|
46
|
+
private agentManager: AgentManager;
|
|
47
|
+
private memoryManager: MemoryManager;
|
|
48
|
+
private mcpManager: MCPManager;
|
|
49
|
+
private checkpointManager: CheckpointManager;
|
|
50
|
+
private currentAgent: any = null;
|
|
51
|
+
private rl: readline.Interface;
|
|
52
|
+
private cancellationManager: CancellationManager;
|
|
53
|
+
private indentLevel: number;
|
|
54
|
+
private indentString: string;
|
|
55
|
+
private remoteConversationId: string | null = null;
|
|
56
|
+
private currentTaskId: string | null = null;
|
|
57
|
+
private taskCompleted: boolean = false;
|
|
58
|
+
private isFirstApiCall: boolean = true;
|
|
59
|
+
|
|
60
|
+
constructor(indentLevel: number = 0) {
|
|
61
|
+
|
|
62
|
+
this.rl = readline.createInterface({
|
|
63
|
+
|
|
64
|
+
input: process.stdin,
|
|
65
|
+
|
|
66
|
+
output: process.stdout
|
|
67
|
+
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
this.configManager = getConfigManager(process.cwd());
|
|
73
|
+
|
|
74
|
+
this.agentManager = getAgentManager(process.cwd());
|
|
75
|
+
|
|
76
|
+
this.memoryManager = getMemoryManager(process.cwd());
|
|
77
|
+
|
|
78
|
+
this.mcpManager = getMCPManager();
|
|
79
|
+
|
|
80
|
+
this.checkpointManager = getCheckpointManager(process.cwd());
|
|
81
|
+
|
|
82
|
+
this.conversationManager = getConversationManager();
|
|
83
|
+
|
|
84
|
+
this.sessionManager = getSessionManager(process.cwd());
|
|
85
|
+
|
|
86
|
+
this.slashCommandHandler = new SlashCommandHandler();
|
|
87
|
+
|
|
88
|
+
// Register /clear callback, clear local conversation when clearing dialogue
|
|
89
|
+
this.slashCommandHandler.setClearCallback(() => {
|
|
90
|
+
this.conversation = [];
|
|
91
|
+
this.toolCalls = [];
|
|
92
|
+
this.currentTaskId = null;
|
|
93
|
+
this.taskCompleted = false;
|
|
94
|
+
this.isFirstApiCall = true;
|
|
95
|
+
this.slashCommandHandler.setConversationHistory([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
// Register MCP update callback, update system prompt
|
|
101
|
+
|
|
102
|
+
this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
|
|
103
|
+
|
|
104
|
+
await this.updateSystemPrompt();
|
|
105
|
+
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
this.executionMode = ExecutionMode.DEFAULT;
|
|
111
|
+
|
|
112
|
+
this.cancellationManager = getCancellationManager();
|
|
113
|
+
|
|
114
|
+
this.indentLevel = indentLevel;
|
|
115
|
+
|
|
116
|
+
this.indentString = ' '.repeat(indentLevel);
|
|
117
|
+
|
|
118
|
+
this.contextCompressor = getContextCompressor();
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private getIndent(): string {
|
|
123
|
+
return this.indentString;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setAIClient(aiClient: AIClient): void {
|
|
127
|
+
this.aiClient = aiClient;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setExecutionMode(mode: ExecutionMode): void {
|
|
131
|
+
this.executionMode = mode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Update system prompt to reflect MCP changes (called after add/remove MCP)
|
|
136
|
+
*/
|
|
137
|
+
async updateSystemPrompt(): Promise<void> {
|
|
138
|
+
const toolRegistry = getToolRegistry();
|
|
139
|
+
const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
|
|
140
|
+
|
|
141
|
+
// Use the current agent's original system prompt as base
|
|
142
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
|
|
143
|
+
const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
144
|
+
|
|
145
|
+
// Replace old system prompt with new one
|
|
146
|
+
this.conversation = this.conversation.filter(msg => msg.role !== 'system');
|
|
147
|
+
this.conversation.unshift({
|
|
148
|
+
role: 'system',
|
|
149
|
+
content: newSystemPrompt,
|
|
150
|
+
timestamp: Date.now()
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Sync to slashCommandHandler
|
|
154
|
+
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
setAgent(agent: any): void {
|
|
158
|
+
this.currentAgent = agent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async start(): Promise<void> {
|
|
162
|
+
// Set this session as the singleton for access from other modules
|
|
163
|
+
setSingletonSession(this);
|
|
164
|
+
|
|
165
|
+
// Initialize taskId for GUI operations
|
|
166
|
+
this.currentTaskId = crypto.randomUUID();
|
|
167
|
+
|
|
168
|
+
const separator = icons.separator.repeat(60);
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
|
|
171
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
172
|
+
console.log(colors.gradient('║') + ' '.repeat(13) + '🤖 ' + colors.gradient('XAGENT CLI') + ' '.repeat(32) + colors.gradient(' ║'));
|
|
173
|
+
console.log(colors.gradient('║') + ' '.repeat(16) + colors.textMuted(`v${packageJson.version}`) + ' '.repeat(36) + colors.gradient(' ║'));
|
|
174
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
175
|
+
console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
|
|
176
|
+
console.log(colors.textMuted(' AI-powered command-line assistant'));
|
|
177
|
+
console.log('');
|
|
178
|
+
|
|
179
|
+
await this.initialize();
|
|
180
|
+
this.showWelcomeMessage();
|
|
181
|
+
|
|
182
|
+
// Track if an operation is in progress
|
|
183
|
+
(this as any)._isOperationInProgress = false;
|
|
184
|
+
|
|
185
|
+
// Listen for ESC cancellation - only cancel operations, don't exit the program
|
|
186
|
+
const cancelHandler = () => {
|
|
187
|
+
if ((this as any)._isOperationInProgress) {
|
|
188
|
+
// An operation is running, let it be cancelled
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// No operation running, ignore ESC or show a message
|
|
192
|
+
};
|
|
193
|
+
this.cancellationManager.on('cancelled', cancelHandler);
|
|
194
|
+
|
|
195
|
+
this.promptLoop();
|
|
196
|
+
|
|
197
|
+
// Keep the promise pending until shutdown
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
(this as any)._shutdownResolver = resolve;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async initialize(): Promise<void> {
|
|
204
|
+
logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// Custom spinner for initialization (like Thinking...)
|
|
208
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
209
|
+
let frameIndex = 0;
|
|
210
|
+
const validatingText = colors.textMuted('Validating authentication...');
|
|
211
|
+
|
|
212
|
+
const spinnerInterval = setInterval(() => {
|
|
213
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${validatingText}`);
|
|
214
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
215
|
+
}, 120);
|
|
216
|
+
logger.debug('[SESSION] 调用 configManager.load()...');
|
|
217
|
+
await this.configManager.load();
|
|
218
|
+
|
|
219
|
+
logger.debug('[SESSION] Config loaded');
|
|
220
|
+
let authConfig = this.configManager.getAuthConfig();
|
|
221
|
+
let selectedAuthType = this.configManager.get('selectedAuthType');
|
|
222
|
+
|
|
223
|
+
logger.debug('[SESSION] authConfig.apiKey exists:', String(!!authConfig.apiKey));
|
|
224
|
+
logger.debug('[SESSION] selectedAuthType (initial):', String(selectedAuthType));
|
|
225
|
+
logger.debug('[SESSION] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
226
|
+
logger.debug('[SESSION] AuthType.OPENAI_COMPATIBLE:', String(AuthType.OPENAI_COMPATIBLE));
|
|
227
|
+
logger.debug('[SESSION] Will validate OAuth:', String(!!(authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT)));
|
|
228
|
+
|
|
229
|
+
// Only validate OAuth tokens, skip validation for third-party API keys
|
|
230
|
+
if (authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
231
|
+
clearInterval(spinnerInterval);
|
|
232
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear the line
|
|
233
|
+
|
|
234
|
+
const baseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
235
|
+
let isValid = await this.validateToken(baseUrl, authConfig.apiKey);
|
|
236
|
+
|
|
237
|
+
// Try refresh token if validation failed
|
|
238
|
+
if (!isValid && authConfig.refreshToken) {
|
|
239
|
+
const refreshingText = colors.textMuted('Refreshing authentication...');
|
|
240
|
+
frameIndex = 0;
|
|
241
|
+
const refreshInterval = setInterval(() => {
|
|
242
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${refreshingText}`);
|
|
243
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
244
|
+
}, 120);
|
|
245
|
+
|
|
246
|
+
const newToken = await this.refreshToken(baseUrl, authConfig.refreshToken);
|
|
247
|
+
clearInterval(refreshInterval);
|
|
248
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
249
|
+
|
|
250
|
+
if (newToken) {
|
|
251
|
+
// Save new token and persist
|
|
252
|
+
await this.configManager.set('apiKey', newToken);
|
|
253
|
+
await this.configManager.save('global');
|
|
254
|
+
authConfig.apiKey = newToken;
|
|
255
|
+
isValid = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!isValid) {
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(colors.warning('Your xAgent session has expired or is not configured'));
|
|
262
|
+
console.log(colors.info('Please select an authentication method to continue.'));
|
|
263
|
+
console.log('');
|
|
264
|
+
|
|
265
|
+
// Clear invalid credentials and persist
|
|
266
|
+
// Note: Do NOT overwrite selectedAuthType - let user re-select their preferred auth method
|
|
267
|
+
await this.configManager.set('apiKey', '');
|
|
268
|
+
await this.configManager.set('refreshToken', '');
|
|
269
|
+
await this.configManager.save('global');
|
|
270
|
+
|
|
271
|
+
await this.configManager.load();
|
|
272
|
+
authConfig = this.configManager.getAuthConfig();
|
|
273
|
+
|
|
274
|
+
await this.setupAuthentication();
|
|
275
|
+
authConfig = this.configManager.getAuthConfig();
|
|
276
|
+
|
|
277
|
+
// Recreate readline interface after inquirer
|
|
278
|
+
this.rl.close();
|
|
279
|
+
this.rl = readline.createInterface({
|
|
280
|
+
input: process.stdin,
|
|
281
|
+
output: process.stdout
|
|
282
|
+
});
|
|
283
|
+
this.rl.on('close', () => {
|
|
284
|
+
// readline closed
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
} else if (!authConfig.apiKey) {
|
|
288
|
+
// No API key configured, need to set up authentication
|
|
289
|
+
clearInterval(spinnerInterval);
|
|
290
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
291
|
+
await this.setupAuthentication();
|
|
292
|
+
authConfig = this.configManager.getAuthConfig();
|
|
293
|
+
selectedAuthType = this.configManager.get('selectedAuthType');
|
|
294
|
+
logger.debug('[SESSION] selectedAuthType (after setup):', String(selectedAuthType));
|
|
295
|
+
|
|
296
|
+
// Recreate readline interface after inquirer
|
|
297
|
+
this.rl.close();
|
|
298
|
+
this.rl = readline.createInterface({
|
|
299
|
+
input: process.stdin,
|
|
300
|
+
output: process.stdout
|
|
301
|
+
});
|
|
302
|
+
this.rl.on('close', () => {
|
|
303
|
+
// readline closed
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
clearInterval(spinnerInterval);
|
|
307
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
308
|
+
}
|
|
309
|
+
// For OPENAI_COMPATIBLE with API key, skip validation and proceed directly
|
|
310
|
+
|
|
311
|
+
this.aiClient = new AIClient(authConfig);
|
|
312
|
+
this.contextCompressor.setAIClient(this.aiClient);
|
|
313
|
+
|
|
314
|
+
// Initialize remote AI client for OAuth XAGENT mode
|
|
315
|
+
logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
|
|
316
|
+
logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
|
|
317
|
+
if (selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
318
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
319
|
+
// In OAuth XAGENT mode, we still pass apiKey (can be empty or used for other purposes)
|
|
320
|
+
this.remoteAIClient = new RemoteAIClient(authConfig.apiKey || '', webBaseUrl, authConfig.showAIDebugInfo);
|
|
321
|
+
logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
|
|
322
|
+
} else {
|
|
323
|
+
logger.debug('[DEBUG Initialize] RemoteAIClient NOT created (not OAuth XAGENT mode)');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
327
|
+
|
|
328
|
+
await this.agentManager.loadAgents();
|
|
329
|
+
await this.memoryManager.loadMemory();
|
|
330
|
+
await this.conversationManager.initialize();
|
|
331
|
+
await this.sessionManager.initialize();
|
|
332
|
+
|
|
333
|
+
// Create a new conversation and session for this interactive session
|
|
334
|
+
const conversation = await this.conversationManager.createConversation();
|
|
335
|
+
await this.sessionManager.createSession(
|
|
336
|
+
conversation.id,
|
|
337
|
+
this.currentAgent?.name || 'general-purpose',
|
|
338
|
+
this.executionMode
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Sync conversation history to slashCommandHandler
|
|
342
|
+
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
343
|
+
|
|
344
|
+
const mcpServers = this.configManager.getMcpServers();
|
|
345
|
+
Object.entries(mcpServers).forEach(([name, config]) => {
|
|
346
|
+
console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
|
|
347
|
+
this.mcpManager.registerServer(name, config);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Eagerly connect to MCP servers to get tool definitions
|
|
351
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
352
|
+
try {
|
|
353
|
+
console.log(`${colors.info(`${icons.brain} Connecting to ${Object.keys(mcpServers).length} MCP server(s)...`)}`);
|
|
354
|
+
await this.mcpManager.connectAllServers();
|
|
355
|
+
const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s: any) => s.isServerConnected()).length;
|
|
356
|
+
const mcpTools = this.mcpManager.getToolDefinitions();
|
|
357
|
+
console.log(`${colors.success(`✓ ${connectedCount}/${Object.keys(mcpServers).length} MCP server(s) connected (${mcpTools.length} tools available)`)}`);
|
|
358
|
+
|
|
359
|
+
// Register MCP tools with the tool registry (hide MCP origin from LLM)
|
|
360
|
+
const toolRegistry = getToolRegistry();
|
|
361
|
+
const allMcpTools = this.mcpManager.getAllTools();
|
|
362
|
+
toolRegistry.registerMCPTools(allMcpTools);
|
|
363
|
+
} catch (error: any) {
|
|
364
|
+
console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const checkpointingConfig = this.configManager.getCheckpointingConfig();
|
|
369
|
+
if (checkpointingConfig.enabled) {
|
|
370
|
+
this.checkpointManager = getCheckpointManager(
|
|
371
|
+
process.cwd(),
|
|
372
|
+
checkpointingConfig.enabled,
|
|
373
|
+
checkpointingConfig.maxCheckpoints
|
|
374
|
+
);
|
|
375
|
+
await this.checkpointManager.initialize();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.currentAgent = this.agentManager.getAgent('general-purpose');
|
|
379
|
+
|
|
380
|
+
console.log(colors.success('✔ Initialization complete'));
|
|
381
|
+
} catch (error: any) {
|
|
382
|
+
const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
|
|
383
|
+
spinner.fail(colors.error(`Initialization failed: ${error.message}`));
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Validate token with the backend
|
|
390
|
+
* Returns true if token is valid, false otherwise
|
|
391
|
+
*/
|
|
392
|
+
private async validateToken(baseUrl: string, apiKey: string): Promise<boolean> {
|
|
393
|
+
logger.debug('[SESSION] validateToken called with baseUrl:', baseUrl);
|
|
394
|
+
logger.debug('[SESSION] apiKey exists:', apiKey ? 'yes' : 'no');
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
// For OAuth XAGENT auth, use /api/auth/me endpoint
|
|
398
|
+
const url = `${baseUrl}/api/auth/me`;
|
|
399
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
400
|
+
|
|
401
|
+
logger.debug('[SESSION] Sending validation request to:', url);
|
|
402
|
+
|
|
403
|
+
const response = await axios.get(url, {
|
|
404
|
+
headers: {
|
|
405
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
406
|
+
'Content-Type': 'application/json'
|
|
407
|
+
},
|
|
408
|
+
httpsAgent,
|
|
409
|
+
timeout: 10000
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
logger.debug('[SESSION] Validation response status:', String(response.status));
|
|
413
|
+
return response.status === 200;
|
|
414
|
+
} catch (error: any) {
|
|
415
|
+
// Network error - log details but still consider token may be invalid
|
|
416
|
+
logger.debug('[SESSION] Error:', error.message);
|
|
417
|
+
if (error.response) {
|
|
418
|
+
logger.debug('[SESSION] Response status:', error.response.status);
|
|
419
|
+
}
|
|
420
|
+
// For network errors, we still return false to trigger re-authentication
|
|
421
|
+
// This ensures security but the user can retry
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private async refreshToken(baseUrl: string, refreshToken: string): Promise<string | null> {
|
|
427
|
+
try {
|
|
428
|
+
const url = `${baseUrl}/api/auth/refresh`;
|
|
429
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
430
|
+
|
|
431
|
+
const response = await axios.post(url, { refreshToken }, {
|
|
432
|
+
httpsAgent,
|
|
433
|
+
timeout: 10000
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (response.status === 200) {
|
|
437
|
+
const data = response.data as { token?: string; refreshToken?: string };
|
|
438
|
+
return data.token || null;
|
|
439
|
+
} else {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
} catch (error: any) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private async setupAuthentication(): Promise<void> {
|
|
448
|
+
const separator = icons.separator.repeat(40);
|
|
449
|
+
console.log('');
|
|
450
|
+
console.log(colors.primaryBright(`${icons.lock} Setup Authentication`));
|
|
451
|
+
console.log(colors.border(separator));
|
|
452
|
+
console.log('');
|
|
453
|
+
|
|
454
|
+
const authType = await selectAuthType();
|
|
455
|
+
this.configManager.set('selectedAuthType', authType);
|
|
456
|
+
|
|
457
|
+
const authService = new AuthService({
|
|
458
|
+
type: authType,
|
|
459
|
+
apiKey: '',
|
|
460
|
+
baseUrl: '',
|
|
461
|
+
modelName: ''
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const success = await authService.authenticate();
|
|
465
|
+
|
|
466
|
+
if (!success) {
|
|
467
|
+
console.log('');
|
|
468
|
+
console.log(colors.error('Authentication failed. Exiting...'));
|
|
469
|
+
console.log('');
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const authConfig = authService.getAuthConfig();
|
|
474
|
+
|
|
475
|
+
// VLM configuration is optional - only show for non-OAuth (local) mode
|
|
476
|
+
// Remote mode uses backend VLM configuration
|
|
477
|
+
if (authType !== AuthType.OAUTH_XAGENT) {
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log(colors.info(`${icons.info} VLM configuration is optional.`));
|
|
480
|
+
console.log(colors.info(`You can configure it later using the /vlm command if needed.`));
|
|
481
|
+
console.log('');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Save LLM config only, skip VLM for now
|
|
485
|
+
await this.configManager.setAuthConfig(authConfig);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private showWelcomeMessage(): void {
|
|
489
|
+
const language = this.configManager.getLanguage();
|
|
490
|
+
const separator = icons.separator.repeat(40);
|
|
491
|
+
|
|
492
|
+
console.log('');
|
|
493
|
+
console.log(colors.border(separator));
|
|
494
|
+
|
|
495
|
+
if (language === 'zh') {
|
|
496
|
+
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
497
|
+
console.log(colors.textMuted('Type /help to see available commands')); } else {
|
|
498
|
+
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
499
|
+
console.log(colors.textMuted('Type /help to see available commands'));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
console.log(colors.border(separator));
|
|
503
|
+
console.log('');
|
|
504
|
+
|
|
505
|
+
this.showExecutionMode();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private showExecutionMode(): void {
|
|
509
|
+
const modeConfig = {
|
|
510
|
+
[ExecutionMode.YOLO]: {
|
|
511
|
+
color: colors.error,
|
|
512
|
+
icon: icons.fire,
|
|
513
|
+
description: 'Execute commands without confirmation'
|
|
514
|
+
},
|
|
515
|
+
[ExecutionMode.ACCEPT_EDITS]: {
|
|
516
|
+
color: colors.warning,
|
|
517
|
+
icon: icons.check,
|
|
518
|
+
description: 'Accept all edits automatically'
|
|
519
|
+
},
|
|
520
|
+
[ExecutionMode.PLAN]: {
|
|
521
|
+
color: colors.info,
|
|
522
|
+
icon: icons.brain,
|
|
523
|
+
description: 'Plan before executing'
|
|
524
|
+
},
|
|
525
|
+
[ExecutionMode.DEFAULT]: {
|
|
526
|
+
color: colors.success,
|
|
527
|
+
icon: icons.bolt,
|
|
528
|
+
description: 'Safe execution with confirmations'
|
|
529
|
+
},
|
|
530
|
+
[ExecutionMode.SMART]: {
|
|
531
|
+
color: colors.primaryBright,
|
|
532
|
+
icon: icons.sparkles,
|
|
533
|
+
description: 'Smart approval with intelligent security checks'
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const config = modeConfig[this.executionMode];
|
|
538
|
+
const modeName = this.executionMode;
|
|
539
|
+
|
|
540
|
+
console.log(colors.textMuted(`${icons.info} Current Mode:`));
|
|
541
|
+
console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
|
|
542
|
+
console.log(` ${colors.textDim(` ${config.description}`)}`);
|
|
543
|
+
console.log('');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private async promptLoop(): Promise<void> {
|
|
547
|
+
// Check if we're shutting down
|
|
548
|
+
if ((this as any)._isShuttingDown) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Recreate readline interface for input
|
|
553
|
+
if (this.rl) {
|
|
554
|
+
this.rl.close();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Enable raw mode BEFORE emitKeypressEvents for better ESC detection
|
|
558
|
+
if (process.stdin.isTTY) {
|
|
559
|
+
process.stdin.setRawMode(true);
|
|
560
|
+
}
|
|
561
|
+
process.stdin.resume();
|
|
562
|
+
readline.emitKeypressEvents(process.stdin);
|
|
563
|
+
|
|
564
|
+
this.rl = readline.createInterface({
|
|
565
|
+
input: process.stdin,
|
|
566
|
+
output: process.stdout
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const prompt = `${colors.primaryBright('❯')} `;
|
|
570
|
+
this.rl.question(prompt, async (input: string) => {
|
|
571
|
+
if ((this as any)._isShuttingDown) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
await this.handleInput(input);
|
|
577
|
+
} catch (err: any) {
|
|
578
|
+
console.log(colors.error(`Error: ${err.message}`));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.promptLoop();
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private async handleInput(input: string): Promise<void> {
|
|
586
|
+
const trimmedInput = input.trim();
|
|
587
|
+
|
|
588
|
+
if (!trimmedInput) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (trimmedInput.startsWith('/')) {
|
|
593
|
+
const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
|
|
594
|
+
if (handled) {
|
|
595
|
+
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
596
|
+
// Sync conversation history to slashCommandHandler
|
|
597
|
+
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (trimmedInput.startsWith('$')) {
|
|
603
|
+
await this.handleSubAgentCommand(trimmedInput);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
await this.processUserMessage(trimmedInput);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private async handleSubAgentCommand(input: string): Promise<void> {
|
|
611
|
+
const [agentType, ...taskParts] = input.slice(1).split(' ');
|
|
612
|
+
const task = taskParts.join(' ');
|
|
613
|
+
|
|
614
|
+
const agent = this.agentManager.getAgent(agentType);
|
|
615
|
+
|
|
616
|
+
if (!agent) {
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log(colors.warning(`Agent not found: ${agentType}`));
|
|
619
|
+
console.log(colors.textMuted('Use /agents list to see available agents'));
|
|
620
|
+
console.log('');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
console.log('');
|
|
625
|
+
console.log(colors.primaryBright(`${icons.robot} Using agent: ${agent.name || agent.agentType}`));
|
|
626
|
+
console.log(colors.border(icons.separator.repeat(40)));
|
|
627
|
+
console.log('');
|
|
628
|
+
|
|
629
|
+
this.currentAgent = agent;
|
|
630
|
+
await this.processUserMessage(task, agent);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
public async processUserMessage(message: string, agent?: any): Promise<void> {
|
|
634
|
+
const inputs = parseInput(message);
|
|
635
|
+
const textInput = inputs.find(i => i.type === 'text');
|
|
636
|
+
const fileInputs = inputs.filter(i => i.type === 'file');
|
|
637
|
+
const commandInput = inputs.find(i => i.type === 'command');
|
|
638
|
+
|
|
639
|
+
if (commandInput) {
|
|
640
|
+
await this.executeShellCommand(commandInput.content);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
let userContent = textInput?.content || '';
|
|
645
|
+
|
|
646
|
+
if (fileInputs.length > 0) {
|
|
647
|
+
const toolRegistry = getToolRegistry();
|
|
648
|
+
for (const fileInput of fileInputs) {
|
|
649
|
+
try {
|
|
650
|
+
const content = await toolRegistry.execute('Read', { filePath: fileInput.content }, this.executionMode);
|
|
651
|
+
userContent += `\n\n--- File: ${fileInput.content} ---\n${content}`;
|
|
652
|
+
} catch (error: any) {
|
|
653
|
+
console.log(chalk.yellow(`Warning: Failed to read file ${fileInput.content}: ${error.message}`));
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Record input to session manager
|
|
659
|
+
const sessionInput = {
|
|
660
|
+
type: 'text' as const,
|
|
661
|
+
content: userContent,
|
|
662
|
+
rawInput: message,
|
|
663
|
+
timestamp: Date.now()
|
|
664
|
+
};
|
|
665
|
+
await this.sessionManager.addInput(sessionInput);
|
|
666
|
+
|
|
667
|
+
// Calculate thinking tokens based on config and user input
|
|
668
|
+
const thinkingConfig = this.configManager.getThinkingConfig();
|
|
669
|
+
let thinkingTokens = 0;
|
|
670
|
+
|
|
671
|
+
if (thinkingConfig.enabled) {
|
|
672
|
+
// If thinking mode is enabled, detect keywords and calculate tokens
|
|
673
|
+
const thinkingMode = detectThinkingKeywords(userContent);
|
|
674
|
+
thinkingTokens = getThinkingTokens(thinkingMode);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const userMessage: ChatMessage = {
|
|
678
|
+
role: 'user',
|
|
679
|
+
content: userContent,
|
|
680
|
+
timestamp: Date.now()
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// Save last user message for recovery after compression
|
|
684
|
+
const lastUserMessage = userMessage;
|
|
685
|
+
|
|
686
|
+
this.conversation.push(userMessage);
|
|
687
|
+
await this.conversationManager.addMessage(userMessage);
|
|
688
|
+
|
|
689
|
+
// Check if context compression is needed
|
|
690
|
+
await this.checkAndCompressContext(lastUserMessage);
|
|
691
|
+
|
|
692
|
+
// Use remote AI client if available (OAuth XAGENT mode)
|
|
693
|
+
const currentSelectedAuthType = this.configManager.get('selectedAuthType');
|
|
694
|
+
logger.debug('[DEBUG processUserMessage] remoteAIClient exists:', !!this.remoteAIClient ? 'true' : 'false');
|
|
695
|
+
logger.debug('[DEBUG processUserMessage] selectedAuthType:', String(currentSelectedAuthType));
|
|
696
|
+
logger.debug('[DEBUG processUserMessage] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
697
|
+
|
|
698
|
+
if (this.remoteAIClient) {
|
|
699
|
+
logger.debug('[DEBUG processUserMessage] Using generateRemoteResponse');
|
|
700
|
+
await this.generateRemoteResponse(thinkingTokens);
|
|
701
|
+
} else {
|
|
702
|
+
logger.debug('[DEBUG processUserMessage] Using generateResponse (local mode)');
|
|
703
|
+
await this.generateResponse(thinkingTokens);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private displayThinkingContent(reasoningContent: string): void {
|
|
708
|
+
const indent = this.getIndent();
|
|
709
|
+
const thinkingConfig = this.configManager.getThinkingConfig();
|
|
710
|
+
const displayMode = thinkingConfig.displayMode || 'compact';
|
|
711
|
+
|
|
712
|
+
const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length);
|
|
713
|
+
|
|
714
|
+
console.log('');
|
|
715
|
+
console.log(`${indent}${colors.border(separator)}`);
|
|
716
|
+
|
|
717
|
+
switch (displayMode) {
|
|
718
|
+
case 'full':
|
|
719
|
+
// Full display, using small font and gray color
|
|
720
|
+
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
|
|
721
|
+
console.log('');
|
|
722
|
+
console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
|
|
723
|
+
break;
|
|
724
|
+
|
|
725
|
+
case 'compact':
|
|
726
|
+
// Compact display, truncate partial content
|
|
727
|
+
const maxLength = 500;
|
|
728
|
+
const truncatedContent = reasoningContent.length > maxLength
|
|
729
|
+
? reasoningContent.substring(0, maxLength) + '... (truncated)'
|
|
730
|
+
: reasoningContent;
|
|
731
|
+
|
|
732
|
+
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
|
|
733
|
+
console.log('');
|
|
734
|
+
console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
|
|
735
|
+
console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
|
|
736
|
+
break;
|
|
737
|
+
|
|
738
|
+
case 'indicator':
|
|
739
|
+
// Show indicator only
|
|
740
|
+
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking process completed`)}`);
|
|
741
|
+
console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars of reasoning]`)}`);
|
|
742
|
+
break;
|
|
743
|
+
|
|
744
|
+
default:
|
|
745
|
+
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking:`)}`);
|
|
746
|
+
console.log('');
|
|
747
|
+
console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
console.log(`${indent}${colors.border(separator)}`);
|
|
751
|
+
console.log('');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Check and compress conversation context
|
|
756
|
+
*/
|
|
757
|
+
private async checkAndCompressContext(lastUserMessage?: ChatMessage): Promise<void> {
|
|
758
|
+
const compressionConfig = this.configManager.getContextCompressionConfig();
|
|
759
|
+
|
|
760
|
+
if (!compressionConfig.enabled) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const { needsCompression, reason } = this.contextCompressor.needsCompression(
|
|
765
|
+
this.conversation,
|
|
766
|
+
compressionConfig
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
if (needsCompression) {
|
|
770
|
+
const indent = this.getIndent();
|
|
771
|
+
console.log('');
|
|
772
|
+
console.log(`${indent}${colors.warning(`${icons.brain} Context compression triggered: ${reason}`)}`);
|
|
773
|
+
|
|
774
|
+
const toolRegistry = getToolRegistry();
|
|
775
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
|
|
776
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
777
|
+
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
778
|
+
|
|
779
|
+
const result: CompressionResult = await this.contextCompressor.compressContext(
|
|
780
|
+
this.conversation,
|
|
781
|
+
enhancedSystemPrompt,
|
|
782
|
+
compressionConfig
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
if (result.wasCompressed) {
|
|
786
|
+
this.conversation = result.compressedMessages;
|
|
787
|
+
// console.log(`${indent}${colors.success(`✓ Compressed ${result.originalMessageCount} messages to ${result.compressedMessageCount} messages`)}`);
|
|
788
|
+
console.log(`${indent}${colors.textMuted(`✓ Size: ${result.originalSize} → ${result.compressedSize} chars (${Math.round((1 - result.compressedSize / result.originalSize) * 100)}% reduction)`)}`);
|
|
789
|
+
|
|
790
|
+
// Display compressed summary content
|
|
791
|
+
const summaryMessage = result.compressedMessages.find(m => m.role === 'assistant');
|
|
792
|
+
if (summaryMessage && summaryMessage.content) {
|
|
793
|
+
const maxPreviewLength = 800;
|
|
794
|
+
let summaryContent = summaryMessage.content;
|
|
795
|
+
const isTruncated = summaryContent.length > maxPreviewLength;
|
|
796
|
+
|
|
797
|
+
if (isTruncated) {
|
|
798
|
+
summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
console.log('');
|
|
802
|
+
console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
|
|
803
|
+
const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
|
|
804
|
+
console.log(`${indent}${colors.border(separator)}`);
|
|
805
|
+
const renderedSummary = renderMarkdown(summaryContent, (process.stdout.columns || 80) - indent.length * 4);
|
|
806
|
+
console.log(`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`);
|
|
807
|
+
if (isTruncated) {
|
|
808
|
+
console.log(`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`);
|
|
809
|
+
}
|
|
810
|
+
console.log(`${indent}${colors.border(separator)}`);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Restore user messages after compression, ensuring user message exists for API calls
|
|
814
|
+
if (lastUserMessage) {
|
|
815
|
+
this.conversation.push(lastUserMessage);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Sync compressed conversation history to slashCommandHandler
|
|
819
|
+
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
private async executeShellCommand(command: string): Promise<void> {
|
|
825
|
+
const indent = this.getIndent();
|
|
826
|
+
console.log('');
|
|
827
|
+
console.log(`${indent}${colors.textMuted(`${icons.code} Executing:`)}`);
|
|
828
|
+
console.log(`${indent}${colors.codeText(` $ ${command}`)}`);
|
|
829
|
+
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
830
|
+
console.log('');
|
|
831
|
+
|
|
832
|
+
const toolRegistry = getToolRegistry();
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
const result = await toolRegistry.execute('Bash', { command }, this.executionMode);
|
|
836
|
+
|
|
837
|
+
if (result.stdout) {
|
|
838
|
+
console.log(`${indent}${result.stdout.replace(/^/gm, indent)}`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (result.stderr) {
|
|
842
|
+
console.log(`${indent}${colors.warning(result.stderr.replace(/^/gm, indent))}`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const toolCall: ToolCall = {
|
|
846
|
+
tool: 'Bash',
|
|
847
|
+
params: { command },
|
|
848
|
+
result,
|
|
849
|
+
timestamp: Date.now()
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
this.toolCalls.push(toolCall);
|
|
853
|
+
|
|
854
|
+
// Record command execution to session manager
|
|
855
|
+
await this.sessionManager.addInput({
|
|
856
|
+
type: 'command',
|
|
857
|
+
content: command,
|
|
858
|
+
rawInput: command,
|
|
859
|
+
timestamp: Date.now()
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
await this.sessionManager.addOutput({
|
|
863
|
+
role: 'tool',
|
|
864
|
+
content: JSON.stringify(result),
|
|
865
|
+
toolName: 'Bash',
|
|
866
|
+
toolParams: { command },
|
|
867
|
+
toolResult: result,
|
|
868
|
+
timestamp: Date.now()
|
|
869
|
+
});
|
|
870
|
+
} catch (error: any) {
|
|
871
|
+
console.log(`${indent}${colors.error(`Command execution failed: ${error.message}`)}`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Create unified LLM Caller
|
|
877
|
+
* Implement transparency: caller doesn't need to care about remote vs local mode
|
|
878
|
+
*/
|
|
879
|
+
private createLLMCaller(taskId: string, status: 'begin' | 'continue') {
|
|
880
|
+
// Remote mode: use RemoteAIClient
|
|
881
|
+
if (this.remoteAIClient) {
|
|
882
|
+
return this.createRemoteCaller(taskId, status);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Local mode: use AIClient
|
|
886
|
+
if (!this.aiClient) {
|
|
887
|
+
throw new Error('AI client not initialized');
|
|
888
|
+
}
|
|
889
|
+
return this.createLocalCaller();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Create remote mode LLM caller
|
|
894
|
+
*/
|
|
895
|
+
private createRemoteCaller(taskId: string, status: 'begin' | 'continue') {
|
|
896
|
+
const client = this.remoteAIClient!;
|
|
897
|
+
return {
|
|
898
|
+
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
899
|
+
client.chatCompletion(messages, { ...options, taskId, status }),
|
|
900
|
+
isRemote: true
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Create local mode LLM caller
|
|
906
|
+
*/
|
|
907
|
+
private createLocalCaller() {
|
|
908
|
+
const client = this.aiClient!;
|
|
909
|
+
return {
|
|
910
|
+
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
911
|
+
client.chatCompletion(messages as any, options),
|
|
912
|
+
isRemote: false
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
private async generateResponse(thinkingTokens: number = 0, customAIClient?: AIClient, existingTaskId?: string): Promise<void> {
|
|
917
|
+
// Use existing taskId or create new one for this user interaction
|
|
918
|
+
// If taskId already exists (e.g., from tool calls), reuse it
|
|
919
|
+
const taskId = existingTaskId || this.currentTaskId || crypto.randomUUID();
|
|
920
|
+
this.currentTaskId = taskId;
|
|
921
|
+
this.isFirstApiCall = true;
|
|
922
|
+
|
|
923
|
+
// Determine status based on whether this is the first API call
|
|
924
|
+
const status: 'begin' | 'continue' = this.isFirstApiCall ? 'begin' : 'continue';
|
|
925
|
+
|
|
926
|
+
// Use custom AI client if provided, otherwise use default logic
|
|
927
|
+
let chatCompletion: (messages: ChatMessage[], options: any) => Promise<any>;
|
|
928
|
+
let isRemote = false;
|
|
929
|
+
|
|
930
|
+
if (customAIClient) {
|
|
931
|
+
// Custom client (used by remote mode) - pass taskId and status
|
|
932
|
+
chatCompletion = (messages: ChatMessage[], options: any) =>
|
|
933
|
+
customAIClient.chatCompletion(messages as any, { ...options, taskId, status });
|
|
934
|
+
isRemote = true;
|
|
935
|
+
} else {
|
|
936
|
+
// Use unified LLM Caller with taskId (automatically selects local or remote mode)
|
|
937
|
+
const caller = this.createLLMCaller(taskId, status);
|
|
938
|
+
chatCompletion = caller.chatCompletion;
|
|
939
|
+
isRemote = caller.isRemote;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (!isRemote && !this.aiClient && !customAIClient) {
|
|
943
|
+
console.log(colors.error('AI client not initialized'));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Mark that an operation is in progress
|
|
948
|
+
(this as any)._isOperationInProgress = true;
|
|
949
|
+
|
|
950
|
+
const indent = this.getIndent();
|
|
951
|
+
const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
|
|
952
|
+
const icon = colors.primary(icons.brain);
|
|
953
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
954
|
+
let frameIndex = 0;
|
|
955
|
+
|
|
956
|
+
// Custom spinner: only icon rotates, text stays static
|
|
957
|
+
const spinnerInterval = setInterval(() => {
|
|
958
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
|
|
959
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
960
|
+
}, 120);
|
|
961
|
+
|
|
962
|
+
try {
|
|
963
|
+
const memory = await this.memoryManager.loadMemory();
|
|
964
|
+
const toolRegistry = getToolRegistry();
|
|
965
|
+
const allowedToolNames = this.currentAgent
|
|
966
|
+
? this.agentManager.getAvailableToolsForAgent(this.currentAgent, this.executionMode)
|
|
967
|
+
: [];
|
|
968
|
+
|
|
969
|
+
// MCP servers are already connected during initialization (eager mode)
|
|
970
|
+
// MCP tools are already registered as local tools via registerMCPTools
|
|
971
|
+
const toolDefinitions = toolRegistry.getToolDefinitions();
|
|
972
|
+
|
|
973
|
+
// Available tools for this session
|
|
974
|
+
const availableTools = this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
|
|
975
|
+
? toolDefinitions.filter((tool: any) => allowedToolNames.includes(tool.function.name))
|
|
976
|
+
: toolDefinitions;
|
|
977
|
+
|
|
978
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt;
|
|
979
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
|
|
980
|
+
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
981
|
+
|
|
982
|
+
const messages: ChatMessage[] = [
|
|
983
|
+
{ role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
|
|
984
|
+
...this.conversation
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
const operationId = `ai-response-${Date.now()}`;
|
|
988
|
+
const response = await this.cancellationManager.withCancellation(
|
|
989
|
+
chatCompletion(messages, {
|
|
990
|
+
tools: availableTools,
|
|
991
|
+
toolChoice: availableTools.length > 0 ? 'auto' : 'none',
|
|
992
|
+
thinkingTokens
|
|
993
|
+
}),
|
|
994
|
+
operationId
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
// Mark that first API call is complete
|
|
998
|
+
this.isFirstApiCall = false;
|
|
999
|
+
|
|
1000
|
+
clearInterval(spinnerInterval);
|
|
1001
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
|
|
1002
|
+
|
|
1003
|
+
const assistantMessage = response.choices[0].message;
|
|
1004
|
+
|
|
1005
|
+
const content = typeof assistantMessage.content === 'string'
|
|
1006
|
+
? assistantMessage.content
|
|
1007
|
+
: '';
|
|
1008
|
+
const reasoningContent = assistantMessage.reasoning_content || '';
|
|
1009
|
+
// Display reasoning content if available and thinking mode is enabled
|
|
1010
|
+
if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
|
|
1011
|
+
this.displayThinkingContent(reasoningContent);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
console.log('');
|
|
1015
|
+
console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
|
|
1016
|
+
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
1017
|
+
console.log('');
|
|
1018
|
+
const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
|
|
1019
|
+
console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
|
|
1020
|
+
console.log('');
|
|
1021
|
+
|
|
1022
|
+
this.conversation.push({
|
|
1023
|
+
role: 'assistant',
|
|
1024
|
+
content,
|
|
1025
|
+
timestamp: Date.now(),
|
|
1026
|
+
reasoningContent,
|
|
1027
|
+
toolCalls: assistantMessage.tool_calls
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// Record output to session manager
|
|
1031
|
+
await this.sessionManager.addOutput({
|
|
1032
|
+
role: 'assistant',
|
|
1033
|
+
content,
|
|
1034
|
+
timestamp: Date.now(),
|
|
1035
|
+
reasoningContent,
|
|
1036
|
+
toolCalls: assistantMessage.tool_calls
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
if (assistantMessage.tool_calls) {
|
|
1040
|
+
await this.handleToolCalls(assistantMessage.tool_calls);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (this.checkpointManager.isEnabled()) {
|
|
1044
|
+
await this.checkpointManager.createCheckpoint(
|
|
1045
|
+
`Response generated at ${new Date().toLocaleString()}`,
|
|
1046
|
+
[...this.conversation],
|
|
1047
|
+
[...this.toolCalls]
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Operation completed successfully, clear the flag
|
|
1052
|
+
(this as any)._isOperationInProgress = false;
|
|
1053
|
+
} catch (error: any) {
|
|
1054
|
+
clearInterval(spinnerInterval);
|
|
1055
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
1056
|
+
|
|
1057
|
+
// Clear the operation flag
|
|
1058
|
+
(this as any)._isOperationInProgress = false;
|
|
1059
|
+
|
|
1060
|
+
if (error.message === 'Operation cancelled by user') {
|
|
1061
|
+
// Mark task as cancelled
|
|
1062
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
1063
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => {});
|
|
1064
|
+
}
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Mark task as cancelled when error occurs (发送 status: 'cancel')
|
|
1069
|
+
logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
|
|
1070
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
1071
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => {});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
console.log(colors.error(`Error: ${error.message}`));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Generate response using remote AI service(OAuth XAGENT 模式)
|
|
1080
|
+
* Support full tool calling loop
|
|
1081
|
+
* 与本地模式 generateResponse 保持一致
|
|
1082
|
+
* @param thinkingTokens - Optional thinking tokens config
|
|
1083
|
+
* @param existingTaskId - Optional existing taskId to reuse (for tool call continuation)
|
|
1084
|
+
*/
|
|
1085
|
+
private async generateRemoteResponse(thinkingTokens: number = 0, existingTaskId?: string): Promise<void> {
|
|
1086
|
+
// Reuse existing taskId or create new one for this user interaction
|
|
1087
|
+
const taskId = existingTaskId || crypto.randomUUID();
|
|
1088
|
+
this.currentTaskId = taskId;
|
|
1089
|
+
logger.debug(`[Session] generateRemoteResponse: taskId=${taskId}, existingTaskId=${!!existingTaskId}`);
|
|
1090
|
+
|
|
1091
|
+
// Reset isFirstApiCall for new task, keep true for continuation
|
|
1092
|
+
if (!existingTaskId) {
|
|
1093
|
+
this.isFirstApiCall = true;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Determine status based on whether this is the first API call
|
|
1097
|
+
const status: 'begin' | 'continue' = this.isFirstApiCall ? 'begin' : 'continue';
|
|
1098
|
+
logger.debug(`[Session] Status for this call: ${status}, isFirstApiCall=${this.isFirstApiCall}`);
|
|
1099
|
+
|
|
1100
|
+
// Check if remote client is available
|
|
1101
|
+
if (!this.remoteAIClient) {
|
|
1102
|
+
console.log(colors.error('Remote AI client not initialized'));
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
try {
|
|
1107
|
+
// Reuse generateResponse with remote client, pass taskId to avoid generating new one
|
|
1108
|
+
await this.generateResponse(thinkingTokens, this.remoteAIClient as any, taskId);
|
|
1109
|
+
|
|
1110
|
+
// Mark task as completed (发送 status: 'end')
|
|
1111
|
+
logger.debug(`[Session] Task completed: taskId=${this.currentTaskId}`);
|
|
1112
|
+
if (this.currentTaskId) {
|
|
1113
|
+
await this.remoteAIClient.completeTask(this.currentTaskId);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
} catch (error: any) {
|
|
1117
|
+
// Clear the operation flag
|
|
1118
|
+
(this as any)._isOperationInProgress = false;
|
|
1119
|
+
|
|
1120
|
+
if (error.message === 'Operation cancelled by user') {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Handle token invalid error - trigger re-authentication
|
|
1125
|
+
if (error instanceof TokenInvalidError) {
|
|
1126
|
+
console.log('');
|
|
1127
|
+
console.log(colors.warning('⚠️ Authentication expired or invalid'));
|
|
1128
|
+
console.log(colors.info('Your browser session has been logged out. Please log in again.'));
|
|
1129
|
+
console.log('');
|
|
1130
|
+
|
|
1131
|
+
// Clear invalid credentials and persist
|
|
1132
|
+
await this.configManager.set('apiKey', '');
|
|
1133
|
+
await this.configManager.set('refreshToken', '');
|
|
1134
|
+
await this.configManager.save('global');
|
|
1135
|
+
|
|
1136
|
+
logger.debug('[DEBUG generateRemoteResponse] Cleared invalid credentials, starting re-authentication...');
|
|
1137
|
+
|
|
1138
|
+
// Re-authenticate
|
|
1139
|
+
await this.setupAuthentication();
|
|
1140
|
+
|
|
1141
|
+
// Reload config to ensure we have the latest authConfig
|
|
1142
|
+
logger.debug('[DEBUG generateRemoteResponse] Re-authentication completed, reloading config...');
|
|
1143
|
+
await this.configManager.load();
|
|
1144
|
+
const authConfig = this.configManager.getAuthConfig();
|
|
1145
|
+
|
|
1146
|
+
logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
|
|
1147
|
+
logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
|
|
1148
|
+
|
|
1149
|
+
// Recreate readline interface after inquirer
|
|
1150
|
+
this.rl.close();
|
|
1151
|
+
this.rl = readline.createInterface({
|
|
1152
|
+
input: process.stdin,
|
|
1153
|
+
output: process.stdout
|
|
1154
|
+
});
|
|
1155
|
+
this.rl.on('close', () => {
|
|
1156
|
+
logger.debug('DEBUG: readline interface closed');
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Reinitialize RemoteAIClient with new token
|
|
1160
|
+
if (authConfig.apiKey) {
|
|
1161
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
1162
|
+
logger.debug('[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token');
|
|
1163
|
+
this.remoteAIClient = new RemoteAIClient(authConfig.apiKey, webBaseUrl, authConfig.showAIDebugInfo);
|
|
1164
|
+
} else {
|
|
1165
|
+
logger.debug('[DEBUG generateRemoteResponse] WARNING: No apiKey after re-authentication!');
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Retry the current operation
|
|
1169
|
+
console.log('');
|
|
1170
|
+
console.log(colors.info('Retrying with new authentication...'));
|
|
1171
|
+
console.log('');
|
|
1172
|
+
return this.generateRemoteResponse(thinkingTokens);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Mark task as cancelled when error occurs (发送 status: 'cancel')
|
|
1176
|
+
logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
|
|
1177
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
1178
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => {});
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
console.log(colors.error(`Error: ${error.message}`));
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
private async handleToolCalls(toolCalls: any[], onComplete?: () => Promise<void>): Promise<void> {
|
|
1187
|
+
// Mark that tool execution is in progress
|
|
1188
|
+
(this as any)._isOperationInProgress = true;
|
|
1189
|
+
|
|
1190
|
+
const toolRegistry = getToolRegistry();
|
|
1191
|
+
const showToolDetails = this.configManager.get('showToolDetails') || false;
|
|
1192
|
+
const indent = this.getIndent();
|
|
1193
|
+
|
|
1194
|
+
// Prepare all tool calls
|
|
1195
|
+
const preparedToolCalls = toolCalls.map((toolCall, index) => {
|
|
1196
|
+
const { name, arguments: params } = toolCall.function;
|
|
1197
|
+
|
|
1198
|
+
let parsedParams: any;
|
|
1199
|
+
try {
|
|
1200
|
+
parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
parsedParams = params;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return { name, params: parsedParams, index, id: toolCall.id };
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
// Display all tool calls info
|
|
1209
|
+
for (const { name, params } of preparedToolCalls) {
|
|
1210
|
+
if (showToolDetails) {
|
|
1211
|
+
console.log('');
|
|
1212
|
+
console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
|
|
1213
|
+
console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
|
|
1214
|
+
} else {
|
|
1215
|
+
const toolDescription = this.getToolDescription(name, params);
|
|
1216
|
+
console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Execute all tools in parallel
|
|
1221
|
+
const results = await toolRegistry.executeAll(
|
|
1222
|
+
preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })),
|
|
1223
|
+
this.executionMode
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
// Process results and maintain order
|
|
1227
|
+
let hasError = false;
|
|
1228
|
+
for (const { tool, result, error } of results) {
|
|
1229
|
+
const toolCall = preparedToolCalls.find(tc => tc.name === tool);
|
|
1230
|
+
if (!toolCall) continue;
|
|
1231
|
+
|
|
1232
|
+
const { params } = toolCall;
|
|
1233
|
+
|
|
1234
|
+
if (error) {
|
|
1235
|
+
if (error === 'Operation cancelled by user') {
|
|
1236
|
+
(this as any)._isOperationInProgress = false;
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
hasError = true;
|
|
1241
|
+
|
|
1242
|
+
console.log('');
|
|
1243
|
+
console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
|
|
1244
|
+
|
|
1245
|
+
// 添加详细的错误信息,包含工具名称和参数,便于 AI 理解和修正
|
|
1246
|
+
this.conversation.push({
|
|
1247
|
+
role: 'tool',
|
|
1248
|
+
content: JSON.stringify({
|
|
1249
|
+
name: tool,
|
|
1250
|
+
parameters: params,
|
|
1251
|
+
error: error
|
|
1252
|
+
}),
|
|
1253
|
+
tool_call_id: toolCall.id,
|
|
1254
|
+
timestamp: Date.now()
|
|
1255
|
+
});
|
|
1256
|
+
} else {
|
|
1257
|
+
// Use correct indent for gui-subagent tasks
|
|
1258
|
+
const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
|
|
1259
|
+
const displayIndent = isGuiSubagent ? indent + ' ' : indent;
|
|
1260
|
+
|
|
1261
|
+
// Always show details for todo tools so users can see their task lists
|
|
1262
|
+
const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
|
|
1263
|
+
|
|
1264
|
+
// Special handling for edit tool with diff
|
|
1265
|
+
const isEditTool = tool === 'Edit';
|
|
1266
|
+
const hasDiff = isEditTool && result?.diff;
|
|
1267
|
+
|
|
1268
|
+
// Special handling for Write tool with file preview
|
|
1269
|
+
const isWriteTool = tool === 'Write';
|
|
1270
|
+
const hasFilePreview = isWriteTool && result?.preview;
|
|
1271
|
+
|
|
1272
|
+
// Special handling for DeleteFile tool
|
|
1273
|
+
const isDeleteTool = tool === 'DeleteFile';
|
|
1274
|
+
const hasDeleteInfo = isDeleteTool && result?.filePath;
|
|
1275
|
+
|
|
1276
|
+
// Special handling for task tool (subagent)
|
|
1277
|
+
const isTaskTool = tool === 'task' && params?.subagent_type;
|
|
1278
|
+
|
|
1279
|
+
// Check if tool is an MCP wrapper tool by looking up in tool registry
|
|
1280
|
+
const { getToolRegistry } = await import('./tools.js');
|
|
1281
|
+
const toolRegistry = getToolRegistry();
|
|
1282
|
+
const toolDef = toolRegistry.get(tool);
|
|
1283
|
+
const isMcpTool = toolDef && (toolDef as any)._isMcpTool === true;
|
|
1284
|
+
|
|
1285
|
+
if (isTodoTool) {
|
|
1286
|
+
console.log('');
|
|
1287
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
|
|
1288
|
+
console.log(this.renderTodoList(result?.todos || [], displayIndent));
|
|
1289
|
+
// Show summary if available
|
|
1290
|
+
if (result?.message) {
|
|
1291
|
+
console.log(`${displayIndent}${colors.textDim(result.message)}`);
|
|
1292
|
+
}
|
|
1293
|
+
} else if (hasDiff) {
|
|
1294
|
+
// Show edit result with diff
|
|
1295
|
+
console.log('');
|
|
1296
|
+
const diffOutput = renderDiff(result.diff);
|
|
1297
|
+
const indentedDiff = diffOutput.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
|
|
1298
|
+
console.log(`${indentedDiff}`);
|
|
1299
|
+
} else if (hasFilePreview) {
|
|
1300
|
+
// Show new file content in diff-like style
|
|
1301
|
+
console.log('');
|
|
1302
|
+
console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
|
|
1303
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
|
|
1304
|
+
console.log('');
|
|
1305
|
+
console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
|
|
1306
|
+
} else if (hasDeleteInfo) {
|
|
1307
|
+
// Show DeleteFile result
|
|
1308
|
+
console.log('');
|
|
1309
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
|
|
1310
|
+
} else if (isTaskTool) {
|
|
1311
|
+
// Special handling for task tool (subagent) - show friendly summary
|
|
1312
|
+
console.log('');
|
|
1313
|
+
const subagentType = params.subagent_type;
|
|
1314
|
+
const subagentName = params.description || (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
|
|
1315
|
+
|
|
1316
|
+
if (result?.success) {
|
|
1317
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
|
|
1318
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
1319
|
+
if (result.message) {
|
|
1320
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
1321
|
+
}
|
|
1322
|
+
} else if (result?.cancelled) {
|
|
1323
|
+
console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
|
|
1324
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
1325
|
+
} else {
|
|
1326
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
|
|
1327
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
1328
|
+
if (result?.message) {
|
|
1329
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
} else if (isMcpTool) {
|
|
1333
|
+
// Special handling for MCP tools - show friendly summary
|
|
1334
|
+
console.log('');
|
|
1335
|
+
// Extract server name and tool name from tool name (format: serverName__toolName)
|
|
1336
|
+
let serverName = 'MCP';
|
|
1337
|
+
let toolDisplayName = tool;
|
|
1338
|
+
if (tool.includes('__')) {
|
|
1339
|
+
const parts = tool.split('__');
|
|
1340
|
+
serverName = parts[0];
|
|
1341
|
+
toolDisplayName = parts.slice(1).join('__');
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Try to extract meaningful content from MCP result
|
|
1345
|
+
let summary = '';
|
|
1346
|
+
if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
|
|
1347
|
+
const firstBlock = result.content[0];
|
|
1348
|
+
if (firstBlock?.type === 'text' && firstBlock?.text) {
|
|
1349
|
+
const text = firstBlock.text;
|
|
1350
|
+
if (typeof text === 'string') {
|
|
1351
|
+
// Detect HTML content
|
|
1352
|
+
if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
|
|
1353
|
+
summary = '[HTML content fetched]';
|
|
1354
|
+
} else {
|
|
1355
|
+
// Try to parse if it's JSON
|
|
1356
|
+
try {
|
|
1357
|
+
const parsed = JSON.parse(text);
|
|
1358
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
|
|
1359
|
+
// Search results format
|
|
1360
|
+
summary = `Found ${parsed.length} result(s)`;
|
|
1361
|
+
} else if (parsed?.message) {
|
|
1362
|
+
summary = parsed.message;
|
|
1363
|
+
} else if (typeof parsed === 'string') {
|
|
1364
|
+
summary = parsed.substring(0, 100);
|
|
1365
|
+
}
|
|
1366
|
+
} catch {
|
|
1367
|
+
// Not JSON, use as-is with truncation
|
|
1368
|
+
summary = text.substring(0, 100);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
} else if (result?.message) {
|
|
1374
|
+
summary = result.message;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (result?.success !== false) {
|
|
1378
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
|
|
1379
|
+
console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
|
|
1380
|
+
if (summary) {
|
|
1381
|
+
console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
|
|
1382
|
+
}
|
|
1383
|
+
} else {
|
|
1384
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
|
|
1385
|
+
console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
|
|
1386
|
+
if (result?.message || result?.error) {
|
|
1387
|
+
console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
} else if (tool === 'InvokeSkill') {
|
|
1391
|
+
// Special handling for InvokeSkill - show friendly summary
|
|
1392
|
+
console.log('');
|
|
1393
|
+
const skillName = params?.skillId || 'Unknown skill';
|
|
1394
|
+
const taskDesc = params?.taskDescription || '';
|
|
1395
|
+
|
|
1396
|
+
if (result?.success) {
|
|
1397
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
|
|
1398
|
+
console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
|
|
1399
|
+
if (taskDesc) {
|
|
1400
|
+
const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
|
|
1401
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
|
|
1402
|
+
}
|
|
1403
|
+
} else {
|
|
1404
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
|
|
1405
|
+
console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
|
|
1406
|
+
if (result?.message) {
|
|
1407
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} else if (showToolDetails) {
|
|
1411
|
+
console.log('');
|
|
1412
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
|
|
1413
|
+
console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
|
|
1414
|
+
} else if (result && result.success === false) {
|
|
1415
|
+
// GUI task or other tool failed
|
|
1416
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
|
|
1417
|
+
} else if (result) {
|
|
1418
|
+
// Show brief preview by default (consistent with subagent behavior)
|
|
1419
|
+
const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
1420
|
+
const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
|
|
1421
|
+
// Indent the preview
|
|
1422
|
+
const indentedPreview = truncatedPreview.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
|
|
1423
|
+
console.log(`${indentedPreview}`);
|
|
1424
|
+
} else {
|
|
1425
|
+
console.log(`${displayIndent}${colors.textDim('(no result)')}`);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const toolCallRecord: ToolCall = {
|
|
1429
|
+
tool,
|
|
1430
|
+
params,
|
|
1431
|
+
result,
|
|
1432
|
+
timestamp: Date.now()
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
this.toolCalls.push(toolCallRecord);
|
|
1436
|
+
|
|
1437
|
+
// Record tool output to session manager
|
|
1438
|
+
await this.sessionManager.addOutput({
|
|
1439
|
+
role: 'tool',
|
|
1440
|
+
content: JSON.stringify(result),
|
|
1441
|
+
toolName: tool,
|
|
1442
|
+
toolParams: params,
|
|
1443
|
+
toolResult: result,
|
|
1444
|
+
timestamp: Date.now()
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// 统一消息格式,包含工具名称和参数
|
|
1448
|
+
this.conversation.push({
|
|
1449
|
+
role: 'tool',
|
|
1450
|
+
content: JSON.stringify({
|
|
1451
|
+
name: tool,
|
|
1452
|
+
parameters: params,
|
|
1453
|
+
result: result
|
|
1454
|
+
}),
|
|
1455
|
+
tool_call_id: toolCall.id,
|
|
1456
|
+
timestamp: Date.now()
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
|
|
1462
|
+
// For all other cases (success, failure, errors), always return results for further processing
|
|
1463
|
+
const guiSubagentCancelled = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent' && results.some(r => r.tool === 'task' && (r.result as any)?.cancelled === true));
|
|
1464
|
+
|
|
1465
|
+
// If GUI agent was cancelled by user, don't continue generating response
|
|
1466
|
+
// This avoids wasting API calls and tokens on cancelled tasks
|
|
1467
|
+
if (guiSubagentCancelled) {
|
|
1468
|
+
console.log('');
|
|
1469
|
+
console.log(`${indent}${colors.textMuted('GUI task cancelled by user')}`);
|
|
1470
|
+
(this as any)._isOperationInProgress = false;
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Handle errors and completion based on whether onComplete callback is provided
|
|
1475
|
+
if (hasError) {
|
|
1476
|
+
(this as any)._isOperationInProgress = false;
|
|
1477
|
+
// 不再抛出异常,而是将错误结果返回给 AI,让 AI 决定如何处理
|
|
1478
|
+
// 这样可以避免工具错误导致程序退出
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// Continue based on mode - 统一处理,无论是否有错误
|
|
1482
|
+
if (onComplete) {
|
|
1483
|
+
// Remote mode: use provided callback
|
|
1484
|
+
await onComplete();
|
|
1485
|
+
} else {
|
|
1486
|
+
// Local mode: default behavior - continue with generateResponse
|
|
1487
|
+
await this.generateResponse();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Get user-friendly description for tool
|
|
1493
|
+
*/
|
|
1494
|
+
private getToolDescription(toolName: string, params: any): string {
|
|
1495
|
+
const descriptions: Record<string, (params: any) => string> = {
|
|
1496
|
+
'Read': (p) => `Read file: ${this.truncatePath(p.filePath)}`,
|
|
1497
|
+
'Write': (p) => `Write file: ${this.truncatePath(p.filePath)}`,
|
|
1498
|
+
'Grep': (p) => `Search text: "${p.pattern}"`,
|
|
1499
|
+
'Bash': (p) => `Execute command: ${this.truncateCommand(p.command)}`,
|
|
1500
|
+
'ListDirectory': (p) => `List directory: ${this.truncatePath(p.path || '.')}`,
|
|
1501
|
+
'SearchFiles': (p) => `Search files: ${p.pattern}`,
|
|
1502
|
+
'DeleteFile': (p) => `Delete file: ${this.truncatePath(p.filePath)}`,
|
|
1503
|
+
'CreateDirectory': (p) => `Create directory: ${this.truncatePath(p.dirPath)}`,
|
|
1504
|
+
'Edit': (p) => `Edit text: ${this.truncatePath(p.file_path)}`,
|
|
1505
|
+
'web_search': (p) => `Web search: "${p.query}"`,
|
|
1506
|
+
'todo_write': () => `Update todo list`,
|
|
1507
|
+
'todo_read': () => `Read todo list`,
|
|
1508
|
+
'task': (p) => `Launch subtask: ${p.description}`,
|
|
1509
|
+
'ReadBashOutput': (p) => `Read task output: ${p.task_id}`,
|
|
1510
|
+
'web_fetch': () => `Fetch web content`,
|
|
1511
|
+
'ask_user_question': () => `Ask user`,
|
|
1512
|
+
'save_memory': () => `Save memory`,
|
|
1513
|
+
'exit_plan_mode': () => `Complete plan`,
|
|
1514
|
+
'xml_escape': (p) => `XML escape: ${this.truncatePath(p.file_path)}`,
|
|
1515
|
+
'image_read': (p) => `Read image: ${this.truncatePath(p.image_input)}`,
|
|
1516
|
+
// 'Skill': (p) => `Execute skill: ${p.skill}`,
|
|
1517
|
+
// 'ListSkills': () => `List available skills`,
|
|
1518
|
+
// 'GetSkillDetails': (p) => `Get skill details: ${p.skill}`,
|
|
1519
|
+
'InvokeSkill': (p) => `Invoke skill: ${p.skillId} - ${this.truncatePath(p.taskDescription || '', 40)}`
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
const getDescription = descriptions[toolName];
|
|
1523
|
+
return getDescription ? getDescription(params) : `Execute tool: ${toolName}`;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/**
|
|
1527
|
+
* Truncate path for display
|
|
1528
|
+
*/
|
|
1529
|
+
private truncatePath(path: string, maxLength: number = 30): string {
|
|
1530
|
+
if (!path) return '';
|
|
1531
|
+
if (path.length <= maxLength) return path;
|
|
1532
|
+
return '...' + path.slice(-(maxLength - 3));
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* Truncate command for display
|
|
1537
|
+
*/
|
|
1538
|
+
private truncateCommand(command: string, maxLength: number = 40): string {
|
|
1539
|
+
if (!command) return '';
|
|
1540
|
+
if (command.length <= maxLength) return command;
|
|
1541
|
+
return command.slice(0, maxLength - 3) + '...';
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* Render todo list in a user-friendly format
|
|
1546
|
+
*/
|
|
1547
|
+
private renderTodoList(todos: any[], indent: string = ''): string {
|
|
1548
|
+
if (!todos || todos.length === 0) {
|
|
1549
|
+
return `${indent}${colors.textMuted('No tasks')}`;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
const statusConfig: Record<string, { icon: string; color: (text: string) => string; label: string }> = {
|
|
1553
|
+
'pending': { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
|
|
1554
|
+
'in_progress': { icon: icons.loading, color: colors.warning, label: 'In Progress' },
|
|
1555
|
+
'completed': { icon: icons.success, color: colors.success, label: 'Completed' },
|
|
1556
|
+
'failed': { icon: icons.error, color: colors.error, label: 'Failed' }
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
const lines: string[] = [];
|
|
1560
|
+
|
|
1561
|
+
for (const todo of todos) {
|
|
1562
|
+
const config = statusConfig[todo.status] || statusConfig['pending'];
|
|
1563
|
+
const statusPrefix = `${config.color(config.icon)} ${config.color(config.label)}:`;
|
|
1564
|
+
lines.push(`${indent} ${statusPrefix} ${colors.text(todo.task)}`);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
return lines.join('\n');
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* Display AI debug information (input or output)
|
|
1572
|
+
*/
|
|
1573
|
+
// AI debug info moved to ai-client.ts implementation
|
|
1574
|
+
// private displayAIDebugInfo(type: 'INPUT' | 'OUTPUT', data: any, extra?: any): void {
|
|
1575
|
+
// const indent = this.getIndent();
|
|
1576
|
+
// const boxChar = {
|
|
1577
|
+
// topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
|
|
1578
|
+
// horizontal: '═', vertical: '║'
|
|
1579
|
+
// };
|
|
1580
|
+
//
|
|
1581
|
+
// console.log('\n' + colors.border(
|
|
1582
|
+
// `${boxChar.topLeft}${boxChar.horizontal.repeat(58)}${boxChar.topRight}`
|
|
1583
|
+
// ));
|
|
1584
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' ' +
|
|
1585
|
+
// colors.primaryBright(type === 'INPUT' ? '🤖 AI INPUT DEBUG' : '📤 AI OUTPUT DEBUG') +
|
|
1586
|
+
// ' '.repeat(36) + colors.border(boxChar.vertical));
|
|
1587
|
+
// console.log(colors.border(
|
|
1588
|
+
// `${boxChar.vertical}${boxChar.horizontal.repeat(58)}${boxChar.vertical}`
|
|
1589
|
+
// ));
|
|
1590
|
+
//
|
|
1591
|
+
// if (type === 'INPUT') {
|
|
1592
|
+
// const messages = data as any[];
|
|
1593
|
+
// const tools = extra as any[];
|
|
1594
|
+
//
|
|
1595
|
+
// // System prompt
|
|
1596
|
+
// const systemMsg = messages.find((m: any) => m.role === 'system');
|
|
1597
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🟫 SYSTEM: ' +
|
|
1598
|
+
// colors.textMuted(systemMsg?.content?.toString().substring(0, 50) || '(none)') + ' '.repeat(3) + colors.border(boxChar.vertical));
|
|
1599
|
+
//
|
|
1600
|
+
// // Messages count
|
|
1601
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 💬 MESSAGES: ' +
|
|
1602
|
+
// colors.text(messages.length.toString()) + ' items' + ' '.repeat(40) + colors.border(boxChar.vertical));
|
|
1603
|
+
//
|
|
1604
|
+
// // Tools count
|
|
1605
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOLS: ' +
|
|
1606
|
+
// colors.text((tools?.length || 0).toString()) + '' + ' '.repeat(43) + colors.border(boxChar.vertical)); //
|
|
1607
|
+
// // Show last 2 messages
|
|
1608
|
+
// const recentMessages = messages.slice(-2);
|
|
1609
|
+
// for (const msg of recentMessages) {
|
|
1610
|
+
// const roleLabel: Record<string, string> = { user: '👤 USER', assistant: '🤖 ASSISTANT', tool: '🔧 TOOL' };
|
|
1611
|
+
// const label = roleLabel[msg.role] || msg.role;
|
|
1612
|
+
// const contentStr = typeof msg.content === 'string'
|
|
1613
|
+
// ? msg.content.substring(0, 100)
|
|
1614
|
+
// : JSON.stringify(msg.content).substring(0, 100);
|
|
1615
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ` ${label}: ` +
|
|
1616
|
+
// colors.textDim(contentStr + '...') + ' '.repeat(Math.max(0, 50 - contentStr.length)) + colors.border(boxChar.vertical));
|
|
1617
|
+
// }
|
|
1618
|
+
// } else {
|
|
1619
|
+
// // OUTPUT
|
|
1620
|
+
// const response = data;
|
|
1621
|
+
// const message = extra;
|
|
1622
|
+
//
|
|
1623
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 📋 MODEL: ' +
|
|
1624
|
+
// colors.text(response.model || 'unknown') + ' '.repeat(45) + colors.border(boxChar.vertical));
|
|
1625
|
+
//
|
|
1626
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' ⏱️ TOKENS: ' +
|
|
1627
|
+
// colors.text(`Prompt: ${response.usage?.prompt_tokens || '?'}, Completion: ${response.usage?.completion_tokens || '?'}`) +
|
|
1628
|
+
// ' '.repeat(15) + colors.border(boxChar.vertical));
|
|
1629
|
+
//
|
|
1630
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOL_CALLS: ' +
|
|
1631
|
+
// colors.text((message.tool_calls?.length || 0).toString()) + '' + ' '.repeat(37) + colors.border(boxChar.vertical));
|
|
1632
|
+
//
|
|
1633
|
+
// // Content preview
|
|
1634
|
+
// const contentStr = typeof message.content === 'string'
|
|
1635
|
+
// ? message.content.substring(0, 100)
|
|
1636
|
+
// : JSON.stringify(message.content).substring(0, 100);
|
|
1637
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 📝 CONTENT: ' +
|
|
1638
|
+
// colors.textDim(contentStr + '...') + ' '.repeat(Math.max(0, 40 - contentStr.length)) + colors.border(boxChar.vertical));
|
|
1639
|
+
// }
|
|
1640
|
+
//
|
|
1641
|
+
// console.log(colors.border(
|
|
1642
|
+
// `${boxChar.bottomLeft}${boxChar.horizontal.repeat(58)}${boxChar.bottomRight}`
|
|
1643
|
+
// ));
|
|
1644
|
+
// }
|
|
1645
|
+
|
|
1646
|
+
shutdown(): void {
|
|
1647
|
+
this.rl.close();
|
|
1648
|
+
this.cancellationManager.cleanup();
|
|
1649
|
+
this.mcpManager.disconnectAllServers();
|
|
1650
|
+
|
|
1651
|
+
// End the current session
|
|
1652
|
+
this.sessionManager.completeCurrentSession();
|
|
1653
|
+
|
|
1654
|
+
const separator = icons.separator.repeat(40);
|
|
1655
|
+
console.log('');
|
|
1656
|
+
console.log(colors.border(separator));
|
|
1657
|
+
console.log(colors.primaryBright(`${icons.sparkles} Goodbye!`));
|
|
1658
|
+
console.log(colors.border(separator));
|
|
1659
|
+
console.log('');
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Get the RemoteAIClient instance
|
|
1664
|
+
* Used by tools.ts to access the remote AI client for GUI operations
|
|
1665
|
+
*/
|
|
1666
|
+
getRemoteAIClient(): RemoteAIClient | null {
|
|
1667
|
+
return this.remoteAIClient;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* Get the current taskId for this user interaction
|
|
1672
|
+
* Used by GUI operations to track the same task
|
|
1673
|
+
*/
|
|
1674
|
+
getTaskId(): string | null {
|
|
1675
|
+
return this.currentTaskId;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
export async function startInteractiveSession(): Promise<void> {
|
|
1680
|
+
const session = new InteractiveSession();
|
|
1681
|
+
|
|
1682
|
+
// Flag to control shutdown
|
|
1683
|
+
(session as any)._isShuttingDown = false;
|
|
1684
|
+
|
|
1685
|
+
// Also listen for raw Ctrl+C on stdin (works in Windows PowerShell)
|
|
1686
|
+
process.stdin.on('data', (chunk: Buffer) => {
|
|
1687
|
+
const str = chunk.toString();
|
|
1688
|
+
// Ctrl+C is character 0x03 or string '\u0003'
|
|
1689
|
+
if (str === '\u0003' || str.charCodeAt(0) === 3) {
|
|
1690
|
+
if (!(session as any)._isShuttingDown) {
|
|
1691
|
+
(session as any)._isShuttingDown = true;
|
|
1692
|
+
|
|
1693
|
+
// Print goodbye immediately
|
|
1694
|
+
const separator = icons.separator.repeat(40);
|
|
1695
|
+
process.stdout.write('\n' + colors.border(separator) + '\n');
|
|
1696
|
+
process.stdout.write(colors.primaryBright(`${icons.sparkles} Goodbye!`) + '\n');
|
|
1697
|
+
process.stdout.write(colors.border(separator) + '\n\n');
|
|
1698
|
+
|
|
1699
|
+
// Force exit
|
|
1700
|
+
process.exit(0);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
process.on('SIGINT', () => {
|
|
1706
|
+
if ((session as any)._isShuttingDown) {
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
(session as any)._isShuttingDown = true;
|
|
1710
|
+
|
|
1711
|
+
// Remove all SIGINT listeners to prevent re-entry
|
|
1712
|
+
process.removeAllListeners('SIGINT');
|
|
1713
|
+
|
|
1714
|
+
// Print goodbye immediately
|
|
1715
|
+
const separator = icons.separator.repeat(40);
|
|
1716
|
+
process.stdout.write('\n' + colors.border(separator) + '\n');
|
|
1717
|
+
process.stdout.write(colors.primaryBright(`${icons.sparkles} Goodbye!`) + '\n');
|
|
1718
|
+
process.stdout.write(colors.border(separator) + '\n\n');
|
|
1719
|
+
|
|
1720
|
+
// Force exit
|
|
1721
|
+
process.exit(0);
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
await session.start();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// Singleton session instance for access from other modules
|
|
1728
|
+
let singletonSession: InteractiveSession | null = null;
|
|
1729
|
+
|
|
1730
|
+
export function setSingletonSession(session: InteractiveSession): void {
|
|
1731
|
+
singletonSession = session;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
export function getSingletonSession(): InteractiveSession | null {
|
|
1735
|
+
return singletonSession;
|
|
1736
|
+
}
|