@xagent-ai/cli 1.0.1 → 1.1.0

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 +539 -104
  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 +704 -156
  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
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import fsSync from 'fs';
3
3
  import path from 'path';
4
+ import { fileURLToPath } from 'url';
4
5
  import { WorkflowConfig } from './workflow.js';
5
6
  import { getConfigManager } from './config.js';
6
7
 
@@ -72,34 +73,8 @@ export class SkillLoader {
72
73
  }
73
74
 
74
75
  private detectSkillsPath(): string {
75
- // Strategy: Find skills folder relative to the script location
76
- // This works regardless of where the user runs xagent from
77
- const scriptDir = path.dirname(process.argv[1]);
78
-
79
- // Possible locations relative to where xagent script is installed
80
- const possiblePaths = [
81
- path.join(scriptDir, '..', 'skills', 'skills'),
82
- path.join(scriptDir, '..', '..', 'skills', 'skills'),
83
- path.join(scriptDir, 'skills', 'skills'),
84
- path.join(scriptDir, '..', '..', '..', 'skills', 'skills'),
85
- // Also try process.cwd() as fallback
86
- path.join(process.cwd(), 'skills', 'skills')
87
- ];
88
-
89
- for (const p of possiblePaths) {
90
- try {
91
- const resolvedPath = path.resolve(p);
92
- const stat = fsSync.statSync(resolvedPath);
93
- if (stat.isDirectory()) {
94
- return resolvedPath;
95
- }
96
- } catch {
97
- continue;
98
- }
99
- }
100
-
101
- // Ultimate fallback
102
- return path.join(process.cwd(), 'skills', 'skills');
76
+ // Skills folder is always at {xagent_root}/skills/skills
77
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'skills', 'skills');
103
78
  }
104
79
 
105
80
  async loadAllSkills(): Promise<SkillInfo[]> {
@@ -211,7 +186,7 @@ export class SkillLoader {
211
186
 
212
187
  try {
213
188
  const content = await fs.readFile(skillMdPath, 'utf-8');
214
- const parsed = this.parseSkillMarkdown(content);
189
+ const parsed = this._parseSkillMarkdown(content);
215
190
 
216
191
  if (parsed.name === skillId) {
217
192
  this.skillDirectories.set(skillId, categoryPath);
@@ -265,10 +240,25 @@ export class SkillLoader {
265
240
 
266
241
  try {
267
242
  const content = await fs.readFile(skillMdPath, 'utf-8');
268
- const parsed = this.parseSkillMarkdown(content);
243
+ const parsed = this._parseSkillMarkdown(content);
269
244
 
270
245
  if (parsed.name) {
271
246
  this.skillDirectories.set(parsed.name, categoryPath);
247
+
248
+ // Also populate loadedSkills so getSkill() can find it
249
+ const skillInfo: SkillInfo = {
250
+ id: parsed.name,
251
+ name: parsed.name,
252
+ description: parsed.description || '',
253
+ license: parsed.license || 'Unknown',
254
+ version: parsed.version || '1.0.0',
255
+ author: parsed.author || 'Anonymous',
256
+ category: category.name,
257
+ markdown: content,
258
+ skillsPath: categoryPath
259
+ };
260
+ this.loadedSkills.set(parsed.name, skillInfo);
261
+
272
262
  skillIds.push(parsed.name);
273
263
  }
274
264
  } catch (error) {
@@ -320,7 +310,7 @@ export class SkillLoader {
320
310
 
321
311
  try {
322
312
  const content = await fs.readFile(skillMdPath, 'utf-8');
323
- const parsed = this.parseSkillMarkdown(content);
313
+ const parsed = this._parseSkillMarkdown(content);
324
314
 
325
315
  if (!parsed.name) {
326
316
  const warning: SkillLoadWarning = {
@@ -360,7 +350,7 @@ export class SkillLoader {
360
350
  }
361
351
  }
362
352
 
363
- private parseSkillMarkdown(content: string): { name: string; description: string; license?: string; version?: string; author?: string } {
353
+ private _parseSkillMarkdown(content: string): { name: string; description: string; license?: string; version?: string; author?: string } {
364
354
  const result = {
365
355
  name: '',
366
356
  description: '',
@@ -371,10 +361,27 @@ export class SkillLoader {
371
361
 
372
362
  // Normalize line endings to LF for consistent parsing
373
363
  const normalizedContent = content.replace(/\r\n/g, '\n');
364
+
365
+ // Try to extract frontmatter - support both formats:
366
+ // 1. Standard YAML: ---name: docx...--- 2. No opening ---: name: docx...
367
+ let frontmatter = '';
368
+ let contentStart = 0;
369
+
374
370
  const frontmatterMatch = normalizedContent.match(/^---\n([\s\S]*?)\n---/);
375
-
376
371
  if (frontmatterMatch) {
377
- const frontmatter = frontmatterMatch[1];
372
+ // Standard format with --- at start and end
373
+ frontmatter = frontmatterMatch[1];
374
+ contentStart = frontmatterMatch[0].length;
375
+ } else {
376
+ // Check for format without opening --- (just YAML at the start)
377
+ const yamlMatch = normalizedContent.match(/^([\s\S]*?)\n---/);
378
+ if (yamlMatch) {
379
+ frontmatter = yamlMatch[1];
380
+ contentStart = yamlMatch[0].length;
381
+ }
382
+ }
383
+
384
+ if (frontmatter) {
378
385
  const lines = frontmatter.split('\n');
379
386
 
380
387
  let currentKey = '';
@@ -425,6 +432,20 @@ export class SkillLoader {
425
432
  return this.loadedSkills.get(skillId);
426
433
  }
427
434
 
435
+ /**
436
+ * Get the directory path for a skill
437
+ */
438
+ getSkillDirectory(skillId: string): string | undefined {
439
+ return this.skillDirectories.get(skillId);
440
+ }
441
+
442
+ /**
443
+ * Public method to parse skill markdown frontmatter
444
+ */
445
+ parseSkillMarkdown(content: string): { name: string; description: string; license?: string; version?: string; author?: string } {
446
+ return this._parseSkillMarkdown(content);
447
+ }
448
+
428
449
  listSkills(): SkillInfo[] {
429
450
  return Array.from(this.loadedSkills.values());
430
451
  }
@@ -1,7 +1,7 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
- import { ExecutionMode, ChatMessage, InputType, ToolCall, Checkpoint, AgentConfig, CompressionConfig } from './types.js';
4
+ import { ExecutionMode, ChatMessage, InputType, ToolCall, Checkpoint, AgentConfig, CompressionConfig, AuthType } from './types.js';
5
5
  import { AIClient, Message, detectThinkingKeywords, getThinkingTokens } from './ai-client.js';
6
6
  import { getToolRegistry } from './tools.js';
7
7
  import { getAgentManager } from './agents.js';
@@ -14,6 +14,7 @@ import { getContextCompressor, ContextCompressor, CompressionResult } from './co
14
14
  import { getConversationManager, ConversationManager } from './conversation.js';
15
15
  import { icons, colors } from './theme.js';
16
16
  import { SystemPromptGenerator } from './system-prompt-generator.js';
17
+ import { AuthService } from './auth.js';
17
18
 
18
19
  const logger = getLogger();
19
20
 
@@ -40,14 +41,14 @@ export class SlashCommandHandler {
40
41
  }
41
42
 
42
43
  /**
43
- * 设置清除对话的回调函数
44
+ * Set callback for clearing conversation
44
45
  */
45
46
  setClearCallback(callback: () => void): void {
46
47
  this.onClearCallback = callback;
47
48
  }
48
49
 
49
50
  /**
50
- * 设置系统提示更新的回调函数
51
+ * Set callback for system prompt update
51
52
  */
52
53
  setSystemPromptUpdateCallback(callback: () => Promise<void>): void {
53
54
  this.onSystemPromptUpdate = callback;
@@ -84,6 +85,9 @@ export class SlashCommandHandler {
84
85
  case 'auth':
85
86
  await this.handleAuth();
86
87
  break;
88
+ case 'login':
89
+ await this.handleLogin();
90
+ break;
87
91
  case 'mode':
88
92
  await this.handleMode(args);
89
93
  break;
@@ -96,6 +100,9 @@ export class SlashCommandHandler {
96
100
  case 'mcp':
97
101
  await this.handleMcp(args);
98
102
  break;
103
+ case 'vlm':
104
+ await this.handleVlm();
105
+ break;
99
106
  case 'memory':
100
107
  await this.handleMemory(args);
101
108
  break;
@@ -111,9 +118,9 @@ export class SlashCommandHandler {
111
118
  case 'theme':
112
119
  await this.handleTheme();
113
120
  break;
114
- case 'language':
115
- await this.handleLanguage();
116
- break;
121
+ // case 'language':
122
+ // await this.handleLanguage();
123
+ // break;
117
124
  case 'about':
118
125
  await this.handleAbout();
119
126
  break;
@@ -212,12 +219,12 @@ export class SlashCommandHandler {
212
219
  detail: 'Enable/disable AI thinking process display',
213
220
  example: '/think on\n/think off\n/think display compact'
214
221
  },
215
- {
216
- cmd: '/language [zh|en]',
217
- desc: 'Switch language',
218
- detail: 'Switch between Chinese and English interface',
219
- example: '/language zh\n/language en'
220
- },
222
+ // {
223
+ // cmd: '/language [zh|en]',
224
+ // desc: 'Switch language',
225
+ // detail: 'Switch between Chinese and English interface',
226
+ // example: '/language zh\n/language en'
227
+ // },
221
228
  {
222
229
  cmd: '/theme',
223
230
  desc: 'Switch theme',
@@ -240,6 +247,12 @@ export class SlashCommandHandler {
240
247
  detail: 'Manage Model Context Protocol servers',
241
248
  example: '/mcp list\n/mcp add server-name'
242
249
  },
250
+ {
251
+ cmd: '/vlm',
252
+ desc: 'Configure VLM for GUI Agent',
253
+ detail: 'Configure Vision-Language Model for browser/desktop automation',
254
+ example: '/vlm'
255
+ },
243
256
  {
244
257
  cmd: '/tools [verbose|simple]',
245
258
  desc: 'Manage tool display',
@@ -334,13 +347,13 @@ export class SlashCommandHandler {
334
347
  }
335
348
 
336
349
  private async handleClear(): Promise<void> {
337
- // 清空本地对话历史
350
+ // Clear local conversation history
338
351
  this.conversationHistory = [];
339
352
 
340
- // 清空 ConversationManager 中的当前对话
353
+ // Clear ConversationManager 中的当前对话
341
354
  await this.conversationManager.clearCurrentConversation();
342
355
 
343
- // 调用回调通知 InteractiveSession 清空对话
356
+ // Call callback to notify InteractiveSession 清空对话
344
357
  if (this.onClearCallback) {
345
358
  this.onClearCallback();
346
359
  }
@@ -393,6 +406,143 @@ export class SlashCommandHandler {
393
406
  }
394
407
  }
395
408
 
409
+ private async handleLogin(): Promise<void> {
410
+ logger.section('Login to xAgent');
411
+
412
+ const authConfig = this.configManager.getAuthConfig();
413
+ const currentAuthType = authConfig.type;
414
+
415
+ if (currentAuthType !== AuthType.OAUTH_XAGENT) {
416
+ console.log(chalk.yellow('\n⚠️ Current authentication type is not OAuth xAgent.'));
417
+ const { proceed } = await inquirer.prompt([
418
+ {
419
+ type: 'confirm',
420
+ name: 'proceed',
421
+ message: 'Do you want to switch to OAuth xAgent authentication?',
422
+ default: false
423
+ }
424
+ ]);
425
+
426
+ if (!proceed) {
427
+ return;
428
+ }
429
+
430
+ // Switch to OAuth xAgent
431
+ await this.configManager.setAuthConfig({
432
+ selectedAuthType: AuthType.OAUTH_XAGENT,
433
+ apiKey: '',
434
+ refreshToken: '',
435
+ baseUrl: ''
436
+ });
437
+ await this.configManager.save('global');
438
+ console.log(chalk.green('✅ Switched to OAuth xAgent authentication.'));
439
+ }
440
+
441
+ console.log(chalk.cyan('\n🔐 Starting OAuth xAgent login...'));
442
+ console.log(chalk.gray(' A browser will open for you to complete authentication.\n'));
443
+
444
+ try {
445
+ const authService = new AuthService({
446
+ type: AuthType.OAUTH_XAGENT,
447
+ apiKey: '',
448
+ baseUrl: '',
449
+ refreshToken: ''
450
+ });
451
+
452
+ const success = await authService.authenticate();
453
+
454
+ if (success) {
455
+ const newConfig = this.configManager.getAuthConfig();
456
+ console.log(chalk.green('\n✅ Login successful!'));
457
+ console.log(chalk.cyan(` Token saved to: ~/.xagent/settings.json`));
458
+ console.log(chalk.gray(' You can now use xAgent CLI with remote AI services.\n'));
459
+ } else {
460
+ console.log(chalk.red('\n❌ Login failed or was cancelled.'));
461
+ }
462
+ } catch (error: any) {
463
+ console.log(chalk.red(`\n❌ Login error: ${error.message || 'Unknown error'}`));
464
+ }
465
+ }
466
+
467
+ private async handleVlm(): Promise<void> {
468
+ logger.section('VLM Configuration for GUI Agent');
469
+
470
+ // Show current VLM config
471
+ const currentVlmConfig = {
472
+ model: this.configManager.get('guiSubagentModel'),
473
+ baseUrl: this.configManager.get('guiSubagentBaseUrl'),
474
+ apiKey: this.configManager.get('guiSubagentApiKey') ? '***' : ''
475
+ };
476
+
477
+ console.log(chalk.cyan('\n📊 Current VLM Configuration:\n'));
478
+ console.log(` Model: ${chalk.yellow(currentVlmConfig.model || 'Not configured')}`);
479
+ console.log(` Base URL: ${chalk.yellow(currentVlmConfig.baseUrl || 'Not configured')}`);
480
+ console.log(` API Key: ${chalk.yellow(currentVlmConfig.apiKey || 'Not configured')}`);
481
+ console.log();
482
+
483
+ const { action } = await inquirer.prompt([
484
+ {
485
+ type: 'list',
486
+ name: 'action',
487
+ message: 'Select action:',
488
+ choices: [
489
+ { name: 'Configure VLM', value: 'configure' },
490
+ { name: 'Remove VLM configuration', value: 'remove' },
491
+ { name: 'Back', value: 'back' }
492
+ ]
493
+ }
494
+ ]);
495
+
496
+ if (action === 'back') {
497
+ return;
498
+ }
499
+
500
+ if (action === 'remove') {
501
+ const { confirm } = await inquirer.prompt([
502
+ {
503
+ type: 'confirm',
504
+ name: 'confirm',
505
+ message: 'Are you sure you want to remove VLM configuration?',
506
+ default: false
507
+ }
508
+ ]);
509
+
510
+ if (confirm) {
511
+ await this.configManager.set('guiSubagentModel', '');
512
+ await this.configManager.set('guiSubagentBaseUrl', '');
513
+ await this.configManager.set('guiSubagentApiKey', '');
514
+ await this.configManager.save('global');
515
+ console.log(chalk.green('✅ VLM configuration removed successfully!'));
516
+ }
517
+ return;
518
+ }
519
+
520
+ if (action === 'configure') {
521
+ // Use AuthService to configure VLM
522
+ const authService = new AuthService({
523
+ type: 'openai_compatible' as any,
524
+ apiKey: '',
525
+ baseUrl: '',
526
+ modelName: ''
527
+ });
528
+
529
+ const vlmConfig = await authService.configureAndValidateVLM();
530
+
531
+ if (vlmConfig) {
532
+ // Save VLM configuration
533
+ await this.configManager.set('guiSubagentModel', vlmConfig.model);
534
+ await this.configManager.set('guiSubagentBaseUrl', vlmConfig.baseUrl);
535
+ await this.configManager.set('guiSubagentApiKey', vlmConfig.apiKey);
536
+ await this.configManager.save('global');
537
+ console.log(chalk.green('✅ VLM configuration saved successfully!'));
538
+ console.log(chalk.cyan(` Model: ${vlmConfig.model}`));
539
+ console.log(chalk.cyan(` Base URL: ${vlmConfig.baseUrl}`));
540
+ } else {
541
+ console.log(chalk.red('❌ VLM configuration failed or cancelled'));
542
+ }
543
+ }
544
+ }
545
+
396
546
  private async handleMode(args: string[]): Promise<void> {
397
547
  const modes = Object.values(ExecutionMode);
398
548
  const currentMode = this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
@@ -1,6 +1,7 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { AIClient, Message } from './ai-client.js';
3
3
  import { getConfigManager } from './config.js';
4
+ import { AuthType } from './types.js';
4
5
  import { getLogger } from './logger.js';
5
6
  import { colors, icons } from './theme.js';
6
7
 
@@ -303,20 +304,26 @@ export class BlacklistChecker {
303
304
  */
304
305
  export class AIApprovalChecker {
305
306
  private aiClient: AIClient | null = null;
307
+ private isRemoteMode: boolean = false;
306
308
 
307
309
  constructor() {
308
310
  this.initializeAIClient();
309
311
  }
310
312
 
311
313
  /**
312
- * Initialize AI client
314
+ * Initialize AI client(s)
313
315
  */
314
316
  private async initializeAIClient(): Promise<void> {
315
317
  try {
316
318
  const configManager = getConfigManager();
317
319
  const authConfig = configManager.getAuthConfig();
318
320
 
319
- if (authConfig.apiKey) {
321
+ // Check if Remote mode (OAuth XAGENT)
322
+ this.isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
323
+
324
+ // Remote mode: AI review handled by remote LLM, no local AIClient needed
325
+ // Local mode: use local AIClient
326
+ if (!this.isRemoteMode && authConfig.apiKey) {
320
327
  this.aiClient = new AIClient(authConfig);
321
328
  }
322
329
  } catch (error) {
@@ -328,11 +335,21 @@ export class AIApprovalChecker {
328
335
  * Use AI for intelligent review
329
336
  */
330
337
  async check(context: ToolCallContext): Promise<{ approved: boolean; analysis: string; riskLevel: RiskLevel }> {
338
+ // In Remote mode, the remote LLM has already approved the tool_calls
339
+ // Local AI review approves directly, no need to repeat
340
+ if (this.isRemoteMode) {
341
+ return {
342
+ approved: true,
343
+ analysis: 'Remote mode: tool approval handled by remote LLM',
344
+ riskLevel: RiskLevel.LOW
345
+ };
346
+ }
347
+
331
348
  if (!this.aiClient) {
332
349
  // If AI client is not initialized, default to medium risk, requires user confirmation
333
350
  return {
334
351
  approved: false,
335
- analysis: 'AI review not available, requires manual user confirmation',
352
+ analysis: 'AI review not available (no local LLM configured), requires manual user confirmation',
336
353
  riskLevel: RiskLevel.MEDIUM
337
354
  };
338
355
  }
@@ -392,6 +409,20 @@ Please return results in JSON format:
392
409
  };
393
410
  } catch (error: any) {
394
411
  logger.error('AI approval check failed', error instanceof Error ? error.message : String(error));
412
+
413
+ // In Remote mode, remote LLM already approved, local failure means auto-approve
414
+ const configManager = getConfigManager();
415
+ const authConfig = configManager.getAuthConfig();
416
+ const isRemoteMode = authConfig.type === AuthType.OAUTH_XAGENT;
417
+
418
+ if (isRemoteMode) {
419
+ return {
420
+ approved: true,
421
+ analysis: 'Remote mode: approved (remote LLM handled approval)',
422
+ riskLevel: RiskLevel.LOW
423
+ };
424
+ }
425
+
395
426
  return {
396
427
  approved: false,
397
428
  analysis: `AI review failed: ${error.message}, requires manual confirmation`,