@loxia-labs/loxia-autopilot-one 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +267 -0
- package/README.md +509 -0
- package/bin/cli.js +117 -0
- package/package.json +94 -0
- package/scripts/install-scanners.js +236 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +266 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +203 -0
- package/src/analyzers/codeCloneDetector/index.js +160 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +59 -0
- package/src/core/agentPool.js +1474 -0
- package/src/core/agentScheduler.js +2147 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/messageProcessor.js +732 -0
- package/src/core/orchestrator.js +548 -0
- package/src/core/stateManager.js +877 -0
- package/src/index.js +631 -0
- package/src/interfaces/cli.js +549 -0
- package/src/interfaces/webServer.js +2162 -0
- package/src/modules/fileExplorer/controller.js +280 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +125 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/aiService.js +1232 -0
- package/src/services/apiKeyManager.js +164 -0
- package/src/services/benchmarkService.js +366 -0
- package/src/services/budgetService.js +539 -0
- package/src/services/contextInjectionService.js +247 -0
- package/src/services/conversationCompactionService.js +637 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +544 -0
- package/src/services/modelRouterService.js +366 -0
- package/src/services/modelsService.js +322 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/tokenCountingService.js +536 -0
- package/src/tools/agentCommunicationTool.js +1344 -0
- package/src/tools/agentDelayTool.js +485 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +800 -0
- package/src/tools/browserTool.js +920 -0
- package/src/tools/cloneDetectionTool.js +621 -0
- package/src/tools/dependencyResolverTool.js +1215 -0
- package/src/tools/fileContentReplaceTool.js +875 -0
- package/src/tools/fileSystemTool.js +1107 -0
- package/src/tools/fileTreeTool.js +853 -0
- package/src/tools/imageTool.js +901 -0
- package/src/tools/importAnalyzerTool.js +1060 -0
- package/src/tools/jobDoneTool.js +248 -0
- package/src/tools/seekTool.js +956 -0
- package/src/tools/staticAnalysisTool.js +1778 -0
- package/src/tools/taskManagerTool.js +2873 -0
- package/src/tools/terminalTool.js +2304 -0
- package/src/tools/webTool.js +1430 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +292 -0
- package/src/utilities/configManager.js +582 -0
- package/src/utilities/constants.js +722 -0
- package/src/utilities/directoryAccessManager.js +535 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/tagParser.js +1246 -0
- package/src/utilities/toolConstants.js +317 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-CjkkcnFA.js +344 -0
- package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncToolManager - Manage long-running tool operations and status monitoring
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Track async tool operations across the system
|
|
6
|
+
* - Provide status monitoring and updates
|
|
7
|
+
* - Handle operation timeouts and cleanup
|
|
8
|
+
* - Coordinate between tools and message processor
|
|
9
|
+
* - Enable operation cancellation and recovery
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import EventEmitter from 'events';
|
|
13
|
+
import {
|
|
14
|
+
TOOL_STATUS,
|
|
15
|
+
SYSTEM_DEFAULTS
|
|
16
|
+
} from '../utilities/constants.js';
|
|
17
|
+
|
|
18
|
+
class AsyncToolManager extends EventEmitter {
|
|
19
|
+
constructor(config = {}, logger = null) {
|
|
20
|
+
super();
|
|
21
|
+
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
|
|
25
|
+
// Active operations tracking
|
|
26
|
+
this.operations = new Map();
|
|
27
|
+
|
|
28
|
+
// Operation history for debugging
|
|
29
|
+
this.operationHistory = [];
|
|
30
|
+
|
|
31
|
+
// Configuration
|
|
32
|
+
this.maxConcurrentOperations = config.maxConcurrentOperations || 10;
|
|
33
|
+
this.defaultTimeout = config.defaultTimeout || 300000; // 5 minutes
|
|
34
|
+
this.cleanupInterval = config.cleanupInterval || 60000; // 1 minute
|
|
35
|
+
this.maxHistorySize = config.maxHistorySize || 1000;
|
|
36
|
+
|
|
37
|
+
// Status monitoring
|
|
38
|
+
this.monitoringInterval = null;
|
|
39
|
+
this.isShuttingDown = false;
|
|
40
|
+
|
|
41
|
+
// Start monitoring
|
|
42
|
+
this.startMonitoring();
|
|
43
|
+
|
|
44
|
+
// Bind event handlers
|
|
45
|
+
this.on('operation:started', this.handleOperationStarted.bind(this));
|
|
46
|
+
this.on('operation:completed', this.handleOperationCompleted.bind(this));
|
|
47
|
+
this.on('operation:failed', this.handleOperationFailed.bind(this));
|
|
48
|
+
this.on('operation:timeout', this.handleOperationTimeout.bind(this));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Start a new async operation
|
|
53
|
+
* @param {string} toolId - Tool identifier
|
|
54
|
+
* @param {string} agentId - Agent identifier
|
|
55
|
+
* @param {Object} parameters - Operation parameters
|
|
56
|
+
* @param {Object} context - Execution context
|
|
57
|
+
* @returns {Promise<string>} Operation ID
|
|
58
|
+
*/
|
|
59
|
+
async startOperation(toolId, agentId, parameters, context = {}) {
|
|
60
|
+
if (this.operations.size >= this.maxConcurrentOperations) {
|
|
61
|
+
throw new Error(`Maximum concurrent operations reached (${this.maxConcurrentOperations})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const operationId = this.generateOperationId();
|
|
65
|
+
const operation = {
|
|
66
|
+
id: operationId,
|
|
67
|
+
toolId,
|
|
68
|
+
agentId,
|
|
69
|
+
parameters,
|
|
70
|
+
context,
|
|
71
|
+
status: TOOL_STATUS.PENDING,
|
|
72
|
+
createdAt: new Date().toISOString(),
|
|
73
|
+
startedAt: null,
|
|
74
|
+
completedAt: null,
|
|
75
|
+
timeout: context.timeout || this.defaultTimeout,
|
|
76
|
+
result: null,
|
|
77
|
+
error: null,
|
|
78
|
+
progress: null,
|
|
79
|
+
retryCount: 0,
|
|
80
|
+
maxRetries: context.maxRetries || 0
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.operations.set(operationId, operation);
|
|
84
|
+
|
|
85
|
+
this.logger?.info(`Async operation started: ${operationId}`, {
|
|
86
|
+
toolId,
|
|
87
|
+
agentId,
|
|
88
|
+
parametersCount: Object.keys(parameters).length,
|
|
89
|
+
timeout: operation.timeout
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Set timeout
|
|
93
|
+
this.setOperationTimeout(operationId);
|
|
94
|
+
|
|
95
|
+
// Emit event
|
|
96
|
+
this.emit('operation:started', operation);
|
|
97
|
+
|
|
98
|
+
return operationId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Update operation status
|
|
103
|
+
* @param {string} operationId - Operation identifier
|
|
104
|
+
* @param {string} status - New status
|
|
105
|
+
* @param {Object} data - Additional data (result, error, progress)
|
|
106
|
+
* @returns {boolean} Success status
|
|
107
|
+
*/
|
|
108
|
+
updateOperation(operationId, status, data = {}) {
|
|
109
|
+
const operation = this.operations.get(operationId);
|
|
110
|
+
if (!operation) {
|
|
111
|
+
this.logger?.warn(`Attempted to update unknown operation: ${operationId}`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const previousStatus = operation.status;
|
|
116
|
+
operation.status = status;
|
|
117
|
+
|
|
118
|
+
// Update timestamps
|
|
119
|
+
if (status === TOOL_STATUS.EXECUTING && !operation.startedAt) {
|
|
120
|
+
operation.startedAt = new Date().toISOString();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (status === TOOL_STATUS.COMPLETED || status === TOOL_STATUS.FAILED) {
|
|
124
|
+
operation.completedAt = new Date().toISOString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Update data
|
|
128
|
+
if (data.result !== undefined) operation.result = data.result;
|
|
129
|
+
if (data.error !== undefined) operation.error = data.error;
|
|
130
|
+
if (data.progress !== undefined) operation.progress = data.progress;
|
|
131
|
+
|
|
132
|
+
this.logger?.debug(`Operation status updated: ${operationId}`, {
|
|
133
|
+
previousStatus,
|
|
134
|
+
newStatus: status,
|
|
135
|
+
hasResult: !!data.result,
|
|
136
|
+
hasError: !!data.error,
|
|
137
|
+
progress: data.progress
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Emit appropriate events
|
|
141
|
+
switch (status) {
|
|
142
|
+
case TOOL_STATUS.COMPLETED:
|
|
143
|
+
this.emit('operation:completed', operation);
|
|
144
|
+
break;
|
|
145
|
+
case TOOL_STATUS.FAILED:
|
|
146
|
+
this.emit('operation:failed', operation);
|
|
147
|
+
break;
|
|
148
|
+
case TOOL_STATUS.EXECUTING:
|
|
149
|
+
this.emit('operation:progress', operation);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get operation status
|
|
158
|
+
* @param {string} operationId - Operation identifier
|
|
159
|
+
* @returns {Object|null} Operation details or null if not found
|
|
160
|
+
*/
|
|
161
|
+
getOperation(operationId) {
|
|
162
|
+
const operation = this.operations.get(operationId);
|
|
163
|
+
if (!operation) return null;
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
id: operation.id,
|
|
167
|
+
toolId: operation.toolId,
|
|
168
|
+
agentId: operation.agentId,
|
|
169
|
+
status: operation.status,
|
|
170
|
+
createdAt: operation.createdAt,
|
|
171
|
+
startedAt: operation.startedAt,
|
|
172
|
+
completedAt: operation.completedAt,
|
|
173
|
+
result: operation.result,
|
|
174
|
+
error: operation.error,
|
|
175
|
+
progress: operation.progress,
|
|
176
|
+
retryCount: operation.retryCount,
|
|
177
|
+
executionTime: this.calculateExecutionTime(operation)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get all operations for an agent
|
|
183
|
+
* @param {string} agentId - Agent identifier
|
|
184
|
+
* @returns {Array<Object>} Array of operation details
|
|
185
|
+
*/
|
|
186
|
+
getAgentOperations(agentId) {
|
|
187
|
+
const operations = [];
|
|
188
|
+
|
|
189
|
+
for (const operation of this.operations.values()) {
|
|
190
|
+
if (operation.agentId === agentId) {
|
|
191
|
+
operations.push(this.getOperation(operation.id));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return operations.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Cancel an operation
|
|
200
|
+
* @param {string} operationId - Operation identifier
|
|
201
|
+
* @param {string} reason - Cancellation reason
|
|
202
|
+
* @returns {boolean} Success status
|
|
203
|
+
*/
|
|
204
|
+
async cancelOperation(operationId, reason = 'Operation cancelled') {
|
|
205
|
+
const operation = this.operations.get(operationId);
|
|
206
|
+
if (!operation) return false;
|
|
207
|
+
|
|
208
|
+
if (operation.status === TOOL_STATUS.COMPLETED || operation.status === TOOL_STATUS.FAILED) {
|
|
209
|
+
this.logger?.warn(`Cannot cancel completed/failed operation: ${operationId}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Update status
|
|
214
|
+
this.updateOperation(operationId, TOOL_STATUS.CANCELLED, {
|
|
215
|
+
error: reason
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Clear timeout
|
|
219
|
+
this.clearOperationTimeout(operationId);
|
|
220
|
+
|
|
221
|
+
// Emit cancellation event
|
|
222
|
+
this.emit('operation:cancelled', operation);
|
|
223
|
+
|
|
224
|
+
// Move to history and cleanup
|
|
225
|
+
await this.cleanupOperation(operationId);
|
|
226
|
+
|
|
227
|
+
this.logger?.info(`Operation cancelled: ${operationId}`, { reason });
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Retry a failed operation
|
|
234
|
+
* @param {string} operationId - Operation identifier
|
|
235
|
+
* @returns {Promise<boolean>} Success status
|
|
236
|
+
*/
|
|
237
|
+
async retryOperation(operationId) {
|
|
238
|
+
const operation = this.operations.get(operationId);
|
|
239
|
+
if (!operation) return false;
|
|
240
|
+
|
|
241
|
+
if (operation.status !== TOOL_STATUS.FAILED) {
|
|
242
|
+
this.logger?.warn(`Cannot retry non-failed operation: ${operationId}`);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (operation.retryCount >= operation.maxRetries) {
|
|
247
|
+
this.logger?.warn(`Maximum retries exceeded for operation: ${operationId}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Reset operation state
|
|
252
|
+
operation.retryCount++;
|
|
253
|
+
operation.status = TOOL_STATUS.PENDING;
|
|
254
|
+
operation.startedAt = null;
|
|
255
|
+
operation.completedAt = null;
|
|
256
|
+
operation.error = null;
|
|
257
|
+
operation.result = null;
|
|
258
|
+
|
|
259
|
+
// Reset timeout
|
|
260
|
+
this.setOperationTimeout(operationId);
|
|
261
|
+
|
|
262
|
+
this.logger?.info(`Operation retry initiated: ${operationId}`, {
|
|
263
|
+
retryCount: operation.retryCount,
|
|
264
|
+
maxRetries: operation.maxRetries
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.emit('operation:retry', operation);
|
|
268
|
+
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get system-wide operation statistics
|
|
274
|
+
* @returns {Object} Operation statistics
|
|
275
|
+
*/
|
|
276
|
+
getStatistics() {
|
|
277
|
+
const stats = {
|
|
278
|
+
total: this.operations.size,
|
|
279
|
+
byStatus: {},
|
|
280
|
+
byTool: {},
|
|
281
|
+
byAgent: {},
|
|
282
|
+
averageExecutionTime: 0,
|
|
283
|
+
oldestOperation: null,
|
|
284
|
+
newestOperation: null
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let totalExecutionTime = 0;
|
|
288
|
+
let executionCount = 0;
|
|
289
|
+
let oldestTime = null;
|
|
290
|
+
let newestTime = null;
|
|
291
|
+
|
|
292
|
+
for (const operation of this.operations.values()) {
|
|
293
|
+
// Count by status
|
|
294
|
+
stats.byStatus[operation.status] = (stats.byStatus[operation.status] || 0) + 1;
|
|
295
|
+
|
|
296
|
+
// Count by tool
|
|
297
|
+
stats.byTool[operation.toolId] = (stats.byTool[operation.toolId] || 0) + 1;
|
|
298
|
+
|
|
299
|
+
// Count by agent
|
|
300
|
+
stats.byAgent[operation.agentId] = (stats.byAgent[operation.agentId] || 0) + 1;
|
|
301
|
+
|
|
302
|
+
// Calculate execution time
|
|
303
|
+
const execTime = this.calculateExecutionTime(operation);
|
|
304
|
+
if (execTime > 0) {
|
|
305
|
+
totalExecutionTime += execTime;
|
|
306
|
+
executionCount++;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Track oldest/newest
|
|
310
|
+
const createdTime = new Date(operation.createdAt).getTime();
|
|
311
|
+
if (!oldestTime || createdTime < oldestTime) {
|
|
312
|
+
oldestTime = createdTime;
|
|
313
|
+
stats.oldestOperation = operation.id;
|
|
314
|
+
}
|
|
315
|
+
if (!newestTime || createdTime > newestTime) {
|
|
316
|
+
newestTime = createdTime;
|
|
317
|
+
stats.newestOperation = operation.id;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
stats.averageExecutionTime = executionCount > 0 ? Math.round(totalExecutionTime / executionCount) : 0;
|
|
322
|
+
|
|
323
|
+
return stats;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Clean up completed operations
|
|
328
|
+
* @param {number} maxAge - Maximum age in milliseconds (default: 1 hour)
|
|
329
|
+
* @returns {number} Number of operations cleaned up
|
|
330
|
+
*/
|
|
331
|
+
async cleanupCompletedOperations(maxAge = 3600000) {
|
|
332
|
+
const cutoffTime = Date.now() - maxAge;
|
|
333
|
+
const toCleanup = [];
|
|
334
|
+
|
|
335
|
+
for (const operation of this.operations.values()) {
|
|
336
|
+
if ((operation.status === TOOL_STATUS.COMPLETED || operation.status === TOOL_STATUS.FAILED || operation.status === TOOL_STATUS.CANCELLED) &&
|
|
337
|
+
new Date(operation.completedAt).getTime() < cutoffTime) {
|
|
338
|
+
toCleanup.push(operation.id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (const operationId of toCleanup) {
|
|
343
|
+
await this.cleanupOperation(operationId);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this.logger?.debug(`Cleaned up ${toCleanup.length} completed operations`);
|
|
347
|
+
|
|
348
|
+
return toCleanup.length;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Start monitoring operations
|
|
353
|
+
* @private
|
|
354
|
+
*/
|
|
355
|
+
startMonitoring() {
|
|
356
|
+
if (this.monitoringInterval) {
|
|
357
|
+
clearInterval(this.monitoringInterval);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.monitoringInterval = setInterval(() => {
|
|
361
|
+
this.checkOperationTimeouts();
|
|
362
|
+
this.cleanupCompletedOperations();
|
|
363
|
+
}, this.cleanupInterval);
|
|
364
|
+
|
|
365
|
+
this.logger?.info('Async tool manager monitoring started');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Stop monitoring operations
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
stopMonitoring() {
|
|
373
|
+
if (this.monitoringInterval) {
|
|
374
|
+
clearInterval(this.monitoringInterval);
|
|
375
|
+
this.monitoringInterval = null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.logger?.info('Async tool manager monitoring stopped');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Check for operation timeouts
|
|
383
|
+
* @private
|
|
384
|
+
*/
|
|
385
|
+
checkOperationTimeouts() {
|
|
386
|
+
const now = Date.now();
|
|
387
|
+
|
|
388
|
+
for (const operation of this.operations.values()) {
|
|
389
|
+
if (operation.status === TOOL_STATUS.EXECUTING || operation.status === TOOL_STATUS.PENDING) {
|
|
390
|
+
const createdTime = new Date(operation.createdAt).getTime();
|
|
391
|
+
if (now - createdTime > operation.timeout) {
|
|
392
|
+
this.handleOperationTimeout(operation);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Set timeout for operation
|
|
400
|
+
* @private
|
|
401
|
+
*/
|
|
402
|
+
setOperationTimeout(operationId) {
|
|
403
|
+
const operation = this.operations.get(operationId);
|
|
404
|
+
if (!operation) return;
|
|
405
|
+
|
|
406
|
+
operation.timeoutHandle = setTimeout(() => {
|
|
407
|
+
this.handleOperationTimeout(operation);
|
|
408
|
+
}, operation.timeout);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Clear timeout for operation
|
|
413
|
+
* @private
|
|
414
|
+
*/
|
|
415
|
+
clearOperationTimeout(operationId) {
|
|
416
|
+
const operation = this.operations.get(operationId);
|
|
417
|
+
if (operation && operation.timeoutHandle) {
|
|
418
|
+
clearTimeout(operation.timeoutHandle);
|
|
419
|
+
delete operation.timeoutHandle;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate unique operation ID
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
generateOperationId() {
|
|
428
|
+
const timestamp = Date.now().toString(36);
|
|
429
|
+
const random = Math.random().toString(36).substr(2, 9);
|
|
430
|
+
return `op-${timestamp}-${random}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Calculate execution time for operation
|
|
435
|
+
* @private
|
|
436
|
+
*/
|
|
437
|
+
calculateExecutionTime(operation) {
|
|
438
|
+
if (!operation.startedAt) return 0;
|
|
439
|
+
|
|
440
|
+
const endTime = operation.completedAt
|
|
441
|
+
? new Date(operation.completedAt).getTime()
|
|
442
|
+
: Date.now();
|
|
443
|
+
|
|
444
|
+
return endTime - new Date(operation.startedAt).getTime();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Move operation to history and remove from active
|
|
449
|
+
* @private
|
|
450
|
+
*/
|
|
451
|
+
async cleanupOperation(operationId) {
|
|
452
|
+
const operation = this.operations.get(operationId);
|
|
453
|
+
if (!operation) return;
|
|
454
|
+
|
|
455
|
+
// Clear timeout
|
|
456
|
+
this.clearOperationTimeout(operationId);
|
|
457
|
+
|
|
458
|
+
// Add to history
|
|
459
|
+
this.operationHistory.push({
|
|
460
|
+
...operation,
|
|
461
|
+
executionTime: this.calculateExecutionTime(operation),
|
|
462
|
+
cleanedUpAt: new Date().toISOString()
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Trim history if needed
|
|
466
|
+
if (this.operationHistory.length > this.maxHistorySize) {
|
|
467
|
+
this.operationHistory = this.operationHistory.slice(-this.maxHistorySize);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Remove from active operations
|
|
471
|
+
this.operations.delete(operationId);
|
|
472
|
+
|
|
473
|
+
this.emit('operation:cleanup', { operationId, toolId: operation.toolId });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Event handler for operation started
|
|
478
|
+
* @private
|
|
479
|
+
*/
|
|
480
|
+
handleOperationStarted(operation) {
|
|
481
|
+
this.logger?.debug(`Operation started: ${operation.id}`, {
|
|
482
|
+
toolId: operation.toolId,
|
|
483
|
+
agentId: operation.agentId
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Event handler for operation completed
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
async handleOperationCompleted(operation) {
|
|
492
|
+
this.logger?.info(`Operation completed: ${operation.id}`, {
|
|
493
|
+
toolId: operation.toolId,
|
|
494
|
+
agentId: operation.agentId,
|
|
495
|
+
executionTime: this.calculateExecutionTime(operation)
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Schedule cleanup
|
|
499
|
+
setTimeout(() => this.cleanupOperation(operation.id), 30000); // 30 seconds
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Event handler for operation failed
|
|
504
|
+
* @private
|
|
505
|
+
*/
|
|
506
|
+
async handleOperationFailed(operation) {
|
|
507
|
+
this.logger?.error(`Operation failed: ${operation.id}`, {
|
|
508
|
+
toolId: operation.toolId,
|
|
509
|
+
agentId: operation.agentId,
|
|
510
|
+
error: operation.error,
|
|
511
|
+
executionTime: this.calculateExecutionTime(operation),
|
|
512
|
+
retryCount: operation.retryCount
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Attempt retry if allowed
|
|
516
|
+
if (operation.retryCount < operation.maxRetries) {
|
|
517
|
+
setTimeout(() => this.retryOperation(operation.id), 5000); // 5 second delay
|
|
518
|
+
} else {
|
|
519
|
+
// Schedule cleanup
|
|
520
|
+
setTimeout(() => this.cleanupOperation(operation.id), 60000); // 1 minute
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Event handler for operation timeout
|
|
526
|
+
* @private
|
|
527
|
+
*/
|
|
528
|
+
async handleOperationTimeout(operation) {
|
|
529
|
+
this.logger?.warn(`Operation timed out: ${operation.id}`, {
|
|
530
|
+
toolId: operation.toolId,
|
|
531
|
+
agentId: operation.agentId,
|
|
532
|
+
timeout: operation.timeout,
|
|
533
|
+
executionTime: this.calculateExecutionTime(operation)
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
this.updateOperation(operation.id, TOOL_STATUS.TIMEOUT, {
|
|
537
|
+
error: `Operation timed out after ${operation.timeout}ms`
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
this.emit('operation:timeout', operation);
|
|
541
|
+
|
|
542
|
+
// Schedule cleanup
|
|
543
|
+
setTimeout(() => this.cleanupOperation(operation.id), 30000); // 30 seconds
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Graceful shutdown
|
|
548
|
+
* @returns {Promise<void>}
|
|
549
|
+
*/
|
|
550
|
+
async shutdown() {
|
|
551
|
+
this.isShuttingDown = true;
|
|
552
|
+
|
|
553
|
+
this.logger?.info('Shutting down async tool manager...');
|
|
554
|
+
|
|
555
|
+
// Stop monitoring
|
|
556
|
+
this.stopMonitoring();
|
|
557
|
+
|
|
558
|
+
// Cancel all pending/executing operations
|
|
559
|
+
const activeOperations = [];
|
|
560
|
+
for (const operation of this.operations.values()) {
|
|
561
|
+
if (operation.status === TOOL_STATUS.PENDING || operation.status === TOOL_STATUS.EXECUTING) {
|
|
562
|
+
activeOperations.push(operation.id);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (const operationId of activeOperations) {
|
|
567
|
+
await this.cancelOperation(operationId, 'System shutdown');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Clear all remaining operations
|
|
571
|
+
this.operations.clear();
|
|
572
|
+
|
|
573
|
+
this.logger?.info(`Async tool manager shutdown complete. Cancelled ${activeOperations.length} operations.`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Get operation history
|
|
578
|
+
* @param {Object} filters - Filtering options
|
|
579
|
+
* @returns {Array<Object>} Filtered operation history
|
|
580
|
+
*/
|
|
581
|
+
getOperationHistory(filters = {}) {
|
|
582
|
+
let history = [...this.operationHistory];
|
|
583
|
+
|
|
584
|
+
if (filters.agentId) {
|
|
585
|
+
history = history.filter(op => op.agentId === filters.agentId);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (filters.toolId) {
|
|
589
|
+
history = history.filter(op => op.toolId === filters.toolId);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (filters.status) {
|
|
593
|
+
history = history.filter(op => op.status === filters.status);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (filters.limit) {
|
|
597
|
+
history = history.slice(-filters.limit);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return history.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export default AsyncToolManager;
|