|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8')); |
|
|
|
|
|
app.use(cors()); |
|
|
app.use(bodyParser.json({ limit: '50mb' })); |
|
|
|
|
|
|
|
|
const validateRequest = (req, res, next) => { |
|
|
const { userId, projectId } = req.body; |
|
|
if (!userId || !projectId) { |
|
|
return res.status(400).json({ error: "Missing userId or projectId" }); |
|
|
} |
|
|
next(); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.post('/new/project', validateRequest, async (req, res) => { |
|
|
const { userId, projectId, description } = req.body; |
|
|
|
|
|
try { |
|
|
|
|
|
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 }] }); |
|
|
|
|
|
|
|
|
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 }] }); |
|
|
|
|
|
|
|
|
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 }] }); |
|
|
|
|
|
|
|
|
await StateManager.updateProject(projectId, { |
|
|
userId, |
|
|
pmHistory, |
|
|
workerHistory, |
|
|
gdd: gddResponse, |
|
|
failureCount: 0 |
|
|
}); |
|
|
|
|
|
|
|
|
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" }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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." }); |
|
|
|
|
|
|
|
|
if (taskComplete) { |
|
|
console.log(`[${projectId}] ✅ TASK COMPLETE. Calling PM & Nuking Worker.`); |
|
|
|
|
|
|
|
|
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 }] }); |
|
|
|
|
|
|
|
|
project.workerHistory = []; |
|
|
|
|
|
|
|
|
await StateManager.updateProject(projectId, { |
|
|
pmHistory: project.pmHistory, |
|
|
workerHistory: [], |
|
|
status: "IDLE" |
|
|
}); |
|
|
|
|
|
return res.json({ success: true, message: "Worker Nuked. PM Notified. Loop Stopped." }); |
|
|
} |
|
|
|
|
|
|
|
|
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.`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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...`); |
|
|
|
|
|
|
|
|
try { |
|
|
const response = await AIEngine.callWorker(project.workerHistory, finalPrompt); |
|
|
|
|
|
|
|
|
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" }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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" }); |
|
|
|
|
|
|
|
|
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" }); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.post('/internal/ping/pm', validateRequest, async (req, res) => { |
|
|
const { projectId, distressMessage } = req.body; |
|
|
const project = await StateManager.getProject(projectId); |
|
|
|
|
|
|
|
|
const pmResponse = await AIEngine.callPM(project.pmHistory, `Worker Distress: ${distressMessage}. Provide solution.`); |
|
|
|
|
|
|
|
|
if (pmResponse.includes("RESET_RECOMMENDED")) { |
|
|
|
|
|
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 { |
|
|
|
|
|
const workerResp = await AIEngine.callWorker(project.workerHistory, `PM FIX: ${pmResponse}`); |
|
|
StateManager.queueCommand(projectId, workerResp); |
|
|
res.json({ action: "GUIDANCE", guidance: pmResponse }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.post('/project/reset', validateRequest, async (req, res) => { |
|
|
const { projectId } = req.body; |
|
|
const project = await StateManager.getProject(projectId); |
|
|
|
|
|
|
|
|
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" }); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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}`); |
|
|
}); |