UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

1,008 lines (863 loc) 25.9 kB
import { z } from 'zod'; import { BaseTool } from './base-tool.js'; import { exec, spawn, ChildProcess } from 'child_process'; import { promisify } from 'util'; import { existsSync } from 'fs'; import { join } from 'path'; import { SecureToolFactory } from '../security/secure-tool-factory.js'; import { logger } from '../logger.js'; const execAsync = promisify(exec); /** * Terminal Command Execution Tool */ export class TerminalExecuteTool extends BaseTool { private runningProcesses: Map<string, ChildProcess> = new Map(); constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ command: z.string().describe('Command to execute'), args: z.array(z.string()).optional().describe('Command arguments'), workingDirectory: z .string() .optional() .describe('Working directory (defaults to agent working directory)'), timeout: z.number().optional().default(30000).describe('Timeout in milliseconds'), environment: z.record(z.string()).optional().describe('Environment variables'), shell: z.boolean().optional().default(true).describe('Execute in shell'), background: z.boolean().optional().default(false).describe('Run in background'), processId: z.string().optional().describe('Unique ID for background processes'), }); super({ name: 'executeCommand', description: 'Execute terminal commands with full control over execution environment', category: 'Terminal', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { const cwd = args.workingDirectory || this.agentContext.workingDirectory; // Validate working directory if (!existsSync(cwd)) { return { error: `Working directory does not exist: ${cwd}` }; } // Build full command const fullCommand = args.args ? `${args.command} ${args.args.join(' ')}` : args.command; // Check if command is safe if (!this.isCommandSafe(args.command)) { return { error: `Command blocked for security: ${args.command}`, suggestion: 'Use system administration tools or request explicit permission for system commands', }; } if (args.background) { return await this.executeInBackground(fullCommand, args, cwd); } else { return await this.executeSync(fullCommand, args, cwd); } } catch (error) { return { error: `Command execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async executeSync(command: string, args: any, cwd: string): Promise<any> { try { const execOptions = { cwd, timeout: args.timeout, maxBuffer: 1024 * 1024 * 10, // 10MB buffer env: { ...process.env, ...args.environment }, shell: args.shell, }; const result = await execAsync(command, execOptions); return { success: true, command, exitCode: 0, stdout: result.stdout, stderr: result.stderr, workingDirectory: cwd, executionTime: Date.now(), }; } catch (error: any) { return { success: false, command, exitCode: error.code || 1, stdout: error.stdout || '', stderr: error.stderr || error.message, workingDirectory: cwd, error: error.message, }; } } private async executeInBackground(command: string, args: any, cwd: string): Promise<any> { try { const processId = args.processId || `bg_${Date.now()}`; const childProcess = spawn(args.command, args.args || [], { cwd, env: { ...process.env, ...args.environment }, stdio: ['ignore', 'pipe', 'pipe'], shell: args.shell, }); this.runningProcesses.set(processId, childProcess); let stdout = ''; let stderr = ''; childProcess.stdout?.on('data', data => { stdout += data.toString(); }); childProcess.stderr?.on('data', data => { stderr += data.toString(); }); // Set timeout if specified let timeoutId: NodeJS.Timeout | undefined; if (args.timeout) { timeoutId = setTimeout(() => { childProcess.kill('SIGTERM'); }, args.timeout); } return new Promise(resolve => { childProcess.on('exit', code => { if (timeoutId) clearTimeout(timeoutId); this.runningProcesses.delete(processId); resolve({ success: code === 0, command, processId, exitCode: code, stdout, stderr, workingDirectory: cwd, completed: true, }); }); childProcess.on('error', error => { if (timeoutId) clearTimeout(timeoutId); this.runningProcesses.delete(processId); resolve({ success: false, command, processId, error: error.message, stdout, stderr, workingDirectory: cwd, completed: true, }); }); // Return immediately for background processes resolve({ success: true, command, processId, status: 'running', background: true, workingDirectory: cwd, message: `Process started with ID: ${processId}`, }); }); } catch (error) { return { success: false, command, error: error instanceof Error ? error.message : 'Unknown error', }; } } private isCommandSafe(command: string): boolean { const dangerousCommands = [ 'rm -rf /', 'format', 'del /f /s /q', 'sudo rm', 'chmod 777', 'dd if=', 'mkfs', 'fdisk', ':(){ :|:& };:', 'shutdown', 'reboot', 'halt', 'init 0', 'init 6', ]; const cmd = command.toLowerCase().trim(); // Check against dangerous patterns for (const dangerous of dangerousCommands) { if (cmd.includes(dangerous.toLowerCase())) { return false; } } // Allow common development commands const safeCommands = [ 'npm', 'yarn', 'pnpm', 'node', 'python', 'pip', 'git', 'gcc', 'make', 'cmake', 'cargo', 'go', 'tsc', 'webpack', 'rollup', 'vite', 'parcel', 'jest', 'mocha', 'cypress', 'playwright', 'ls', 'dir', 'cat', 'type', 'head', 'tail', 'grep', 'find', 'locate', 'which', 'where', 'echo', 'printf', 'curl', 'wget', 'ping', 'ps', 'top', 'htop', 'whoami', 'pwd', 'mkdir', 'touch', 'cp', 'mv', 'ln', ]; const firstWord = cmd.split(' ')[0]; return safeCommands.some(safe => firstWord.startsWith(safe)); } } /** * Process Management Tool */ export class ProcessManagementTool extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ action: z.enum(['list', 'kill', 'status', 'killAll']), processId: z.string().optional().describe('Process ID for specific operations'), signal: z .string() .optional() .default('SIGTERM') .describe('Signal to send when killing process'), }); super({ name: 'manageProcesses', description: 'Manage background processes: list, kill, check status', category: 'Terminal', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { // Use secure tool factory instead of direct TerminalExecuteTool const { RBACSystem } = await import('../security/production-rbac-system.js'); const { SecurityAuditLogger } = await import('../security/security-audit-logger.js'); const { SecretsManager } = await import('../security/secrets-manager.js'); const secretsManager = new SecretsManager(); const rbacSystem = new RBACSystem(null as any, secretsManager); const auditLogger = new SecurityAuditLogger(secretsManager, './audit-logs'); const secureToolFactory = new SecureToolFactory(rbacSystem, auditLogger); const terminalTool = secureToolFactory.createTerminalTool(this.agentContext); switch (args.action) { case 'list': return await this.listProcesses(); case 'status': return await this.getProcessStatus(args.processId!); case 'kill': return await this.killProcess(args.processId!, args.signal); case 'killAll': return await this.killAllProcesses(); default: return { error: `Unknown action: ${args.action}` }; } } catch (error) { return { error: `Process management failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async listProcesses(): Promise<any> { try { // Get system processes const result = await execAsync('ps aux || tasklist', { cwd: this.agentContext.workingDirectory, }); return { success: true, processes: result.stdout, count: result.stdout.split('\n').length - 1, }; } catch (error) { return { error: `Failed to list processes: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async getProcessStatus(processId: string): Promise<any> { try { // Check if process is running const isWindows = process.platform === 'win32'; const command = isWindows ? `tasklist /FI "PID eq ${processId}"` : `ps -p ${processId}`; const result = await execAsync(command); return { success: true, processId, running: result.stdout.includes(processId), details: result.stdout, }; } catch (error) { return { success: false, processId, running: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } private async killProcess(processId: string, signal: string): Promise<any> { try { const isWindows = process.platform === 'win32'; const command = isWindows ? `taskkill /PID ${processId} /F` : `kill -${signal} ${processId}`; const result = await execAsync(command); return { success: true, processId, signal, message: `Process ${processId} killed`, output: result.stdout, }; } catch (error) { return { success: false, processId, error: error instanceof Error ? error.message : 'Unknown error', }; } } private async killAllProcesses(): Promise<any> { // This is a dangerous operation, so we'll be very restrictive return { error: 'killAll operation not allowed for security reasons', suggestion: 'Use individual process management instead', }; } } /** * Shell Environment Tool */ export class ShellEnvironmentTool extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ action: z.enum(['getEnv', 'setEnv', 'getPath', 'which', 'whereis', 'pwd', 'whoami']), variable: z.string().optional().describe('Environment variable name'), value: z.string().optional().describe('Environment variable value'), command: z.string().optional().describe('Command to locate'), }); super({ name: 'shellEnvironment', description: 'Manage shell environment: get/set variables, locate commands, get system info', category: 'Terminal', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { switch (args.action) { case 'getEnv': return this.getEnvironmentVariable(args.variable); case 'setEnv': return this.setEnvironmentVariable(args.variable!, args.value!); case 'getPath': return this.getPathVariable(); case 'which': return await this.locateCommand(args.command!, 'which'); case 'whereis': return await this.locateCommand(args.command!, 'whereis'); case 'pwd': return this.getCurrentDirectory(); case 'whoami': return await this.getCurrentUser(); default: return { error: `Unknown action: ${args.action}` }; } } catch (error) { return { error: `Shell environment operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private getEnvironmentVariable(variable?: string): any { if (variable) { return { variable, value: process.env[variable] || null, exists: variable in process.env, }; } else { return { environment: process.env, count: Object.keys(process.env).length, }; } } private setEnvironmentVariable(variable: string, value: string): any { try { process.env[variable] = value; return { success: true, variable, value, message: `Environment variable ${variable} set`, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } private getPathVariable(): any { const pathVar = process.env.PATH || process.env.Path; const paths = pathVar ? pathVar.split(process.platform === 'win32' ? ';' : ':') : []; return { path: pathVar, paths, count: paths.length, }; } private async locateCommand(command: string, method: 'which' | 'whereis'): Promise<any> { try { const cmd = process.platform === 'win32' ? `where ${command}` : `${method} ${command}`; const result = await execAsync(cmd); return { success: true, command, method, location: result.stdout.trim(), found: result.stdout.trim().length > 0, }; } catch (error) { return { success: false, command, method, found: false, error: error instanceof Error ? error.message : 'Command not found', }; } } private getCurrentDirectory(): any { return { workingDirectory: this.agentContext.workingDirectory, currentDirectory: process.cwd(), platform: process.platform, }; } private async getCurrentUser(): Promise<any> { try { const command = process.platform === 'win32' ? 'whoami' : 'whoami'; const result = await execAsync(command); return { success: true, user: result.stdout.trim(), platform: process.platform, nodeVersion: process.version, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', }; } } } /** * Package Manager Tool */ export class PackageManagerTool extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ manager: z .enum(['npm', 'yarn', 'pnpm', 'pip', 'cargo', 'go']) .optional() .describe('Package manager to use (auto-detect if not specified)'), action: z.enum([ 'install', 'uninstall', 'update', 'list', 'info', 'search', 'init', 'run', 'test', 'build', ]), packages: z.array(z.string()).optional().describe('Package names'), script: z.string().optional().describe('Script name to run'), flags: z.array(z.string()).optional().describe('Additional flags'), global: z.boolean().optional().default(false).describe('Global installation'), dev: z.boolean().optional().default(false).describe('Development dependency'), }); super({ name: 'packageManager', description: 'Manage packages and dependencies using various package managers', category: 'Package Management', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<any> { try { const manager = args.manager || (await this.detectPackageManager()); const command = this.buildPackageCommand(manager, args); if (!command) { return { error: `Unable to build command for manager: ${manager}` }; } // Execute command using terminal tool // Use secure tool factory for terminal execution const { RBACSystem } = await import('../security/production-rbac-system.js'); const { SecurityAuditLogger } = await import('../security/security-audit-logger.js'); const { SecretsManager } = await import('../security/secrets-manager.js'); const secretsManager = new SecretsManager(); const rbacSystem = new RBACSystem(null as any, secretsManager); const auditLogger = new SecurityAuditLogger(secretsManager, './audit-logs'); const secureToolFactory = new SecureToolFactory(rbacSystem, auditLogger); const terminalTool = secureToolFactory.createTerminalTool(this.agentContext); const result = await terminalTool.execute({ command: command, timeout: 120000, // 2 minutes for package operations workingDirectory: this.agentContext.workingDirectory, }); return { ...result, manager, action: args.action, packages: args.packages, }; } catch (error) { return { error: `Package management failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async detectPackageManager(): Promise<string> { const cwd = this.agentContext.workingDirectory; // Check for lock files to determine package manager if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn'; if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; if (existsSync(join(cwd, 'package-lock.json'))) return 'npm'; if (existsSync(join(cwd, 'Cargo.toml'))) return 'cargo'; if (existsSync(join(cwd, 'go.mod'))) return 'go'; if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) return 'pip'; // Default to npm for Node.js projects if (existsSync(join(cwd, 'package.json'))) return 'npm'; return 'npm'; } private buildPackageCommand(manager: string, args: any): string | null { const flags = args.flags || []; const globalFlag = args.global; const devFlag = args.dev; switch (manager) { case 'npm': return this.buildNpmCommand( args.action, args.packages, args.script, flags, globalFlag, devFlag ); case 'yarn': return this.buildYarnCommand( args.action, args.packages, args.script, flags, globalFlag, devFlag ); case 'pnpm': return this.buildPnpmCommand( args.action, args.packages, args.script, flags, globalFlag, devFlag ); case 'pip': return this.buildPipCommand(args.action, args.packages, flags, globalFlag); case 'cargo': return this.buildCargoCommand(args.action, args.packages, flags); case 'go': return this.buildGoCommand(args.action, args.packages, flags); default: return null; } } private buildNpmCommand( action: string, packages?: string[], script?: string, flags: string[] = [], global = false, dev = false ): string { let cmd = 'npm'; switch (action) { case 'install': cmd += ' install'; if (packages) cmd += ` ${packages.join(' ')}`; if (global) cmd += ' -g'; if (dev) cmd += ' --save-dev'; break; case 'uninstall': cmd += ' uninstall'; if (packages) cmd += ` ${packages.join(' ')}`; if (global) cmd += ' -g'; break; case 'update': cmd += ' update'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'list': cmd += ' list'; if (global) cmd += ' -g'; break; case 'info': cmd += ' info'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'search': cmd += ' search'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'init': cmd += ' init'; if (!flags.includes('-y')) cmd += ' -y'; break; case 'run': cmd += ' run'; if (script) cmd += ` ${script}`; break; case 'test': cmd += ' test'; break; case 'build': cmd += ' run build'; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } private buildYarnCommand( action: string, packages?: string[], script?: string, flags: string[] = [], global = false, dev = false ): string { let cmd = 'yarn'; switch (action) { case 'install': if (packages) { cmd += ' add'; cmd += ` ${packages.join(' ')}`; if (dev) cmd += ' --dev'; } else { cmd += ' install'; } if (global) cmd += ' global'; break; case 'uninstall': cmd += ' remove'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'update': cmd += ' upgrade'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'list': cmd += ' list'; break; case 'info': cmd += ' info'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'init': cmd += ' init'; if (!flags.includes('-y')) cmd += ' -y'; break; case 'run': if (script) cmd += ` ${script}`; break; case 'test': cmd += ' test'; break; case 'build': cmd += ' build'; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } private buildPnpmCommand( action: string, packages?: string[], script?: string, flags: string[] = [], global = false, dev = false ): string { let cmd = 'pnpm'; switch (action) { case 'install': if (packages) { cmd += ' add'; cmd += ` ${packages.join(' ')}`; if (dev) cmd += ' --save-dev'; } else { cmd += ' install'; } if (global) cmd += ' -g'; break; case 'uninstall': cmd += ' remove'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'update': cmd += ' update'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'list': cmd += ' list'; if (global) cmd += ' -g'; break; case 'run': if (script) cmd += ` ${script}`; break; case 'test': cmd += ' test'; break; case 'build': cmd += ' build'; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } private buildPipCommand( action: string, packages?: string[], flags: string[] = [], global = false ): string { let cmd = 'pip'; switch (action) { case 'install': cmd += ' install'; if (packages) cmd += ` ${packages.join(' ')}`; if (!global) cmd += ' --user'; break; case 'uninstall': cmd += ' uninstall'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'update': cmd += ' install --upgrade'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'list': cmd += ' list'; break; case 'info': cmd += ' show'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'search': cmd += ' search'; if (packages) cmd += ` ${packages.join(' ')}`; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } private buildCargoCommand(action: string, packages?: string[], flags: string[] = []): string { let cmd = 'cargo'; switch (action) { case 'install': cmd += ' install'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'uninstall': cmd += ' uninstall'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'update': cmd += ' update'; break; case 'build': cmd += ' build'; break; case 'test': cmd += ' test'; break; case 'run': cmd += ' run'; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } private buildGoCommand(action: string, packages?: string[], flags: string[] = []): string { let cmd = 'go'; switch (action) { case 'install': cmd += ' install'; if (packages) cmd += ` ${packages.join(' ')}`; break; case 'build': cmd += ' build'; break; case 'test': cmd += ' test'; break; case 'run': cmd += ' run'; break; } if (flags.length > 0) { cmd += ` ${flags.join(' ')}`; } return cmd; } }