@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.
Files changed (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. 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;