(name, args)
| 275 | } |
| 276 | |
| 277 | async _executeTool(name, args) { |
| 278 | const fs = require('fs'); |
| 279 | const { execSync } = require('child_process'); |
| 280 | const cwd = this.config.cwd; |
| 281 | |
| 282 | switch (name) { |
| 283 | case 'read_file': { |
| 284 | const safe = safeResolvePath(args.path, cwd); |
| 285 | if (!safe.ok) return { error: `read_file rejected: ${safe.reason}` }; |
| 286 | const filePath = safe.fullPath; |
| 287 | if (!fs.existsSync(filePath)) return { error: `File not found: ${args.path}` }; |
| 288 | const content = fs.readFileSync(filePath, 'utf-8'); |
| 289 | const lines = content.split('\n'); |
| 290 | const start = (args.start_line || 1) - 1; |
| 291 | const end = args.end_line || lines.length; |
| 292 | const numbered = lines.slice(start, end).map((l, i) => `${String(start + i + 1).padStart(4)}│ ${sanitizeToolOutput(l)}`).join('\n'); |
| 293 | return { result: `${args.path} (${lines.length} lines):\n${numbered}` }; |
| 294 | } |
| 295 | |
| 296 | case 'write_file': { |
| 297 | const safe = safeResolvePath(args.path, cwd); |
| 298 | if (!safe.ok) return { error: `write_file rejected: ${safe.reason}` }; |
| 299 | const filePath = safe.fullPath; |
| 300 | const dir = path.dirname(filePath); |
| 301 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); |
| 302 | const existed = fs.existsSync(filePath); |
| 303 | fs.writeFileSync(filePath, args.content); |
| 304 | const action = existed ? 'Updated' : 'Created'; |
| 305 | return { result: `${action} ${args.path} (${args.content.split('\n').length} lines)`, action, path: args.path }; |
| 306 | } |
| 307 | |
| 308 | case 'patch': { |
| 309 | const safe = safeResolvePath(args.path, cwd); |
| 310 | if (!safe.ok) return { error: `patch rejected: ${safe.reason}` }; |
| 311 | const filePath = safe.fullPath; |
| 312 | if (!fs.existsSync(filePath)) return { error: `File not found: ${args.path}` }; |
| 313 | let content = fs.readFileSync(filePath, 'utf-8'); |
| 314 | const count = content.split(args.old_str).length - 1; |
| 315 | if (count === 0) return { error: `old_str not found in ${args.path}` }; |
| 316 | if (count > 1) return { error: `old_str matches ${count} locations. Be more specific.` }; |
| 317 | content = content.replace(args.old_str, args.new_str); |
| 318 | fs.writeFileSync(filePath, content); |
| 319 | return { result: `Patched ${args.path}`, action: 'Edited', path: args.path }; |
| 320 | } |
| 321 | |
| 322 | case 'bash': { |
| 323 | let command = args.command; |
| 324 | if (process.platform === 'win32') { |
| 325 | command = command.replace(/^ls\b/, 'dir').replace(/^cat /, 'type '); |
| 326 | } |
| 327 | try { |
| 328 | const output = execSync(command, { encoding: 'utf-8', timeout: 30000, cwd, maxBuffer: 1024 * 1024 }); |
| 329 | return { result: sanitizeToolOutput(output).slice(0, 3000) || '(no output)', command }; |
| 330 | } catch (e) { |
| 331 | const output = (e.stdout || '') + (e.stderr || ''); |
| 332 | return { result: sanitizeToolOutput(output).slice(0, 2000) || sanitizeToolOutput(e.message || ''), error: `Exit code ${e.status || 'unknown'}`, command }; |
| 333 | } |
| 334 | } |
no test coverage detected