// server.js import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import { StateManager } from './stateManager.js'; import { AIEngine } from './aiEngine.js'; import fs from 'fs'; const app = express(); const PORT = process.env.PORT || 7860; // Load prompts for dynamic usage const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8')); app.use(cors()); app.use(bodyParser.json({ limit: '50mb' })); // Increased limit for images // Middleware: Validate User/Project IDs const validateRequest = (req, res, next) => { const { userId, projectId } = req.body; if (!userId || !projectId) { return res.status(400).json({ error: "Missing userId or projectId" }); } next(); }; /** * 1. NEW PROJECT * Flow: PM (GDD) -> PM (Tasks) -> Worker (Task 1) */ app.post('/new/project', validateRequest, async (req, res) => { const { userId, projectId, description } = req.body; try { // 1. Generate GDD with PM (High Thinking) const pmHistory = []; const gddPrompt = `Create a comprehensive GDD for: ${description}`; const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt); pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] }); pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] }); // 2. Generate Initial Tasks const taskPrompt = "Based on the GDD, generate a prioritized list of initial technical tasks. Output them as a JSON list."; const taskResponse = await AIEngine.callPM(pmHistory, taskPrompt); pmHistory.push({ role: 'user', parts: [{ text: taskPrompt }] }); pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] }); // 3. Initialize Worker with GDD Context and Execute First Task const workerHistory = []; const initialWorkerPrompt = `Here is the GDD: ${gddResponse}. \n\n Here are the tasks: ${taskResponse}. \n\n EXECUTE THE FIRST TASK NOW.`; const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt); workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] }); workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] }); // Save State await StateManager.updateProject(projectId, { userId, pmHistory, workerHistory, gdd: gddResponse, failureCount: 0 }); // Queue the result for the plugin StateManager.queueCommand(projectId, workerResponse); res.json({ success: true, message: "Project initialized", gddPreview: gddResponse.substring(0, 200) }); } catch (err) { console.error(err); res.status(500).json({ error: "AI Processing Failed" }); } }); /** * 2. FEEDBACK (The Main Loop Endpoint) * Accepts text + optional image. Hits Worker. Checks for escalation. */ app.post('/project/feedback', async (req, res) => { const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete } = req.body; const project = await StateManager.getProject(projectId); if (!project) return res.status(404).json({ error: "Project not found." }); // 1. MISSION COMPLETE: CALL PM & NUKE WORKER if (taskComplete) { console.log(`[${projectId}] ✅ TASK COMPLETE. Calling PM & Nuking Worker.`); // A. Notify PM (Add to PM History) const summary = `Worker has successfully completed the previous task. Logs: ${logContext?.logs || "Clean"}. Ready for next assignment.`; project.pmHistory.push({ role: 'user', parts: [{ text: summary }] }); // B. Nuke Worker (Reset History) project.workerHistory = []; // C. Update State await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, workerHistory: [], // THE NUKE status: "IDLE" // Waiting for user or PM to start next task }); return res.json({ success: true, message: "Worker Nuked. PM Notified. Loop Stopped." }); } // 2. ERROR DETECTION (Safety Net) let isFailure = false; if (logContext && logContext.logs) { const errorKeywords = ["Infinite yield", "Stack Begin", "Error:", "Exception", "failed"]; if (errorKeywords.some(keyword => logContext.logs.includes(keyword))) { isFailure = true; console.log(`[${projectId}] ⚠️ Detected Error in Logs.`); } } // 3. BUILD AI PROMPT const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext }); let finalPrompt = fullInput; if (isFailure && (!prompt || prompt.length < 5)) { finalPrompt += "\n[SYSTEM ALERT]: Runtime Error detected. Fix the code immediately. Do not use WaitForChild. Do not yield."; } console.log(`[${projectId}] Processing Feedback...`); // 4. CALL AI try { const response = await AIEngine.callWorker(project.workerHistory, finalPrompt); // Escalation Logic if (response.includes("STATUS: ESCALATE_TO_PM") || (project.failureCount > 2)) { console.log(`[${projectId}] Escalating to PM...`); const guidance = await AIEngine.callPM(project.pmHistory, `Worker Stuck. Input: ${fullInput}`); const fixed = await AIEngine.callWorker(project.workerHistory, `PM GUIDANCE: ${guidance}`); await StateManager.updateProject(projectId, { failureCount: 0 }); project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] }); project.workerHistory.push({ role: 'model', parts: [{ text: fixed }] }); await StateManager.queueCommand(projectId, fixed); return res.json({ success: true, message: "Escalated & Fixed." }); } project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] }); project.workerHistory.push({ role: 'model', parts: [{ text: response }] }); await StateManager.updateProject(projectId, { workerHistory: project.workerHistory }); await StateManager.queueCommand(projectId, response); res.json({ success: true, message: "Input Processed." }); } catch (err) { console.error("AI Error:", err); res.status(500).json({ error: "AI Failed" }); } }); /** * 3. INTERNAL PING WORKER * PM sends guidance to Worker. */ app.post('/internal/ping/worker', validateRequest, async (req, res) => { const { projectId, guidancePrompt } = req.body; const project = await StateManager.getProject(projectId); if (!project) return res.status(404).json({ error: "Project not found" }); // Inject PM Context (GDD/History) into the message if requested const fullContextPrompt = `PM CONTEXT: ${JSON.stringify(project.pmHistory)}. \n GUIDANCE: ${guidancePrompt}`; const response = await AIEngine.callWorker(project.workerHistory, fullContextPrompt); StateManager.queueCommand(projectId, response); res.json({ success: true, message: "Guidance sent to Worker" }); }); /** * 4. INTERNAL PING PM * Worker signals distress -> PM responds. */ app.post('/internal/ping/pm', validateRequest, async (req, res) => { const { projectId, distressMessage } = req.body; const project = await StateManager.getProject(projectId); // PM analyzes distress const pmResponse = await AIEngine.callPM(project.pmHistory, `Worker Distress: ${distressMessage}. Provide solution.`); // Logic: Decide to reset worker OR guide worker if (pmResponse.includes("RESET_RECOMMENDED")) { // Call reset endpoint logic (internal function call) project.workerHistory = [{ role: 'user', parts: [{ text: sysPrompts.worker_reset_prompt.replace('{{INSTRUCTION}}', pmResponse) }] }]; await StateManager.updateProject(projectId, { workerHistory: project.workerHistory }); res.json({ action: "RESET", guidance: pmResponse }); } else { // Just guide const workerResp = await AIEngine.callWorker(project.workerHistory, `PM FIX: ${pmResponse}`); StateManager.queueCommand(projectId, workerResp); res.json({ action: "GUIDANCE", guidance: pmResponse }); } }); /** * 5. PROJECT RESET * Resets the worker thread completely. */ app.post('/project/reset', validateRequest, async (req, res) => { const { projectId } = req.body; const project = await StateManager.getProject(projectId); // Clear history, keep GDD context const resetPrompt = `System reset. Current GDD: ${project.gdd}. Await instructions.`; await StateManager.updateProject(projectId, { workerHistory: [{ role: 'user', parts: [{ text: resetPrompt }] }], failureCount: 0 }); res.json({ success: true, message: "Worker thread reset" }); }); /** * 6. PROJECT PING (The Roblox Plugin Endpoint) * Plugin calls this to fetch executable code. */ /* app.post('/project/ping', validateRequest, async (req, res) => { const { projectId } = req.body; const project = await StateManager.getProject(projectId); if (!project || !project.commandQueue || project.commandQueue.length === 0) { return res.json({ status: "IDLE" }); } // Pop the oldest command const command = project.commandQueue.shift(); await StateManager.updateProject(projectId, { commandQueue: project.commandQueue }); // Return the whole response, but specifically parsed code for execution res.json({ status: "EXECUTE", fullResponse: command.raw, codeSnippet: command.code // The markdown snippet extracted in StateManager }); }); */ /** * 7. INACTIVITY CLEANUP */ app.delete('/internal/cleanup', async (req, res) => { const count = StateManager.cleanupInactivity(); res.json({ success: true, removed: count }); }); app.post('/project/ping', async (req, res) => { const { projectId } = req.body; const command = await StateManager.popCommand(projectId); if (command) { // Sends: { action: "EXECUTE", target: "...", code: "..." } res.json({ action: command.type, target: command.payload, code: command.type === 'EXECUTE' ? command.payload : null }); } else { res.json({ action: "IDLE" }); } }); app.listen(PORT, () => { console.log(`AI Builder Backend running on port ${PORT}`); });