ai_plugin_server / server.js
everydaycats's picture
Update server.js
ca47b28 verified
// 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}`);
});