import * as fs from 'fs'; import * as path from 'path'; import { validatePath } from '../../security'; import { FileSystemError } from '../../core/errors'; import { HandlerContext } from './types'; /** * `` + `` action handler. * * AI 가 한 턴에 여러 개의 create/edit 태그를 내뱉을 수 있으므로, 각 태그마다 * regex 로 잡아서 순서대로 처리한다. validatePath() 로 sandbox 보장 + 매 파일 * 쓰기 전에 transactionManager.record() 로 롤백 지점 기록. */ export async function applyFileCreateEditActions(ctx: HandlerContext): Promise { const { aiMessage, rootPath, activeBrainDir, report } = ctx; // Action 1: Create File const createRegex = /([\s\S]*?)<\/create_file>/gi; let match; while ((match = createRegex.exec(aiMessage)) !== null) { const relPath = match[1].trim(); const content = match[2].trim(); try { const absPath = validatePath(rootPath, relPath); await ctx.transactionManager.record(absPath); fs.mkdirSync(path.dirname(absPath), { recursive: true }); fs.writeFileSync(absPath, content, 'utf-8'); report.push(`✅ Created: ${relPath}`); ctx.setFirstCreated(absPath); if (absPath.startsWith(activeBrainDir)) ctx.markBrainModified(); } catch (err: any) { throw new FileSystemError(`Failed to create file ${relPath}: ${err.message}`, relPath, err); } } // Action 2: Edit File const editRegex = /([\s\S]*?)<\/edit_file>/gi; while ((match = editRegex.exec(aiMessage)) !== null) { const relPath = match[1].trim(); const editContent = match[2].trim(); try { const absPath = validatePath(rootPath, relPath); if (fs.existsSync(absPath)) { await ctx.transactionManager.record(absPath); let currentContent = fs.readFileSync(absPath, 'utf-8'); const searchMatch = editContent.match(/([\s\S]*?)<\/search>\s*([\s\S]*?)<\/replace>/i); if (searchMatch) { const searchStr = searchMatch[1]; const replaceStr = searchMatch[2]; if (currentContent.includes(searchStr)) { currentContent = currentContent.replace(searchStr, replaceStr); fs.writeFileSync(absPath, currentContent, 'utf-8'); report.push(`📝 Updated: ${relPath}`); } else { report.push(`⚠️ Search string not found in ${relPath}`); } } else { fs.writeFileSync(absPath, editContent, 'utf-8'); report.push(`📝 Updated (Full): ${relPath}`); } if (absPath.startsWith(activeBrainDir)) ctx.markBrainModified(); } else { report.push(`❌ File not found: ${relPath}`); } } catch (err: any) { throw new FileSystemError(`Failed to edit file ${relPath}: ${err.message}`, relPath, err); } } }