@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/src/auth.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import axios from 'axios';
2
2
  import open from 'open';
3
3
  import inquirer from 'inquirer';
4
- import http from 'http';
4
+ import https from 'https';
5
5
  import { AuthConfig, AuthType } from './types.js';
6
6
  import { getLogger } from './logger.js';
7
7
 
@@ -140,75 +140,47 @@ export class AuthService {
140
140
  }
141
141
 
142
142
  async authenticate(): Promise<boolean> {
143
+ let result: boolean;
143
144
  switch (this.authConfig.type) {
144
145
  case AuthType.OAUTH_XAGENT:
145
- return await this.authenticateWithXAgent();
146
- case AuthType.API_KEY:
147
- return await this.authenticateWithApiKey();
146
+ result = await this.authenticateWithXAgent();
147
+ break;
148
148
  case AuthType.OPENAI_COMPATIBLE:
149
- return await this.authenticateWithOpenAICompatible();
149
+ result = await this.authenticateWithOpenAICompatible();
150
+ break;
150
151
  default:
151
152
  throw new Error(`Unknown auth type: ${this.authConfig.type}`);
152
153
  }
154
+
155
+ return result;
153
156
  }
154
157
 
155
158
  private async authenticateWithXAgent(): Promise<boolean> {
156
159
  logger.info('Authenticating with xAgent...', 'Please complete the authentication in your browser');
157
160
 
158
161
  try {
159
- // 1. 先启动 HTTP 服务器接收回调
162
+ // 1. Start HTTP server to receive callback
160
163
  const token = await this.retrieveXAgentToken();
161
164
 
162
- // 2. 设置认证配置
163
- this.authConfig.baseUrl = 'http://xagent-colife.net:3000/v1';
164
- this.authConfig.xagentApiBaseUrl = 'http://xagent-colife.net:3000';
165
+ // 2. 调用后端验证用户
166
+ const xagentApiBaseUrl = this.authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
167
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
168
+ const response = await axios.get(`${xagentApiBaseUrl}/api/auth/me`, {
169
+ headers: { 'Authorization': `Bearer ${token}` },
170
+ httpsAgent
171
+ });
172
+
173
+ // 3. Set authentication configuration
174
+ this.authConfig.baseUrl = 'https://154.8.140.52:443/v1';
175
+ this.authConfig.xagentApiBaseUrl = xagentApiBaseUrl;
165
176
  this.authConfig.apiKey = token;
177
+ this.authConfig.type = AuthType.OAUTH_XAGENT;
166
178
 
167
179
  logger.success('Successfully authenticated with xAgent!');
168
- logger.info(`LLM API: http://xagent-colife.net:3000/v1`);
169
- logger.info(`VLM API: http://xagent-colife.net:3000/v3`);
170
180
  return true;
171
181
  } catch (error: any) {
172
- const errorMsg = error?.message || 'Unknown error';
173
- if (errorMsg.includes('timeout') || errorMsg.includes('Timeout')) {
174
- logger.error('Authentication timed out', 'The browser authentication took too long. Please try again.');
175
- } else if (errorMsg.includes('No token') || errorMsg.includes('no token')) {
176
- logger.error('Authentication was cancelled or failed', 'No token was received from the browser. Please try again.');
177
- } else {
178
- logger.error('xAgent authentication failed', errorMsg || 'Check your network connection and try again');
179
- }
180
- return false;
181
- }
182
- }
183
-
184
- private async authenticateWithApiKey(): Promise<boolean> {
185
- logger.info('Authenticating with API Key...');
186
-
187
- const answers = await inquirer.prompt([
188
- {
189
- type: 'input',
190
- name: 'apiKey',
191
- message: 'Enter your xAgent API Key:',
192
- validate: (input: string) => {
193
- if (!input || input.trim().length === 0) {
194
- return 'API Key cannot be empty';
195
- }
196
- return true;
197
- }
198
- }
199
- ]);
200
-
201
- const apiKey = answers.apiKey as string;
202
-
203
- this.authConfig.apiKey = apiKey.trim();
204
- this.authConfig.baseUrl = 'https://apis.xagent.cn/v1';
205
-
206
- const isValid = await this.validateApiKey();
207
- if (isValid) {
208
- logger.success('API Key verified successfully!', 'You can now start using xAgent CLI');
209
- return true;
210
- } else {
211
- logger.error('Invalid API Key, please try again.', 'Make sure you entered the correct API Key');
182
+ logger.error('Authentication failed', error.message || 'Unknown error');
183
+ logger.debug('Full error:', JSON.stringify(error.response?.data || error.message));
212
184
  return false;
213
185
  }
214
186
  }
@@ -323,6 +295,7 @@ export class AuthService {
323
295
  this.authConfig.baseUrl = baseUrl;
324
296
  this.authConfig.apiKey = (apiKey as string).trim();
325
297
  this.authConfig.modelName = modelName;
298
+ this.authConfig.type = AuthType.OPENAI_COMPATIBLE;
326
299
 
327
300
  const isValid = await this.validateApiKey();
328
301
  if (isValid) {
@@ -402,87 +375,80 @@ export class AuthService {
402
375
  }
403
376
 
404
377
  private async retrieveXAgentToken(): Promise<string> {
405
- // 使用前端登录页面(支持 callback 参数)
406
- const authUrl = 'http://xagent-colife.net:3000/login';
407
- const callbackUrl = 'http://localhost:8080/callback';
378
+ // Use xagentApiBaseUrl from config, fallback to default
379
+ const webBaseUrl = this.authConfig.xagentApiBaseUrl || 'https://154.8.140.52';
380
+ const authUrl = `${webBaseUrl}/login`;
381
+ // Callback URL tells frontend where to store token
382
+ const callbackUrl = 'https://154.8.140.52:443/callback';
383
+
384
+ // 如果已有保存的token,通过URL参数传给Web页面
385
+ const existingToken = this.authConfig.apiKey;
386
+ const existingRefreshToken = this.authConfig.refreshToken;
387
+
388
+ // 构建登录URL - 如果已有token也传给Web
389
+ let loginUrl = `${authUrl}?callback=${encodeURIComponent(callbackUrl)}`;
390
+ if (existingToken) {
391
+ loginUrl += `&existingToken=${encodeURIComponent(existingToken)}`;
392
+ if (existingRefreshToken) {
393
+ loginUrl += `&existingRefreshToken=${encodeURIComponent(existingRefreshToken)}`;
394
+ }
395
+ }
408
396
 
409
- logger.debug(`[OAuth] Opening browser for authentication`);
410
- logger.debug(`[OAuth] Login URL: ${authUrl}?callback=${encodeURIComponent(callbackUrl)}`);
397
+ // Open browser for login, then poll server for token
398
+ await open(loginUrl);
399
+ logger.info('Waiting for authentication...', 'Please complete login in your browser');
411
400
 
412
- // 启动 HTTP 服务器接收回调,然后打开浏览器
401
+ // Poll server to get token
413
402
  return new Promise((resolve, reject) => {
414
- let timeoutId: NodeJS.Timeout | null = null;
415
- let server: http.Server | null = null;
416
-
417
- const cleanup = () => {
418
- if (timeoutId) {
419
- clearTimeout(timeoutId);
420
- timeoutId = null;
403
+ const pollInterval = 2000; // Poll every 2 seconds
404
+ const maxWaitTime = 30 * 60 * 1000; // 30 minutes timeout
405
+ const startTime = Date.now();
406
+
407
+ const poll = async () => {
408
+ if (Date.now() - startTime > maxWaitTime) {
409
+ logger.warn('Authentication timeout after 30 minutes');
410
+ reject(new Error('Authentication timeout'));
411
+ return;
421
412
  }
422
- };
423
-
424
- const serverCallback = (req: any, res: any) => {
425
- logger.debug(`[OAuth] Received request: ${req.url}`);
426
413
 
427
- if (req.url.startsWith('/callback')) {
428
- const url = new URL(req.url, `http://${req.headers.host}`);
429
- const token = url.searchParams.get('token');
430
- const refreshToken = url.searchParams.get('refreshToken');
431
-
432
- logger.debug(`[OAuth] Callback received, token: ${token ? 'present' : 'missing'}`);
433
- logger.debug(`[OAuth] Refresh token: ${refreshToken ? 'present' : 'missing'}`);
434
-
435
- if (token) {
436
- cleanup();
437
-
414
+ try {
415
+ // Create HTTPS agent that ignores certificate errors (for IP-based access)
416
+ const httpsAgent = new https.Agent({ rejectUnauthorized: false });
417
+
418
+ const response = await axios.get(`${webBaseUrl}/api/cli/get-token`, {
419
+ timeout: 10000,
420
+ httpsAgent
421
+ });
422
+
423
+ if (response.data.token) {
424
+ logger.success('Authentication successful! Received token');
425
+ logger.debug('[CLI-Auth] Token stored, key:', response.data.token.substring(0, 20) + '...');
438
426
  // Save refresh token if provided
439
- if (refreshToken) {
440
- this.authConfig.refreshToken = refreshToken;
441
- logger.debug(`[OAuth] Refresh token saved`);
427
+ if (response.data.refreshToken) {
428
+ this.authConfig.refreshToken = response.data.refreshToken;
442
429
  }
443
-
444
- // Redirect directly to home page after successful authentication
445
- res.writeHead(302, { 'Location': 'http://localhost:3000/' });
446
- res.end();
447
- if (server) {
448
- server.close();
449
- }
450
- resolve(token);
430
+ resolve(response.data.token);
431
+ return;
432
+ }
433
+ } catch (error: any) {
434
+ if (error.response?.status === 404) {
435
+ // Token not ready yet, continue polling
451
436
  } else {
452
- cleanup();
453
-
454
- res.writeHead(400, { 'Content-Type': 'text/html' });
455
- res.end('<h1>Authentication Failed: No token</h1>');
456
- if (server) {
457
- server.close();
458
- }
459
- reject(new Error('No token received'));
437
+ console.error('[CLI-Auth] Polling error:', error.message);
460
438
  }
461
439
  }
440
+
441
+ // Continue polling
442
+ setTimeout(poll, pollInterval);
462
443
  };
463
-
464
- server = http.createServer(serverCallback);
465
444
 
466
- // 设置超时定时器(在 server 创建后)
467
- timeoutId = setTimeout(() => {
468
- logger.warn('[OAuth] Authentication timeout after 30 minutes');
469
- if (server) {
470
- server.close();
471
- }
472
- reject(new Error('Authentication timeout'));
473
- }, 1800000); // 30 minutes
474
-
475
- server.listen(8080, async () => {
476
- logger.info('Waiting for authentication...', 'Opening browser for login...');
477
- const fullUrl = `${authUrl}?callback=${encodeURIComponent(callbackUrl)}`;
478
- logger.debug(`[OAuth] Full URL: ${fullUrl}`);
479
- await open(fullUrl);
480
- });
445
+ // Start polling
446
+ setTimeout(poll, pollInterval);
481
447
  });
482
448
  }
483
449
 
484
450
  getAuthConfig(): AuthConfig {
485
- return { ...this.authConfig };
451
+ return { ...this.authConfig, type: this.authConfig.type };
486
452
  }
487
453
 
488
454
  updateAuthConfig(config: Partial<AuthConfig>): void {
@@ -173,4 +173,4 @@ export function getCancellationManager(): CancellationManager {
173
173
  cancellationManagerInstance = new CancellationManager();
174
174
  }
175
175
  return cancellationManagerInstance;
176
- }
176
+ }
package/src/cli.ts CHANGED
@@ -10,6 +10,17 @@ import { getMCPManager } from './mcp.js';
10
10
  import { getLogger, setConfigProvider } from './logger.js';
11
11
  import { theme, icons, colors } from './theme.js';
12
12
  import { getCancellationManager } from './cancellation.js';
13
+ import { readFileSync } from 'fs';
14
+ import { dirname, join } from 'path';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ // Get current directory
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ // Read package.json
22
+ const packageJsonPath = join(__dirname, '../package.json');
23
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
13
24
 
14
25
  const logger = getLogger();
15
26
 
@@ -26,10 +37,72 @@ setConfigProvider(() => {
26
37
 
27
38
  const program = new Command();
28
39
 
40
+ /**
41
+ * Format error message for user-friendly display
42
+ */
43
+ function formatError(error: unknown): { message: string; suggestion: string } {
44
+ const errorMessage = error instanceof Error ? error.message : String(error);
45
+
46
+ // Network errors
47
+ if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED')) {
48
+ return {
49
+ message: 'Unable to connect to the server',
50
+ suggestion: 'Please check your network connection and try again.'
51
+ };
52
+ }
53
+ if (errorMessage.includes('ETIMEDOUT') || errorMessage.includes('ECONNRESET')) {
54
+ return {
55
+ message: 'Connection timed out',
56
+ suggestion: 'The server may be busy. Please wait a moment and try again.'
57
+ };
58
+ }
59
+ // Authentication errors
60
+ if (errorMessage.includes('401') || errorMessage.includes('Unauthorized') || errorMessage.includes('invalid token')) {
61
+ return {
62
+ message: 'Authentication failed',
63
+ suggestion: 'Please login again using: xagent auth'
64
+ };
65
+ }
66
+ // Token expired
67
+ if (errorMessage.includes('token') && errorMessage.includes('expired')) {
68
+ return {
69
+ message: 'Session expired',
70
+ suggestion: 'Please login again using: xagent auth'
71
+ };
72
+ }
73
+ // Permission errors
74
+ if (errorMessage.includes('EACCES') || errorMessage.includes('permission denied')) {
75
+ return {
76
+ message: 'Permission denied',
77
+ suggestion: 'Please check your file permissions or run with appropriate privileges.'
78
+ };
79
+ }
80
+ // File not found
81
+ if (errorMessage.includes('ENOENT') || errorMessage.includes('not found')) {
82
+ return {
83
+ message: 'File or resource not found',
84
+ suggestion: 'Please check the path and try again.'
85
+ };
86
+ }
87
+ // Invalid JSON
88
+ if (errorMessage.includes('JSON') || errorMessage.includes('parse')) {
89
+ return {
90
+ message: 'Invalid data format',
91
+ suggestion: 'The configuration file may be corrupted. Please check the file content.'
92
+ };
93
+ }
94
+
95
+ // Default friendly message
96
+ return {
97
+ message: 'An error occurred',
98
+ suggestion: 'Please try again. If the problem persists, check your configuration.'
99
+ };
100
+ }
101
+
29
102
  program
30
103
  .name('xagent')
31
104
  .description('AI-powered command-line assistant')
32
- .version('1.0.0')
105
+ .version(packageJson.version)
33
106
  .option('-h, --help', 'Show help');
34
107
 
35
108
  program
@@ -144,9 +217,10 @@ program
144
217
  console.log(colors.success(`Agent ${options.remove} removed successfully`));
145
218
  console.log('');
146
219
  } catch (error: any) {
220
+ const { message, suggestion } = formatError(error);
147
221
  console.log('');
148
- console.log(colors.error(`Failed to remove agent: ${error.message}`));
149
- console.log(colors.textMuted('Check if the agent exists and try again'));
222
+ console.log(colors.error(`Failed to remove agent: ${message}`));
223
+ console.log(colors.textMuted(suggestion));
150
224
  console.log('');
151
225
  }
152
226
  } else {
@@ -207,9 +281,10 @@ program
207
281
  console.log(colors.success(`MCP server ${options.remove} removed successfully`));
208
282
  console.log('');
209
283
  } catch (error: any) {
284
+ const { message, suggestion } = formatError(error);
210
285
  console.log('');
211
- console.log(colors.error(`Failed to remove MCP server: ${error.message}`));
212
- console.log(colors.textMuted('Check if the server exists and try again'));
286
+ console.log(colors.error(`Failed to remove MCP server: ${message}`));
287
+ console.log(colors.textMuted(suggestion));
213
288
  console.log('');
214
289
  }
215
290
  } else {
@@ -238,8 +313,9 @@ program
238
313
  console.log(colors.textMuted('You can now run "xagent start" to begin'));
239
314
  console.log('');
240
315
  } catch (error: any) {
241
- console.log(colors.error(`Initialization failed: ${error.message}`));
242
- console.log(colors.textMuted('Check if you have write permissions for this directory'));
316
+ const { message, suggestion } = formatError(error);
317
+ console.log(colors.error(`Initialization failed: ${message}`));
318
+ console.log(colors.textMuted(suggestion));
243
319
  console.log('');
244
320
  process.exit(1);
245
321
  }
@@ -285,9 +361,10 @@ program
285
361
  console.log(colors.success(`Workflow ${options.add} added successfully!`));
286
362
  console.log('');
287
363
  } catch (error: any) {
364
+ const { message, suggestion } = formatError(error);
288
365
  console.log('');
289
- console.log(colors.error(error.message));
290
- console.log(colors.textMuted('Check the workflow ID and try again'));
366
+ console.log(colors.error(message));
367
+ console.log(colors.textMuted(suggestion));
291
368
  console.log('');
292
369
  process.exit(1);
293
370
  }
@@ -298,9 +375,10 @@ program
298
375
  console.log(colors.success(`Workflow ${options.remove} removed successfully!`));
299
376
  console.log('');
300
377
  } catch (error: any) {
378
+ const { message, suggestion } = formatError(error);
301
379
  console.log('');
302
- console.log(colors.error(error.message));
303
- console.log(colors.textMuted('Check if the workflow exists and try again'));
380
+ console.log(colors.error(message));
381
+ console.log(colors.textMuted(suggestion));
304
382
  console.log('');
305
383
  process.exit(1);
306
384
  }
@@ -326,7 +404,7 @@ program
326
404
  console.log('');
327
405
  console.log(colors.border(separator));
328
406
  console.log('');
329
- console.log(` ${icons.info} ${colors.textMuted('Version:')} ${colors.primaryBright('1.0.0')}`);
407
+ console.log(` ${icons.info} ${colors.textMuted('Version:')} ${colors.primaryBright(packageJson.version)}`);
330
408
  console.log(` ${icons.code} ${colors.textMuted('Node.js:')} ${colors.textMuted(process.version)}`);
331
409
  console.log(` ${icons.bolt} ${colors.textMuted('Platform:')} ${colors.textMuted(process.platform + ' ' + process.arch)}`);
332
410
  console.log('');
@@ -349,10 +427,70 @@ program
349
427
  console.log('');
350
428
 
351
429
  try {
430
+ const configManager = getConfigManager();
431
+ const authConfig = configManager.getAuthConfig();
432
+
433
+ // Get GUI-specific VLM configuration
434
+ const baseUrl = configManager.get('guiSubagentBaseUrl') || configManager.get('baseUrl') || '';
435
+ const apiKey = configManager.get('guiSubagentApiKey') || configManager.get('apiKey') || '';
436
+ const modelName = configManager.get('guiSubagentModel') || configManager.get('modelName') || '';
437
+
438
+ // Determine mode: local (openai_compatible) or remote
439
+ const isLocalMode = authConfig.type === 'openai_compatible';
440
+
441
+ if (isLocalMode) {
442
+ // Local mode: require baseUrl configuration
443
+ if (!baseUrl) {
444
+ console.log(colors.error('No VLM API URL configured for GUI subagent.'));
445
+ console.log(colors.textMuted('Please run "xagent auth" and configure guiSubagentBaseUrl.'));
446
+ console.log('');
447
+ return;
448
+ }
449
+ console.log(colors.info(`${icons.brain} Using local VLM configuration`));
450
+ console.log(colors.textMuted(` Model: ${modelName}`));
451
+ console.log(colors.textMuted(` Base URL: ${baseUrl}`));
452
+ console.log('');
453
+ } else {
454
+ // Remote mode
455
+ console.log(colors.info(`${icons.brain} Using remote VLM service`));
456
+ console.log(colors.textMuted(` Auth Type: ${authConfig.type}`));
457
+ console.log('');
458
+ }
459
+
352
460
  const { createGUISubAgent } = await import('./gui-subagent/index.js');
353
461
 
462
+ // Create remoteVlmCaller for remote mode (uses full messages for consistent behavior)
463
+ let remoteVlmCaller: ((messages: any[], systemPrompt: string) => Promise<string>) | undefined;
464
+
465
+ if (!isLocalMode && authConfig.baseUrl) {
466
+ const remoteBaseUrl = `${authConfig.baseUrl}/api/agent/vlm`;
467
+ remoteVlmCaller = async (messages: any[], _systemPrompt: string): Promise<string> => {
468
+ const response = await fetch(remoteBaseUrl, {
469
+ method: 'POST',
470
+ headers: {
471
+ 'Content-Type': 'application/json',
472
+ 'Authorization': `Bearer ${authConfig.apiKey || ''}`,
473
+ },
474
+ body: JSON.stringify({
475
+ messages
476
+ }),
477
+ });
478
+ if (!response.ok) {
479
+ const errorText = await response.text();
480
+ throw new Error(`Remote VLM error: ${response.status} - ${errorText}`);
481
+ }
482
+ const result = await response.json() as { response?: string; content?: string; message?: string };
483
+ return result.response || result.content || result.message || '';
484
+ };
485
+ }
486
+
354
487
  const guiAgent = await createGUISubAgent({
355
488
  headless: options.headless ?? false,
489
+ model: isLocalMode ? modelName : undefined,
490
+ modelBaseUrl: isLocalMode ? baseUrl : undefined,
491
+ modelApiKey: isLocalMode ? apiKey : undefined,
492
+ remoteVlmCaller,
493
+ isLocalMode,
356
494
  });
357
495
 
358
496
  console.log(colors.success('✅ GUI Subagent initialized successfully!'));
@@ -371,8 +509,10 @@ program
371
509
  console.log(colors.primaryBright('Use the GUI tools in the interactive session to control the computer.'));
372
510
  console.log('');
373
511
  } catch (error: any) {
512
+ const { message, suggestion } = formatError(error);
374
513
  console.log('');
375
- console.log(colors.error(`Failed to start GUI Subagent: ${error.message}`));
514
+ console.log(colors.error(`Failed to start GUI Subagent: ${message}`));
515
+ console.log(colors.textMuted(suggestion));
376
516
  console.log('');
377
517
  }
378
518
  });
@@ -382,3 +522,28 @@ program.parse(process.argv);
382
522
  if (!process.argv.slice(2).length) {
383
523
  program.outputHelp();
384
524
  }
525
+
526
+ // ============================================================
527
+ // Global error handling - prevent crashes from uncaught errors
528
+ // ============================================================
529
+
530
+ // Handle uncaught promise rejections
531
+ process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
532
+ console.error('\n❌ An unexpected error occurred');
533
+ if (reason instanceof Error) {
534
+ console.error(` ${reason.message}`);
535
+ } else if (reason) {
536
+ console.error(` ${String(reason)}`);
537
+ }
538
+ console.error('\n If this problem persists, please report this issue.');
539
+ console.error('');
540
+ process.exit(1);
541
+ });
542
+
543
+ // Handle uncaught exceptions
544
+ process.on('uncaughtException', (error: Error) => {
545
+ console.error('\n❌ Critical error - application will exit');
546
+ console.error(` ${error.message}`);
547
+ console.error('');
548
+ process.exit(1);
549
+ });
package/src/config.ts CHANGED
@@ -10,12 +10,12 @@ const DEFAULT_SETTINGS: Settings = {
10
10
  theme: 'Default',
11
11
  selectedAuthType: AuthType.OAUTH_XAGENT,
12
12
  apiKey: '',
13
- // LLM API - 用于主对话和任务处理 (OpenAI兼容格式)
13
+ // LLM API - for main conversation and task processing (OpenAI compatible format)
14
14
  baseUrl: 'http://xagent-colife.net:3000/v1',
15
15
  modelName: 'Qwen3-Coder',
16
- // xAgent API - 用于 token 验证等后端调用 (不含 /v1)
16
+ // xAgent API - for token validation and other backend calls (without /v1)
17
17
  xagentApiBaseUrl: 'http://xagent-colife.net:3000',
18
- // VLM API - 用于 GUI 自动化(浏览器/桌面操作)
18
+ // VLM API - for GUI automation (browser/desktop operations)
19
19
  guiSubagentModel: 'Qwen3-Coder',
20
20
  guiSubagentBaseUrl: 'http://xagent-colife.net:3000/v3',
21
21
  guiSubagentApiKey: '',
@@ -66,18 +66,32 @@ export class ConfigManager {
66
66
  }
67
67
 
68
68
  async load(): Promise<Settings> {
69
+ logger.debug('[CONFIG] ========== load() 开始 ==========');
70
+ logger.debug('[CONFIG] globalConfigPath:', this.globalConfigPath);
71
+ logger.debug('[CONFIG] projectConfigPath:', this.projectConfigPath);
72
+ logger.debug('[CONFIG] 检查文件是否存在...');
73
+
69
74
  try {
70
75
  const globalConfig = this.readConfigFile(this.globalConfigPath);
76
+ logger.debug('[CONFIG] globalConfig 读取成功:', JSON.stringify(globalConfig, null, 2));
77
+
71
78
  this.settings = { ...DEFAULT_SETTINGS, ...globalConfig };
79
+ logger.debug('[CONFIG] 合并后的 settings:', JSON.stringify(this.settings, null, 2));
72
80
 
73
81
  if (this.projectConfigPath) {
74
82
  const projectConfig = this.readConfigFile(this.projectConfigPath);
83
+ logger.debug('[CONFIG] projectConfig 读取成功:', JSON.stringify(projectConfig, null, 2));
75
84
  this.settings = { ...this.settings, ...projectConfig };
76
85
  }
77
86
 
87
+ logger.debug('[CONFIG] 最终 settings.apiKey:', this.settings.apiKey ? this.settings.apiKey.substring(0, 30) + '...' : 'empty');
88
+ logger.debug('[CONFIG] 最终 settings.refreshToken:', this.settings.refreshToken ? 'exists' : 'empty');
89
+ logger.debug('[CONFIG] ========== load() 结束 ==========');
90
+
78
91
  return this.settings;
79
92
  } catch (error) {
80
- logger.error('Failed to load config', 'Check if config files exist and are valid');
93
+ logger.debug('[CONFIG] load() 捕获到错误:', error instanceof Error ? error.message : String(error));
94
+ logger.debug('[CONFIG] ========== load() 结束 (使用默认值) ==========');
81
95
  return { ...DEFAULT_SETTINGS };
82
96
  }
83
97
  }
@@ -111,7 +125,7 @@ export class ConfigManager {
111
125
  }
112
126
 
113
127
  getAuthConfig() {
114
- return {
128
+ const result = {
115
129
  type: this.settings.selectedAuthType,
116
130
  apiKey: this.settings.apiKey,
117
131
  refreshToken: this.settings.refreshToken,
@@ -121,10 +135,30 @@ export class ConfigManager {
121
135
  searchApiKey: this.settings.searchApiKey,
122
136
  showAIDebugInfo: this.settings.showAIDebugInfo
123
137
  };
138
+
139
+ logger.debug('[CONFIG] getAuthConfig() 返回:');
140
+ logger.debug(' - type:', result.type);
141
+ logger.debug(' - apiKey:', result.apiKey ? result.apiKey.substring(0, 30) + '...' : 'empty');
142
+ logger.debug(' - refreshToken:', result.refreshToken ? 'exists' : 'empty');
143
+ logger.debug(' - baseUrl:', result.baseUrl);
144
+ logger.debug(' - xagentApiBaseUrl:', result.xagentApiBaseUrl);
145
+
146
+ return result;
124
147
  }
125
148
 
126
- async setAuthConfig(config: Partial<Settings>): Promise<void> {
127
- Object.assign(this.settings, config);
149
+ async setAuthConfig(config: Partial<Settings> & { xagentApiBaseUrl?: string }): Promise<void> {
150
+ // Extract xagentApiBaseUrl separately since it's not in Settings
151
+ const { xagentApiBaseUrl, type, ...otherConfig } = config as any;
152
+
153
+ // Map 'type' to 'selectedAuthType' (AuthConfig uses 'type', Settings uses 'selectedAuthType')
154
+ if (type !== undefined) {
155
+ this.settings.selectedAuthType = type;
156
+ }
157
+
158
+ Object.assign(this.settings, otherConfig);
159
+ if (xagentApiBaseUrl !== undefined) {
160
+ this.settings.xagentApiBaseUrl = xagentApiBaseUrl;
161
+ }
128
162
  await this.save('global');
129
163
  }
130
164
 
@@ -221,7 +255,22 @@ export class ConfigManager {
221
255
  }
222
256
 
223
257
  getWorkspacePath(): string | undefined {
224
- return this.settings.workspacePath;
258
+ if (this.settings.workspacePath) {
259
+ return this.settings.workspacePath;
260
+ }
261
+ // Auto-detect: ~/.xagent/workspace
262
+ const detectedPath = path.join(os.homedir(), '.xagent', 'workspace');
263
+
264
+ // Ensure directory exists
265
+ try {
266
+ if (!fs.existsSync(detectedPath)) {
267
+ fs.mkdirSync(detectedPath, { recursive: true });
268
+ }
269
+ } catch {
270
+ // Ignore errors - caller will handle missing path
271
+ }
272
+
273
+ return detectedPath;
225
274
  }
226
275
 
227
276
  setWorkspacePath(path: string): void {