@xagent-ai/cli 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/README_CN.md +1 -1
  3. package/dist/agents.js +164 -164
  4. package/dist/agents.js.map +1 -1
  5. package/dist/ai-client.d.ts +4 -6
  6. package/dist/ai-client.d.ts.map +1 -1
  7. package/dist/ai-client.js +137 -115
  8. package/dist/ai-client.js.map +1 -1
  9. package/dist/auth.js +4 -4
  10. package/dist/auth.js.map +1 -1
  11. package/dist/cli.js +184 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.js +3 -3
  14. package/dist/config.js.map +1 -1
  15. package/dist/context-compressor.d.ts.map +1 -1
  16. package/dist/context-compressor.js +65 -81
  17. package/dist/context-compressor.js.map +1 -1
  18. package/dist/conversation.d.ts +1 -1
  19. package/dist/conversation.d.ts.map +1 -1
  20. package/dist/conversation.js +5 -31
  21. package/dist/conversation.js.map +1 -1
  22. package/dist/memory.d.ts +5 -1
  23. package/dist/memory.d.ts.map +1 -1
  24. package/dist/memory.js +77 -37
  25. package/dist/memory.js.map +1 -1
  26. package/dist/remote-ai-client.d.ts +1 -8
  27. package/dist/remote-ai-client.d.ts.map +1 -1
  28. package/dist/remote-ai-client.js +55 -65
  29. package/dist/remote-ai-client.js.map +1 -1
  30. package/dist/retry.d.ts +35 -0
  31. package/dist/retry.d.ts.map +1 -0
  32. package/dist/retry.js +166 -0
  33. package/dist/retry.js.map +1 -0
  34. package/dist/session.d.ts +0 -5
  35. package/dist/session.d.ts.map +1 -1
  36. package/dist/session.js +243 -312
  37. package/dist/session.js.map +1 -1
  38. package/dist/slash-commands.d.ts +1 -0
  39. package/dist/slash-commands.d.ts.map +1 -1
  40. package/dist/slash-commands.js +91 -9
  41. package/dist/slash-commands.js.map +1 -1
  42. package/dist/smart-approval.d.ts.map +1 -1
  43. package/dist/smart-approval.js +18 -17
  44. package/dist/smart-approval.js.map +1 -1
  45. package/dist/system-prompt-generator.d.ts.map +1 -1
  46. package/dist/system-prompt-generator.js +149 -139
  47. package/dist/system-prompt-generator.js.map +1 -1
  48. package/dist/theme.d.ts +48 -0
  49. package/dist/theme.d.ts.map +1 -1
  50. package/dist/theme.js +254 -0
  51. package/dist/theme.js.map +1 -1
  52. package/dist/tools/edit-diff.d.ts +32 -0
  53. package/dist/tools/edit-diff.d.ts.map +1 -0
  54. package/dist/tools/edit-diff.js +185 -0
  55. package/dist/tools/edit-diff.js.map +1 -0
  56. package/dist/tools/edit.d.ts +11 -0
  57. package/dist/tools/edit.d.ts.map +1 -0
  58. package/dist/tools/edit.js +129 -0
  59. package/dist/tools/edit.js.map +1 -0
  60. package/dist/tools.d.ts +19 -5
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +979 -631
  63. package/dist/tools.js.map +1 -1
  64. package/dist/types.d.ts +6 -31
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +3 -2
  67. package/src/agents.ts +504 -504
  68. package/src/ai-client.ts +1559 -1458
  69. package/src/auth.ts +4 -4
  70. package/src/cli.ts +195 -1
  71. package/src/config.ts +3 -3
  72. package/src/memory.ts +55 -14
  73. package/src/remote-ai-client.ts +663 -683
  74. package/src/retry.ts +217 -0
  75. package/src/session.ts +1736 -1840
  76. package/src/slash-commands.ts +98 -9
  77. package/src/smart-approval.ts +626 -625
  78. package/src/system-prompt-generator.ts +853 -843
  79. package/src/theme.ts +284 -0
  80. package/src/tools.ts +390 -70
package/dist/session.js CHANGED
@@ -21,7 +21,7 @@ import { getConversationManager } from './conversation.js';
21
21
  import { getSessionManager } from './session-manager.js';
22
22
  import { SlashCommandHandler, parseInput } from './slash-commands.js';
23
23
  import { SystemPromptGenerator } from './system-prompt-generator.js';
24
- import { theme, icons, colors, styleHelpers, renderMarkdown } from './theme.js';
24
+ import { theme, icons, colors, styleHelpers, renderMarkdown, renderDiff, renderLines } from './theme.js';
25
25
  import { getCancellationManager } from './cancellation.js';
26
26
  import { getContextCompressor } from './context-compressor.js';
27
27
  import { getLogger } from './logger.js';
@@ -66,6 +66,11 @@ export class InteractiveSession {
66
66
  // Register /clear callback, clear local conversation when clearing dialogue
67
67
  this.slashCommandHandler.setClearCallback(() => {
68
68
  this.conversation = [];
69
+ this.toolCalls = [];
70
+ this.currentTaskId = null;
71
+ this.taskCompleted = false;
72
+ this.isFirstApiCall = true;
73
+ this.slashCommandHandler.setConversationHistory([]);
69
74
  });
70
75
  // Register MCP update callback, update system prompt
71
76
  this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
@@ -117,8 +122,8 @@ export class InteractiveSession {
117
122
  console.log('');
118
123
  console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
119
124
  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(' ║'));
125
+ console.log(colors.gradient('║') + ' '.repeat(13) + '🤖 ' + colors.gradient('XAGENT CLI') + ' '.repeat(32) + colors.gradient(' ║'));
126
+ console.log(colors.gradient('║') + ' '.repeat(16) + colors.textMuted(`v${packageJson.version}`) + ' '.repeat(36) + colors.gradient(' ║'));
122
127
  console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
123
128
  console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
124
129
  console.log(colors.textMuted(' AI-powered command-line assistant'));
@@ -167,7 +172,7 @@ export class InteractiveSession {
167
172
  if (authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT) {
168
173
  clearInterval(spinnerInterval);
169
174
  process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear the line
170
- const baseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
175
+ const baseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
171
176
  let isValid = await this.validateToken(baseUrl, authConfig.apiKey);
172
177
  // Try refresh token if validation failed
173
178
  if (!isValid && authConfig.refreshToken) {
@@ -242,7 +247,7 @@ export class InteractiveSession {
242
247
  logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
243
248
  logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
244
249
  if (selectedAuthType === AuthType.OAUTH_XAGENT) {
245
- const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
250
+ const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
246
251
  // In OAuth XAGENT mode, we still pass apiKey (can be empty or used for other purposes)
247
252
  this.remoteAIClient = new RemoteAIClient(authConfig.apiKey || '', webBaseUrl, authConfig.showAIDebugInfo);
248
253
  logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
@@ -633,23 +638,19 @@ export class InteractiveSession {
633
638
  const summaryMessage = result.compressedMessages.find(m => m.role === 'assistant');
634
639
  if (summaryMessage && summaryMessage.content) {
635
640
  const maxPreviewLength = 800;
636
- // Handle both string and MessageContent[] types
637
- const summaryContent = typeof summaryMessage.content === 'string'
638
- ? summaryMessage.content
639
- : summaryMessage.content.filter(c => c.type === 'text').map(c => c.text).join('\n');
640
- const contentLength = summaryContent.length;
641
- const isTruncated = contentLength > maxPreviewLength;
642
- const displayContent = isTruncated
643
- ? summaryContent.substring(0, maxPreviewLength) + '\n...'
644
- : summaryContent;
641
+ let summaryContent = summaryMessage.content;
642
+ const isTruncated = summaryContent.length > maxPreviewLength;
643
+ if (isTruncated) {
644
+ summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
645
+ }
645
646
  console.log('');
646
647
  console.log(`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`);
647
648
  const separator = icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length * 2);
648
649
  console.log(`${indent}${colors.border(separator)}`);
649
- const renderedSummary = renderMarkdown(displayContent, (process.stdout.columns || 80) - indent.length * 4);
650
+ const renderedSummary = renderMarkdown(summaryContent, (process.stdout.columns || 80) - indent.length * 4);
650
651
  console.log(`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`);
651
652
  if (isTruncated) {
652
- console.log(`${indent}${colors.textMuted(`(... ${contentLength - maxPreviewLength} more chars hidden)`)}`);
653
+ console.log(`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`);
653
654
  }
654
655
  console.log(`${indent}${colors.border(separator)}`);
655
656
  }
@@ -740,16 +741,29 @@ export class InteractiveSession {
740
741
  isRemote: false
741
742
  };
742
743
  }
743
- async generateResponse(thinkingTokens = 0) {
744
- // Create taskId for this user interaction (for remote mode tracking)
745
- const taskId = crypto.randomUUID();
744
+ async generateResponse(thinkingTokens = 0, customAIClient, existingTaskId) {
745
+ // Use existing taskId or create new one for this user interaction
746
+ // If taskId already exists (e.g., from tool calls), reuse it
747
+ const taskId = existingTaskId || this.currentTaskId || crypto.randomUUID();
746
748
  this.currentTaskId = taskId;
747
749
  this.isFirstApiCall = true;
748
750
  // Determine status based on whether this is the first API call
749
751
  const status = this.isFirstApiCall ? 'begin' : 'continue';
750
- // Use unified LLM Caller with taskId (automatically selects local or remote mode)
751
- const { chatCompletion, isRemote } = this.createLLMCaller(taskId, status);
752
- if (!isRemote && !this.aiClient) {
752
+ // Use custom AI client if provided, otherwise use default logic
753
+ let chatCompletion;
754
+ let isRemote = false;
755
+ if (customAIClient) {
756
+ // Custom client (used by remote mode) - pass taskId and status
757
+ chatCompletion = (messages, options) => customAIClient.chatCompletion(messages, { ...options, taskId, status });
758
+ isRemote = true;
759
+ }
760
+ else {
761
+ // Use unified LLM Caller with taskId (automatically selects local or remote mode)
762
+ const caller = this.createLLMCaller(taskId, status);
763
+ chatCompletion = caller.chatCompletion;
764
+ isRemote = caller.isRemote;
765
+ }
766
+ if (!isRemote && !this.aiClient && !customAIClient) {
753
767
  console.log(colors.error('AI client not initialized'));
754
768
  return;
755
769
  }
@@ -783,11 +797,7 @@ export class InteractiveSession {
783
797
  const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
784
798
  const messages = [
785
799
  { role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
786
- ...this.conversation.map(msg => ({
787
- role: msg.role,
788
- content: msg.content,
789
- timestamp: msg.timestamp
790
- }))
800
+ ...this.conversation
791
801
  ];
792
802
  const operationId = `ai-response-${Date.now()}`;
793
803
  const response = await this.cancellationManager.withCancellation(chatCompletion(messages, {
@@ -815,23 +825,17 @@ export class InteractiveSession {
815
825
  const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
816
826
  console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
817
827
  console.log('');
818
- // Build content array with thinking block if present (pi-mono style)
819
- const assistantContent = [];
820
- if (reasoningContent) {
821
- assistantContent.push({ type: 'thinking', thinking: reasoningContent });
822
- }
823
- if (content) {
824
- assistantContent.push({ type: 'text', text: content });
825
- }
826
828
  this.conversation.push({
827
829
  role: 'assistant',
828
- content: assistantContent,
829
- timestamp: Date.now()
830
+ content,
831
+ timestamp: Date.now(),
832
+ reasoningContent,
833
+ toolCalls: assistantMessage.tool_calls
830
834
  });
831
835
  // Record output to session manager
832
836
  await this.sessionManager.addOutput({
833
837
  role: 'assistant',
834
- content: assistantContent,
838
+ content,
835
839
  timestamp: Date.now(),
836
840
  reasoningContent,
837
841
  toolCalls: assistantMessage.tool_calls
@@ -853,14 +857,14 @@ export class InteractiveSession {
853
857
  if (error.message === 'Operation cancelled by user') {
854
858
  // Mark task as cancelled
855
859
  if (this.remoteAIClient && this.currentTaskId) {
856
- await this.remoteAIClient.cancelTask(this.currentTaskId);
860
+ await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
857
861
  }
858
862
  return;
859
863
  }
860
864
  // Mark task as cancelled when error occurs (发送 status: 'cancel')
861
865
  logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
862
866
  if (this.remoteAIClient && this.currentTaskId) {
863
- await this.remoteAIClient.cancelTask(this.currentTaskId);
867
+ await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
864
868
  }
865
869
  console.log(colors.error(`Error: ${error.message}`));
866
870
  }
@@ -884,131 +888,21 @@ export class InteractiveSession {
884
888
  // Determine status based on whether this is the first API call
885
889
  const status = this.isFirstApiCall ? 'begin' : 'continue';
886
890
  logger.debug(`[Session] Status for this call: ${status}, isFirstApiCall=${this.isFirstApiCall}`);
887
- // 使用统一的 LLM Caller
888
- const { chatCompletion, isRemote } = this.createLLMCaller(taskId, status);
889
- if (!isRemote) {
890
- // 如果不是远程模式,回退到本地模式
891
- return this.generateResponse(thinkingTokens);
891
+ // Check if remote client is available
892
+ if (!this.remoteAIClient) {
893
+ console.log(colors.error('Remote AI client not initialized'));
894
+ return;
892
895
  }
893
- const indent = this.getIndent();
894
- const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
895
- const icon = colors.primary(icons.brain);
896
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
897
- let frameIndex = 0;
898
- // Mark that an operation is in progress
899
- this._isOperationInProgress = true;
900
- // Custom spinner: only icon rotates, text stays static
901
- const spinnerInterval = setInterval(() => {
902
- process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
903
- frameIndex = (frameIndex + 1) % frames.length;
904
- }, 120);
905
896
  try {
906
- // Load memory (与本地模式一致)
907
- const memory = await this.memoryManager.loadMemory();
908
- // Get tool definitions
909
- const toolRegistry = getToolRegistry();
910
- const allowedToolNames = this.currentAgent
911
- ? this.agentManager.getAvailableToolsForAgent(this.currentAgent, this.executionMode)
912
- : [];
913
- const allToolDefinitions = toolRegistry.getToolDefinitions();
914
- const availableTools = this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
915
- ? allToolDefinitions.filter((tool) => allowedToolNames.includes(tool.function.name))
916
- : allToolDefinitions;
917
- // Convert to the format expected by backend (与本地模式一致使用 availableTools)
918
- const tools = availableTools.map((tool) => ({
919
- type: 'function',
920
- function: {
921
- name: tool.function.name,
922
- description: tool.function.description || '',
923
- parameters: tool.function.parameters || {
924
- type: 'object',
925
- properties: {}
926
- }
927
- }
928
- }));
929
- // Generate system prompt (与本地模式一致)
930
- const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
931
- const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
932
- const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
933
- // Build messages with system prompt (与本地模式一致)
934
- const messages = [
935
- { role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
936
- ...this.conversation.map(msg => ({
937
- role: msg.role,
938
- content: msg.content,
939
- timestamp: msg.timestamp
940
- }))
941
- ];
942
- // Call unified LLM API with cancellation support
943
- const operationId = `remote-ai-response-${Date.now()}`;
944
- const response = await this.cancellationManager.withCancellation(chatCompletion(messages, {
945
- tools,
946
- toolChoice: tools.length > 0 ? 'auto' : 'none',
947
- thinkingTokens
948
- }), operationId);
949
- // Mark that first API call is complete
950
- this.isFirstApiCall = false;
951
- clearInterval(spinnerInterval);
952
- process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
953
- console.log('');
954
- // 使用统一的响应格式(与本地模式一致)
955
- const assistantMessage = response.choices[0].message;
956
- const content = typeof assistantMessage.content === 'string'
957
- ? assistantMessage.content
958
- : '';
959
- const reasoningContent = assistantMessage.reasoning_content || '';
960
- const toolCalls = assistantMessage.tool_calls || [];
961
- // Display reasoning content if available and thinking mode is enabled (与本地模式一致)
962
- if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
963
- this.displayThinkingContent(reasoningContent);
964
- }
965
- console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
966
- console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
967
- console.log('');
968
- const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
969
- console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
970
- console.log('');
971
- // Build content array with thinking block if present (pi-mono style)
972
- const assistantContent = [];
973
- if (reasoningContent) {
974
- assistantContent.push({ type: 'thinking', thinking: reasoningContent });
975
- }
976
- if (content) {
977
- assistantContent.push({ type: 'text', text: content });
978
- }
979
- // Add assistant message to conversation
980
- this.conversation.push({
981
- role: 'assistant',
982
- content: assistantContent,
983
- timestamp: Date.now()
984
- });
985
- // Record output to session manager
986
- await this.sessionManager.addOutput({
987
- role: 'assistant',
988
- content: assistantContent,
989
- timestamp: Date.now(),
990
- reasoningContent,
991
- toolCalls
992
- });
993
- // Handle tool calls
994
- if (toolCalls.length > 0) {
995
- await this.handleRemoteToolCalls(toolCalls);
996
- }
997
- // Checkpoint support (consistent with local mode)
998
- if (this.checkpointManager.isEnabled()) {
999
- await this.checkpointManager.createCheckpoint(`Response generated at ${new Date().toLocaleString()}`, [...this.conversation], [...this.toolCalls]);
1000
- }
1001
- // Operation completed successfully
1002
- this._isOperationInProgress = false;
897
+ // Reuse generateResponse with remote client, pass taskId to avoid generating new one
898
+ await this.generateResponse(thinkingTokens, this.remoteAIClient, taskId);
1003
899
  // Mark task as completed (发送 status: 'end')
1004
900
  logger.debug(`[Session] Task completed: taskId=${this.currentTaskId}`);
1005
- if (this.remoteAIClient && this.currentTaskId) {
901
+ if (this.currentTaskId) {
1006
902
  await this.remoteAIClient.completeTask(this.currentTaskId);
1007
903
  }
1008
904
  }
1009
905
  catch (error) {
1010
- clearInterval(spinnerInterval);
1011
- process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
1012
906
  // Clear the operation flag
1013
907
  this._isOperationInProgress = false;
1014
908
  if (error.message === 'Operation cancelled by user') {
@@ -1021,7 +915,6 @@ export class InteractiveSession {
1021
915
  console.log(colors.info('Your browser session has been logged out. Please log in again.'));
1022
916
  console.log('');
1023
917
  // Clear invalid credentials and persist
1024
- // Note: Do NOT overwrite selectedAuthType - preserve user's chosen auth method
1025
918
  await this.configManager.set('apiKey', '');
1026
919
  await this.configManager.set('refreshToken', '');
1027
920
  await this.configManager.save('global');
@@ -1034,7 +927,6 @@ export class InteractiveSession {
1034
927
  const authConfig = this.configManager.getAuthConfig();
1035
928
  logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
1036
929
  logger.debug(' - authConfig.apiKey exists:', !!authConfig.apiKey ? 'true' : 'false');
1037
- logger.debug(' - authConfig.apiKey prefix:', authConfig.apiKey ? authConfig.apiKey.substring(0, 20) + '...' : 'empty');
1038
930
  // Recreate readline interface after inquirer
1039
931
  this.rl.close();
1040
932
  this.rl = readline.createInterface({
@@ -1046,10 +938,9 @@ export class InteractiveSession {
1046
938
  });
1047
939
  // Reinitialize RemoteAIClient with new token
1048
940
  if (authConfig.apiKey) {
1049
- const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
941
+ const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
1050
942
  logger.debug('[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token');
1051
- const newWebBaseUrl = authConfig.xagentApiBaseUrl || 'https://154.8.140.52:443';
1052
- this.remoteAIClient = new RemoteAIClient(authConfig.apiKey, newWebBaseUrl, authConfig.showAIDebugInfo);
943
+ this.remoteAIClient = new RemoteAIClient(authConfig.apiKey, webBaseUrl, authConfig.showAIDebugInfo);
1053
944
  }
1054
945
  else {
1055
946
  logger.debug('[DEBUG generateRemoteResponse] WARNING: No apiKey after re-authentication!');
@@ -1063,21 +954,21 @@ export class InteractiveSession {
1063
954
  // Mark task as cancelled when error occurs (发送 status: 'cancel')
1064
955
  logger.debug(`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}`);
1065
956
  if (this.remoteAIClient && this.currentTaskId) {
1066
- await this.remoteAIClient.cancelTask(this.currentTaskId);
957
+ await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => { });
1067
958
  }
1068
959
  console.log(colors.error(`Error: ${error.message}`));
1069
960
  return;
1070
961
  }
1071
962
  }
1072
- async handleToolCalls(toolCalls) {
963
+ async handleToolCalls(toolCalls, onComplete) {
1073
964
  // Mark that tool execution is in progress
1074
965
  this._isOperationInProgress = true;
1075
966
  const toolRegistry = getToolRegistry();
1076
967
  const showToolDetails = this.configManager.get('showToolDetails') || false;
1077
968
  const indent = this.getIndent();
1078
- // Prepare all tool calls (keep the original id for toolCallId)
969
+ // Prepare all tool calls
1079
970
  const preparedToolCalls = toolCalls.map((toolCall, index) => {
1080
- const { id, name, arguments: params } = toolCall.function;
971
+ const { name, arguments: params } = toolCall.function;
1081
972
  let parsedParams;
1082
973
  try {
1083
974
  parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
@@ -1085,7 +976,7 @@ export class InteractiveSession {
1085
976
  catch (e) {
1086
977
  parsedParams = params;
1087
978
  }
1088
- return { id, name, params: parsedParams, index };
979
+ return { name, params: parsedParams, index, id: toolCall.id };
1089
980
  });
1090
981
  // Display all tool calls info
1091
982
  for (const { name, params } of preparedToolCalls) {
@@ -1096,30 +987,35 @@ export class InteractiveSession {
1096
987
  }
1097
988
  else {
1098
989
  const toolDescription = this.getToolDescription(name, params);
1099
- console.log('');
1100
990
  console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1101
991
  }
1102
992
  }
1103
993
  // Execute all tools in parallel
1104
994
  const results = await toolRegistry.executeAll(preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })), this.executionMode);
1105
995
  // Process results and maintain order
996
+ let hasError = false;
1106
997
  for (const { tool, result, error } of results) {
1107
998
  const toolCall = preparedToolCalls.find(tc => tc.name === tool);
1108
999
  if (!toolCall)
1109
1000
  continue;
1110
1001
  const { params } = toolCall;
1111
1002
  if (error) {
1112
- // Clear the operation flag
1113
- this._isOperationInProgress = false;
1114
1003
  if (error === 'Operation cancelled by user') {
1004
+ this._isOperationInProgress = false;
1115
1005
  return;
1116
1006
  }
1007
+ hasError = true;
1117
1008
  console.log('');
1118
- console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${error}`)}`);
1009
+ console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
1010
+ // 添加详细的错误信息,包含工具名称和参数,便于 AI 理解和修正
1119
1011
  this.conversation.push({
1120
1012
  role: 'tool',
1121
- content: [{ type: 'text', text: JSON.stringify({ error }) }],
1122
- toolCallId: toolCall.id,
1013
+ content: JSON.stringify({
1014
+ name: tool,
1015
+ parameters: params,
1016
+ error: error
1017
+ }),
1018
+ tool_call_id: toolCall.id,
1123
1019
  timestamp: Date.now()
1124
1020
  });
1125
1021
  }
@@ -1129,6 +1025,22 @@ export class InteractiveSession {
1129
1025
  const displayIndent = isGuiSubagent ? indent + ' ' : indent;
1130
1026
  // Always show details for todo tools so users can see their task lists
1131
1027
  const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1028
+ // Special handling for edit tool with diff
1029
+ const isEditTool = tool === 'Edit';
1030
+ const hasDiff = isEditTool && result?.diff;
1031
+ // Special handling for Write tool with file preview
1032
+ const isWriteTool = tool === 'Write';
1033
+ const hasFilePreview = isWriteTool && result?.preview;
1034
+ // Special handling for DeleteFile tool
1035
+ const isDeleteTool = tool === 'DeleteFile';
1036
+ const hasDeleteInfo = isDeleteTool && result?.filePath;
1037
+ // Special handling for task tool (subagent)
1038
+ const isTaskTool = tool === 'task' && params?.subagent_type;
1039
+ // Check if tool is an MCP wrapper tool by looking up in tool registry
1040
+ const { getToolRegistry } = await import('./tools.js');
1041
+ const toolRegistry = getToolRegistry();
1042
+ const toolDef = toolRegistry.get(tool);
1043
+ const isMcpTool = toolDef && toolDef._isMcpTool === true;
1132
1044
  if (isTodoTool) {
1133
1045
  console.log('');
1134
1046
  console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
@@ -1138,6 +1050,134 @@ export class InteractiveSession {
1138
1050
  console.log(`${displayIndent}${colors.textDim(result.message)}`);
1139
1051
  }
1140
1052
  }
1053
+ else if (hasDiff) {
1054
+ // Show edit result with diff
1055
+ console.log('');
1056
+ const diffOutput = renderDiff(result.diff);
1057
+ const indentedDiff = diffOutput.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
1058
+ console.log(`${indentedDiff}`);
1059
+ }
1060
+ else if (hasFilePreview) {
1061
+ // Show new file content in diff-like style
1062
+ console.log('');
1063
+ console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
1064
+ console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
1065
+ console.log('');
1066
+ console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
1067
+ }
1068
+ else if (hasDeleteInfo) {
1069
+ // Show DeleteFile result
1070
+ console.log('');
1071
+ console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
1072
+ }
1073
+ else if (isTaskTool) {
1074
+ // Special handling for task tool (subagent) - show friendly summary
1075
+ console.log('');
1076
+ const subagentType = params.subagent_type;
1077
+ const subagentName = params.description || (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
1078
+ if (result?.success) {
1079
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
1080
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1081
+ if (result.message) {
1082
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1083
+ }
1084
+ }
1085
+ else if (result?.cancelled) {
1086
+ console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
1087
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1088
+ }
1089
+ else {
1090
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
1091
+ console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
1092
+ if (result?.message) {
1093
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1094
+ }
1095
+ }
1096
+ }
1097
+ else if (isMcpTool) {
1098
+ // Special handling for MCP tools - show friendly summary
1099
+ console.log('');
1100
+ // Extract server name and tool name from tool name (format: serverName__toolName)
1101
+ let serverName = 'MCP';
1102
+ let toolDisplayName = tool;
1103
+ if (tool.includes('__')) {
1104
+ const parts = tool.split('__');
1105
+ serverName = parts[0];
1106
+ toolDisplayName = parts.slice(1).join('__');
1107
+ }
1108
+ // Try to extract meaningful content from MCP result
1109
+ let summary = '';
1110
+ if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
1111
+ const firstBlock = result.content[0];
1112
+ if (firstBlock?.type === 'text' && firstBlock?.text) {
1113
+ const text = firstBlock.text;
1114
+ if (typeof text === 'string') {
1115
+ // Detect HTML content
1116
+ if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
1117
+ summary = '[HTML content fetched]';
1118
+ }
1119
+ else {
1120
+ // Try to parse if it's JSON
1121
+ try {
1122
+ const parsed = JSON.parse(text);
1123
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
1124
+ // Search results format
1125
+ summary = `Found ${parsed.length} result(s)`;
1126
+ }
1127
+ else if (parsed?.message) {
1128
+ summary = parsed.message;
1129
+ }
1130
+ else if (typeof parsed === 'string') {
1131
+ summary = parsed.substring(0, 100);
1132
+ }
1133
+ }
1134
+ catch {
1135
+ // Not JSON, use as-is with truncation
1136
+ summary = text.substring(0, 100);
1137
+ }
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+ else if (result?.message) {
1143
+ summary = result.message;
1144
+ }
1145
+ if (result?.success !== false) {
1146
+ console.log(`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`);
1147
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1148
+ if (summary) {
1149
+ console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
1150
+ }
1151
+ }
1152
+ else {
1153
+ console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
1154
+ console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
1155
+ if (result?.message || result?.error) {
1156
+ console.log(`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`);
1157
+ }
1158
+ }
1159
+ }
1160
+ else if (tool === 'InvokeSkill') {
1161
+ // Special handling for InvokeSkill - show friendly summary
1162
+ console.log('');
1163
+ const skillName = params?.skillId || 'Unknown skill';
1164
+ const taskDesc = params?.taskDescription || '';
1165
+ if (result?.success) {
1166
+ console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
1167
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1168
+ if (taskDesc) {
1169
+ const truncatedTask = taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
1170
+ console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
1171
+ }
1172
+ }
1173
+ else {
1174
+ console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
1175
+ console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
1176
+ if (result?.message) {
1177
+ console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
1178
+ }
1179
+ }
1180
+ }
1141
1181
  else if (showToolDetails) {
1142
1182
  console.log('');
1143
1183
  console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
@@ -1148,7 +1188,12 @@ export class InteractiveSession {
1148
1188
  console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1149
1189
  }
1150
1190
  else if (result) {
1151
- console.log(`${displayIndent}${colors.success(`${icons.check} Completed`)}`);
1191
+ // Show brief preview by default (consistent with subagent behavior)
1192
+ const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
1193
+ const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
1194
+ // Indent the preview
1195
+ const indentedPreview = truncatedPreview.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
1196
+ console.log(`${indentedPreview}`);
1152
1197
  }
1153
1198
  else {
1154
1199
  console.log(`${displayIndent}${colors.textDim('(no result)')}`);
@@ -1169,16 +1214,21 @@ export class InteractiveSession {
1169
1214
  toolResult: result,
1170
1215
  timestamp: Date.now()
1171
1216
  });
1217
+ // 统一消息格式,包含工具名称和参数
1172
1218
  this.conversation.push({
1173
1219
  role: 'tool',
1174
- content: JSON.stringify(result),
1220
+ content: JSON.stringify({
1221
+ name: tool,
1222
+ parameters: params,
1223
+ result: result
1224
+ }),
1225
+ tool_call_id: toolCall.id,
1175
1226
  timestamp: Date.now()
1176
1227
  });
1177
1228
  }
1178
1229
  }
1179
1230
  // Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
1180
1231
  // For all other cases (success, failure, errors), always return results for further processing
1181
- const guiSubagentFailed = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent');
1182
1232
  const guiSubagentCancelled = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent' && results.some(r => r.tool === 'task' && r.result?.cancelled === true));
1183
1233
  // If GUI agent was cancelled by user, don't continue generating response
1184
1234
  // This avoids wasting API calls and tokens on cancelled tasks
@@ -1188,9 +1238,21 @@ export class InteractiveSession {
1188
1238
  this._isOperationInProgress = false;
1189
1239
  return;
1190
1240
  }
1191
- // For all other cases (GUI success/failure, other tool errors), return results to main agent
1192
- // This allows main agent to decide how to handle failures (retry, fallback, user notification, etc.)
1193
- await this.generateResponse();
1241
+ // Handle errors and completion based on whether onComplete callback is provided
1242
+ if (hasError) {
1243
+ this._isOperationInProgress = false;
1244
+ // 不再抛出异常,而是将错误结果返回给 AI,让 AI 决定如何处理
1245
+ // 这样可以避免工具错误导致程序退出
1246
+ }
1247
+ // Continue based on mode - 统一处理,无论是否有错误
1248
+ if (onComplete) {
1249
+ // Remote mode: use provided callback
1250
+ await onComplete();
1251
+ }
1252
+ else {
1253
+ // Local mode: default behavior - continue with generateResponse
1254
+ await this.generateResponse();
1255
+ }
1194
1256
  }
1195
1257
  /**
1196
1258
  * Get user-friendly description for tool
@@ -1202,10 +1264,10 @@ export class InteractiveSession {
1202
1264
  'Grep': (p) => `Search text: "${p.pattern}"`,
1203
1265
  'Bash': (p) => `Execute command: ${this.truncateCommand(p.command)}`,
1204
1266
  'ListDirectory': (p) => `List directory: ${this.truncatePath(p.path || '.')}`,
1205
- 'SearchCodebase': (p) => `Search files: ${p.pattern}`,
1267
+ 'SearchFiles': (p) => `Search files: ${p.pattern}`,
1206
1268
  'DeleteFile': (p) => `Delete file: ${this.truncatePath(p.filePath)}`,
1207
1269
  'CreateDirectory': (p) => `Create directory: ${this.truncatePath(p.dirPath)}`,
1208
- 'replace': (p) => `Replace text: ${this.truncatePath(p.file_path)}`,
1270
+ 'Edit': (p) => `Edit text: ${this.truncatePath(p.file_path)}`,
1209
1271
  'web_search': (p) => `Web search: "${p.query}"`,
1210
1272
  'todo_write': () => `Update todo list`,
1211
1273
  'todo_read': () => `Read todo list`,
@@ -1225,137 +1287,6 @@ export class InteractiveSession {
1225
1287
  const getDescription = descriptions[toolName];
1226
1288
  return getDescription ? getDescription(params) : `Execute tool: ${toolName}`;
1227
1289
  }
1228
- /**
1229
- * Handle tool calls for remote AI mode
1230
- * Executes tools and then continues the conversation with results
1231
- */
1232
- async handleRemoteToolCalls(toolCalls) {
1233
- // Mark that tool execution is in progress
1234
- this._isOperationInProgress = true;
1235
- const toolRegistry = getToolRegistry();
1236
- const showToolDetails = this.configManager.get('showToolDetails') || false;
1237
- const indent = this.getIndent();
1238
- // Prepare all tool calls (include id for toolCallId)
1239
- const preparedToolCalls = toolCalls.map((toolCall, index) => {
1240
- const { id, name, arguments: params } = toolCall.function;
1241
- let parsedParams;
1242
- try {
1243
- parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
1244
- }
1245
- catch (e) {
1246
- parsedParams = params;
1247
- }
1248
- return { id, name, params: parsedParams, index };
1249
- });
1250
- // Display all tool calls info
1251
- for (const { name, params } of preparedToolCalls) {
1252
- if (showToolDetails) {
1253
- console.log('');
1254
- console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
1255
- console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
1256
- }
1257
- else {
1258
- const toolDescription = this.getToolDescription(name, params);
1259
- console.log('');
1260
- console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
1261
- }
1262
- }
1263
- // Execute all tools in parallel
1264
- const results = await toolRegistry.executeAll(preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })), this.executionMode);
1265
- // Process results and maintain order
1266
- let hasError = false;
1267
- for (const { tool, result, error } of results) {
1268
- const toolCall = preparedToolCalls.find(tc => tc.name === tool);
1269
- if (!toolCall)
1270
- continue;
1271
- const { params } = toolCall;
1272
- if (error) {
1273
- // Clear the operation flag
1274
- this._isOperationInProgress = false;
1275
- if (error === 'Operation cancelled by user') {
1276
- return;
1277
- }
1278
- hasError = true;
1279
- console.log('');
1280
- console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${error}`)}`);
1281
- this.conversation.push({
1282
- role: 'tool',
1283
- content: [{ type: 'text', text: JSON.stringify({ error }) }],
1284
- toolCallId: toolCall.id,
1285
- timestamp: Date.now()
1286
- });
1287
- }
1288
- else {
1289
- // Use correct indent for gui-subagent tasks
1290
- const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
1291
- const displayIndent = isGuiSubagent ? indent + ' ' : indent;
1292
- // Always show details for todo tools so users can see their task lists
1293
- const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
1294
- if (isTodoTool) {
1295
- console.log('');
1296
- console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
1297
- console.log(this.renderTodoList(result.todos || result.todos, displayIndent));
1298
- // Show summary if available
1299
- if (result.message) {
1300
- console.log(`${displayIndent}${colors.textDim(result.message)}`);
1301
- }
1302
- }
1303
- else if (showToolDetails) {
1304
- console.log('');
1305
- console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
1306
- console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
1307
- }
1308
- else if (result.success === false) {
1309
- // GUI task or other tool failed
1310
- console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
1311
- }
1312
- else {
1313
- console.log(`${displayIndent}${colors.success(`${icons.check} Completed`)}`);
1314
- }
1315
- const toolCallRecord = {
1316
- tool,
1317
- params,
1318
- result,
1319
- timestamp: Date.now()
1320
- };
1321
- this.toolCalls.push(toolCallRecord);
1322
- // Record tool output to session manager
1323
- await this.sessionManager.addOutput({
1324
- role: 'tool',
1325
- content: JSON.stringify(result),
1326
- toolName: tool,
1327
- toolParams: params,
1328
- toolResult: result,
1329
- timestamp: Date.now()
1330
- });
1331
- this.conversation.push({
1332
- role: 'tool',
1333
- content: JSON.stringify(result),
1334
- timestamp: Date.now()
1335
- });
1336
- }
1337
- }
1338
- // Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
1339
- // For all other cases (success, failure, errors), always return results for further processing
1340
- const guiSubagentFailed = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent');
1341
- const guiSubagentCancelled = preparedToolCalls.some(tc => tc.name === 'task' && tc.params?.subagent_type === 'gui-subagent' && results.some(r => r.tool === 'task' && r.result?.cancelled === true));
1342
- // If GUI agent was cancelled by user, don't continue generating response
1343
- // This avoids wasting API calls and tokens on cancelled tasks
1344
- if (guiSubagentCancelled) {
1345
- console.log('');
1346
- console.log(`${indent}${colors.textMuted('GUI task cancelled by user')}`);
1347
- this._isOperationInProgress = false;
1348
- return;
1349
- }
1350
- // If any tool call failed, throw error to mark task as cancelled
1351
- if (hasError) {
1352
- throw new Error('Tool execution failed');
1353
- }
1354
- // For all other cases (GUI success/failure, other tool errors), return results to main agent
1355
- // This allows main agent to decide how to handle failures (retry, fallback, user notification, etc.)
1356
- // Reuse existing taskId instead of generating new one
1357
- await this.generateRemoteResponse(0, this.currentTaskId || undefined);
1358
- }
1359
1290
  /**
1360
1291
  * Truncate path for display
1361
1292
  */