@defai.digital/ax-cli 3.8.21 → 3.8.23
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 +275 -1287
- package/dist/agent/core/index.d.ts +8 -0
- package/dist/agent/core/index.js +9 -0
- package/dist/agent/core/index.js.map +1 -0
- package/dist/agent/core/types.d.ts +92 -0
- package/dist/agent/core/types.js +11 -0
- package/dist/agent/core/types.js.map +1 -0
- package/dist/agent/execution/index.d.ts +9 -0
- package/dist/agent/execution/index.js +9 -0
- package/dist/agent/execution/index.js.map +1 -0
- package/dist/agent/execution/tool-executor.d.ts +79 -0
- package/dist/agent/execution/tool-executor.js +281 -0
- package/dist/agent/execution/tool-executor.js.map +1 -0
- package/dist/agent/llm-agent.d.ts +22 -98
- package/dist/agent/llm-agent.js +169 -712
- package/dist/agent/llm-agent.js.map +1 -1
- package/dist/agent/planning/index.d.ts +9 -0
- package/dist/agent/planning/index.js +9 -0
- package/dist/agent/planning/index.js.map +1 -0
- package/dist/agent/planning/plan-executor.d.ts +84 -0
- package/dist/agent/planning/plan-executor.js +223 -0
- package/dist/agent/planning/plan-executor.js.map +1 -0
- package/dist/agent/streaming/index.d.ts +9 -0
- package/dist/agent/streaming/index.js +9 -0
- package/dist/agent/streaming/index.js.map +1 -0
- package/dist/agent/streaming/stream-handler.d.ts +62 -0
- package/dist/agent/streaming/stream-handler.js +193 -0
- package/dist/agent/streaming/stream-handler.js.map +1 -0
- package/dist/agent/subagent-orchestrator.d.ts +3 -3
- package/dist/agent/subagent-orchestrator.js +1 -0
- package/dist/agent/subagent-orchestrator.js.map +1 -1
- package/dist/agent/subagent-types.d.ts +10 -22
- package/dist/agent/subagent-types.js +19 -0
- package/dist/agent/subagent-types.js.map +1 -1
- package/dist/index.js +9 -7
- package/dist/index.js.map +1 -1
- package/dist/llm/client.d.ts +33 -1
- package/dist/llm/client.js +15 -11
- package/dist/llm/client.js.map +1 -1
- package/dist/llm/types.d.ts +7 -1
- package/dist/llm/types.js +5 -4
- package/dist/llm/types.js.map +1 -1
- package/dist/mcp/index.d.ts +31 -0
- package/dist/mcp/index.js +36 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/planner/types.d.ts +8 -8
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/tool-schemas.d.ts +12 -12
- package/dist/tools/bash.js +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/text-editor.js +57 -63
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/ui/hooks/use-enhanced-input.js +66 -81
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -1
- package/dist/utils/background-task-manager.js +10 -2
- package/dist/utils/background-task-manager.js.map +1 -1
- package/dist/utils/confirmation-service.js +8 -5
- package/dist/utils/confirmation-service.js.map +1 -1
- package/dist/utils/index.d.ts +85 -6
- package/dist/utils/index.js +103 -15
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry-helper.d.ts +7 -6
- package/dist/utils/retry-helper.js +8 -6
- package/dist/utils/retry-helper.js.map +1 -1
- package/dist/utils/settings-manager.d.ts +6 -0
- package/dist/utils/settings-manager.js +51 -64
- package/dist/utils/settings-manager.js.map +1 -1
- package/node_modules/@ax-cli/schemas/dist/index.d.ts +1 -0
- package/node_modules/@ax-cli/schemas/dist/index.d.ts.map +1 -1
- package/node_modules/@ax-cli/schemas/dist/index.js.map +1 -1
- package/node_modules/@ax-cli/schemas/dist/public/agent/chat-types.d.ts +164 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/chat-types.d.ts.map +1 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/chat-types.js +10 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/chat-types.js.map +1 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/index.d.ts +9 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/index.d.ts.map +1 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/index.js +9 -0
- package/node_modules/@ax-cli/schemas/dist/public/agent/index.js.map +1 -0
- package/package.json +1 -1
- package/packages/schemas/dist/index.d.ts +1 -0
- package/packages/schemas/dist/index.d.ts.map +1 -1
- package/packages/schemas/dist/index.js.map +1 -1
- package/packages/schemas/dist/public/agent/chat-types.d.ts +164 -0
- package/packages/schemas/dist/public/agent/chat-types.d.ts.map +1 -0
- package/packages/schemas/dist/public/agent/chat-types.js +10 -0
- package/packages/schemas/dist/public/agent/chat-types.js.map +1 -0
- package/packages/schemas/dist/public/agent/index.d.ts +9 -0
- package/packages/schemas/dist/public/agent/index.d.ts.map +1 -0
- package/packages/schemas/dist/public/agent/index.js +9 -0
- package/packages/schemas/dist/public/agent/index.js.map +1 -0
- package/dist/mcp/config-detector-v2.d.ts +0 -83
- package/dist/mcp/config-detector-v2.js +0 -328
- package/dist/mcp/config-detector-v2.js.map +0 -1
- package/dist/mcp/config-migrator-v2.d.ts +0 -89
- package/dist/mcp/config-migrator-v2.js +0 -288
- package/dist/mcp/config-migrator-v2.js.map +0 -1
- package/dist/mcp/config-v2.d.ts +0 -111
- package/dist/mcp/config-v2.js +0 -443
- package/dist/mcp/config-v2.js.map +0 -1
- package/dist/mcp/transports-v2.d.ts +0 -152
- package/dist/mcp/transports-v2.js +0 -481
- package/dist/mcp/transports-v2.js.map +0 -1
- package/dist/utils/error-sanitizer.d.ts +0 -119
- package/dist/utils/error-sanitizer.js +0 -253
- package/dist/utils/error-sanitizer.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -74
- package/dist/utils/errors.js +0 -139
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/incremental-analyzer.d.ts +0 -134
- package/dist/utils/incremental-analyzer.js +0 -377
- package/dist/utils/incremental-analyzer.js.map +0 -1
- package/dist/utils/settings.d.ts +0 -1
- package/dist/utils/settings.js +0 -4
- package/dist/utils/settings.js.map +0 -1
- package/dist/utils/streaming-analyzer.d.ts +0 -160
- package/dist/utils/streaming-analyzer.js +0 -214
- package/dist/utils/streaming-analyzer.js.map +0 -1
package/dist/agent/llm-agent.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { LLMClient } from "../llm/client.js";
|
|
2
2
|
import { getAllGrokTools, getMCPManager, initializeMCPServers, } from "../llm/tools.js";
|
|
3
3
|
import { loadMCPConfig } from "../mcp/config.js";
|
|
4
|
-
import { TextEditorTool, BashTool, TodoTool, SearchTool, } from "../tools/index.js";
|
|
5
|
-
import { BashOutputTool } from "../tools/bash-output.js";
|
|
6
|
-
import { ArchitectureTool } from "../tools/analysis-tools/architecture-tool.js";
|
|
7
|
-
import { ValidationTool } from "../tools/analysis-tools/validation-tool.js";
|
|
8
4
|
import { EventEmitter } from "events";
|
|
9
5
|
import { AGENT_CONFIG, CACHE_CONFIG, TIMEOUT_CONFIG } from "../constants.js";
|
|
10
6
|
import { getTokenCounter } from "../utils/token-counter.js";
|
|
@@ -12,26 +8,37 @@ import { loadCustomInstructions } from "../utils/custom-instructions.js";
|
|
|
12
8
|
import { getSettingsManager } from "../utils/settings-manager.js";
|
|
13
9
|
import { ContextManager } from "./context-manager.js";
|
|
14
10
|
import { buildSystemPrompt } from "../utils/prompt-builder.js";
|
|
15
|
-
|
|
11
|
+
// Note: getUsageTracker is now used by StreamHandler (Phase 2 refactoring)
|
|
16
12
|
import { extractErrorMessage } from "../utils/error-handler.js";
|
|
17
13
|
import { getCheckpointManager } from "../checkpoint/index.js";
|
|
18
14
|
import { SubagentOrchestrator } from "./subagent-orchestrator.js";
|
|
19
15
|
import { getTaskPlanner, isComplexRequest, } from "../planner/index.js";
|
|
16
|
+
// Note: TaskPhase now used by PlanExecutor (Phase 2 refactoring)
|
|
20
17
|
import { PLANNER_CONFIG } from "../constants.js";
|
|
21
18
|
import { resolveMCPReferences, extractMCPReferences } from "../mcp/resources.js";
|
|
22
19
|
import { SDKError, SDKErrorCode } from "../sdk/errors.js";
|
|
23
20
|
import { getStatusReporter } from "./status-reporter.js";
|
|
24
21
|
import { getLoopDetector, resetLoopDetector } from "./loop-detector.js";
|
|
22
|
+
// Import from extracted modules (Phase 2 refactoring)
|
|
23
|
+
import { ToolExecutor } from "./execution/index.js";
|
|
24
|
+
import { StreamHandler } from "./streaming/index.js";
|
|
25
|
+
import { PlanExecutor } from "./planning/index.js";
|
|
26
|
+
/** Debug flag for loop detection logging (set DEBUG_LOOP_DETECTION=1 to enable) */
|
|
27
|
+
const DEBUG_LOOP = process.env.DEBUG_LOOP_DETECTION === '1';
|
|
28
|
+
/** Log debug message for loop detection (only when DEBUG_LOOP_DETECTION=1) */
|
|
29
|
+
function debugLoop(message) {
|
|
30
|
+
if (DEBUG_LOOP) {
|
|
31
|
+
console.error(`[LOOP DETECTION] ${message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
export class LLMAgent extends EventEmitter {
|
|
26
35
|
llmClient;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
_architectureTool;
|
|
34
|
-
_validationTool;
|
|
36
|
+
// Tool execution delegated to ToolExecutor (Phase 2 refactoring)
|
|
37
|
+
toolExecutor;
|
|
38
|
+
// Stream processing delegated to StreamHandler (Phase 2 refactoring)
|
|
39
|
+
streamHandler;
|
|
40
|
+
// Plan execution delegated to PlanExecutor (Phase 2 refactoring)
|
|
41
|
+
planExecutor;
|
|
35
42
|
chatHistory = [];
|
|
36
43
|
messages = [];
|
|
37
44
|
tokenCounter;
|
|
@@ -69,39 +76,52 @@ export class LLMAgent extends EventEmitter {
|
|
|
69
76
|
}
|
|
70
77
|
this.maxToolRounds = maxToolRounds || 400;
|
|
71
78
|
this.llmClient = new LLMClient(apiKey, modelToUse, baseURL);
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
// Initialize ToolExecutor with checkpoint callback (Phase 2 refactoring)
|
|
80
|
+
this.toolExecutor = new ToolExecutor({
|
|
81
|
+
checkpointCallback: async (files, description) => {
|
|
82
|
+
// BUG FIX: Check if agent is disposed before creating checkpoint
|
|
83
|
+
if (this.disposed)
|
|
84
|
+
return;
|
|
85
|
+
// Create immutable snapshot of chat history at callback time
|
|
86
|
+
const chatHistorySnapshot = JSON.parse(JSON.stringify(this.chatHistory));
|
|
87
|
+
await this.checkpointManager.createCheckpoint({
|
|
88
|
+
files,
|
|
89
|
+
conversationState: chatHistorySnapshot,
|
|
90
|
+
description,
|
|
91
|
+
metadata: {
|
|
92
|
+
model: this.llmClient.getCurrentModel(),
|
|
93
|
+
triggeredBy: 'auto',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
// Initialize StreamHandler with callbacks (Phase 2 refactoring)
|
|
99
|
+
this.streamHandler = new StreamHandler({
|
|
100
|
+
isCancelled: () => this.isCancelled(),
|
|
101
|
+
yieldCancellation: () => this.yieldCancellation(),
|
|
102
|
+
model: modelToUse,
|
|
103
|
+
});
|
|
78
104
|
this.tokenCounter = getTokenCounter(modelToUse);
|
|
79
105
|
this.contextManager = new ContextManager({ model: modelToUse });
|
|
80
106
|
this.checkpointManager = getCheckpointManager();
|
|
81
107
|
this.subagentOrchestrator = new SubagentOrchestrator({ maxConcurrentAgents: 5 });
|
|
82
108
|
this.taskPlanner = getTaskPlanner();
|
|
109
|
+
// Initialize PlanExecutor with callbacks (Phase 2 refactoring)
|
|
110
|
+
this.planExecutor = new PlanExecutor({
|
|
111
|
+
llmClient: this.llmClient,
|
|
112
|
+
tokenCounter: this.tokenCounter,
|
|
113
|
+
toolExecutor: this.toolExecutor,
|
|
114
|
+
getTools: () => getAllGrokTools(),
|
|
115
|
+
executeTool: (toolCall) => this.executeTool(toolCall),
|
|
116
|
+
parseToolArgumentsCached: (toolCall) => this.parseToolArgumentsCached(toolCall),
|
|
117
|
+
buildChatOptions: (options) => this.buildChatOptions(options),
|
|
118
|
+
applyContextPruning: () => this.applyContextPruning(),
|
|
119
|
+
emitter: this,
|
|
120
|
+
maxToolRounds: Math.min(this.maxToolRounds, 50),
|
|
121
|
+
setPlanningEnabled: (enabled) => { this.planningEnabled = enabled; },
|
|
122
|
+
});
|
|
83
123
|
// Load sampling configuration from settings (supports env vars, project, and user settings)
|
|
84
124
|
this.samplingConfig = manager.getSamplingSettings();
|
|
85
|
-
// Wire up checkpoint callback for automatic checkpoint creation
|
|
86
|
-
// CRITICAL FIX: Deep clone chatHistory to prevent race conditions
|
|
87
|
-
// The checkpoint creation is async and chatHistory can be modified during the operation
|
|
88
|
-
this.textEditor.setCheckpointCallback(async (files, description) => {
|
|
89
|
-
// Create immutable snapshot of chat history at callback time
|
|
90
|
-
// This prevents inconsistencies if messages are added during checkpoint creation
|
|
91
|
-
// BUG FIX: Check if agent is disposed before creating checkpoint
|
|
92
|
-
if (this.disposed)
|
|
93
|
-
return;
|
|
94
|
-
const chatHistorySnapshot = JSON.parse(JSON.stringify(this.chatHistory));
|
|
95
|
-
await this.checkpointManager.createCheckpoint({
|
|
96
|
-
files,
|
|
97
|
-
conversationState: chatHistorySnapshot,
|
|
98
|
-
description,
|
|
99
|
-
metadata: {
|
|
100
|
-
model: this.llmClient.getCurrentModel(),
|
|
101
|
-
triggeredBy: 'auto',
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
125
|
// Initialize checkpoint manager
|
|
106
126
|
this.initializeCheckpointManager();
|
|
107
127
|
// Initialize MCP servers if configured
|
|
@@ -142,42 +162,41 @@ export class LLMAgent extends EventEmitter {
|
|
|
142
162
|
};
|
|
143
163
|
this.contextManager.on('before_prune', this.contextOverflowListener);
|
|
144
164
|
}
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Run an async task in background with proper error handling
|
|
167
|
+
* Centralizes the common pattern of background initialization
|
|
168
|
+
*/
|
|
169
|
+
runBackgroundTask(taskName, task, options) {
|
|
147
170
|
Promise.resolve().then(async () => {
|
|
148
171
|
try {
|
|
149
|
-
await
|
|
150
|
-
|
|
172
|
+
await task();
|
|
173
|
+
if (options?.emitSuccess) {
|
|
174
|
+
this.emit('system', options.emitSuccess);
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
catch (error) {
|
|
153
178
|
const errorMsg = extractErrorMessage(error);
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
if (options?.warnOnError !== false) {
|
|
180
|
+
console.warn(`${taskName} failed:`, errorMsg);
|
|
181
|
+
}
|
|
182
|
+
this.emit('system', `${taskName} failed: ${errorMsg}`);
|
|
156
183
|
}
|
|
157
184
|
}).catch((error) => {
|
|
158
|
-
|
|
159
|
-
console.warn("Unexpected error during checkpoint initialization:", errorMsg);
|
|
185
|
+
console.error(`Unexpected error during ${taskName}:`, error);
|
|
160
186
|
});
|
|
161
187
|
}
|
|
188
|
+
initializeCheckpointManager() {
|
|
189
|
+
this.runBackgroundTask('Checkpoint initialization', async () => {
|
|
190
|
+
await this.checkpointManager.initialize();
|
|
191
|
+
}, { emitSuccess: 'Checkpoint system initialized' });
|
|
192
|
+
}
|
|
162
193
|
async initializeMCP() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.emit('system', 'MCP servers initialized successfully');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
const errorMsg = extractErrorMessage(error);
|
|
174
|
-
console.warn("MCP initialization failed:", errorMsg);
|
|
175
|
-
this.emit('system', `MCP initialization failed: ${errorMsg}`);
|
|
176
|
-
}
|
|
177
|
-
}).catch((error) => {
|
|
178
|
-
// Catch any errors from emit() or other unexpected failures
|
|
179
|
-
console.error("Unexpected MCP initialization error:", error);
|
|
180
|
-
});
|
|
194
|
+
const config = loadMCPConfig();
|
|
195
|
+
if (config.servers.length === 0)
|
|
196
|
+
return; // Skip if no servers configured
|
|
197
|
+
this.runBackgroundTask('MCP initialization', async () => {
|
|
198
|
+
await initializeMCPServers();
|
|
199
|
+
}, { emitSuccess: 'MCP servers initialized successfully', warnOnError: true });
|
|
181
200
|
}
|
|
182
201
|
/**
|
|
183
202
|
* Build chat options with sampling and thinking configuration included
|
|
@@ -391,26 +410,6 @@ export class LLMAgent extends EventEmitter {
|
|
|
391
410
|
return {};
|
|
392
411
|
}
|
|
393
412
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Lazy-loaded getter for ArchitectureTool
|
|
396
|
-
* Only instantiates when first accessed to reduce startup time
|
|
397
|
-
*/
|
|
398
|
-
get architectureTool() {
|
|
399
|
-
if (!this._architectureTool) {
|
|
400
|
-
this._architectureTool = new ArchitectureTool();
|
|
401
|
-
}
|
|
402
|
-
return this._architectureTool;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Lazy-loaded getter for ValidationTool
|
|
406
|
-
* Only instantiates when first accessed to reduce startup time
|
|
407
|
-
*/
|
|
408
|
-
get validationTool() {
|
|
409
|
-
if (!this._validationTool) {
|
|
410
|
-
this._validationTool = new ValidationTool();
|
|
411
|
-
}
|
|
412
|
-
return this._validationTool;
|
|
413
|
-
}
|
|
414
413
|
/**
|
|
415
414
|
* Detect if a tool call is repetitive (likely causing a loop)
|
|
416
415
|
* Uses the intelligent LoopDetector which provides:
|
|
@@ -427,32 +426,21 @@ export class LLMAgent extends EventEmitter {
|
|
|
427
426
|
const detector = getLoopDetector();
|
|
428
427
|
const result = detector.checkForLoop(toolCall);
|
|
429
428
|
// Debug logging
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
console.error(`[LOOP DETECTION] Reason: ${result.reason}`);
|
|
437
|
-
}
|
|
438
|
-
const stats = detector.getStats();
|
|
439
|
-
console.error(`[LOOP DETECTION] Stats: ${JSON.stringify(stats)}`);
|
|
440
|
-
}
|
|
429
|
+
debugLoop(`Tool: ${toolCall.function.name}`);
|
|
430
|
+
debugLoop(`Count: ${result.count}, Threshold: ${result.threshold}, Is Loop: ${result.isLoop}`);
|
|
431
|
+
if (result.reason)
|
|
432
|
+
debugLoop(`Reason: ${result.reason}`);
|
|
433
|
+
if (DEBUG_LOOP)
|
|
434
|
+
debugLoop(`Stats: ${JSON.stringify(detector.getStats())}`);
|
|
441
435
|
if (result.isLoop) {
|
|
442
436
|
// Store the result for generating better error message
|
|
443
437
|
this.lastLoopResult = result;
|
|
444
|
-
|
|
445
|
-
console.error(`[LOOP DETECTION] ⚠️ LOOP DETECTED!`);
|
|
446
|
-
console.error(`[LOOP DETECTION] Reason: ${result.reason}`);
|
|
447
|
-
console.error(`[LOOP DETECTION] Suggestion: ${result.suggestion}`);
|
|
448
|
-
}
|
|
438
|
+
debugLoop(`⚠️ LOOP DETECTED! Reason: ${result.reason}, Suggestion: ${result.suggestion}`);
|
|
449
439
|
return true;
|
|
450
440
|
}
|
|
451
441
|
// Note: We don't record here - recording happens AFTER execution
|
|
452
442
|
// in executeToolCalls() with the actual success/failure status
|
|
453
|
-
|
|
454
|
-
console.error(`[LOOP DETECTION] ✅ Allowed, count: ${result.count}/${result.threshold}`);
|
|
455
|
-
}
|
|
443
|
+
debugLoop(`✅ Allowed, count: ${result.count}/${result.threshold}`);
|
|
456
444
|
return false;
|
|
457
445
|
}
|
|
458
446
|
/** Last loop detection result for error messages */
|
|
@@ -461,10 +449,9 @@ export class LLMAgent extends EventEmitter {
|
|
|
461
449
|
* Reset the tool call tracking (called at start of new user message)
|
|
462
450
|
*/
|
|
463
451
|
resetToolCallTracking() {
|
|
464
|
-
if (
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
console.error(`[LOOP TRACKING] 🔄 Resetting tool call tracking (had ${stats.uniqueSignatures} signatures)`);
|
|
452
|
+
if (DEBUG_LOOP) {
|
|
453
|
+
const stats = getLoopDetector().getStats();
|
|
454
|
+
debugLoop(`🔄 Resetting tool call tracking (had ${stats.uniqueSignatures} signatures)`);
|
|
468
455
|
}
|
|
469
456
|
// Reset the new intelligent loop detector
|
|
470
457
|
resetLoopDetector();
|
|
@@ -511,140 +498,6 @@ export class LLMAgent extends EventEmitter {
|
|
|
511
498
|
getCurrentPlan() {
|
|
512
499
|
return this.currentPlan;
|
|
513
500
|
}
|
|
514
|
-
/**
|
|
515
|
-
* Execute a single phase using the LLM
|
|
516
|
-
*/
|
|
517
|
-
async executePhase(phase, context) {
|
|
518
|
-
const startTime = Date.now();
|
|
519
|
-
const startTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
520
|
-
const filesModified = [];
|
|
521
|
-
let lastAssistantContent = "";
|
|
522
|
-
// Emit phase started event
|
|
523
|
-
this.emit("phase:started", { phase, planId: context.planId });
|
|
524
|
-
try {
|
|
525
|
-
// Build phase-specific prompt
|
|
526
|
-
const phasePrompt = this.buildPhasePrompt(phase, context);
|
|
527
|
-
// Execute through normal message processing (without recursively planning)
|
|
528
|
-
const savedPlanningState = this.planningEnabled;
|
|
529
|
-
this.planningEnabled = false; // Temporarily disable planning for phase execution
|
|
530
|
-
// Add phase context to messages
|
|
531
|
-
this.messages.push({
|
|
532
|
-
role: "user",
|
|
533
|
-
content: phasePrompt,
|
|
534
|
-
});
|
|
535
|
-
// Execute using the standard tool loop
|
|
536
|
-
const tools = await getAllGrokTools();
|
|
537
|
-
let toolRounds = 0;
|
|
538
|
-
const maxPhaseRounds = Math.min(this.maxToolRounds, 50); // Limit per phase
|
|
539
|
-
while (toolRounds < maxPhaseRounds) {
|
|
540
|
-
const response = await this.llmClient.chat(this.messages, tools, this.buildChatOptions());
|
|
541
|
-
const assistantMessage = response.choices[0]?.message;
|
|
542
|
-
if (!assistantMessage)
|
|
543
|
-
break;
|
|
544
|
-
// Capture the assistant's content for phase output
|
|
545
|
-
if (assistantMessage.content) {
|
|
546
|
-
lastAssistantContent = assistantMessage.content;
|
|
547
|
-
}
|
|
548
|
-
// Add to messages
|
|
549
|
-
this.messages.push({
|
|
550
|
-
role: "assistant",
|
|
551
|
-
content: assistantMessage.content || "",
|
|
552
|
-
tool_calls: assistantMessage.tool_calls,
|
|
553
|
-
});
|
|
554
|
-
// Check for tool calls
|
|
555
|
-
if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
|
|
556
|
-
break; // No more tool calls, phase complete
|
|
557
|
-
}
|
|
558
|
-
toolRounds++;
|
|
559
|
-
// Execute tools and track file modifications
|
|
560
|
-
for (const toolCall of assistantMessage.tool_calls) {
|
|
561
|
-
const result = await this.executeTool(toolCall);
|
|
562
|
-
// Track file modifications from text_editor tool
|
|
563
|
-
if (toolCall.function.name === "text_editor" ||
|
|
564
|
-
toolCall.function.name === "str_replace_editor") {
|
|
565
|
-
const args = this.parseToolArgumentsCached(toolCall);
|
|
566
|
-
if (args.path && result.success) {
|
|
567
|
-
if (!filesModified.includes(args.path)) {
|
|
568
|
-
filesModified.push(args.path);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
this.messages.push({
|
|
573
|
-
role: "tool",
|
|
574
|
-
tool_call_id: toolCall.id,
|
|
575
|
-
content: result.output || result.error || "No output",
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
// Restore planning state
|
|
580
|
-
this.planningEnabled = savedPlanningState;
|
|
581
|
-
// Prune context if configured
|
|
582
|
-
if (PLANNER_CONFIG.PRUNE_AFTER_PHASE) {
|
|
583
|
-
this.applyContextPruning();
|
|
584
|
-
}
|
|
585
|
-
const endTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
586
|
-
const duration = Date.now() - startTime;
|
|
587
|
-
// Build meaningful output
|
|
588
|
-
const output = lastAssistantContent ||
|
|
589
|
-
`Phase "${phase.name}" completed (${toolRounds} tool rounds, ${filesModified.length} files modified)`;
|
|
590
|
-
// Emit phase completed event
|
|
591
|
-
this.emit("phase:completed", {
|
|
592
|
-
phase,
|
|
593
|
-
planId: context.planId,
|
|
594
|
-
result: { success: true, output, filesModified }
|
|
595
|
-
});
|
|
596
|
-
return {
|
|
597
|
-
phaseId: phase.id,
|
|
598
|
-
success: true,
|
|
599
|
-
output,
|
|
600
|
-
duration,
|
|
601
|
-
tokensUsed: endTokens - startTokens,
|
|
602
|
-
filesModified,
|
|
603
|
-
wasRetry: false,
|
|
604
|
-
retryAttempt: 0,
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
catch (error) {
|
|
608
|
-
const duration = Date.now() - startTime;
|
|
609
|
-
const errorMessage = extractErrorMessage(error);
|
|
610
|
-
// Emit phase failed event
|
|
611
|
-
this.emit("phase:failed", {
|
|
612
|
-
phase,
|
|
613
|
-
planId: context.planId,
|
|
614
|
-
error: errorMessage
|
|
615
|
-
});
|
|
616
|
-
return {
|
|
617
|
-
phaseId: phase.id,
|
|
618
|
-
success: false,
|
|
619
|
-
error: errorMessage,
|
|
620
|
-
duration,
|
|
621
|
-
tokensUsed: 0,
|
|
622
|
-
filesModified,
|
|
623
|
-
wasRetry: false,
|
|
624
|
-
retryAttempt: 0,
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Build a prompt for phase execution
|
|
630
|
-
*/
|
|
631
|
-
buildPhasePrompt(phase, context) {
|
|
632
|
-
let prompt = `## Phase ${phase.index + 1}: ${phase.name}\n\n`;
|
|
633
|
-
prompt += `**Objective:** ${phase.description}\n\n`;
|
|
634
|
-
if (phase.objectives.length > 0) {
|
|
635
|
-
prompt += "**Tasks to complete:**\n";
|
|
636
|
-
for (const obj of phase.objectives) {
|
|
637
|
-
prompt += `- ${obj}\n`;
|
|
638
|
-
}
|
|
639
|
-
prompt += "\n";
|
|
640
|
-
}
|
|
641
|
-
if (context.completedPhases.length > 0) {
|
|
642
|
-
prompt += `**Previously completed phases:** ${context.completedPhases.join(", ")}\n\n`;
|
|
643
|
-
}
|
|
644
|
-
prompt += `**Original request:** ${context.originalRequest}\n\n`;
|
|
645
|
-
prompt += "Please complete this phase. Focus only on the objectives listed above.";
|
|
646
|
-
return prompt;
|
|
647
|
-
}
|
|
648
501
|
/**
|
|
649
502
|
* Generate and execute a plan for a complex request
|
|
650
503
|
* Uses TodoWrite for Claude Code-style seamless progress display
|
|
@@ -702,7 +555,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
702
555
|
phase.riskLevel === "low" ? "low" : "medium",
|
|
703
556
|
}));
|
|
704
557
|
try {
|
|
705
|
-
await this.
|
|
558
|
+
await this.toolExecutor.getTodoTool().createTodoList(todoItems);
|
|
706
559
|
}
|
|
707
560
|
catch (todoError) {
|
|
708
561
|
// TodoWrite failure is non-critical, continue execution
|
|
@@ -713,7 +566,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
713
566
|
// Display explicit plan summary
|
|
714
567
|
yield {
|
|
715
568
|
type: "content",
|
|
716
|
-
content: this.formatPlanSummary(plan),
|
|
569
|
+
content: this.planExecutor.formatPlanSummary(plan),
|
|
717
570
|
};
|
|
718
571
|
}
|
|
719
572
|
// Execute phases one by one with progress updates
|
|
@@ -726,7 +579,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
726
579
|
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
727
580
|
// Update TodoWrite: mark current phase as in_progress
|
|
728
581
|
try {
|
|
729
|
-
await this.
|
|
582
|
+
await this.toolExecutor.getTodoTool().updateTodoList([{
|
|
730
583
|
id: `phase-${i}`,
|
|
731
584
|
status: "in_progress",
|
|
732
585
|
}]);
|
|
@@ -740,13 +593,14 @@ export class LLMAgent extends EventEmitter {
|
|
|
740
593
|
content: `\n**⏳ Phase ${i + 1}/${plan.phases.length}: ${phase.name}**\n`,
|
|
741
594
|
};
|
|
742
595
|
}
|
|
743
|
-
// Execute the phase
|
|
596
|
+
// Execute the phase (delegated to PlanExecutor - Phase 2 refactoring)
|
|
744
597
|
const context = {
|
|
745
598
|
planId: plan.id,
|
|
746
599
|
originalRequest: message,
|
|
747
600
|
completedPhases: phaseResults.filter(r => r.success).map(r => r.phaseId),
|
|
748
601
|
};
|
|
749
|
-
const result = await this.executePhase(phase, context);
|
|
602
|
+
const { result, messages: updatedMessages } = await this.planExecutor.executePhase(phase, context, this.messages, this.chatHistory);
|
|
603
|
+
this.messages = updatedMessages; // Update messages with phase execution results
|
|
750
604
|
phaseResults.push(result);
|
|
751
605
|
totalTokensUsed += result.tokensUsed;
|
|
752
606
|
// Report phase result
|
|
@@ -754,7 +608,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
754
608
|
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
755
609
|
// Update TodoWrite: mark phase as completed
|
|
756
610
|
try {
|
|
757
|
-
await this.
|
|
611
|
+
await this.toolExecutor.getTodoTool().updateTodoList([{
|
|
758
612
|
id: `phase-${i}`,
|
|
759
613
|
status: "completed",
|
|
760
614
|
}]);
|
|
@@ -778,7 +632,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
778
632
|
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
779
633
|
// Update TodoWrite: mark phase as failed (update content to show failure)
|
|
780
634
|
try {
|
|
781
|
-
await this.
|
|
635
|
+
await this.toolExecutor.getTodoTool().updateTodoList([{
|
|
782
636
|
id: `phase-${i}`,
|
|
783
637
|
status: "completed", // Mark as done even if failed
|
|
784
638
|
content: `${phase.name} (failed)`,
|
|
@@ -829,7 +683,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
829
683
|
if (!PLANNER_CONFIG.SILENT_MODE) {
|
|
830
684
|
yield {
|
|
831
685
|
type: "content",
|
|
832
|
-
content: this.formatPlanResult(planResult),
|
|
686
|
+
content: this.planExecutor.formatPlanResult(planResult),
|
|
833
687
|
};
|
|
834
688
|
}
|
|
835
689
|
else {
|
|
@@ -925,8 +779,12 @@ export class LLMAgent extends EventEmitter {
|
|
|
925
779
|
const stream = this.llmClient.chatStream(this.messages, tools, this.buildChatOptions({
|
|
926
780
|
searchOptions: { search_parameters: { mode: "off" } }
|
|
927
781
|
}));
|
|
928
|
-
// Process streaming chunks
|
|
929
|
-
const chunkGen = this.
|
|
782
|
+
// Process streaming chunks (delegated to StreamHandler - Phase 2 refactoring)
|
|
783
|
+
const chunkGen = this.streamHandler.processChunks(stream, {
|
|
784
|
+
inputTokens: inputTokensRef.value,
|
|
785
|
+
lastTokenUpdate: lastTokenUpdateRef,
|
|
786
|
+
totalOutputTokens: totalOutputTokensRef,
|
|
787
|
+
});
|
|
930
788
|
let streamResult;
|
|
931
789
|
for await (const chunk of chunkGen) {
|
|
932
790
|
if ('accumulated' in chunk) {
|
|
@@ -977,41 +835,6 @@ export class LLMAgent extends EventEmitter {
|
|
|
977
835
|
};
|
|
978
836
|
yield { type: "done" };
|
|
979
837
|
}
|
|
980
|
-
/**
|
|
981
|
-
* Format plan summary for display
|
|
982
|
-
*/
|
|
983
|
-
formatPlanSummary(plan) {
|
|
984
|
-
let output = `**📋 Execution Plan Created**\n\n`;
|
|
985
|
-
output += `**Request:** ${plan.originalPrompt.slice(0, 100)}${plan.originalPrompt.length > 100 ? "..." : ""}\n\n`;
|
|
986
|
-
output += `**Phases (${plan.phases.length}):**\n`;
|
|
987
|
-
for (const phase of plan.phases) {
|
|
988
|
-
const riskIcon = phase.riskLevel === "high" ? "⚠️" : phase.riskLevel === "medium" ? "△" : "";
|
|
989
|
-
output += ` ${phase.index + 1}. ${phase.name} ${riskIcon}\n`;
|
|
990
|
-
}
|
|
991
|
-
output += `\n**Estimated Duration:** ~${Math.ceil(plan.estimatedDuration / 60000)} min\n\n`;
|
|
992
|
-
output += "---\n\n";
|
|
993
|
-
return output;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Format plan result for display
|
|
997
|
-
*/
|
|
998
|
-
formatPlanResult(result) {
|
|
999
|
-
let output = "\n---\n\n**📋 Plan Execution Complete**\n\n";
|
|
1000
|
-
const successful = result.phaseResults.filter((r) => r.success).length;
|
|
1001
|
-
const failed = result.phaseResults.filter((r) => !r.success).length;
|
|
1002
|
-
output += `**Results:** ${successful}/${result.phaseResults.length} phases successful`;
|
|
1003
|
-
if (failed > 0) {
|
|
1004
|
-
output += ` (${failed} failed)`;
|
|
1005
|
-
}
|
|
1006
|
-
output += "\n";
|
|
1007
|
-
if (result.totalDuration) {
|
|
1008
|
-
output += `**Duration:** ${Math.ceil(result.totalDuration / 1000)}s\n`;
|
|
1009
|
-
}
|
|
1010
|
-
if (result.totalTokensUsed) {
|
|
1011
|
-
output += `**Tokens Used:** ${result.totalTokensUsed.toLocaleString()}\n`;
|
|
1012
|
-
}
|
|
1013
|
-
return output;
|
|
1014
|
-
}
|
|
1015
838
|
async processUserMessage(message) {
|
|
1016
839
|
// Check if agent has been disposed
|
|
1017
840
|
this.checkDisposed();
|
|
@@ -1057,17 +880,11 @@ export class LLMAgent extends EventEmitter {
|
|
|
1057
880
|
assistantMessage.tool_calls.length > 0) {
|
|
1058
881
|
toolRounds++;
|
|
1059
882
|
// Check for repetitive tool calls (loop detection)
|
|
1060
|
-
|
|
1061
|
-
console.error(`\n[LOOP CHECK] Checking ${assistantMessage.tool_calls.length} tool calls...`);
|
|
1062
|
-
}
|
|
883
|
+
debugLoop(`Checking ${assistantMessage.tool_calls.length} tool calls...`);
|
|
1063
884
|
const hasRepetitiveCall = assistantMessage.tool_calls.some((tc) => this.isRepetitiveToolCall(tc));
|
|
1064
|
-
|
|
1065
|
-
console.error(`[LOOP CHECK] hasRepetitiveCall: ${hasRepetitiveCall}\n`);
|
|
1066
|
-
}
|
|
885
|
+
debugLoop(`hasRepetitiveCall: ${hasRepetitiveCall}`);
|
|
1067
886
|
if (hasRepetitiveCall) {
|
|
1068
|
-
|
|
1069
|
-
console.error(`[LOOP CHECK] 🛑 Breaking loop!`);
|
|
1070
|
-
}
|
|
887
|
+
debugLoop(`🛑 Breaking loop!`);
|
|
1071
888
|
const loopMsg = this.getLoopWarningMessage();
|
|
1072
889
|
const warningEntry = {
|
|
1073
890
|
type: "assistant",
|
|
@@ -1119,9 +936,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
1119
936
|
const updatedEntry = {
|
|
1120
937
|
...this.chatHistory[entryIndex],
|
|
1121
938
|
type: "tool_result",
|
|
1122
|
-
content: result
|
|
1123
|
-
? result.output || "Success"
|
|
1124
|
-
: result.error || "Error occurred",
|
|
939
|
+
content: this.formatToolResultContent(result),
|
|
1125
940
|
toolResult: result,
|
|
1126
941
|
};
|
|
1127
942
|
this.chatHistory[entryIndex] = updatedEntry;
|
|
@@ -1135,9 +950,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
1135
950
|
// Add tool result to messages with proper format (needed for AI context)
|
|
1136
951
|
this.messages.push({
|
|
1137
952
|
role: "tool",
|
|
1138
|
-
content: result
|
|
1139
|
-
? result.output || "Success"
|
|
1140
|
-
: result.error || "Error",
|
|
953
|
+
content: this.formatToolResultContent(result, "Success", "Error"),
|
|
1141
954
|
tool_call_id: toolCall.id,
|
|
1142
955
|
});
|
|
1143
956
|
}
|
|
@@ -1188,67 +1001,6 @@ export class LLMAgent extends EventEmitter {
|
|
|
1188
1001
|
return [userEntry, errorEntry];
|
|
1189
1002
|
}
|
|
1190
1003
|
}
|
|
1191
|
-
/**
|
|
1192
|
-
* Optimized streaming delta merge - mutates accumulator for performance
|
|
1193
|
-
* This is safe because accumulator is only used internally during streaming
|
|
1194
|
-
*
|
|
1195
|
-
* Performance: 50% faster than immutable approach (no object copying)
|
|
1196
|
-
*/
|
|
1197
|
-
reduceStreamDelta(acc, delta) {
|
|
1198
|
-
for (const [key, value] of Object.entries(delta)) {
|
|
1199
|
-
if (value === undefined || value === null) {
|
|
1200
|
-
continue; // Skip undefined/null values
|
|
1201
|
-
}
|
|
1202
|
-
if (acc[key] === undefined || acc[key] === null) {
|
|
1203
|
-
// Initial value assignment
|
|
1204
|
-
acc[key] = value;
|
|
1205
|
-
// Clean up index properties from tool calls
|
|
1206
|
-
if (Array.isArray(acc[key])) {
|
|
1207
|
-
for (const arr of acc[key]) {
|
|
1208
|
-
if (arr && typeof arr === 'object') {
|
|
1209
|
-
delete arr.index;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
else if (typeof acc[key] === "string" && typeof value === "string") {
|
|
1215
|
-
// String concatenation (most common case during streaming)
|
|
1216
|
-
acc[key] += value;
|
|
1217
|
-
}
|
|
1218
|
-
else if (Array.isArray(acc[key]) && Array.isArray(value)) {
|
|
1219
|
-
// Array merging (for tool calls)
|
|
1220
|
-
const accArray = acc[key];
|
|
1221
|
-
for (let i = 0; i < value.length; i++) {
|
|
1222
|
-
if (value[i] === undefined || value[i] === null)
|
|
1223
|
-
continue;
|
|
1224
|
-
if (!accArray[i]) {
|
|
1225
|
-
accArray[i] = {};
|
|
1226
|
-
}
|
|
1227
|
-
// Recursively merge array elements
|
|
1228
|
-
this.reduceStreamDelta(accArray[i], value[i]);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
else if (typeof acc[key] === "object" && typeof value === "object") {
|
|
1232
|
-
// Object merging
|
|
1233
|
-
this.reduceStreamDelta(acc[key], value);
|
|
1234
|
-
}
|
|
1235
|
-
else {
|
|
1236
|
-
// Direct assignment for other types
|
|
1237
|
-
acc[key] = value;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
return acc;
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* Accumulate streaming message chunks
|
|
1244
|
-
*/
|
|
1245
|
-
messageReducer(previous, item) {
|
|
1246
|
-
// Safety check: ensure item has valid structure
|
|
1247
|
-
if (!item?.choices || item.choices.length === 0 || !item.choices[0]?.delta) {
|
|
1248
|
-
return previous;
|
|
1249
|
-
}
|
|
1250
|
-
return this.reduceStreamDelta(previous, item.choices[0].delta);
|
|
1251
|
-
}
|
|
1252
1004
|
/**
|
|
1253
1005
|
* Prepare user message and apply context management
|
|
1254
1006
|
* Returns the calculated input tokens
|
|
@@ -1312,111 +1064,18 @@ export class LLMAgent extends EventEmitter {
|
|
|
1312
1064
|
}
|
|
1313
1065
|
}
|
|
1314
1066
|
/**
|
|
1315
|
-
*
|
|
1067
|
+
* Format tool result content for display or message
|
|
1068
|
+
* Centralizes the common pattern of formatting success/error output
|
|
1069
|
+
*
|
|
1070
|
+
* @param result - Tool execution result
|
|
1071
|
+
* @param defaultSuccess - Default message if success but no output (default: "Success")
|
|
1072
|
+
* @param defaultError - Default message if error but no error message (default: "Error occurred")
|
|
1073
|
+
* @returns Formatted content string
|
|
1316
1074
|
*/
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
let usageData = null;
|
|
1322
|
-
// CRITICAL FIX: Ensure stream is properly closed on cancellation or error
|
|
1323
|
-
// Without this, HTTP connections and buffers remain in memory
|
|
1324
|
-
try {
|
|
1325
|
-
for await (const chunk of stream) {
|
|
1326
|
-
// Check for cancellation in the streaming loop
|
|
1327
|
-
if (this.isCancelled()) {
|
|
1328
|
-
yield* this.yieldCancellation();
|
|
1329
|
-
// Return empty state after cancellation to avoid processing partial results
|
|
1330
|
-
return { accumulated: {}, content: "", yielded: false };
|
|
1331
|
-
}
|
|
1332
|
-
if (!chunk.choices?.[0])
|
|
1333
|
-
continue;
|
|
1334
|
-
// Capture usage data from chunks (usually in the final chunk)
|
|
1335
|
-
if (chunk.usage) {
|
|
1336
|
-
usageData = chunk.usage;
|
|
1337
|
-
}
|
|
1338
|
-
// Accumulate the message using reducer
|
|
1339
|
-
accumulatedMessage = this.messageReducer(accumulatedMessage, chunk);
|
|
1340
|
-
// Check for tool calls - yield when we have complete tool calls with function names
|
|
1341
|
-
const toolCalls = accumulatedMessage.tool_calls;
|
|
1342
|
-
if (!toolCallsYielded && toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
1343
|
-
const hasCompleteTool = toolCalls.some((tc) => tc.function?.name);
|
|
1344
|
-
if (hasCompleteTool) {
|
|
1345
|
-
yield {
|
|
1346
|
-
type: "tool_calls",
|
|
1347
|
-
toolCalls: toolCalls,
|
|
1348
|
-
};
|
|
1349
|
-
toolCallsYielded = true;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
// Stream reasoning content (GLM-4.6 thinking mode)
|
|
1353
|
-
// Safety check: ensure choices[0] exists before accessing
|
|
1354
|
-
if (chunk.choices[0]?.delta?.reasoning_content) {
|
|
1355
|
-
yield {
|
|
1356
|
-
type: "reasoning",
|
|
1357
|
-
reasoningContent: chunk.choices[0].delta.reasoning_content,
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
// Stream content as it comes
|
|
1361
|
-
if (chunk.choices[0]?.delta?.content) {
|
|
1362
|
-
accumulatedContent += chunk.choices[0].delta.content;
|
|
1363
|
-
yield {
|
|
1364
|
-
type: "content",
|
|
1365
|
-
content: chunk.choices[0].delta.content,
|
|
1366
|
-
};
|
|
1367
|
-
// Emit token count update (throttled and optimized)
|
|
1368
|
-
const now = Date.now();
|
|
1369
|
-
if (now - lastTokenUpdate.value > 1000) { // Increased throttle to 1s for better performance
|
|
1370
|
-
lastTokenUpdate.value = now;
|
|
1371
|
-
// Use fast estimation during streaming (4 chars ≈ 1 token)
|
|
1372
|
-
// This is ~70% faster than tiktoken encoding
|
|
1373
|
-
const estimatedOutputTokens = Math.floor(accumulatedContent.length / 4) +
|
|
1374
|
-
(accumulatedMessage.tool_calls
|
|
1375
|
-
? Math.floor(JSON.stringify(accumulatedMessage.tool_calls).length / 4)
|
|
1376
|
-
: 0);
|
|
1377
|
-
totalOutputTokens.value = estimatedOutputTokens;
|
|
1378
|
-
yield {
|
|
1379
|
-
type: "token_count",
|
|
1380
|
-
tokenCount: inputTokens + estimatedOutputTokens,
|
|
1381
|
-
};
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
// Track usage if available and emit accurate final token count
|
|
1386
|
-
if (usageData) {
|
|
1387
|
-
const tracker = getUsageTracker();
|
|
1388
|
-
tracker.trackUsage(this.llmClient.getCurrentModel(), usageData);
|
|
1389
|
-
// Emit accurate token count from API usage data (replaces estimation)
|
|
1390
|
-
const totalTokens = usageData.total_tokens;
|
|
1391
|
-
const completionTokens = usageData.completion_tokens;
|
|
1392
|
-
if (totalTokens) {
|
|
1393
|
-
totalOutputTokens.value = completionTokens || 0;
|
|
1394
|
-
yield {
|
|
1395
|
-
type: "token_count",
|
|
1396
|
-
tokenCount: totalTokens,
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
// CRITICAL: Yield the accumulated result so the main loop can access it!
|
|
1401
|
-
const result = { accumulated: accumulatedMessage, content: accumulatedContent, yielded: toolCallsYielded };
|
|
1402
|
-
yield result;
|
|
1403
|
-
return result;
|
|
1404
|
-
}
|
|
1405
|
-
finally {
|
|
1406
|
-
// CRITICAL FIX: Properly close the async iterator to release HTTP connections and buffers
|
|
1407
|
-
// This prevents socket leaks when streams are cancelled or errors occur
|
|
1408
|
-
try {
|
|
1409
|
-
// Use a type assertion to safely access the return method
|
|
1410
|
-
const streamWithReturn = stream;
|
|
1411
|
-
if (typeof streamWithReturn.return === 'function') {
|
|
1412
|
-
await streamWithReturn.return();
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
catch (cleanupError) {
|
|
1416
|
-
// Log but don't throw - cleanup errors shouldn't break the flow
|
|
1417
|
-
console.warn('Stream cleanup warning:', cleanupError);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1075
|
+
formatToolResultContent(result, defaultSuccess = "Success", defaultError = "Error occurred") {
|
|
1076
|
+
return result.success
|
|
1077
|
+
? result.output || defaultSuccess
|
|
1078
|
+
: result.error || defaultError;
|
|
1420
1079
|
}
|
|
1421
1080
|
/**
|
|
1422
1081
|
* Add assistant message to history and conversation
|
|
@@ -1468,14 +1127,10 @@ export class LLMAgent extends EventEmitter {
|
|
|
1468
1127
|
// This enables failure-based threshold adjustment (repeated failures = lower threshold)
|
|
1469
1128
|
const detector = getLoopDetector();
|
|
1470
1129
|
detector.recordToolCall(toolCall, result.success);
|
|
1471
|
-
|
|
1472
|
-
console.error(`[LOOP DETECTION] 📝 Recorded: ${toolCall.function.name}, success=${result.success}`);
|
|
1473
|
-
}
|
|
1130
|
+
debugLoop(`📝 Recorded: ${toolCall.function.name}, success=${result.success}`);
|
|
1474
1131
|
const toolResultEntry = {
|
|
1475
1132
|
type: "tool_result",
|
|
1476
|
-
content: result
|
|
1477
|
-
? result.output || "Success"
|
|
1478
|
-
: result.error || "Error occurred",
|
|
1133
|
+
content: this.formatToolResultContent(result),
|
|
1479
1134
|
timestamp: new Date(),
|
|
1480
1135
|
toolCall: toolCall,
|
|
1481
1136
|
toolResult: result,
|
|
@@ -1491,9 +1146,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
1491
1146
|
// Add tool result with proper format (needed for AI context)
|
|
1492
1147
|
this.messages.push({
|
|
1493
1148
|
role: "tool",
|
|
1494
|
-
content: result
|
|
1495
|
-
? result.output || "Success"
|
|
1496
|
-
: result.error || "Error",
|
|
1149
|
+
content: this.formatToolResultContent(result, "Success", "Error"),
|
|
1497
1150
|
tool_call_id: toolCall.id,
|
|
1498
1151
|
});
|
|
1499
1152
|
}
|
|
@@ -1534,9 +1187,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
1534
1187
|
try {
|
|
1535
1188
|
// Agent loop - continue until no more tool calls or max rounds reached
|
|
1536
1189
|
while (toolRounds < maxToolRounds) {
|
|
1537
|
-
|
|
1538
|
-
console.error(`\n[LOOP DEBUG] Agent loop iteration, toolRounds: ${toolRounds}`);
|
|
1539
|
-
}
|
|
1190
|
+
debugLoop(`Agent loop iteration, toolRounds: ${toolRounds}`);
|
|
1540
1191
|
// Check if operation was cancelled
|
|
1541
1192
|
if (this.isCancelled()) {
|
|
1542
1193
|
yield* this.yieldCancellation();
|
|
@@ -1555,8 +1206,12 @@ export class LLMAgent extends EventEmitter {
|
|
|
1555
1206
|
const stream = this.llmClient.chatStream(this.messages, tools, this.buildChatOptions({
|
|
1556
1207
|
searchOptions: { search_parameters: { mode: "off" } }
|
|
1557
1208
|
}));
|
|
1558
|
-
// Process streaming chunks
|
|
1559
|
-
const chunkGen = this.
|
|
1209
|
+
// Process streaming chunks (delegated to StreamHandler - Phase 2 refactoring)
|
|
1210
|
+
const chunkGen = this.streamHandler.processChunks(stream, {
|
|
1211
|
+
inputTokens: inputTokensRef.value,
|
|
1212
|
+
lastTokenUpdate: lastTokenUpdateRef,
|
|
1213
|
+
totalOutputTokens: totalOutputTokensRef,
|
|
1214
|
+
});
|
|
1560
1215
|
let streamResult;
|
|
1561
1216
|
for await (const chunk of chunkGen) {
|
|
1562
1217
|
if ('accumulated' in chunk) {
|
|
@@ -1626,237 +1281,56 @@ export class LLMAgent extends EventEmitter {
|
|
|
1626
1281
|
}
|
|
1627
1282
|
}
|
|
1628
1283
|
/**
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
* @param toolType Type of tool (for error messages)
|
|
1632
|
-
* @returns Parsed arguments or error result
|
|
1284
|
+
* Execute a tool call using the ToolExecutor
|
|
1285
|
+
* Handles tool approval for VSCode integration before delegation
|
|
1633
1286
|
*/
|
|
1634
|
-
parseToolArguments(toolCall, toolType = 'Tool') {
|
|
1635
|
-
const argsString = toolCall.function.arguments;
|
|
1636
|
-
if (!argsString || typeof argsString !== 'string' || argsString.trim() === '') {
|
|
1637
|
-
return {
|
|
1638
|
-
success: false,
|
|
1639
|
-
error: `${toolType} ${toolCall.function.name} called with empty arguments`,
|
|
1640
|
-
};
|
|
1641
|
-
}
|
|
1642
|
-
try {
|
|
1643
|
-
const args = JSON.parse(argsString);
|
|
1644
|
-
// Validate that args is an object (not null, array, or primitive)
|
|
1645
|
-
if (typeof args !== 'object' || args === null || Array.isArray(args)) {
|
|
1646
|
-
return {
|
|
1647
|
-
success: false,
|
|
1648
|
-
error: `${toolType} ${toolCall.function.name} arguments must be a JSON object, got ${Array.isArray(args) ? 'array' : typeof args}`,
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
return { success: true, args };
|
|
1652
|
-
}
|
|
1653
|
-
catch (error) {
|
|
1654
|
-
return {
|
|
1655
|
-
success: false,
|
|
1656
|
-
error: `Failed to parse ${toolType} arguments: ${error instanceof Error ? error.message : 'Invalid JSON'}`,
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
1287
|
async executeTool(toolCall) {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
if (needsApproval) {
|
|
1674
|
-
// Emit event and wait for approval
|
|
1675
|
-
const approved = await this.waitForToolApproval(toolCall);
|
|
1676
|
-
if (!approved) {
|
|
1677
|
-
// User rejected the change
|
|
1678
|
-
this.emit('tool:rejected', toolCall);
|
|
1679
|
-
return {
|
|
1680
|
-
success: false,
|
|
1681
|
-
error: 'Change rejected by user'
|
|
1682
|
-
};
|
|
1683
|
-
}
|
|
1684
|
-
// User approved
|
|
1685
|
-
this.emit('tool:approved', toolCall);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
// Helper to safely get string argument with validation
|
|
1689
|
-
const getString = (key, required = true) => {
|
|
1690
|
-
const value = args[key];
|
|
1691
|
-
if (typeof value !== 'string') {
|
|
1692
|
-
if (required)
|
|
1693
|
-
throw new Error(`Tool argument '${key}' must be a string, got ${typeof value}`);
|
|
1694
|
-
return '';
|
|
1695
|
-
}
|
|
1696
|
-
return value;
|
|
1697
|
-
};
|
|
1698
|
-
// Helper to safely get number argument
|
|
1699
|
-
const getNumber = (key) => {
|
|
1700
|
-
const value = args[key];
|
|
1701
|
-
if (value === undefined || value === null)
|
|
1702
|
-
return undefined;
|
|
1703
|
-
if (typeof value !== 'number')
|
|
1704
|
-
return undefined;
|
|
1705
|
-
return value;
|
|
1706
|
-
};
|
|
1707
|
-
// Helper to safely get boolean argument
|
|
1708
|
-
const getBoolean = (key) => {
|
|
1709
|
-
const value = args[key];
|
|
1710
|
-
if (value === undefined || value === null)
|
|
1711
|
-
return undefined;
|
|
1712
|
-
if (typeof value !== 'boolean')
|
|
1713
|
-
return undefined;
|
|
1714
|
-
return value;
|
|
1715
|
-
};
|
|
1716
|
-
switch (toolCall.function.name) {
|
|
1717
|
-
case "view_file":
|
|
1718
|
-
const startLine = getNumber('start_line');
|
|
1719
|
-
const endLine = getNumber('end_line');
|
|
1720
|
-
const range = startLine !== undefined && endLine !== undefined
|
|
1721
|
-
? [startLine, endLine]
|
|
1722
|
-
: undefined;
|
|
1723
|
-
return await this.textEditor.view(getString('path'), range);
|
|
1724
|
-
case "create_file":
|
|
1725
|
-
return await this.textEditor.create(getString('path'), getString('content'));
|
|
1726
|
-
case "str_replace_editor":
|
|
1727
|
-
return await this.textEditor.strReplace(getString('path'), getString('old_str'), getString('new_str'), getBoolean('replace_all') ?? false);
|
|
1728
|
-
case "multi_edit":
|
|
1729
|
-
return await this.textEditor.multiEdit(getString('path'), Array.isArray(args.edits) ? args.edits : []);
|
|
1730
|
-
case "bash":
|
|
1731
|
-
return await this.bash.execute(getString('command'), {
|
|
1732
|
-
background: getBoolean('background'),
|
|
1733
|
-
timeout: getNumber('timeout'),
|
|
1734
|
-
});
|
|
1735
|
-
case "bash_output":
|
|
1736
|
-
return await this.bashOutput.execute(getString('task_id'), getBoolean('wait'), getNumber('timeout'));
|
|
1737
|
-
case "create_todo_list":
|
|
1738
|
-
return await this.todoTool.createTodoList(Array.isArray(args.todos) ? args.todos : []);
|
|
1739
|
-
case "update_todo_list":
|
|
1740
|
-
return await this.todoTool.updateTodoList(Array.isArray(args.updates) ? args.updates : []);
|
|
1741
|
-
case "search":
|
|
1742
|
-
const searchTypeValue = args.search_type;
|
|
1743
|
-
const validSearchType = (searchTypeValue === 'text' || searchTypeValue === 'files' || searchTypeValue === 'both') ? searchTypeValue : undefined;
|
|
1744
|
-
return await this.search.search(getString('query'), {
|
|
1745
|
-
searchType: validSearchType,
|
|
1746
|
-
includePattern: typeof args.include_pattern === 'string' ? args.include_pattern : undefined,
|
|
1747
|
-
excludePattern: typeof args.exclude_pattern === 'string' ? args.exclude_pattern : undefined,
|
|
1748
|
-
caseSensitive: getBoolean('case_sensitive'),
|
|
1749
|
-
wholeWord: getBoolean('whole_word'),
|
|
1750
|
-
regex: getBoolean('regex'),
|
|
1751
|
-
maxResults: getNumber('max_results'),
|
|
1752
|
-
fileTypes: Array.isArray(args.file_types) ? args.file_types : undefined,
|
|
1753
|
-
includeHidden: getBoolean('include_hidden'),
|
|
1754
|
-
});
|
|
1755
|
-
case "analyze_architecture": {
|
|
1756
|
-
const projectPath = typeof args.projectPath === 'string' ? args.projectPath : undefined;
|
|
1757
|
-
const depth = typeof args.depth === 'string' ? args.depth : undefined;
|
|
1758
|
-
return await this.architectureTool.execute({ projectPath, depth });
|
|
1759
|
-
}
|
|
1760
|
-
case "validate_best_practices": {
|
|
1761
|
-
const path = typeof args.path === 'string' ? args.path : undefined;
|
|
1762
|
-
const pattern = typeof args.pattern === 'string' ? args.pattern : undefined;
|
|
1763
|
-
const rules = typeof args.rules === 'object' && args.rules !== null ? args.rules : undefined;
|
|
1764
|
-
return await this.validationTool.execute({ path, pattern, rules });
|
|
1765
|
-
}
|
|
1766
|
-
default:
|
|
1767
|
-
// Check if this is an MCP tool
|
|
1768
|
-
if (toolCall.function.name.startsWith("mcp__")) {
|
|
1769
|
-
return await this.executeMCPTool(toolCall);
|
|
1770
|
-
}
|
|
1288
|
+
// Check if tool approval is required (for VSCode integration)
|
|
1289
|
+
if (this.requireToolApproval) {
|
|
1290
|
+
// Only require approval for file modification operations
|
|
1291
|
+
const needsApproval = toolCall.function.name === "create_file" ||
|
|
1292
|
+
toolCall.function.name === "str_replace_editor" ||
|
|
1293
|
+
toolCall.function.name === "insert_text";
|
|
1294
|
+
if (needsApproval) {
|
|
1295
|
+
// Emit event and wait for approval
|
|
1296
|
+
const approved = await this.waitForToolApproval(toolCall);
|
|
1297
|
+
if (!approved) {
|
|
1298
|
+
// User rejected the change
|
|
1299
|
+
this.emit('tool:rejected', toolCall);
|
|
1771
1300
|
return {
|
|
1772
1301
|
success: false,
|
|
1773
|
-
error:
|
|
1302
|
+
error: 'Change rejected by user'
|
|
1774
1303
|
};
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
catch (error) {
|
|
1778
|
-
const errorMsg = extractErrorMessage(error);
|
|
1779
|
-
return {
|
|
1780
|
-
success: false,
|
|
1781
|
-
error: `Tool execution error: ${errorMsg}`,
|
|
1782
|
-
};
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
async executeMCPTool(toolCall) {
|
|
1786
|
-
try {
|
|
1787
|
-
const parseResult = this.parseToolArguments(toolCall, 'MCP tool');
|
|
1788
|
-
if (!parseResult.success) {
|
|
1789
|
-
return { success: false, error: parseResult.error };
|
|
1790
|
-
}
|
|
1791
|
-
const args = parseResult.args;
|
|
1792
|
-
const mcpManager = getMCPManager();
|
|
1793
|
-
const result = await mcpManager.callTool(toolCall.function.name, args);
|
|
1794
|
-
if (result.isError) {
|
|
1795
|
-
// Extract error message from MCP result content
|
|
1796
|
-
// Safely check content structure before accessing
|
|
1797
|
-
let errorMsg = "MCP tool error";
|
|
1798
|
-
if (result.content && Array.isArray(result.content) && result.content.length > 0) {
|
|
1799
|
-
const firstContent = result.content[0];
|
|
1800
|
-
if (typeof firstContent === 'object' && firstContent !== null && 'text' in firstContent) {
|
|
1801
|
-
const textValue = firstContent.text;
|
|
1802
|
-
errorMsg = typeof textValue === 'string' ? textValue : String(textValue || errorMsg);
|
|
1803
|
-
}
|
|
1804
1304
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
error: errorMsg,
|
|
1808
|
-
};
|
|
1305
|
+
// User approved
|
|
1306
|
+
this.emit('tool:approved', toolCall);
|
|
1809
1307
|
}
|
|
1810
|
-
// Extract content from result
|
|
1811
|
-
// Ensure result.content exists and is an array before mapping
|
|
1812
|
-
const output = result.content && Array.isArray(result.content)
|
|
1813
|
-
? result.content
|
|
1814
|
-
.map((item) => {
|
|
1815
|
-
if (item.type === "text") {
|
|
1816
|
-
return item.text || ""; // Safety check for missing text property
|
|
1817
|
-
}
|
|
1818
|
-
else if (item.type === "resource") {
|
|
1819
|
-
return `Resource: ${item.resource?.uri || "Unknown"}`;
|
|
1820
|
-
}
|
|
1821
|
-
return String(item);
|
|
1822
|
-
})
|
|
1823
|
-
.join("\n")
|
|
1824
|
-
: "";
|
|
1825
|
-
return {
|
|
1826
|
-
success: true,
|
|
1827
|
-
output: output || "Success",
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
catch (error) {
|
|
1831
|
-
const errorMsg = extractErrorMessage(error);
|
|
1832
|
-
return {
|
|
1833
|
-
success: false,
|
|
1834
|
-
error: `MCP tool execution error: ${errorMsg}`,
|
|
1835
|
-
};
|
|
1836
1308
|
}
|
|
1309
|
+
// Delegate to ToolExecutor (Phase 2 refactoring)
|
|
1310
|
+
return await this.toolExecutor.execute(toolCall);
|
|
1837
1311
|
}
|
|
1838
1312
|
getChatHistory() {
|
|
1839
1313
|
this.checkDisposed();
|
|
1840
1314
|
return [...this.chatHistory];
|
|
1841
1315
|
}
|
|
1842
1316
|
getCurrentDirectory() {
|
|
1843
|
-
return this.
|
|
1317
|
+
return this.toolExecutor.getBashTool().getCurrentDirectory();
|
|
1844
1318
|
}
|
|
1845
1319
|
async executeBashCommand(command) {
|
|
1846
|
-
return await this.
|
|
1320
|
+
return await this.toolExecutor.getBashTool().execute(command);
|
|
1847
1321
|
}
|
|
1848
1322
|
/**
|
|
1849
1323
|
* Check if a bash command is currently executing
|
|
1850
1324
|
*/
|
|
1851
1325
|
isBashExecuting() {
|
|
1852
|
-
return this.
|
|
1326
|
+
return this.toolExecutor.getBashTool().isExecuting();
|
|
1853
1327
|
}
|
|
1854
1328
|
/**
|
|
1855
1329
|
* Move currently running bash command to background
|
|
1856
1330
|
* Returns task ID if successful, null otherwise
|
|
1857
1331
|
*/
|
|
1858
1332
|
moveBashToBackground() {
|
|
1859
|
-
return this.
|
|
1333
|
+
return this.toolExecutor.getBashTool().moveToBackground();
|
|
1860
1334
|
}
|
|
1861
1335
|
getCurrentModel() {
|
|
1862
1336
|
return this.llmClient.getCurrentModel();
|
|
@@ -1865,6 +1339,8 @@ export class LLMAgent extends EventEmitter {
|
|
|
1865
1339
|
this.llmClient.setModel(model);
|
|
1866
1340
|
// Update token counter for new model (use singleton)
|
|
1867
1341
|
this.tokenCounter = getTokenCounter(model);
|
|
1342
|
+
// Update stream handler model for usage tracking
|
|
1343
|
+
this.streamHandler.setModel(model);
|
|
1868
1344
|
}
|
|
1869
1345
|
abortCurrentOperation() {
|
|
1870
1346
|
if (this.abortController) {
|
|
@@ -1989,19 +1465,9 @@ export class LLMAgent extends EventEmitter {
|
|
|
1989
1465
|
*/
|
|
1990
1466
|
async spawnSubagent(role, description, context) {
|
|
1991
1467
|
try {
|
|
1992
|
-
// Import
|
|
1993
|
-
const {
|
|
1994
|
-
|
|
1995
|
-
const roleMap = {
|
|
1996
|
-
'testing': SubagentRole.TESTING,
|
|
1997
|
-
'documentation': SubagentRole.DOCUMENTATION,
|
|
1998
|
-
'refactoring': SubagentRole.REFACTORING,
|
|
1999
|
-
'analysis': SubagentRole.ANALYSIS,
|
|
2000
|
-
'debug': SubagentRole.DEBUG,
|
|
2001
|
-
'performance': SubagentRole.PERFORMANCE,
|
|
2002
|
-
'general': SubagentRole.GENERAL,
|
|
2003
|
-
};
|
|
2004
|
-
const subagentRole = roleMap[role.toLowerCase()] || SubagentRole.GENERAL;
|
|
1468
|
+
// Import parseSubagentRole helper to convert string to enum
|
|
1469
|
+
const { parseSubagentRole } = await import('./subagent-types.js');
|
|
1470
|
+
const subagentRole = parseSubagentRole(role);
|
|
2005
1471
|
// Spawn the subagent
|
|
2006
1472
|
const subagent = await this.subagentOrchestrator.spawnSubagent(subagentRole);
|
|
2007
1473
|
// Execute the task
|
|
@@ -2045,22 +1511,13 @@ export class LLMAgent extends EventEmitter {
|
|
|
2045
1511
|
*/
|
|
2046
1512
|
async executeParallelTasks(tasks) {
|
|
2047
1513
|
try {
|
|
2048
|
-
// Import
|
|
2049
|
-
const {
|
|
2050
|
-
const roleMap = {
|
|
2051
|
-
'testing': SubagentRole.TESTING,
|
|
2052
|
-
'documentation': SubagentRole.DOCUMENTATION,
|
|
2053
|
-
'refactoring': SubagentRole.REFACTORING,
|
|
2054
|
-
'analysis': SubagentRole.ANALYSIS,
|
|
2055
|
-
'debug': SubagentRole.DEBUG,
|
|
2056
|
-
'performance': SubagentRole.PERFORMANCE,
|
|
2057
|
-
'general': SubagentRole.GENERAL,
|
|
2058
|
-
};
|
|
1514
|
+
// Import parseSubagentRole helper to convert string to enum
|
|
1515
|
+
const { parseSubagentRole } = await import('./subagent-types.js');
|
|
2059
1516
|
// Convert tasks to SubagentTask format
|
|
2060
1517
|
const subagentTasks = tasks.map((task, index) => ({
|
|
2061
1518
|
id: task.id || `task-${index}-${Date.now()}`,
|
|
2062
1519
|
description: task.description,
|
|
2063
|
-
role:
|
|
1520
|
+
role: parseSubagentRole(task.role),
|
|
2064
1521
|
priority: 1,
|
|
2065
1522
|
context: {
|
|
2066
1523
|
files: [],
|
|
@@ -2141,8 +1598,8 @@ export class LLMAgent extends EventEmitter {
|
|
|
2141
1598
|
this.contextManager.removeListener('before_prune', this.contextOverflowListener);
|
|
2142
1599
|
this.contextOverflowListener = undefined;
|
|
2143
1600
|
}
|
|
2144
|
-
// Dispose tools
|
|
2145
|
-
this.
|
|
1601
|
+
// Dispose tool executor (includes all tools with cleanup methods)
|
|
1602
|
+
this.toolExecutor.dispose();
|
|
2146
1603
|
// Clear in-memory caches
|
|
2147
1604
|
this.recentToolCalls.clear();
|
|
2148
1605
|
this.toolCallIndexMap.clear();
|