codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
227 lines • 8.49 kB
JavaScript
/**
* File Explorer Agent - Handles file system operations and project exploration
* Integrates with the existing UnifiedAgent system and MCP tools
*/
import { UnifiedAgent } from '../agent.js';
import { logger } from '../logger.js';
import { promises as fs } from 'fs';
import { join, relative } from 'path';
export class FileExplorerAgent extends UnifiedAgent {
constructor(modelClient, performanceMonitor) {
super(modelClient, performanceMonitor);
}
async processRequest(input) {
logger.info('📁 File Explorer Agent processing request');
// Check if this is a file system operation request
if (this.isFileSystemRequest(input)) {
return await this.handleFileSystemOperation(input);
}
// For general file-related analysis, use the UnifiedAgent with file-specific context
const request = {
id: `file-explorer-${Date.now()}`,
input: `File System Analysis: ${input}`,
type: 'file-analysis',
mode: 'fast', // File operations should be fast
};
const response = await this.execute(request);
// Enhance with file-specific metadata
if (response.success && response.result) {
const enhancedResult = await this.enhanceWithFileContext(input, response.result);
return {
...response,
result: enhancedResult,
};
}
return response;
}
isFileSystemRequest(input) {
const fileSystemKeywords = [
'list files',
'show directory',
'file structure',
'explore folder',
'find file',
'search files',
'directory tree',
'project structure',
];
return fileSystemKeywords.some(keyword => input.toLowerCase().includes(keyword));
}
async handleFileSystemOperation(input) {
try {
const workingDir = process.cwd();
if (input.toLowerCase().includes('project structure') ||
input.toLowerCase().includes('directory tree')) {
const structure = await this.getProjectStructureInternal(workingDir);
return {
success: true,
result: {
content: structure,
operation: 'project-structure',
directory: workingDir,
},
workflowId: `file-op-${Date.now()}`,
executionTime: 0,
};
}
if (input.toLowerCase().includes('list files')) {
const files = await this.listFiles(workingDir);
return {
success: true,
result: {
content: `Files in ${workingDir}:\n${files.join('\n')}`,
operation: 'list-files',
files,
directory: workingDir,
},
workflowId: `file-op-${Date.now()}`,
executionTime: 0,
};
}
// Default: provide file system status
const stats = await this.getDirectoryStats(workingDir);
return {
success: true,
result: {
content: `Directory analysis for ${workingDir}:\n${JSON.stringify(stats, null, 2)}`,
operation: 'directory-stats',
stats,
directory: workingDir,
},
workflowId: `file-op-${Date.now()}`,
executionTime: 0,
};
}
catch (error) {
return {
success: false,
result: {},
error: `File system operation failed: ${error instanceof Error ? error.message : String(error)}`,
workflowId: `file-op-${Date.now()}`,
executionTime: 0,
};
}
}
async getProjectStructureInternal(rootPath) {
const structure = [];
const maxDepth = 3;
const ignorePatterns = ['node_modules', '.git', 'dist', 'build', '.vscode'];
const walkDirectory = async (dirPath, depth = 0) => {
if (depth > maxDepth)
return;
try {
const items = await fs.readdir(dirPath);
for (const item of items) {
if (ignorePatterns.some(pattern => item.includes(pattern)))
continue;
const itemPath = join(dirPath, item);
const stats = await fs.stat(itemPath);
const relativePath = relative(rootPath, itemPath);
if (stats.isDirectory()) {
structure.push(`${' '.repeat(depth)}📁 ${relativePath}/`);
await walkDirectory(itemPath, depth + 1);
}
else if (stats.isFile()) {
const ext = item.split('.').pop()?.toLowerCase();
const icon = this.getFileIcon(ext);
structure.push(`${' '.repeat(depth)}${icon} ${relativePath}`);
}
}
}
catch (error) {
structure.push(`${' '.repeat(depth)}❌ Error reading ${relative(rootPath, dirPath)}`);
}
};
await walkDirectory(rootPath);
return `Project Structure:\n${structure.slice(0, 100).join('\n')}${structure.length > 100 ? '\n... (truncated)' : ''}`;
}
async listFiles(dirPath) {
try {
const items = await fs.readdir(dirPath);
const files = [];
for (const item of items) {
const stats = await fs.stat(join(dirPath, item));
if (stats.isFile()) {
files.push(item);
}
else if (stats.isDirectory()) {
files.push(`${item}/`);
}
}
return files;
}
catch (error) {
return [`Error reading directory: ${error instanceof Error ? error.message : String(error)}`];
}
}
async getDirectoryStats(dirPath) {
try {
const items = await fs.readdir(dirPath);
let fileCount = 0;
let dirCount = 0;
const fileTypes = {};
for (const item of items) {
const stats = await fs.stat(join(dirPath, item));
if (stats.isFile()) {
fileCount++;
const ext = item.split('.').pop()?.toLowerCase() || 'no-extension';
fileTypes[ext] = (fileTypes[ext] || 0) + 1;
}
else if (stats.isDirectory()) {
dirCount++;
}
}
return {
totalFiles: fileCount,
totalDirectories: dirCount,
fileTypeBreakdown: fileTypes,
path: dirPath,
};
}
catch (error) {
return {
error: error instanceof Error ? error.message : String(error),
path: dirPath,
};
}
}
getFileIcon(ext) {
const iconMap = {
js: '📄',
ts: '📄',
tsx: '📄',
jsx: '📄',
json: '⚙️',
md: '📝',
css: '🎨',
html: '🌐',
py: '🐍',
java: '☕',
cpp: '⚡',
c: '⚡',
rs: '🦀',
};
return iconMap[ext || ''] || '📄';
}
async enhanceWithFileContext(input, result) {
return {
...result,
fileSystemContext: {
workingDirectory: process.cwd(),
requestType: 'file-exploration',
capabilities: [
'Project structure analysis',
'File listing and navigation',
'Directory statistics',
'File type analysis',
],
},
metadata: {
...(result.metadata || {}),
agentType: 'file-explorer',
enhanced: true,
},
};
}
}
//# sourceMappingURL=file-explorer-agent.js.map