@xagent-ai/cli 1.0.1 → 1.1.1
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/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/README.md +280 -280
- package/README_CN.md +3 -3
- package/dist/ai-client.d.ts.map +1 -1
- package/dist/ai-client.js +84 -82
- package/dist/ai-client.js.map +1 -1
- package/dist/auth.d.ts +0 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +75 -105
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +166 -13
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -7
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts +5 -5
- package/dist/context-compressor.js +8 -8
- package/dist/context-compressor.js.map +1 -1
- package/dist/gui-subagent/action-parser/actionParser.d.ts +7 -0
- package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -1
- package/dist/gui-subagent/action-parser/actionParser.js +6 -3
- package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
- package/dist/gui-subagent/action-parser/constants.d.ts +6 -0
- package/dist/gui-subagent/action-parser/constants.d.ts.map +1 -1
- package/dist/gui-subagent/action-parser/constants.js +5 -3
- package/dist/gui-subagent/action-parser/constants.js.map +1 -1
- package/dist/gui-subagent/action-parser/index.d.ts +6 -0
- package/dist/gui-subagent/action-parser/index.d.ts.map +1 -1
- package/dist/gui-subagent/action-parser/index.js +5 -3
- package/dist/gui-subagent/action-parser/index.js.map +1 -1
- package/dist/gui-subagent/action-parser/types.d.ts +4 -0
- package/dist/gui-subagent/action-parser/types.d.ts.map +1 -1
- package/dist/gui-subagent/action-parser/types.js +3 -3
- package/dist/gui-subagent/agent/gui-agent.d.ts +39 -0
- package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
- package/dist/gui-subagent/agent/gui-agent.js +164 -89
- package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
- package/dist/gui-subagent/agent/index.d.ts +1 -1
- package/dist/gui-subagent/agent/index.d.ts.map +1 -1
- package/dist/gui-subagent/agent/index.js.map +1 -1
- package/dist/gui-subagent/index.d.ts +27 -1
- package/dist/gui-subagent/index.d.ts.map +1 -1
- package/dist/gui-subagent/index.js +6 -0
- package/dist/gui-subagent/index.js.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +140 -29
- package/dist/mcp.js.map +1 -1
- package/dist/remote-ai-client.d.ts +111 -0
- package/dist/remote-ai-client.d.ts.map +1 -0
- package/dist/remote-ai-client.js +558 -0
- package/dist/remote-ai-client.js.map +1 -0
- package/dist/sdk-output-adapter.d.ts +232 -0
- package/dist/sdk-output-adapter.d.ts.map +1 -0
- package/dist/sdk-output-adapter.js +636 -0
- package/dist/sdk-output-adapter.js.map +1 -0
- package/dist/sdk-session-v2.d.ts +13 -0
- package/dist/sdk-session-v2.d.ts.map +1 -0
- package/dist/sdk-session-v2.js +46 -0
- package/dist/sdk-session-v2.js.map +1 -0
- package/dist/sdk-session.d.ts +13 -0
- package/dist/sdk-session.d.ts.map +1 -0
- package/dist/sdk-session.js +48 -0
- package/dist/sdk-session.js.map +1 -0
- package/dist/session-manager.js +3 -3
- package/dist/session-manager.js.map +1 -1
- package/dist/session.d.ts +46 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +564 -117
- package/dist/session.js.map +1 -1
- package/dist/skill-invoker.d.ts +40 -4
- package/dist/skill-invoker.d.ts.map +1 -1
- package/dist/skill-invoker.js +310 -1184
- package/dist/skill-invoker.js.map +1 -1
- package/dist/skill-loader.d.ts +15 -1
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +49 -32
- package/dist/skill-loader.js.map +1 -1
- package/dist/slash-commands.d.ts +4 -2
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +149 -15
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts +2 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +29 -3
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.d.ts +4 -5
- package/dist/system-prompt-generator.d.ts.map +1 -1
- package/dist/system-prompt-generator.js +131 -81
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/tools.d.ts +17 -6
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +264 -211
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/docs/architecture/mcp-integration-guide.md +194 -131
- package/docs/architecture/overview.md +169 -93
- package/docs/architecture/tool-system-design.md +56 -11
- package/docs/cli/commands.md +238 -189
- package/docs/smart-mode.md +281 -257
- package/docs/third-party-models.md +247 -256
- package/package.json +6 -2
- package/src/ai-client.ts +107 -105
- package/src/auth.ts +82 -116
- package/src/cancellation.ts +1 -1
- package/src/cli.ts +178 -13
- package/src/config.ts +57 -8
- package/src/context-compressor.ts +8 -8
- package/src/gui-subagent/action-parser/actionParser.ts +6 -3
- package/src/gui-subagent/action-parser/constants.ts +5 -3
- package/src/gui-subagent/action-parser/index.ts +5 -3
- package/src/gui-subagent/action-parser/types.ts +3 -3
- package/src/gui-subagent/agent/gui-agent.ts +210 -103
- package/src/gui-subagent/agent/index.ts +1 -1
- package/src/gui-subagent/index.ts +26 -2
- package/src/index.ts +18 -18
- package/src/logger.ts +1 -1
- package/src/mcp.ts +149 -30
- package/src/remote-ai-client.ts +671 -0
- package/src/session-manager.ts +3 -3
- package/src/session.ts +742 -178
- package/src/skill-invoker.ts +340 -1293
- package/src/skill-loader.ts +55 -34
- package/src/slash-commands.ts +165 -15
- package/src/smart-approval.ts +34 -3
- package/src/system-prompt-generator.ts +145 -88
- package/src/tools.ts +309 -224
- package/src/types.ts +0 -1
- package/scripts/init-skills-path.js +0 -58
package/dist/session.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import readline from 'readline';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import crypto from 'crypto';
|
|
3
6
|
import ora from 'ora';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const packageJson = require('../package.json');
|
|
4
10
|
import { ExecutionMode, AuthType } from './types.js';
|
|
5
11
|
import { AIClient, detectThinkingKeywords, getThinkingTokens } from './ai-client.js';
|
|
12
|
+
import { RemoteAIClient, TokenInvalidError } from './remote-ai-client.js';
|
|
6
13
|
import { getConfigManager } from './config.js';
|
|
7
14
|
import { AuthService, selectAuthType } from './auth.js';
|
|
8
15
|
import { getToolRegistry } from './tools.js';
|
|
@@ -17,12 +24,14 @@ import { SystemPromptGenerator } from './system-prompt-generator.js';
|
|
|
17
24
|
import { theme, icons, colors, styleHelpers, renderMarkdown } from './theme.js';
|
|
18
25
|
import { getCancellationManager } from './cancellation.js';
|
|
19
26
|
import { getContextCompressor } from './context-compressor.js';
|
|
27
|
+
import { getLogger } from './logger.js';
|
|
28
|
+
const logger = getLogger();
|
|
20
29
|
export class InteractiveSession {
|
|
21
30
|
conversationManager;
|
|
22
31
|
sessionManager;
|
|
23
32
|
contextCompressor;
|
|
24
|
-
rl;
|
|
25
33
|
aiClient = null;
|
|
34
|
+
remoteAIClient = null;
|
|
26
35
|
conversation = [];
|
|
27
36
|
toolCalls = [];
|
|
28
37
|
executionMode;
|
|
@@ -33,9 +42,14 @@ export class InteractiveSession {
|
|
|
33
42
|
mcpManager;
|
|
34
43
|
checkpointManager;
|
|
35
44
|
currentAgent = null;
|
|
45
|
+
rl;
|
|
36
46
|
cancellationManager;
|
|
37
47
|
indentLevel;
|
|
38
48
|
indentString;
|
|
49
|
+
remoteConversationId = null;
|
|
50
|
+
currentTaskId = null;
|
|
51
|
+
taskCompleted = false;
|
|
52
|
+
isFirstApiCall = true;
|
|
39
53
|
constructor(indentLevel = 0) {
|
|
40
54
|
this.rl = readline.createInterface({
|
|
41
55
|
input: process.stdin,
|
|
@@ -49,18 +63,18 @@ export class InteractiveSession {
|
|
|
49
63
|
this.conversationManager = getConversationManager();
|
|
50
64
|
this.sessionManager = getSessionManager(process.cwd());
|
|
51
65
|
this.slashCommandHandler = new SlashCommandHandler();
|
|
52
|
-
//
|
|
66
|
+
// Register /clear callback, clear local conversation when clearing dialogue
|
|
53
67
|
this.slashCommandHandler.setClearCallback(() => {
|
|
54
68
|
this.conversation = [];
|
|
55
69
|
});
|
|
56
|
-
//
|
|
70
|
+
// Register MCP update callback, update system prompt
|
|
57
71
|
this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
|
|
58
72
|
await this.updateSystemPrompt();
|
|
59
73
|
});
|
|
60
74
|
this.executionMode = ExecutionMode.DEFAULT;
|
|
61
75
|
this.cancellationManager = getCancellationManager();
|
|
62
76
|
this.indentLevel = indentLevel;
|
|
63
|
-
this.indentString = '
|
|
77
|
+
this.indentString = ' '.repeat(indentLevel);
|
|
64
78
|
this.contextCompressor = getContextCompressor();
|
|
65
79
|
}
|
|
66
80
|
getIndent() {
|
|
@@ -77,7 +91,7 @@ export class InteractiveSession {
|
|
|
77
91
|
*/
|
|
78
92
|
async updateSystemPrompt() {
|
|
79
93
|
const toolRegistry = getToolRegistry();
|
|
80
|
-
const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
94
|
+
const promptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
|
|
81
95
|
// Use the current agent's original system prompt as base
|
|
82
96
|
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
|
|
83
97
|
const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
@@ -95,13 +109,17 @@ export class InteractiveSession {
|
|
|
95
109
|
this.currentAgent = agent;
|
|
96
110
|
}
|
|
97
111
|
async start() {
|
|
112
|
+
// Set this session as the singleton for access from other modules
|
|
113
|
+
setSingletonSession(this);
|
|
114
|
+
// Initialize taskId for GUI operations
|
|
115
|
+
this.currentTaskId = crypto.randomUUID();
|
|
98
116
|
const separator = icons.separator.repeat(60);
|
|
99
117
|
console.log('');
|
|
100
118
|
console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
|
|
101
|
-
console.log(colors.gradient('║') + ' '.repeat(
|
|
102
|
-
console.log(' '.repeat(
|
|
103
|
-
console.log(' '.repeat(
|
|
104
|
-
console.log(colors.gradient('║') + ' '.repeat(
|
|
119
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
120
|
+
console.log(' '.repeat(14) + '🤖 ' + colors.gradient('XAGENT CLI') + ' '.repeat(32) + colors.gradient(' ║'));
|
|
121
|
+
console.log(' '.repeat(17) + colors.textMuted(`v${packageJson.version}`) + ' '.repeat(36) + colors.gradient(' ║'));
|
|
122
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
105
123
|
console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
|
|
106
124
|
console.log(colors.textMuted(' AI-powered command-line assistant'));
|
|
107
125
|
console.log('');
|
|
@@ -125,42 +143,63 @@ export class InteractiveSession {
|
|
|
125
143
|
});
|
|
126
144
|
}
|
|
127
145
|
async initialize() {
|
|
146
|
+
logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
|
|
128
147
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
// Custom spinner for initialization (like Thinking...)
|
|
149
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
150
|
+
let frameIndex = 0;
|
|
151
|
+
const validatingText = colors.textMuted('Validating authentication...');
|
|
152
|
+
const spinnerInterval = setInterval(() => {
|
|
153
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${validatingText}`);
|
|
154
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
155
|
+
}, 120);
|
|
156
|
+
logger.debug('[SESSION] 调用 configManager.load()...');
|
|
134
157
|
await this.configManager.load();
|
|
158
|
+
logger.debug('[SESSION] Config loaded');
|
|
135
159
|
let authConfig = this.configManager.getAuthConfig();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
let selectedAuthType = this.configManager.get('selectedAuthType');
|
|
161
|
+
logger.debug('[SESSION] authConfig.apiKey exists:', String(!!authConfig.apiKey));
|
|
162
|
+
logger.debug('[SESSION] selectedAuthType (initial):', String(selectedAuthType));
|
|
163
|
+
logger.debug('[SESSION] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
164
|
+
logger.debug('[SESSION] AuthType.OPENAI_COMPATIBLE:', String(AuthType.OPENAI_COMPATIBLE));
|
|
165
|
+
logger.debug('[SESSION] Will validate OAuth:', String(!!(authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT)));
|
|
166
|
+
// Only validate OAuth tokens, skip validation for third-party API keys
|
|
167
|
+
if (authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
168
|
+
clearInterval(spinnerInterval);
|
|
169
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear the line
|
|
170
|
+
const baseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
|
|
140
171
|
let isValid = await this.validateToken(baseUrl, authConfig.apiKey);
|
|
141
172
|
// Try refresh token if validation failed
|
|
142
173
|
if (!isValid && authConfig.refreshToken) {
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
const refreshingText = colors.textMuted('Refreshing authentication...');
|
|
175
|
+
frameIndex = 0;
|
|
176
|
+
const refreshInterval = setInterval(() => {
|
|
177
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${refreshingText}`);
|
|
178
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
179
|
+
}, 120);
|
|
145
180
|
const newToken = await this.refreshToken(baseUrl, authConfig.refreshToken);
|
|
181
|
+
clearInterval(refreshInterval);
|
|
182
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
146
183
|
if (newToken) {
|
|
147
|
-
// Save new token
|
|
184
|
+
// Save new token and persist
|
|
148
185
|
await this.configManager.set('apiKey', newToken);
|
|
186
|
+
await this.configManager.save('global');
|
|
149
187
|
authConfig.apiKey = newToken;
|
|
150
188
|
isValid = true;
|
|
151
|
-
console.log(colors.success(`[DEBUG] Token refreshed successfully!`));
|
|
152
189
|
}
|
|
153
190
|
}
|
|
154
191
|
if (!isValid) {
|
|
155
|
-
spinner.stop();
|
|
156
192
|
console.log('');
|
|
157
193
|
console.log(colors.warning('⚠️ Authentication expired or invalid'));
|
|
158
194
|
console.log(colors.info('Please log in again to continue.'));
|
|
159
195
|
console.log('');
|
|
160
|
-
// Clear invalid credentials
|
|
196
|
+
// Clear invalid credentials and persist
|
|
197
|
+
// Note: Do NOT overwrite selectedAuthType - let user re-select their preferred auth method
|
|
161
198
|
await this.configManager.set('apiKey', '');
|
|
162
199
|
await this.configManager.set('refreshToken', '');
|
|
163
|
-
await this.configManager.
|
|
200
|
+
await this.configManager.save('global');
|
|
201
|
+
await this.configManager.load();
|
|
202
|
+
authConfig = this.configManager.getAuthConfig();
|
|
164
203
|
await this.setupAuthentication();
|
|
165
204
|
authConfig = this.configManager.getAuthConfig();
|
|
166
205
|
// Recreate readline interface after inquirer
|
|
@@ -170,15 +209,18 @@ export class InteractiveSession {
|
|
|
170
209
|
output: process.stdout
|
|
171
210
|
});
|
|
172
211
|
this.rl.on('close', () => {
|
|
173
|
-
|
|
212
|
+
// readline closed
|
|
174
213
|
});
|
|
175
|
-
spinner.start();
|
|
176
214
|
}
|
|
177
215
|
}
|
|
178
|
-
else {
|
|
179
|
-
|
|
216
|
+
else if (!authConfig.apiKey) {
|
|
217
|
+
// No API key configured, need to set up authentication
|
|
218
|
+
clearInterval(spinnerInterval);
|
|
219
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
180
220
|
await this.setupAuthentication();
|
|
181
221
|
authConfig = this.configManager.getAuthConfig();
|
|
222
|
+
selectedAuthType = this.configManager.get('selectedAuthType');
|
|
223
|
+
logger.debug('[SESSION] selectedAuthType (after setup):', String(selectedAuthType));
|
|
182
224
|
// Recreate readline interface after inquirer
|
|
183
225
|
this.rl.close();
|
|
184
226
|
this.rl = readline.createInterface({
|
|
@@ -186,12 +228,28 @@ export class InteractiveSession {
|
|
|
186
228
|
output: process.stdout
|
|
187
229
|
});
|
|
188
230
|
this.rl.on('close', () => {
|
|
189
|
-
|
|
231
|
+
// readline closed
|
|
190
232
|
});
|
|
191
|
-
spinner.start();
|
|
192
233
|
}
|
|
234
|
+
else {
|
|
235
|
+
clearInterval(spinnerInterval);
|
|
236
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
237
|
+
}
|
|
238
|
+
// For OPENAI_COMPATIBLE with API key, skip validation and proceed directly
|
|
193
239
|
this.aiClient = new AIClient(authConfig);
|
|
194
240
|
this.contextCompressor.setAIClient(this.aiClient);
|
|
241
|
+
// Initialize remote AI client for OAuth XAGENT mode
|
|
242
|
+
logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
|
|
243
|
+
logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
|
|
244
|
+
if (selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
245
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
|
|
246
|
+
// In OAuth XAGENT mode, we still pass apiKey (can be empty or used for other purposes)
|
|
247
|
+
this.remoteAIClient = new RemoteAIClient(authConfig.apiKey || '', webBaseUrl, authConfig.showAIDebugInfo);
|
|
248
|
+
logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
logger.debug('[DEBUG Initialize] RemoteAIClient NOT created (not OAuth XAGENT mode)');
|
|
252
|
+
}
|
|
195
253
|
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
196
254
|
await this.agentManager.loadAgents();
|
|
197
255
|
await this.memoryManager.loadMemory();
|
|
@@ -200,10 +258,9 @@ export class InteractiveSession {
|
|
|
200
258
|
// Create a new conversation and session for this interactive session
|
|
201
259
|
const conversation = await this.conversationManager.createConversation();
|
|
202
260
|
await this.sessionManager.createSession(conversation.id, this.currentAgent?.name || 'general-purpose', this.executionMode);
|
|
203
|
-
//
|
|
261
|
+
// Sync conversation history to slashCommandHandler
|
|
204
262
|
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
205
263
|
const mcpServers = this.configManager.getMcpServers();
|
|
206
|
-
console.log(`📋 Loading ${Object.keys(mcpServers).length} MCP servers from config`);
|
|
207
264
|
Object.entries(mcpServers).forEach(([name, config]) => {
|
|
208
265
|
console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
|
|
209
266
|
this.mcpManager.registerServer(name, config);
|
|
@@ -231,7 +288,7 @@ export class InteractiveSession {
|
|
|
231
288
|
await this.checkpointManager.initialize();
|
|
232
289
|
}
|
|
233
290
|
this.currentAgent = this.agentManager.getAgent('general-purpose');
|
|
234
|
-
|
|
291
|
+
console.log(colors.success('✔ Initialization complete'));
|
|
235
292
|
}
|
|
236
293
|
catch (error) {
|
|
237
294
|
const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
|
|
@@ -244,49 +301,52 @@ export class InteractiveSession {
|
|
|
244
301
|
* Returns true if token is valid, false otherwise
|
|
245
302
|
*/
|
|
246
303
|
async validateToken(baseUrl, apiKey) {
|
|
304
|
+
logger.debug('[SESSION] validateToken called with baseUrl:', baseUrl);
|
|
305
|
+
logger.debug('[SESSION] apiKey exists:', apiKey ? 'yes' : 'no');
|
|
247
306
|
try {
|
|
248
307
|
// For OAuth XAGENT auth, use /api/auth/me endpoint
|
|
249
308
|
const url = `${baseUrl}/api/auth/me`;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
309
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
310
|
+
logger.debug('[SESSION] Sending validation request to:', url);
|
|
311
|
+
const response = await axios.get(url, {
|
|
253
312
|
headers: {
|
|
254
313
|
'Authorization': `Bearer ${apiKey}`,
|
|
255
314
|
'Content-Type': 'application/json'
|
|
256
|
-
}
|
|
315
|
+
},
|
|
316
|
+
httpsAgent,
|
|
317
|
+
timeout: 10000
|
|
257
318
|
});
|
|
258
|
-
|
|
259
|
-
return response.
|
|
319
|
+
logger.debug('[SESSION] Validation response status:', String(response.status));
|
|
320
|
+
return response.status === 200;
|
|
260
321
|
}
|
|
261
322
|
catch (error) {
|
|
262
|
-
|
|
263
|
-
|
|
323
|
+
// Network error - log details but still consider token may be invalid
|
|
324
|
+
logger.debug('[SESSION] Error:', error.message);
|
|
325
|
+
if (error.response) {
|
|
326
|
+
logger.debug('[SESSION] Response status:', error.response.status);
|
|
327
|
+
}
|
|
328
|
+
// For network errors, we still return false to trigger re-authentication
|
|
329
|
+
// This ensures security but the user can retry
|
|
264
330
|
return false;
|
|
265
331
|
}
|
|
266
332
|
}
|
|
267
333
|
async refreshToken(baseUrl, refreshToken) {
|
|
268
334
|
try {
|
|
269
335
|
const url = `${baseUrl}/api/auth/refresh`;
|
|
270
|
-
|
|
271
|
-
const response = await
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
'Content-Type': 'application/json'
|
|
275
|
-
},
|
|
276
|
-
body: JSON.stringify({ refreshToken })
|
|
336
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
337
|
+
const response = await axios.post(url, { refreshToken }, {
|
|
338
|
+
httpsAgent,
|
|
339
|
+
timeout: 10000
|
|
277
340
|
});
|
|
278
|
-
if (response.
|
|
279
|
-
const data =
|
|
280
|
-
console.log(colors.success(`[DEBUG] Token refreshed successfully`));
|
|
341
|
+
if (response.status === 200) {
|
|
342
|
+
const data = response.data;
|
|
281
343
|
return data.token || null;
|
|
282
344
|
}
|
|
283
345
|
else {
|
|
284
|
-
console.log(colors.warning(`[DEBUG] Token refresh failed: ${response.status}`));
|
|
285
346
|
return null;
|
|
286
347
|
}
|
|
287
348
|
}
|
|
288
349
|
catch (error) {
|
|
289
|
-
console.log(colors.warning(`[DEBUG] Token refresh error: ${error}`));
|
|
290
350
|
return null;
|
|
291
351
|
}
|
|
292
352
|
}
|
|
@@ -377,7 +437,7 @@ export class InteractiveSession {
|
|
|
377
437
|
if (this._isShuttingDown) {
|
|
378
438
|
return;
|
|
379
439
|
}
|
|
380
|
-
// Recreate readline interface
|
|
440
|
+
// Recreate readline interface for input
|
|
381
441
|
if (this.rl) {
|
|
382
442
|
this.rl.close();
|
|
383
443
|
}
|
|
@@ -414,7 +474,7 @@ export class InteractiveSession {
|
|
|
414
474
|
const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
|
|
415
475
|
if (handled) {
|
|
416
476
|
this.executionMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
417
|
-
//
|
|
477
|
+
// Sync conversation history to slashCommandHandler
|
|
418
478
|
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
419
479
|
}
|
|
420
480
|
return;
|
|
@@ -490,9 +550,21 @@ export class InteractiveSession {
|
|
|
490
550
|
const lastUserMessage = userMessage;
|
|
491
551
|
this.conversation.push(userMessage);
|
|
492
552
|
await this.conversationManager.addMessage(userMessage);
|
|
493
|
-
//
|
|
553
|
+
// Check if context compression is needed
|
|
494
554
|
await this.checkAndCompressContext(lastUserMessage);
|
|
495
|
-
|
|
555
|
+
// Use remote AI client if available (OAuth XAGENT mode)
|
|
556
|
+
const currentSelectedAuthType = this.configManager.get('selectedAuthType');
|
|
557
|
+
logger.debug('[DEBUG processUserMessage] remoteAIClient exists:', !!this.remoteAIClient ? 'true' : 'false');
|
|
558
|
+
logger.debug('[DEBUG processUserMessage] selectedAuthType:', String(currentSelectedAuthType));
|
|
559
|
+
logger.debug('[DEBUG processUserMessage] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
560
|
+
if (this.remoteAIClient) {
|
|
561
|
+
logger.debug('[DEBUG processUserMessage] Using generateRemoteResponse');
|
|
562
|
+
await this.generateRemoteResponse(thinkingTokens);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
logger.debug('[DEBUG processUserMessage] Using generateResponse (local mode)');
|
|
566
|
+
await this.generateResponse(thinkingTokens);
|
|
567
|
+
}
|
|
496
568
|
}
|
|
497
569
|
displayThinkingContent(reasoningContent) {
|
|
498
570
|
const indent = this.getIndent();
|
|
@@ -533,7 +605,7 @@ export class InteractiveSession {
|
|
|
533
605
|
console.log('');
|
|
534
606
|
}
|
|
535
607
|
/**
|
|
536
|
-
*
|
|
608
|
+
* Check and compress conversation context
|
|
537
609
|
*/
|
|
538
610
|
async checkAndCompressContext(lastUserMessage) {
|
|
539
611
|
const compressionConfig = this.configManager.getContextCompressionConfig();
|
|
@@ -554,7 +626,7 @@ export class InteractiveSession {
|
|
|
554
626
|
this.conversation = result.compressedMessages;
|
|
555
627
|
// console.log(`${indent}${colors.success(`✓ Compressed ${result.originalMessageCount} messages to ${result.compressedMessageCount} messages`)}`);
|
|
556
628
|
console.log(`${indent}${colors.textMuted(`✓ Size: ${result.originalSize} → ${result.compressedSize} chars (${Math.round((1 - result.compressedSize / result.originalSize) * 100)}% reduction)`)}`);
|
|
557
|
-
//
|
|
629
|
+
// Display compressed summary content
|
|
558
630
|
const summaryMessage = result.compressedMessages.find(m => m.role === 'assistant');
|
|
559
631
|
if (summaryMessage && summaryMessage.content) {
|
|
560
632
|
const maxPreviewLength = 800;
|
|
@@ -574,11 +646,11 @@ export class InteractiveSession {
|
|
|
574
646
|
}
|
|
575
647
|
console.log(`${indent}${colors.border(separator)}`);
|
|
576
648
|
}
|
|
577
|
-
//
|
|
649
|
+
// Restore user messages after compression, ensuring user message exists for API calls
|
|
578
650
|
if (lastUserMessage) {
|
|
579
651
|
this.conversation.push(lastUserMessage);
|
|
580
652
|
}
|
|
581
|
-
//
|
|
653
|
+
// Sync compressed conversation history to slashCommandHandler
|
|
582
654
|
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
583
655
|
}
|
|
584
656
|
}
|
|
@@ -626,8 +698,51 @@ export class InteractiveSession {
|
|
|
626
698
|
console.log(`${indent}${colors.error(`Command execution failed: ${error.message}`)}`);
|
|
627
699
|
}
|
|
628
700
|
}
|
|
629
|
-
|
|
701
|
+
/**
|
|
702
|
+
* Create unified LLM Caller
|
|
703
|
+
* Implement transparency: caller doesn't need to care about remote vs local mode
|
|
704
|
+
*/
|
|
705
|
+
createLLMCaller(taskId, status) {
|
|
706
|
+
// Remote mode: use RemoteAIClient
|
|
707
|
+
if (this.remoteAIClient) {
|
|
708
|
+
return this.createRemoteCaller(taskId, status);
|
|
709
|
+
}
|
|
710
|
+
// Local mode: use AIClient
|
|
630
711
|
if (!this.aiClient) {
|
|
712
|
+
throw new Error('AI client not initialized');
|
|
713
|
+
}
|
|
714
|
+
return this.createLocalCaller();
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Create remote mode LLM caller
|
|
718
|
+
*/
|
|
719
|
+
createRemoteCaller(taskId, status) {
|
|
720
|
+
const client = this.remoteAIClient;
|
|
721
|
+
return {
|
|
722
|
+
chatCompletion: (messages, options) => client.chatCompletion(messages, { ...options, taskId, status }),
|
|
723
|
+
isRemote: true
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Create local mode LLM caller
|
|
728
|
+
*/
|
|
729
|
+
createLocalCaller() {
|
|
730
|
+
const client = this.aiClient;
|
|
731
|
+
return {
|
|
732
|
+
chatCompletion: (messages, options) => client.chatCompletion(messages, options),
|
|
733
|
+
isRemote: false
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
async generateResponse(thinkingTokens = 0) {
|
|
737
|
+
// Create taskId for this user interaction (for remote mode tracking)
|
|
738
|
+
const taskId = crypto.randomUUID();
|
|
739
|
+
this.currentTaskId = taskId;
|
|
740
|
+
this.isFirstApiCall = true;
|
|
741
|
+
// Determine status based on whether this is the first API call
|
|
742
|
+
const status = this.isFirstApiCall ? 'begin' : 'continue';
|
|
743
|
+
// Use unified LLM Caller with taskId (automatically selects local or remote mode)
|
|
744
|
+
const { chatCompletion, isRemote } = this.createLLMCaller(taskId, status);
|
|
745
|
+
if (!isRemote && !this.aiClient) {
|
|
631
746
|
console.log(colors.error('AI client not initialized'));
|
|
632
747
|
return;
|
|
633
748
|
}
|
|
@@ -650,66 +765,38 @@ export class InteractiveSession {
|
|
|
650
765
|
? this.agentManager.getAvailableToolsForAgent(this.currentAgent, this.executionMode)
|
|
651
766
|
: [];
|
|
652
767
|
// MCP servers are already connected during initialization (eager mode)
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
//
|
|
656
|
-
const allToolDefinitions = [...allLocalToolDefinitions, ...mcpToolDefinitions];
|
|
768
|
+
// MCP tools are already registered as local tools via registerMCPTools
|
|
769
|
+
const toolDefinitions = toolRegistry.getToolDefinitions();
|
|
770
|
+
// Available tools for this session
|
|
657
771
|
const availableTools = this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
|
|
658
|
-
?
|
|
659
|
-
:
|
|
772
|
+
? toolDefinitions.filter((tool) => allowedToolNames.includes(tool.function.name))
|
|
773
|
+
: toolDefinitions;
|
|
660
774
|
const baseSystemPrompt = this.currentAgent?.systemPrompt;
|
|
661
|
-
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
775
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode, undefined, this.mcpManager);
|
|
662
776
|
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
663
777
|
const messages = [
|
|
664
|
-
{ role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}
|
|
778
|
+
{ role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
|
|
665
779
|
...this.conversation.map(msg => ({
|
|
666
780
|
role: msg.role,
|
|
667
|
-
content: msg.content
|
|
781
|
+
content: msg.content,
|
|
782
|
+
timestamp: msg.timestamp
|
|
668
783
|
}))
|
|
669
784
|
];
|
|
670
|
-
// Debug: 打印完整的 prompt 信息
|
|
671
|
-
// const logger = new Logger({ minLevel: LogLevel.DEBUG });
|
|
672
|
-
// logger.debug('[DEBUG] 即将发送给 AI 的完整 Prompt:');
|
|
673
|
-
// console.log('\n' + '='.repeat(60));
|
|
674
|
-
// console.log('【SYSTEM PROMPT】');
|
|
675
|
-
// console.log('-'.repeat(60));
|
|
676
|
-
// console.log(messages[0]?.content || '(无)');
|
|
677
|
-
// console.log('='.repeat(60));
|
|
678
|
-
// console.log('【CONVERSATION】');
|
|
679
|
-
// console.log('-'.repeat(60));
|
|
680
|
-
// messages.slice(1).forEach((msg, idx) => {
|
|
681
|
-
// const contentStr = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
682
|
-
// console.log(`[${idx + 1}] [${msg.role}]: ${contentStr.substring(0, 200)}${contentStr.length > 200 ? '...' : ''}`);
|
|
683
|
-
// });
|
|
684
|
-
// console.log('='.repeat(60));
|
|
685
|
-
// console.log(`【AVAILABLE TOOLS】: ${availableTools.length} 个工具`);
|
|
686
|
-
// availableTools.forEach((tool: any) => {
|
|
687
|
-
// console.log(` - ${tool.function.name}`);
|
|
688
|
-
// }); // console.log('='.repeat(60) + '\n');
|
|
689
|
-
// Debug: 打印AI输入信息 (已移至 ai-client.ts)
|
|
690
|
-
// if (this.configManager.get('showAIDebugInfo')) {
|
|
691
|
-
// this.displayAIDebugInfo('INPUT', messages, availableTools);
|
|
692
|
-
// }
|
|
693
785
|
const operationId = `ai-response-${Date.now()}`;
|
|
694
|
-
const
|
|
786
|
+
const response = await this.cancellationManager.withCancellation(chatCompletion(messages, {
|
|
695
787
|
tools: availableTools,
|
|
696
788
|
toolChoice: availableTools.length > 0 ? 'auto' : 'none',
|
|
697
789
|
thinkingTokens
|
|
698
|
-
});
|
|
699
|
-
|
|
790
|
+
}), operationId);
|
|
791
|
+
// Mark that first API call is complete
|
|
792
|
+
this.isFirstApiCall = false;
|
|
700
793
|
clearInterval(spinnerInterval);
|
|
701
794
|
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
|
|
702
795
|
const assistantMessage = response.choices[0].message;
|
|
703
|
-
// Debug: 打印AI输出信息 (已移至 ai-client.ts)
|
|
704
|
-
// if (this.configManager.get('showAIDebugInfo')) {
|
|
705
|
-
// this.displayAIDebugInfo('OUTPUT', response, assistantMessage);
|
|
706
|
-
// }
|
|
707
796
|
const content = typeof assistantMessage.content === 'string'
|
|
708
797
|
? assistantMessage.content
|
|
709
798
|
: '';
|
|
710
799
|
const reasoningContent = assistantMessage.reasoning_content || '';
|
|
711
|
-
// console.error('[SESSION DEBUG] assistantMessage:', JSON.stringify(assistantMessage).substring(0, 200));
|
|
712
|
-
// console.error('[SESSION DEBUG] content:', content);
|
|
713
800
|
// Display reasoning content if available and thinking mode is enabled
|
|
714
801
|
if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
|
|
715
802
|
this.displayThinkingContent(reasoningContent);
|
|
@@ -751,10 +838,216 @@ export class InteractiveSession {
|
|
|
751
838
|
// Clear the operation flag
|
|
752
839
|
this._isOperationInProgress = false;
|
|
753
840
|
if (error.message === 'Operation cancelled by user') {
|
|
754
|
-
//
|
|
841
|
+
// Mark task as cancelled
|
|
842
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
843
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId);
|
|
844
|
+
}
|
|
755
845
|
return;
|
|
756
846
|
}
|
|
847
|
+
// Mark task as cancelled when error occurs (发送 status: 'cancel')
|
|
848
|
+
logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
|
|
849
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
850
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId);
|
|
851
|
+
}
|
|
852
|
+
console.log(colors.error(`Error: ${error.message}`));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Generate response using remote AI service(OAuth XAGENT 模式)
|
|
857
|
+
* Support full tool calling loop
|
|
858
|
+
* 与本地模式 generateResponse 保持一致
|
|
859
|
+
* @param thinkingTokens - Optional thinking tokens config
|
|
860
|
+
* @param existingTaskId - Optional existing taskId to reuse (for tool call continuation)
|
|
861
|
+
*/
|
|
862
|
+
async generateRemoteResponse(thinkingTokens = 0, existingTaskId) {
|
|
863
|
+
// Reuse existing taskId or create new one for this user interaction
|
|
864
|
+
const taskId = existingTaskId || crypto.randomUUID();
|
|
865
|
+
this.currentTaskId = taskId;
|
|
866
|
+
logger.debug(`[Session] generateRemoteResponse: taskId=${taskId}, existingTaskId=${!!existingTaskId}`);
|
|
867
|
+
// Reset isFirstApiCall for new task, keep true for continuation
|
|
868
|
+
if (!existingTaskId) {
|
|
869
|
+
this.isFirstApiCall = true;
|
|
870
|
+
}
|
|
871
|
+
// Determine status based on whether this is the first API call
|
|
872
|
+
const status = this.isFirstApiCall ? 'begin' : 'continue';
|
|
873
|
+
logger.debug(`[Session] Status for this call: ${status}, isFirstApiCall=${this.isFirstApiCall}`);
|
|
874
|
+
// 使用统一的 LLM Caller
|
|
875
|
+
const { chatCompletion, isRemote } = this.createLLMCaller(taskId, status);
|
|
876
|
+
if (!isRemote) {
|
|
877
|
+
// 如果不是远程模式,回退到本地模式
|
|
878
|
+
return this.generateResponse(thinkingTokens);
|
|
879
|
+
}
|
|
880
|
+
const indent = this.getIndent();
|
|
881
|
+
const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
|
|
882
|
+
const icon = colors.primary(icons.brain);
|
|
883
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
884
|
+
let frameIndex = 0;
|
|
885
|
+
// Mark that an operation is in progress
|
|
886
|
+
this._isOperationInProgress = true;
|
|
887
|
+
// Custom spinner: only icon rotates, text stays static
|
|
888
|
+
const spinnerInterval = setInterval(() => {
|
|
889
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
|
|
890
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
891
|
+
}, 120);
|
|
892
|
+
try {
|
|
893
|
+
// Load memory (与本地模式一致)
|
|
894
|
+
const memory = await this.memoryManager.loadMemory();
|
|
895
|
+
// Get tool definitions
|
|
896
|
+
const toolRegistry = getToolRegistry();
|
|
897
|
+
const allowedToolNames = this.currentAgent
|
|
898
|
+
? this.agentManager.getAvailableToolsForAgent(this.currentAgent, this.executionMode)
|
|
899
|
+
: [];
|
|
900
|
+
const allToolDefinitions = toolRegistry.getToolDefinitions();
|
|
901
|
+
const availableTools = this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
|
|
902
|
+
? allToolDefinitions.filter((tool) => allowedToolNames.includes(tool.function.name))
|
|
903
|
+
: allToolDefinitions;
|
|
904
|
+
// Convert to the format expected by backend (与本地模式一致使用 availableTools)
|
|
905
|
+
const tools = availableTools.map((tool) => ({
|
|
906
|
+
type: 'function',
|
|
907
|
+
function: {
|
|
908
|
+
name: tool.function.name,
|
|
909
|
+
description: tool.function.description || '',
|
|
910
|
+
parameters: tool.function.parameters || {
|
|
911
|
+
type: 'object',
|
|
912
|
+
properties: {}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}));
|
|
916
|
+
// Generate system prompt (与本地模式一致)
|
|
917
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
|
|
918
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
919
|
+
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
920
|
+
// Build messages with system prompt (与本地模式一致)
|
|
921
|
+
const messages = [
|
|
922
|
+
{ role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
|
|
923
|
+
...this.conversation.map(msg => ({
|
|
924
|
+
role: msg.role,
|
|
925
|
+
content: msg.content,
|
|
926
|
+
timestamp: msg.timestamp
|
|
927
|
+
}))
|
|
928
|
+
];
|
|
929
|
+
// Call unified LLM API with cancellation support
|
|
930
|
+
const operationId = `remote-ai-response-${Date.now()}`;
|
|
931
|
+
const response = await this.cancellationManager.withCancellation(chatCompletion(messages, {
|
|
932
|
+
tools,
|
|
933
|
+
toolChoice: tools.length > 0 ? 'auto' : 'none',
|
|
934
|
+
thinkingTokens
|
|
935
|
+
}), operationId);
|
|
936
|
+
// Mark that first API call is complete
|
|
937
|
+
this.isFirstApiCall = false;
|
|
938
|
+
clearInterval(spinnerInterval);
|
|
939
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
940
|
+
console.log('');
|
|
941
|
+
// 使用统一的响应格式(与本地模式一致)
|
|
942
|
+
const assistantMessage = response.choices[0].message;
|
|
943
|
+
const content = typeof assistantMessage.content === 'string'
|
|
944
|
+
? assistantMessage.content
|
|
945
|
+
: '';
|
|
946
|
+
const reasoningContent = assistantMessage.reasoning_content || '';
|
|
947
|
+
const toolCalls = assistantMessage.tool_calls || [];
|
|
948
|
+
// Display reasoning content if available and thinking mode is enabled (与本地模式一致)
|
|
949
|
+
if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
|
|
950
|
+
this.displayThinkingContent(reasoningContent);
|
|
951
|
+
}
|
|
952
|
+
console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
|
|
953
|
+
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
954
|
+
console.log('');
|
|
955
|
+
const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
|
|
956
|
+
console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
|
|
957
|
+
console.log('');
|
|
958
|
+
// Add assistant message to conversation (consistent with local mode, including reasoningContent)
|
|
959
|
+
this.conversation.push({
|
|
960
|
+
role: 'assistant',
|
|
961
|
+
content,
|
|
962
|
+
timestamp: Date.now(),
|
|
963
|
+
reasoningContent,
|
|
964
|
+
toolCalls: toolCalls
|
|
965
|
+
});
|
|
966
|
+
// Record output to session manager (consistent with local mode, including reasoningContent and toolCalls)
|
|
967
|
+
await this.sessionManager.addOutput({
|
|
968
|
+
role: 'assistant',
|
|
969
|
+
content,
|
|
970
|
+
timestamp: Date.now(),
|
|
971
|
+
reasoningContent,
|
|
972
|
+
toolCalls
|
|
973
|
+
});
|
|
974
|
+
// Handle tool calls
|
|
975
|
+
if (toolCalls.length > 0) {
|
|
976
|
+
await this.handleRemoteToolCalls(toolCalls);
|
|
977
|
+
}
|
|
978
|
+
// Checkpoint support (consistent with local mode)
|
|
979
|
+
if (this.checkpointManager.isEnabled()) {
|
|
980
|
+
await this.checkpointManager.createCheckpoint(`Response generated at ${new Date().toLocaleString()}`, [...this.conversation], [...this.toolCalls]);
|
|
981
|
+
}
|
|
982
|
+
// Operation completed successfully
|
|
983
|
+
this._isOperationInProgress = false;
|
|
984
|
+
// Mark task as completed (发送 status: 'end')
|
|
985
|
+
logger.debug(`[Session] Task completed: taskId=${this.currentTaskId}`);
|
|
986
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
987
|
+
await this.remoteAIClient.completeTask(this.currentTaskId);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
clearInterval(spinnerInterval);
|
|
992
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
993
|
+
// Clear the operation flag
|
|
994
|
+
this._isOperationInProgress = false;
|
|
995
|
+
if (error.message === 'Operation cancelled by user') {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
// Handle token invalid error - trigger re-authentication
|
|
999
|
+
if (error instanceof TokenInvalidError) {
|
|
1000
|
+
console.log('');
|
|
1001
|
+
console.log(colors.warning('⚠️ Authentication expired or invalid'));
|
|
1002
|
+
console.log(colors.info('Your browser session has been logged out. Please log in again.'));
|
|
1003
|
+
console.log('');
|
|
1004
|
+
// Clear invalid credentials and persist
|
|
1005
|
+
// Note: Do NOT overwrite selectedAuthType - preserve user's chosen auth method
|
|
1006
|
+
await this.configManager.set('apiKey', '');
|
|
1007
|
+
await this.configManager.set('refreshToken', '');
|
|
1008
|
+
await this.configManager.save('global');
|
|
1009
|
+
logger.debug('[DEBUG generateRemoteResponse] Cleared invalid credentials, starting re-authentication...');
|
|
1010
|
+
// Re-authenticate
|
|
1011
|
+
await this.setupAuthentication();
|
|
1012
|
+
// Reload config to ensure we have the latest authConfig
|
|
1013
|
+
logger.debug('[DEBUG generateRemoteResponse] Re-authentication completed, reloading config...');
|
|
1014
|
+
await this.configManager.load();
|
|
1015
|
+
const authConfig = this.configManager.getAuthConfig();
|
|
1016
|
+
logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
|
|
1017
|
+
logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
|
|
1018
|
+
logger.debug(' - authConfig.apiKey prefix:', authConfig.apiKey ? authConfig.apiKey.substring(0, 20) + '...' : 'empty');
|
|
1019
|
+
// Recreate readline interface after inquirer
|
|
1020
|
+
this.rl.close();
|
|
1021
|
+
this.rl = readline.createInterface({
|
|
1022
|
+
input: process.stdin,
|
|
1023
|
+
output: process.stdout
|
|
1024
|
+
});
|
|
1025
|
+
this.rl.on('close', () => {
|
|
1026
|
+
logger.debug('DEBUG: readline interface closed');
|
|
1027
|
+
});
|
|
1028
|
+
// Reinitialize RemoteAIClient with new token
|
|
1029
|
+
if (authConfig.apiKey) {
|
|
1030
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
|
|
1031
|
+
logger.debug('[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token');
|
|
1032
|
+
const newWebBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
|
|
1033
|
+
this.remoteAIClient = new RemoteAIClient(authConfig.apiKey, newWebBaseUrl, authConfig.showAIDebugInfo);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
logger.debug('[DEBUG generateRemoteResponse] WARNING: No apiKey after re-authentication!');
|
|
1037
|
+
}
|
|
1038
|
+
// Retry the current operation
|
|
1039
|
+
console.log('');
|
|
1040
|
+
console.log(colors.info('Retrying with new authentication...'));
|
|
1041
|
+
console.log('');
|
|
1042
|
+
return this.generateRemoteResponse(thinkingTokens);
|
|
1043
|
+
}
|
|
1044
|
+
// Mark task as cancelled when error occurs (发送 status: 'cancel')
|
|
1045
|
+
logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
|
|
1046
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
1047
|
+
await this.remoteAIClient.cancelTask(this.currentTaskId);
|
|
1048
|
+
}
|
|
757
1049
|
console.log(colors.error(`Error: ${error.message}`));
|
|
1050
|
+
return;
|
|
758
1051
|
}
|
|
759
1052
|
}
|
|
760
1053
|
async handleToolCalls(toolCalls) {
|
|
@@ -819,9 +1112,9 @@ export class InteractiveSession {
|
|
|
819
1112
|
if (isTodoTool) {
|
|
820
1113
|
console.log('');
|
|
821
1114
|
console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
|
|
822
|
-
console.log(this.renderTodoList(result
|
|
1115
|
+
console.log(this.renderTodoList(result?.todos || [], displayIndent));
|
|
823
1116
|
// Show summary if available
|
|
824
|
-
if (result
|
|
1117
|
+
if (result?.message) {
|
|
825
1118
|
console.log(`${displayIndent}${colors.textDim(result.message)}`);
|
|
826
1119
|
}
|
|
827
1120
|
}
|
|
@@ -830,13 +1123,16 @@ export class InteractiveSession {
|
|
|
830
1123
|
console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
|
|
831
1124
|
console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
|
|
832
1125
|
}
|
|
833
|
-
else if (result.success === false) {
|
|
1126
|
+
else if (result && result.success === false) {
|
|
834
1127
|
// GUI task or other tool failed
|
|
835
1128
|
console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
|
|
836
1129
|
}
|
|
837
|
-
else {
|
|
1130
|
+
else if (result) {
|
|
838
1131
|
console.log(`${displayIndent}${colors.success(`${icons.check} Completed`)}`);
|
|
839
1132
|
}
|
|
1133
|
+
else {
|
|
1134
|
+
console.log(`${displayIndent}${colors.textDim('(no result)')}`);
|
|
1135
|
+
}
|
|
840
1136
|
const toolCallRecord = {
|
|
841
1137
|
tool,
|
|
842
1138
|
params,
|
|
@@ -909,6 +1205,136 @@ export class InteractiveSession {
|
|
|
909
1205
|
const getDescription = descriptions[toolName];
|
|
910
1206
|
return getDescription ? getDescription(params) : `Execute tool: ${toolName}`;
|
|
911
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Handle tool calls for remote AI mode
|
|
1210
|
+
* Executes tools and then continues the conversation with results
|
|
1211
|
+
*/
|
|
1212
|
+
async handleRemoteToolCalls(toolCalls) {
|
|
1213
|
+
// Mark that tool execution is in progress
|
|
1214
|
+
this._isOperationInProgress = true;
|
|
1215
|
+
const toolRegistry = getToolRegistry();
|
|
1216
|
+
const showToolDetails = this.configManager.get('showToolDetails') || false;
|
|
1217
|
+
const indent = this.getIndent();
|
|
1218
|
+
// Prepare all tool calls
|
|
1219
|
+
const preparedToolCalls = toolCalls.map((toolCall, index) => {
|
|
1220
|
+
const { name, arguments: params } = toolCall.function;
|
|
1221
|
+
let parsedParams;
|
|
1222
|
+
try {
|
|
1223
|
+
parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
|
|
1224
|
+
}
|
|
1225
|
+
catch (e) {
|
|
1226
|
+
parsedParams = params;
|
|
1227
|
+
}
|
|
1228
|
+
return { name, params: parsedParams, index };
|
|
1229
|
+
});
|
|
1230
|
+
// Display all tool calls info
|
|
1231
|
+
for (const { name, params } of preparedToolCalls) {
|
|
1232
|
+
if (showToolDetails) {
|
|
1233
|
+
console.log('');
|
|
1234
|
+
console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
|
|
1235
|
+
console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
const toolDescription = this.getToolDescription(name, params);
|
|
1239
|
+
console.log('');
|
|
1240
|
+
console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
// Execute all tools in parallel
|
|
1244
|
+
const results = await toolRegistry.executeAll(preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })), this.executionMode);
|
|
1245
|
+
// Process results and maintain order
|
|
1246
|
+
let hasError = false;
|
|
1247
|
+
for (const { tool, result, error } of results) {
|
|
1248
|
+
const toolCall = preparedToolCalls.find(tc => tc.name === tool);
|
|
1249
|
+
if (!toolCall)
|
|
1250
|
+
continue;
|
|
1251
|
+
const { params } = toolCall;
|
|
1252
|
+
if (error) {
|
|
1253
|
+
// Clear the operation flag
|
|
1254
|
+
this._isOperationInProgress = false;
|
|
1255
|
+
if (error === 'Operation cancelled by user') {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
hasError = true;
|
|
1259
|
+
console.log('');
|
|
1260
|
+
console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${error}`)}`);
|
|
1261
|
+
this.conversation.push({
|
|
1262
|
+
role: 'tool',
|
|
1263
|
+
content: JSON.stringify({ error }),
|
|
1264
|
+
timestamp: Date.now()
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
// Use correct indent for gui-subagent tasks
|
|
1269
|
+
const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
|
|
1270
|
+
const displayIndent = isGuiSubagent ? indent + ' ' : indent;
|
|
1271
|
+
// Always show details for todo tools so users can see their task lists
|
|
1272
|
+
const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
|
|
1273
|
+
if (isTodoTool) {
|
|
1274
|
+
console.log('');
|
|
1275
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
|
|
1276
|
+
console.log(this.renderTodoList(result.todos || result.todos, displayIndent));
|
|
1277
|
+
// Show summary if available
|
|
1278
|
+
if (result.message) {
|
|
1279
|
+
console.log(`${displayIndent}${colors.textDim(result.message)}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
else if (showToolDetails) {
|
|
1283
|
+
console.log('');
|
|
1284
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
|
|
1285
|
+
console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
|
|
1286
|
+
}
|
|
1287
|
+
else if (result.success === false) {
|
|
1288
|
+
// GUI task or other tool failed
|
|
1289
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
|
|
1290
|
+
}
|
|
1291
|
+
else {
|
|
1292
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Completed`)}`);
|
|
1293
|
+
}
|
|
1294
|
+
const toolCallRecord = {
|
|
1295
|
+
tool,
|
|
1296
|
+
params,
|
|
1297
|
+
result,
|
|
1298
|
+
timestamp: Date.now()
|
|
1299
|
+
};
|
|
1300
|
+
this.toolCalls.push(toolCallRecord);
|
|
1301
|
+
// Record tool output to session manager
|
|
1302
|
+
await this.sessionManager.addOutput({
|
|
1303
|
+
role: 'tool',
|
|
1304
|
+
content: JSON.stringify(result),
|
|
1305
|
+
toolName: tool,
|
|
1306
|
+
toolParams: params,
|
|
1307
|
+
toolResult: result,
|
|
1308
|
+
timestamp: Date.now()
|
|
1309
|
+
});
|
|
1310
|
+
this.conversation.push({
|
|
1311
|
+
role: 'tool',
|
|
1312
|
+
content: JSON.stringify(result),
|
|
1313
|
+
timestamp: Date.now()
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
// Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
|
|
1318
|
+
// For all other cases (success, failure, errors), always return results for further processing
|
|
1319
|
+
const guiSubagentFailed = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent');
|
|
1320
|
+
const guiSubagentCancelled = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent' && results.some(r => r.tool === 'task' && r.result?.cancelled === true));
|
|
1321
|
+
// If GUI agent was cancelled by user, don't continue generating response
|
|
1322
|
+
// This avoids wasting API calls and tokens on cancelled tasks
|
|
1323
|
+
if (guiSubagentCancelled) {
|
|
1324
|
+
console.log('');
|
|
1325
|
+
console.log(`${indent}${colors.textMuted('GUI task cancelled by user')}`);
|
|
1326
|
+
this._isOperationInProgress = false;
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
// If any tool call failed, throw error to mark task as cancelled
|
|
1330
|
+
if (hasError) {
|
|
1331
|
+
throw new Error('Tool execution failed');
|
|
1332
|
+
}
|
|
1333
|
+
// For all other cases (GUI success/failure, other tool errors), return results to main agent
|
|
1334
|
+
// This allows main agent to decide how to handle failures (retry, fallback, user notification, etc.)
|
|
1335
|
+
// Reuse existing taskId instead of generating new one
|
|
1336
|
+
await this.generateRemoteResponse(0, this.currentTaskId || undefined);
|
|
1337
|
+
}
|
|
912
1338
|
/**
|
|
913
1339
|
* Truncate path for display
|
|
914
1340
|
*/
|
|
@@ -953,7 +1379,7 @@ export class InteractiveSession {
|
|
|
953
1379
|
/**
|
|
954
1380
|
* Display AI debug information (input or output)
|
|
955
1381
|
*/
|
|
956
|
-
// AI
|
|
1382
|
+
// AI debug info moved to ai-client.ts implementation
|
|
957
1383
|
// private displayAIDebugInfo(type: 'INPUT' | 'OUTPUT', data: any, extra?: any): void {
|
|
958
1384
|
// const indent = this.getIndent();
|
|
959
1385
|
// const boxChar = {
|
|
@@ -978,16 +1404,15 @@ export class InteractiveSession {
|
|
|
978
1404
|
// // System prompt
|
|
979
1405
|
// const systemMsg = messages.find((m: any) => m.role === 'system');
|
|
980
1406
|
// console.log(colors.border(`${boxChar.vertical}`) + ' 🟫 SYSTEM: ' +
|
|
981
|
-
// colors.textMuted(systemMsg?.content?.toString().substring(0, 50) || '(
|
|
1407
|
+
// colors.textMuted(systemMsg?.content?.toString().substring(0, 50) || '(none)') + ' '.repeat(3) + colors.border(boxChar.vertical));
|
|
982
1408
|
//
|
|
983
1409
|
// // Messages count
|
|
984
1410
|
// console.log(colors.border(`${boxChar.vertical}`) + ' 💬 MESSAGES: ' +
|
|
985
|
-
// colors.text(messages.length.toString()) + '
|
|
1411
|
+
// colors.text(messages.length.toString()) + ' items' + ' '.repeat(40) + colors.border(boxChar.vertical));
|
|
986
1412
|
//
|
|
987
1413
|
// // Tools count
|
|
988
1414
|
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOLS: ' +
|
|
989
|
-
// colors.text((tools?.length || 0).toString()) + '
|
|
990
|
-
//
|
|
1415
|
+
// colors.text((tools?.length || 0).toString()) + '' + ' '.repeat(43) + colors.border(boxChar.vertical)); //
|
|
991
1416
|
// // Show last 2 messages
|
|
992
1417
|
// const recentMessages = messages.slice(-2);
|
|
993
1418
|
// for (const msg of recentMessages) {
|
|
@@ -1011,8 +1436,8 @@ export class InteractiveSession {
|
|
|
1011
1436
|
// colors.text(`Prompt: ${response.usage?.prompt_tokens || '?'}, Completion: ${response.usage?.completion_tokens || '?'}`) +
|
|
1012
1437
|
// ' '.repeat(15) + colors.border(boxChar.vertical));
|
|
1013
1438
|
//
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1439
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOL_CALLS: ' +
|
|
1440
|
+
// colors.text((message.tool_calls?.length || 0).toString()) + '' + ' '.repeat(37) + colors.border(boxChar.vertical));
|
|
1016
1441
|
//
|
|
1017
1442
|
// // Content preview
|
|
1018
1443
|
// const contentStr = typeof message.content === 'string'
|
|
@@ -1028,8 +1453,8 @@ export class InteractiveSession {
|
|
|
1028
1453
|
// }
|
|
1029
1454
|
shutdown() {
|
|
1030
1455
|
this.rl.close();
|
|
1031
|
-
this.mcpManager.disconnectAllServers();
|
|
1032
1456
|
this.cancellationManager.cleanup();
|
|
1457
|
+
this.mcpManager.disconnectAllServers();
|
|
1033
1458
|
// End the current session
|
|
1034
1459
|
this.sessionManager.completeCurrentSession();
|
|
1035
1460
|
const separator = icons.separator.repeat(40);
|
|
@@ -1039,6 +1464,20 @@ export class InteractiveSession {
|
|
|
1039
1464
|
console.log(colors.border(separator));
|
|
1040
1465
|
console.log('');
|
|
1041
1466
|
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Get the RemoteAIClient instance
|
|
1469
|
+
* Used by tools.ts to access the remote AI client for GUI operations
|
|
1470
|
+
*/
|
|
1471
|
+
getRemoteAIClient() {
|
|
1472
|
+
return this.remoteAIClient;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Get the current taskId for this user interaction
|
|
1476
|
+
* Used by GUI operations to track the same task
|
|
1477
|
+
*/
|
|
1478
|
+
getTaskId() {
|
|
1479
|
+
return this.currentTaskId;
|
|
1480
|
+
}
|
|
1042
1481
|
}
|
|
1043
1482
|
export async function startInteractiveSession() {
|
|
1044
1483
|
const session = new InteractiveSession();
|
|
@@ -1078,4 +1517,12 @@ export async function startInteractiveSession() {
|
|
|
1078
1517
|
});
|
|
1079
1518
|
await session.start();
|
|
1080
1519
|
}
|
|
1520
|
+
// Singleton session instance for access from other modules
|
|
1521
|
+
let singletonSession = null;
|
|
1522
|
+
export function setSingletonSession(session) {
|
|
1523
|
+
singletonSession = session;
|
|
1524
|
+
}
|
|
1525
|
+
export function getSingletonSession() {
|
|
1526
|
+
return singletonSession;
|
|
1527
|
+
}
|
|
1081
1528
|
//# sourceMappingURL=session.js.map
|