@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,582 @@
1
+ /**
2
+ * ConfigManager - Centralized configuration management for the Loxia AI Agents System
3
+ *
4
+ * Purpose:
5
+ * - Load and merge configuration from multiple sources
6
+ * - Environment variable support
7
+ * - Configuration validation
8
+ * - Runtime configuration updates
9
+ * - Default configuration management
10
+ */
11
+
12
+ import { promises as fs } from 'fs';
13
+ import path from 'path';
14
+
15
+ import {
16
+ SYSTEM_DEFAULTS,
17
+ MODELS,
18
+ MODEL_ROUTING,
19
+ TOOL_NAMES,
20
+ STATE_DIRECTORIES,
21
+ ERROR_TYPES
22
+ } from './constants.js';
23
+
24
+ class ConfigManager {
25
+ constructor(options = {}) {
26
+ this.configPaths = options.configPaths || [];
27
+ this.envPrefix = options.envPrefix || 'LOXIA';
28
+ this.config = {};
29
+ this.watchers = new Map();
30
+ this.changeListeners = new Set();
31
+
32
+ // Default configuration
33
+ this.defaultConfig = this.getDefaultConfig();
34
+ }
35
+
36
+ /**
37
+ * Load configuration from all sources
38
+ * @returns {Promise<Object>} Loaded configuration
39
+ */
40
+ async loadConfig() {
41
+ let config = { ...this.defaultConfig };
42
+
43
+ // Load from config files
44
+ for (const configPath of this.configPaths) {
45
+ try {
46
+ const fileConfig = await this.loadConfigFile(configPath);
47
+ config = this.mergeConfig(config, fileConfig);
48
+ } catch (error) {
49
+ console.warn(`Failed to load config file ${configPath}:`, error.message);
50
+ }
51
+ }
52
+
53
+ // Override with environment variables
54
+ const envConfig = this.loadEnvironmentConfig();
55
+ config = this.mergeConfig(config, envConfig);
56
+
57
+ // Validate configuration
58
+ const validation = this.validateConfig(config);
59
+ if (!validation.valid) {
60
+ throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
61
+ }
62
+
63
+ // Apply configuration transformations
64
+ config = this.transformConfig(config);
65
+
66
+ this.config = config;
67
+
68
+ // Notify listeners of config change
69
+ this.notifyConfigChange(config);
70
+
71
+ return config;
72
+ }
73
+
74
+ /**
75
+ * Get current configuration
76
+ * @returns {Object} Current configuration
77
+ */
78
+ getConfig() {
79
+ return { ...this.config };
80
+ }
81
+
82
+ /**
83
+ * Get configuration value by path
84
+ * @param {string} path - Configuration path (e.g., 'system.maxAgentsPerProject')
85
+ * @param {*} defaultValue - Default value if path not found
86
+ * @returns {*} Configuration value
87
+ */
88
+ get(path, defaultValue = undefined) {
89
+ const keys = path.split('.');
90
+ let value = this.config;
91
+
92
+ for (const key of keys) {
93
+ if (value && typeof value === 'object' && key in value) {
94
+ value = value[key];
95
+ } else {
96
+ return defaultValue;
97
+ }
98
+ }
99
+
100
+ return value;
101
+ }
102
+
103
+ /**
104
+ * Set configuration value by path
105
+ * @param {string} path - Configuration path
106
+ * @param {*} value - Value to set
107
+ */
108
+ set(path, value) {
109
+ const keys = path.split('.');
110
+ const lastKey = keys.pop();
111
+ let target = this.config;
112
+
113
+ // Navigate to parent object
114
+ for (const key of keys) {
115
+ if (!(key in target) || typeof target[key] !== 'object') {
116
+ target[key] = {};
117
+ }
118
+ target = target[key];
119
+ }
120
+
121
+ target[lastKey] = value;
122
+
123
+ // Notify listeners of config change
124
+ this.notifyConfigChange(this.config);
125
+ }
126
+
127
+ /**
128
+ * Watch configuration files for changes
129
+ * @param {boolean} enable - Enable or disable watching
130
+ * @returns {Promise<void>}
131
+ */
132
+ async watchConfig(enable = true) {
133
+ if (!enable) {
134
+ // Stop all watchers
135
+ for (const [filePath, watcher] of this.watchers) {
136
+ watcher.close();
137
+ this.watchers.delete(filePath);
138
+ }
139
+ return;
140
+ }
141
+
142
+ // Start watching config files
143
+ for (const configPath of this.configPaths) {
144
+ if (this.watchers.has(configPath)) continue;
145
+
146
+ try {
147
+ const { watch } = await import('fs');
148
+ const watcher = watch(configPath, async (eventType) => {
149
+ if (eventType === 'change') {
150
+ try {
151
+ await this.loadConfig();
152
+ } catch (error) {
153
+ console.error(`Failed to reload config after change in ${configPath}:`, error.message);
154
+ }
155
+ }
156
+ });
157
+
158
+ this.watchers.set(configPath, watcher);
159
+ } catch (error) {
160
+ console.warn(`Failed to watch config file ${configPath}:`, error.message);
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Add configuration change listener
167
+ * @param {Function} listener - Change listener function
168
+ */
169
+ addChangeListener(listener) {
170
+ this.changeListeners.add(listener);
171
+ }
172
+
173
+ /**
174
+ * Remove configuration change listener
175
+ * @param {Function} listener - Change listener function
176
+ */
177
+ removeChangeListener(listener) {
178
+ this.changeListeners.delete(listener);
179
+ }
180
+
181
+ /**
182
+ * Load configuration from file
183
+ * @private
184
+ */
185
+ async loadConfigFile(filePath) {
186
+ try {
187
+ const content = await fs.readFile(filePath, 'utf8');
188
+
189
+ const ext = path.extname(filePath).toLowerCase();
190
+ switch (ext) {
191
+ case '.json':
192
+ return JSON.parse(content);
193
+
194
+ case '.js':
195
+ // For .js files, use dynamic import
196
+ const fullPath = path.resolve(filePath);
197
+ const module = await import(fullPath);
198
+ return module.default || module;
199
+
200
+ case '.yaml':
201
+ case '.yml':
202
+ // Would need yaml parser dependency
203
+ throw new Error('YAML configuration files not supported in this implementation');
204
+
205
+ default:
206
+ throw new Error(`Unsupported configuration file format: ${ext}`);
207
+ }
208
+ } catch (error) {
209
+ throw new Error(`Failed to load config file ${filePath}: ${error.message}`);
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Load configuration from environment variables
215
+ * @private
216
+ */
217
+ loadEnvironmentConfig() {
218
+ const envConfig = {};
219
+
220
+ // Map environment variables to config paths
221
+ const envMappings = {
222
+ [`${this.envPrefix}_API_KEY`]: 'apiKey',
223
+ [`${this.envPrefix}_LOG_LEVEL`]: 'logging.level',
224
+ [`${this.envPrefix}_MAX_AGENTS`]: 'system.maxAgentsPerProject',
225
+ [`${this.envPrefix}_DEFAULT_MODEL`]: 'system.defaultModel',
226
+ [`${this.envPrefix}_BACKEND_URL`]: 'backend.baseUrl',
227
+ [`${this.envPrefix}_BACKEND_TIMEOUT`]: 'backend.timeout',
228
+ [`${this.envPrefix}_STATE_DIR`]: 'system.stateDirectory',
229
+ [`${this.envPrefix}_BUDGET_LIMIT`]: 'budget.limit',
230
+ [`${this.envPrefix}_TOOLS_ENABLED`]: 'tools.enabled'
231
+ };
232
+
233
+ for (const [envVar, configPath] of Object.entries(envMappings)) {
234
+ const value = process.env[envVar];
235
+ if (value !== undefined) {
236
+ this.setNestedValue(envConfig, configPath, this.parseEnvValue(value));
237
+ }
238
+ }
239
+
240
+ return envConfig;
241
+ }
242
+
243
+ /**
244
+ * Parse environment variable value
245
+ * @private
246
+ */
247
+ parseEnvValue(value) {
248
+ // Try to parse as JSON first
249
+ try {
250
+ return JSON.parse(value);
251
+ } catch {
252
+ // Return as string if not valid JSON
253
+ return value;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Set nested object value by path
259
+ * @private
260
+ */
261
+ setNestedValue(obj, path, value) {
262
+ const keys = path.split('.');
263
+ const lastKey = keys.pop();
264
+ let target = obj;
265
+
266
+ for (const key of keys) {
267
+ if (!(key in target)) {
268
+ target[key] = {};
269
+ }
270
+ target = target[key];
271
+ }
272
+
273
+ target[lastKey] = value;
274
+ }
275
+
276
+ /**
277
+ * Merge configuration objects
278
+ * @private
279
+ */
280
+ mergeConfig(base, override) {
281
+ const result = { ...base };
282
+
283
+ for (const [key, value] of Object.entries(override)) {
284
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
285
+ result[key] = this.mergeConfig(result[key] || {}, value);
286
+ } else {
287
+ result[key] = value;
288
+ }
289
+ }
290
+
291
+ return result;
292
+ }
293
+
294
+ /**
295
+ * Validate configuration
296
+ * @private
297
+ */
298
+ validateConfig(config) {
299
+ const errors = [];
300
+
301
+ // Validate system configuration
302
+ if (config.system) {
303
+ const { maxAgentsPerProject, defaultModel, stateDirectory } = config.system;
304
+
305
+ if (maxAgentsPerProject && (typeof maxAgentsPerProject !== 'number' || maxAgentsPerProject < 1)) {
306
+ errors.push('system.maxAgentsPerProject must be a positive number');
307
+ }
308
+
309
+ if (defaultModel && !Object.values(MODELS).includes(defaultModel)) {
310
+ errors.push(`system.defaultModel must be one of: ${Object.values(MODELS).join(', ')}`);
311
+ }
312
+
313
+ if (stateDirectory && typeof stateDirectory !== 'string') {
314
+ errors.push('system.stateDirectory must be a string');
315
+ }
316
+ }
317
+
318
+ // Validate backend configuration
319
+ if (config.backend) {
320
+ const { baseUrl, timeout, retryAttempts } = config.backend;
321
+
322
+ if (baseUrl && typeof baseUrl !== 'string') {
323
+ errors.push('backend.baseUrl must be a string');
324
+ }
325
+
326
+ if (timeout && (typeof timeout !== 'number' || timeout < 1000)) {
327
+ errors.push('backend.timeout must be a number >= 1000');
328
+ }
329
+
330
+ if (retryAttempts && (typeof retryAttempts !== 'number' || retryAttempts < 0)) {
331
+ errors.push('backend.retryAttempts must be a non-negative number');
332
+ }
333
+ }
334
+
335
+ // Validate tool configuration
336
+ if (config.tools) {
337
+ for (const [toolName, toolConfig] of Object.entries(config.tools)) {
338
+ if (toolConfig && typeof toolConfig !== 'object') {
339
+ errors.push(`tools.${toolName} must be an object`);
340
+ }
341
+
342
+ if (toolConfig?.timeout && (typeof toolConfig.timeout !== 'number' || toolConfig.timeout < 1000)) {
343
+ errors.push(`tools.${toolName}.timeout must be a number >= 1000`);
344
+ }
345
+ }
346
+ }
347
+
348
+ // Validate model routing
349
+ if (config.models?.routingTable) {
350
+ const routingTable = config.models.routingTable;
351
+
352
+ for (const [task, models] of Object.entries(routingTable)) {
353
+ if (!Array.isArray(models)) {
354
+ errors.push(`models.routingTable.${task} must be an array`);
355
+ continue;
356
+ }
357
+
358
+ for (const model of models) {
359
+ if (!Object.values(MODELS).includes(model)) {
360
+ errors.push(`Invalid model in routing table: ${model}`);
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ return {
367
+ valid: errors.length === 0,
368
+ errors
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Transform configuration after loading
374
+ * @private
375
+ */
376
+ transformConfig(config) {
377
+ // Ensure required nested objects exist
378
+ if (!config.system) config.system = {};
379
+ if (!config.backend) config.backend = {};
380
+ if (!config.tools) config.tools = {};
381
+ if (!config.models) config.models = {};
382
+ if (!config.context) config.context = {};
383
+ if (!config.logging) config.logging = {};
384
+
385
+ // Apply defaults for missing values
386
+ config.system.maxAgentsPerProject = config.system.maxAgentsPerProject || SYSTEM_DEFAULTS.MAX_AGENTS_PER_PROJECT;
387
+ config.system.defaultModel = config.system.defaultModel || SYSTEM_DEFAULTS.DEFAULT_MODEL;
388
+ config.system.stateDirectory = config.system.stateDirectory || SYSTEM_DEFAULTS.STATE_DIRECTORY;
389
+ config.system.maxPauseDuration = config.system.maxPauseDuration || SYSTEM_DEFAULTS.MAX_PAUSE_DURATION;
390
+
391
+ config.context.maxSize = config.context.maxSize || SYSTEM_DEFAULTS.MAX_CONTEXT_SIZE;
392
+ config.context.maxReferences = config.context.maxReferences || SYSTEM_DEFAULTS.MAX_CONTEXT_REFERENCES;
393
+ config.context.autoValidation = config.context.autoValidation !== false;
394
+ config.context.cacheExpiry = config.context.cacheExpiry || SYSTEM_DEFAULTS.CACHE_EXPIRY;
395
+
396
+ // Ensure model routing table exists
397
+ if (!config.models.routingTable) {
398
+ config.models.routingTable = { ...MODEL_ROUTING };
399
+ }
400
+
401
+ // Ensure essential tools are configured
402
+ for (const toolName of Object.values(TOOL_NAMES)) {
403
+ if (!config.tools[toolName]) {
404
+ config.tools[toolName] = {
405
+ enabled: true,
406
+ timeout: SYSTEM_DEFAULTS.MAX_TOOL_EXECUTION_TIME
407
+ };
408
+ }
409
+ }
410
+
411
+ return config;
412
+ }
413
+
414
+ /**
415
+ * Notify configuration change listeners
416
+ * @private
417
+ */
418
+ notifyConfigChange(config) {
419
+ for (const listener of this.changeListeners) {
420
+ try {
421
+ listener(config);
422
+ } catch (error) {
423
+ console.error('Config change listener error:', error.message);
424
+ }
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Get default configuration
430
+ * @private
431
+ */
432
+ getDefaultConfig() {
433
+ return {
434
+ system: {
435
+ maxAgentsPerProject: SYSTEM_DEFAULTS.MAX_AGENTS_PER_PROJECT,
436
+ qualityInspectorInterval: SYSTEM_DEFAULTS.QUALITY_INSPECTOR_INTERVAL,
437
+ defaultModel: SYSTEM_DEFAULTS.DEFAULT_MODEL,
438
+ stateDirectory: SYSTEM_DEFAULTS.STATE_DIRECTORY,
439
+ maxPauseDuration: SYSTEM_DEFAULTS.MAX_PAUSE_DURATION
440
+ },
441
+
442
+ context: {
443
+ maxSize: SYSTEM_DEFAULTS.MAX_CONTEXT_SIZE,
444
+ maxReferences: SYSTEM_DEFAULTS.MAX_CONTEXT_REFERENCES,
445
+ autoValidation: true,
446
+ cacheExpiry: SYSTEM_DEFAULTS.CACHE_EXPIRY
447
+ },
448
+
449
+ models: {
450
+ routingTable: { ...MODEL_ROUTING }
451
+ },
452
+
453
+ tools: {
454
+ [TOOL_NAMES.TERMINAL]: {
455
+ timeout: 30000,
456
+ enabled: true
457
+ },
458
+ [TOOL_NAMES.FILESYSTEM]: {
459
+ maxFileSize: SYSTEM_DEFAULTS.MAX_FILE_SIZE,
460
+ enabled: true
461
+ },
462
+ [TOOL_NAMES.BROWSER]: {
463
+ timeout: 60000,
464
+ enabled: true
465
+ },
466
+ [TOOL_NAMES.AGENT_DELAY]: {
467
+ maxDuration: SYSTEM_DEFAULTS.MAX_PAUSE_DURATION,
468
+ enabled: true
469
+ }
470
+ },
471
+
472
+ backend: {
473
+ baseUrl: 'https://api.loxia.ai',
474
+ timeout: 60000,
475
+ retryAttempts: 3
476
+ },
477
+
478
+ budget: {
479
+ limit: 100.00,
480
+ alertThreshold: 0.8,
481
+ trackUsage: true
482
+ },
483
+
484
+ logging: {
485
+ level: 'info',
486
+ outputs: ['console'],
487
+ colors: true,
488
+ timestamp: true
489
+ },
490
+
491
+ interfaces: {
492
+ cli: {
493
+ enabled: true,
494
+ historySize: 1000
495
+ },
496
+ web: {
497
+ enabled: true,
498
+ port: 3000,
499
+ host: 'localhost'
500
+ },
501
+ vscode: {
502
+ enabled: true,
503
+ contextMenus: true,
504
+ statusBar: true
505
+ }
506
+ }
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Export configuration to file
512
+ * @param {string} filePath - Target file path
513
+ * @param {Object} options - Export options
514
+ * @returns {Promise<void>}
515
+ */
516
+ async exportConfig(filePath, options = {}) {
517
+ const config = options.includeDefaults ?
518
+ this.getConfig() :
519
+ this.getConfigWithoutDefaults();
520
+
521
+ const ext = path.extname(filePath).toLowerCase();
522
+ let content;
523
+
524
+ switch (ext) {
525
+ case '.json':
526
+ content = JSON.stringify(config, null, 2);
527
+ break;
528
+
529
+ case '.js':
530
+ content = `module.exports = ${JSON.stringify(config, null, 2)};`;
531
+ break;
532
+
533
+ default:
534
+ throw new Error(`Unsupported export format: ${ext}`);
535
+ }
536
+
537
+ await fs.writeFile(filePath, content, 'utf8');
538
+ }
539
+
540
+ /**
541
+ * Get configuration without default values
542
+ * @private
543
+ */
544
+ getConfigWithoutDefaults() {
545
+ // This would return only explicitly set values
546
+ // For now, return the full config
547
+ return this.getConfig();
548
+ }
549
+
550
+ /**
551
+ * Reset configuration to defaults
552
+ */
553
+ resetToDefaults() {
554
+ this.config = { ...this.defaultConfig };
555
+ this.notifyConfigChange(this.config);
556
+ }
557
+
558
+ /**
559
+ * Cleanup resources
560
+ */
561
+ cleanup() {
562
+ // Stop watching files
563
+ for (const watcher of this.watchers.values()) {
564
+ watcher.close();
565
+ }
566
+ this.watchers.clear();
567
+
568
+ // Clear listeners
569
+ this.changeListeners.clear();
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Create a configuration manager instance
575
+ * @param {Object} options - Configuration options
576
+ * @returns {ConfigManager} ConfigManager instance
577
+ */
578
+ function createConfigManager(options = {}) {
579
+ return new ConfigManager(options);
580
+ }
581
+
582
+ export { ConfigManager, createConfigManager };