@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.
- package/README.md +1 -1
- package/README_CN.md +1 -1
- package/dist/agents.js +164 -164
- package/dist/agents.js.map +1 -1
- package/dist/ai-client.d.ts +4 -6
- package/dist/ai-client.d.ts.map +1 -1
- package/dist/ai-client.js +137 -115
- package/dist/ai-client.js.map +1 -1
- package/dist/auth.js +4 -4
- package/dist/auth.js.map +1 -1
- package/dist/cli.js +184 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts.map +1 -1
- package/dist/context-compressor.js +65 -81
- package/dist/context-compressor.js.map +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +5 -31
- package/dist/conversation.js.map +1 -1
- package/dist/memory.d.ts +5 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +77 -37
- package/dist/memory.js.map +1 -1
- package/dist/remote-ai-client.d.ts +1 -8
- package/dist/remote-ai-client.d.ts.map +1 -1
- package/dist/remote-ai-client.js +55 -65
- package/dist/remote-ai-client.js.map +1 -1
- package/dist/retry.d.ts +35 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +166 -0
- package/dist/retry.js.map +1 -0
- package/dist/session.d.ts +0 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +243 -312
- package/dist/session.js.map +1 -1
- package/dist/slash-commands.d.ts +1 -0
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +91 -9
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +18 -17
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.d.ts.map +1 -1
- package/dist/system-prompt-generator.js +149 -139
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/theme.d.ts +48 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +254 -0
- package/dist/theme.js.map +1 -1
- package/dist/tools/edit-diff.d.ts +32 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +185 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +11 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +129 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools.d.ts +19 -5
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +979 -631
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +6 -31
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/agents.ts +504 -504
- package/src/ai-client.ts +1559 -1458
- package/src/auth.ts +4 -4
- package/src/cli.ts +195 -1
- package/src/config.ts +3 -3
- package/src/memory.ts +55 -14
- package/src/remote-ai-client.ts +663 -683
- package/src/retry.ts +217 -0
- package/src/session.ts +1736 -1840
- package/src/slash-commands.ts +98 -9
- package/src/smart-approval.ts +626 -625
- package/src/system-prompt-generator.ts +853 -843
- package/src/theme.ts +284 -0
- 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(
|
|
121
|
-
console.log(' '.repeat(
|
|
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://
|
|
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://
|
|
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
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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(
|
|
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(`(... ${
|
|
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
|
-
//
|
|
745
|
-
|
|
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
|
|
751
|
-
|
|
752
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
//
|
|
907
|
-
|
|
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.
|
|
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://
|
|
941
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
1050
942
|
logger.debug('[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token');
|
|
1051
|
-
|
|
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
|
|
969
|
+
// Prepare all tool calls
|
|
1079
970
|
const preparedToolCalls = toolCalls.map((toolCall, index) => {
|
|
1080
|
-
const {
|
|
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 {
|
|
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:
|
|
1122
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
//
|
|
1192
|
-
|
|
1193
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
*/
|