@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.
Files changed (136) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  3. package/README.md +280 -280
  4. package/README_CN.md +3 -3
  5. package/dist/ai-client.d.ts.map +1 -1
  6. package/dist/ai-client.js +84 -82
  7. package/dist/ai-client.js.map +1 -1
  8. package/dist/auth.d.ts +0 -1
  9. package/dist/auth.d.ts.map +1 -1
  10. package/dist/auth.js +75 -105
  11. package/dist/auth.js.map +1 -1
  12. package/dist/cli.js +166 -13
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config.d.ts +3 -1
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +48 -7
  17. package/dist/config.js.map +1 -1
  18. package/dist/context-compressor.d.ts +5 -5
  19. package/dist/context-compressor.js +8 -8
  20. package/dist/context-compressor.js.map +1 -1
  21. package/dist/gui-subagent/action-parser/actionParser.d.ts +7 -0
  22. package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -1
  23. package/dist/gui-subagent/action-parser/actionParser.js +6 -3
  24. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
  25. package/dist/gui-subagent/action-parser/constants.d.ts +6 -0
  26. package/dist/gui-subagent/action-parser/constants.d.ts.map +1 -1
  27. package/dist/gui-subagent/action-parser/constants.js +5 -3
  28. package/dist/gui-subagent/action-parser/constants.js.map +1 -1
  29. package/dist/gui-subagent/action-parser/index.d.ts +6 -0
  30. package/dist/gui-subagent/action-parser/index.d.ts.map +1 -1
  31. package/dist/gui-subagent/action-parser/index.js +5 -3
  32. package/dist/gui-subagent/action-parser/index.js.map +1 -1
  33. package/dist/gui-subagent/action-parser/types.d.ts +4 -0
  34. package/dist/gui-subagent/action-parser/types.d.ts.map +1 -1
  35. package/dist/gui-subagent/action-parser/types.js +3 -3
  36. package/dist/gui-subagent/agent/gui-agent.d.ts +39 -0
  37. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
  38. package/dist/gui-subagent/agent/gui-agent.js +164 -89
  39. package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
  40. package/dist/gui-subagent/agent/index.d.ts +1 -1
  41. package/dist/gui-subagent/agent/index.d.ts.map +1 -1
  42. package/dist/gui-subagent/agent/index.js.map +1 -1
  43. package/dist/gui-subagent/index.d.ts +27 -1
  44. package/dist/gui-subagent/index.d.ts.map +1 -1
  45. package/dist/gui-subagent/index.js +6 -0
  46. package/dist/gui-subagent/index.js.map +1 -1
  47. package/dist/logger.js +1 -1
  48. package/dist/logger.js.map +1 -1
  49. package/dist/mcp.d.ts +1 -0
  50. package/dist/mcp.d.ts.map +1 -1
  51. package/dist/mcp.js +140 -29
  52. package/dist/mcp.js.map +1 -1
  53. package/dist/remote-ai-client.d.ts +111 -0
  54. package/dist/remote-ai-client.d.ts.map +1 -0
  55. package/dist/remote-ai-client.js +558 -0
  56. package/dist/remote-ai-client.js.map +1 -0
  57. package/dist/sdk-output-adapter.d.ts +232 -0
  58. package/dist/sdk-output-adapter.d.ts.map +1 -0
  59. package/dist/sdk-output-adapter.js +636 -0
  60. package/dist/sdk-output-adapter.js.map +1 -0
  61. package/dist/sdk-session-v2.d.ts +13 -0
  62. package/dist/sdk-session-v2.d.ts.map +1 -0
  63. package/dist/sdk-session-v2.js +46 -0
  64. package/dist/sdk-session-v2.js.map +1 -0
  65. package/dist/sdk-session.d.ts +13 -0
  66. package/dist/sdk-session.d.ts.map +1 -0
  67. package/dist/sdk-session.js +48 -0
  68. package/dist/sdk-session.js.map +1 -0
  69. package/dist/session-manager.js +3 -3
  70. package/dist/session-manager.js.map +1 -1
  71. package/dist/session.d.ts +46 -3
  72. package/dist/session.d.ts.map +1 -1
  73. package/dist/session.js +564 -117
  74. package/dist/session.js.map +1 -1
  75. package/dist/skill-invoker.d.ts +40 -4
  76. package/dist/skill-invoker.d.ts.map +1 -1
  77. package/dist/skill-invoker.js +310 -1184
  78. package/dist/skill-invoker.js.map +1 -1
  79. package/dist/skill-loader.d.ts +15 -1
  80. package/dist/skill-loader.d.ts.map +1 -1
  81. package/dist/skill-loader.js +49 -32
  82. package/dist/skill-loader.js.map +1 -1
  83. package/dist/slash-commands.d.ts +4 -2
  84. package/dist/slash-commands.d.ts.map +1 -1
  85. package/dist/slash-commands.js +149 -15
  86. package/dist/slash-commands.js.map +1 -1
  87. package/dist/smart-approval.d.ts +2 -1
  88. package/dist/smart-approval.d.ts.map +1 -1
  89. package/dist/smart-approval.js +29 -3
  90. package/dist/smart-approval.js.map +1 -1
  91. package/dist/system-prompt-generator.d.ts +4 -5
  92. package/dist/system-prompt-generator.d.ts.map +1 -1
  93. package/dist/system-prompt-generator.js +131 -81
  94. package/dist/system-prompt-generator.js.map +1 -1
  95. package/dist/tools.d.ts +17 -6
  96. package/dist/tools.d.ts.map +1 -1
  97. package/dist/tools.js +264 -211
  98. package/dist/tools.js.map +1 -1
  99. package/dist/types.d.ts +0 -1
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/types.js +0 -1
  102. package/dist/types.js.map +1 -1
  103. package/docs/architecture/mcp-integration-guide.md +194 -131
  104. package/docs/architecture/overview.md +169 -93
  105. package/docs/architecture/tool-system-design.md +56 -11
  106. package/docs/cli/commands.md +238 -189
  107. package/docs/smart-mode.md +281 -257
  108. package/docs/third-party-models.md +247 -256
  109. package/package.json +6 -2
  110. package/src/ai-client.ts +107 -105
  111. package/src/auth.ts +82 -116
  112. package/src/cancellation.ts +1 -1
  113. package/src/cli.ts +178 -13
  114. package/src/config.ts +57 -8
  115. package/src/context-compressor.ts +8 -8
  116. package/src/gui-subagent/action-parser/actionParser.ts +6 -3
  117. package/src/gui-subagent/action-parser/constants.ts +5 -3
  118. package/src/gui-subagent/action-parser/index.ts +5 -3
  119. package/src/gui-subagent/action-parser/types.ts +3 -3
  120. package/src/gui-subagent/agent/gui-agent.ts +210 -103
  121. package/src/gui-subagent/agent/index.ts +1 -1
  122. package/src/gui-subagent/index.ts +26 -2
  123. package/src/index.ts +18 -18
  124. package/src/logger.ts +1 -1
  125. package/src/mcp.ts +149 -30
  126. package/src/remote-ai-client.ts +671 -0
  127. package/src/session-manager.ts +3 -3
  128. package/src/session.ts +742 -178
  129. package/src/skill-invoker.ts +340 -1293
  130. package/src/skill-loader.ts +55 -34
  131. package/src/slash-commands.ts +165 -15
  132. package/src/smart-approval.ts +34 -3
  133. package/src/system-prompt-generator.ts +145 -88
  134. package/src/tools.ts +309 -224
  135. package/src/types.ts +0 -1
  136. 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
- // 注册 /clear 回调,清除对话时同步清空本地 conversation
66
+ // Register /clear callback, clear local conversation when clearing dialogue
53
67
  this.slashCommandHandler.setClearCallback(() => {
54
68
  this.conversation = [];
55
69
  });
56
- // 注册 MCP 更新回调,更新系统提示
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 = ' '.repeat(indentLevel);
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(56) + colors.gradient('║'));
102
- console.log(' '.repeat(12) + colors.gradient('🤖 XAGENT CLI') + ' '.repeat(37) + colors.gradient('║'));
103
- console.log(' '.repeat(14) + colors.textMuted('v1.0.0') + ' '.repeat(40) + colors.gradient('║'));
104
- console.log(colors.gradient('║') + ' '.repeat(56) + colors.gradient('║'));
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
- const spinner = ora({
130
- text: colors.textMuted('Initializing XAGENT CLI...'),
131
- spinner: 'dots',
132
- color: 'cyan'
133
- }).start();
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
- // If there's an API key, validate it with the backend
137
- if (authConfig.apiKey) {
138
- spinner.text = colors.textMuted('Validating authentication...');
139
- const baseUrl = authConfig.xagentApiBaseUrl || 'http://xagent-colife.net:3000';
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
- console.log(colors.info(`[DEBUG] Token expired, trying to refresh...`));
144
- spinner.text = colors.textMuted('Refreshing authentication...');
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.set('selectedAuthType', AuthType.OAUTH_XAGENT);
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
- console.error('DEBUG: readline interface closed');
212
+ // readline closed
174
213
  });
175
- spinner.start();
176
214
  }
177
215
  }
178
- else {
179
- spinner.stop();
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
- console.error('DEBUG: readline interface closed');
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
- // 同步对话历史到 slashCommandHandler
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
- spinner.succeed(colors.success('Initialization complete'));
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
- console.log(colors.info(`[DEBUG] Validating token with: ${url}`));
251
- const response = await fetch(url, {
252
- method: 'GET',
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
- console.log(colors.info(`[DEBUG] Response status: ${response.status}`));
259
- return response.ok;
319
+ logger.debug('[SESSION] Validation response status:', String(response.status));
320
+ return response.status === 200;
260
321
  }
261
322
  catch (error) {
262
- console.log(colors.warning(`[DEBUG] Network error: ${error}`));
263
- // Network error - could be server down, consider token invalid
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
- console.log(colors.info(`[DEBUG] Refreshing token with: ${url}`));
271
- const response = await fetch(url, {
272
- method: 'POST',
273
- headers: {
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.ok) {
279
- const data = await response.json();
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
- // 同步对话历史到 slashCommandHandler
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
- await this.generateResponse(thinkingTokens);
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
- // 压缩后恢复用户消息,确保 API 调用时有 user 消息
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
- // 同步压缩后的对话历史到 slashCommandHandler
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
- async generateResponse(thinkingTokens = 0) {
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
- const allLocalToolDefinitions = toolRegistry.getToolDefinitions();
654
- const mcpToolDefinitions = this.mcpManager.getToolDefinitions();
655
- // Merge local tools and MCP tools
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
- ? allToolDefinitions.filter((tool) => allowedToolNames.includes(tool.function.name))
659
- : allToolDefinitions;
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 responsePromise = this.aiClient.chatCompletion(messages, {
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
- const response = await this.cancellationManager.withCancellation(responsePromise, operationId);
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
- // Message is already logged by CancellationManager
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.todos || result.todos, displayIndent));
1115
+ console.log(this.renderTodoList(result?.todos || [], displayIndent));
823
1116
  // Show summary if available
824
- if (result.message) {
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 调试信息已移至 ai-client.ts 实现
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) || '()') + ' '.repeat(3) + colors.border(boxChar.vertical));
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()) + ' ' + ' '.repeat(40) + colors.border(boxChar.vertical));
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()) + '' + ' '.repeat(43) + colors.border(boxChar.vertical));
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
- // console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOL_CALLS: ' +
1015
- // colors.text((message.tool_calls?.length || 0).toString()) + '' + ' '.repeat(37) + colors.border(boxChar.vertical));
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