everydaycats commited on
Commit
b6fcea9
·
verified ·
1 Parent(s): 1e7438c

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +198 -157
app.js CHANGED
@@ -3,211 +3,252 @@ import bodyParser from 'body-parser';
3
  import cors from 'cors';
4
  import { StateManager } from './stateManager.js';
5
  import { AIEngine } from './aiEngine.js';
 
6
 
7
  const app = express();
 
 
 
 
 
8
  app.use(cors());
9
  app.use(bodyParser.json({ limit: '50mb' }));
10
- const PORT = process.env.PORT || 7860;
11
 
12
- // Helper to format Roblox context for AI
13
- const formatContext = (ctx) => {
14
- let s = "";
15
- if (ctx.hierarchyContext) s += `\nCURRENT HIERARCHY:\n${ctx.hierarchyContext.tree}\n`;
16
- if (ctx.scriptContext) s += `\nEDITING SCRIPT: ${ctx.scriptContext.targetName}\nSOURCE:\n${ctx.scriptContext.scriptSource}\n`;
17
- if (ctx.logContext) s += `\nRECENT LOGS (Errors/Output):\n${ctx.logContext.logs}\n`;
18
- return s;
19
  };
20
 
21
- // =======================================================
22
- // 1. NEW PROJECT (Non-Blocking / Background)
23
- // =======================================================
24
- app.post('/new/project', async (req, res) => {
 
25
  const { userId, projectId, description } = req.body;
26
-
27
- if (!userId || !projectId || !description) {
28
- return res.status(400).json({ error: "Missing required fields." });
29
- }
30
 
31
- // Initialize State immediately so Ping works
32
- await StateManager.updateProject(projectId, {
33
- userId,
34
- status: "INITIALIZING", // Flag to track state
35
- pmHistory: [],
36
- workerHistory: []
37
- });
38
-
39
- console.log(`[${projectId}] Request accepted. Starting Background Init...`);
40
-
41
- // 1. Respond IMMEDIATELY to client
42
- res.json({
43
- success: true,
44
- message: "Project initialization started in background. The AI will start outputting commands shortly."
45
- });
46
-
47
- // 2. Run Heavy AI Logic asynchronously (Fire & Forget)
48
- startBackgroundInit(projectId, description).catch(err => {
49
- console.error(`[${projectId}] CRITICAL INIT FAILURE:`, err);
50
- // Queue an error message to the plugin console so the user knows it failed
51
- StateManager.queueCommand(projectId, `print("CRITICAL AI ERROR: Project Init Failed. ${err.message}")`);
52
- });
53
- });
54
 
55
- // The Background Logic
56
- async function startBackgroundInit(projectId, description) {
57
- const pmHist = [];
58
-
59
- // A. PM Generates GDD
60
- console.log(`[${projectId}] Generating GDD...`);
61
- const gdd = await AIEngine.callPM(pmHist, `New Project: ${description}. Generate a concise GDD.`);
62
- pmHist.push({ role: 'user', parts: [{ text: description }] });
63
- pmHist.push({ role: 'model', parts: [{ text: gdd }] });
64
-
65
- // B. PM Generates Tasks
66
- console.log(`[${projectId}] Generating Tasks...`);
67
- const tasks = await AIEngine.callPM(pmHist, "Create a list of the first 3 technical tasks.");
68
-
69
- // C. Worker Starts Task 1 (FORCE CODE GENERATION)
70
- console.log(`[${projectId}] Worker executing Task 1...`);
71
- const workerHist = [];
72
-
73
- // We add a system instruction suffix to ensure it creates code
74
- const firstPrompt = `CONTEXT:\n${gdd}\n\nTASKS:\n${tasks}\n\nINSTRUCTION:\nCreate the Script for Task 1 immediately. Output the Lua code block now. Do not talk.`;
75
-
76
- const code = await AIEngine.callWorker(workerHist, firstPrompt);
77
-
78
- workerHist.push({ role: 'user', parts: [{ text: firstPrompt }] });
79
- workerHist.push({ role: 'model', parts: [{ text: code }] });
80
-
81
- // Update State
82
- await StateManager.updateProject(projectId, {
83
- pmHistory: pmHist,
84
- workerHistory: workerHist,
85
- gdd: gdd,
86
- status: "ACTIVE"
87
- });
88
-
89
- // Queue the code
90
- await StateManager.queueCommand(projectId, code);
91
- console.log(`[${projectId}] Init Complete. First command queued.`);
92
- }
93
 
94
- // =======================================================
95
- // 2. FEEDBACK
96
- // =======================================================
 
 
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  app.post('/project/feedback', async (req, res) => {
99
- // Added 'taskComplete' to the destructured body
100
  const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete } = req.body;
101
 
102
  const project = await StateManager.getProject(projectId);
103
  if (!project) return res.status(404).json({ error: "Project not found." });
104
 
105
- // 1. CHECK FOR LOOP STOPPER
106
  if (taskComplete) {
107
- console.log(`[${projectId}] ✅ TASK COMPLETE. Stopping Feedback Loop.`);
108
 
109
- // We log it to history but DO NOT call the Worker AI
110
- project.workerHistory.push({ role: 'user', parts: [{ text: "USER: Task Execution Confirmed. Logs: " + (logContext?.logs || "OK") }] });
111
- await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
112
 
113
- return res.json({ success: true, message: "Loop Stopped. Waiting for new instructions." });
114
- }
 
115
 
116
- // 2. ERROR DETECTION (Safety Net)
117
- let isFailure = false;
118
- if (logContext && logContext.logs) {
119
- const errorKeywords = ["Infinite yield", "Stack Begin", "Error:", "Exception", "failed"];
120
- if (errorKeywords.some(keyword => logContext.logs.includes(keyword))) {
121
- isFailure = true;
122
- console.log(`[${projectId}] ⚠️ Detected Error in Logs.`);
123
  }
124
- }
125
 
126
- // 3. BUILD AI PROMPT
127
- const fullInput = `USER: ${prompt || "Automatic Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
128
-
129
- let finalPrompt = fullInput;
130
- if (isFailure && (!prompt || prompt.length < 5)) {
131
- finalPrompt += "\n[SYSTEM ALERT]: The logs indicate a Runtime Error or Infinite Yield. Fix the code immediately. Do not use WaitForChild.";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
- console.log(`[${projectId}] Processing Feedback...`);
 
 
 
 
 
135
 
136
- // 4. CALL AI (Continue Loop)
137
- try {
138
- const response = await AIEngine.callWorker(project.workerHistory, finalPrompt);
 
 
 
139
 
140
- // Check Escalation
141
- if (response.includes("STATUS: ESCALATE_TO_PM") || (project.failureCount > 2)) {
142
- console.log(`[${projectId}] Escalating to PM...`);
143
- const guidance = await AIEngine.callPM(project.pmHistory, `Worker Stuck. Input: ${fullInput}`);
144
- const fixed = await AIEngine.callWorker(project.workerHistory, `PM GUIDANCE: ${guidance}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- await StateManager.updateProject(projectId, { failureCount: 0 });
147
- project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
148
- project.workerHistory.push({ role: 'model', parts: [{ text: fixed }] });
149
- await StateManager.queueCommand(projectId, fixed);
150
- return res.json({ success: true, message: "Escalated & Fixed." });
 
151
  }
 
152
 
 
 
 
153
  project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
154
  project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
155
- await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
 
 
 
 
 
156
  await StateManager.queueCommand(projectId, response);
157
-
158
- res.json({ success: true, message: "Input Processed." });
159
 
160
  } catch (err) {
161
- console.error("AI Error:", err);
162
  res.status(500).json({ error: "AI Failed" });
163
  }
164
  });
165
- ;
 
 
 
 
166
 
167
- // =======================================================
168
- // 3. INTERNAL / PING
169
- // =======================================================
170
- app.post('/project/ping', async (req, res) => {
171
- const { projectId } = req.body;
172
 
173
- // We check project existence here to avoid crashing on random pings
174
- const project = await StateManager.getProject(projectId);
175
- if (!project) return res.json({ action: "IDLE" });
 
176
 
177
- const command = await StateManager.popCommand(projectId);
 
 
178
 
179
- if (command) {
180
- // Parse raw command string into specific actions for the plugin
181
- let action = command.type; // EXECUTE, READ_SCRIPT, etc.
182
- let target = command.payload;
183
- let code = (command.type === 'EXECUTE') ? command.payload : null;
184
 
185
- res.json({ action, target, code });
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  } else {
187
  res.json({ action: "IDLE" });
188
  }
189
  });
190
 
191
- // Direct PM Intervention Endpoint
192
- app.post('/internal/ping/pm', async (req, res) => {
193
- const { projectId, distressMessage } = req.body;
194
- const project = await StateManager.getProject(projectId);
195
-
196
- if(!project) return res.status(404).json({error: "Project not found"});
197
-
198
- console.log(`[${projectId}] Direct PM Intervention: ${distressMessage}`);
199
 
200
- // PM analyzes distress
201
- const pmResponse = await AIEngine.callPM(project.pmHistory, `DIRECT INTERVENTION: ${distressMessage}. Provide solution.`);
202
-
203
- // Guide worker
204
- const workerResp = await AIEngine.callWorker(project.workerHistory, `PM INTERVENTION: ${pmResponse}`);
205
-
206
- project.workerHistory.push({ role: 'model', parts: [{ text: workerResp }] });
207
- await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
208
- await StateManager.queueCommand(projectId, workerResp);
209
-
210
- res.json({ success: true, message: "PM Intervened." });
211
- });
212
 
213
- app.listen(PORT, () => console.log(`🚀 AI Server running on port ${PORT}`));
 
3
  import cors from 'cors';
4
  import { StateManager } from './stateManager.js';
5
  import { AIEngine } from './aiEngine.js';
6
+ import fs from 'fs';
7
 
8
  const app = express();
9
+ const PORT = process.env.PORT || 7860;
10
+
11
+ // Load prompts
12
+ const sysPrompts = JSON.parse(fs.readFileSync('./prompts.json', 'utf8'));
13
+
14
  app.use(cors());
15
  app.use(bodyParser.json({ limit: '50mb' }));
 
16
 
17
+ const validateRequest = (req, res, next) => {
18
+ const { userId, projectId } = req.body;
19
+ if (!userId || !projectId) return res.status(400).json({ error: "Missing ID" });
20
+ next();
 
 
 
21
  };
22
 
23
+ /**
24
+ * 1. NEW PROJECT
25
+ * PM Creates GDD -> PM Creates Isolated Worker Prompt -> Worker Starts
26
+ */
27
+ app.post('/new/project', validateRequest, async (req, res) => {
28
  const { userId, projectId, description } = req.body;
 
 
 
 
29
 
30
+ try {
31
+ // 1. PM: Generate GDD
32
+ const pmHistory = [];
33
+ const gddPrompt = `Create a GDD for: ${description}`;
34
+ const gddResponse = await AIEngine.callPM(pmHistory, gddPrompt);
35
+
36
+ pmHistory.push({ role: 'user', parts: [{ text: gddPrompt }] });
37
+ pmHistory.push({ role: 'model', parts: [{ text: gddResponse }] });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ // 2. PM: Create First Isolated Task
40
+ // We force the PM to extract just the necessary context for Task 1
41
+ const taskGenPrompt = "Based on the GDD, define the first technical task. \nOutput format:\nTASK_NAME: ...\nWORKER_PROMPT: ... (Write this as a direct command to the worker. Do not include the full GDD, just the relevant specs for this task).";
42
+ const taskResponse = await AIEngine.callPM(pmHistory, taskGenPrompt);
43
+
44
+ pmHistory.push({ role: 'user', parts: [{ text: taskGenPrompt }] });
45
+ pmHistory.push({ role: 'model', parts: [{ text: taskResponse }] });
46
+
47
+ // 3. Parse PM Response to get Worker Prompt
48
+ const workerInstruction = extractWorkerPrompt(taskResponse) || `Initialize project structure for: ${description}`;
49
+
50
+ // 4. Initialize Worker (Fresh Scope)
51
+ const workerHistory = [];
52
+ const initialWorkerPrompt = `CONTEXT: None. \nINSTRUCTION: ${workerInstruction}`;
53
+ const workerResponse = await AIEngine.callWorker(workerHistory, initialWorkerPrompt);
54
+
55
+ workerHistory.push({ role: 'user', parts: [{ text: initialWorkerPrompt }] });
56
+ workerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ // Save State
59
+ await StateManager.updateProject(projectId, {
60
+ userId, pmHistory, workerHistory,
61
+ gdd: gddResponse, failureCount: 0
62
+ });
63
 
64
+ StateManager.queueCommand(projectId, workerResponse);
65
+ res.json({ success: true, message: "Project Started", workerInstruction });
66
+
67
+ } catch (err) {
68
+ console.error(err);
69
+ res.status(500).json({ error: "Init Failed" });
70
+ }
71
+ });
72
+
73
+ /**
74
+ * 2. FEEDBACK LOOP
75
+ * Handles Termination and Scope Isolation
76
+ */
77
  app.post('/project/feedback', async (req, res) => {
 
78
  const { projectId, prompt, hierarchyContext, scriptContext, logContext, taskComplete } = req.body;
79
 
80
  const project = await StateManager.getProject(projectId);
81
  if (!project) return res.status(404).json({ error: "Project not found." });
82
 
83
+ // --- A. TASK COMPLETE: PM Assigns Next Task ---
84
  if (taskComplete) {
85
+ console.log(`[${projectId}] ✅ TASK COMPLETE. Calling PM.`);
86
 
87
+ // 1. Tell PM the task is done
88
+ const summary = `Worker completed the task. Logs: ${logContext?.logs || "Clean"}. Generate the NEXT task. \nREMEMBER: Output 'WORKER_PROMPT: ...' with isolated instructions.`;
 
89
 
90
+ const pmResponse = await AIEngine.callPM(project.pmHistory, summary);
91
+ project.pmHistory.push({ role: 'user', parts: [{ text: summary }] });
92
+ project.pmHistory.push({ role: 'model', parts: [{ text: pmResponse }] });
93
 
94
+ // 2. Parse new instruction
95
+ const nextWorkerInstruction = extractWorkerPrompt(pmResponse);
96
+
97
+ if (!nextWorkerInstruction) {
98
+ // PM thinks project is done or failed to format
99
+ await StateManager.updateProject(projectId, { pmHistory: project.pmHistory, status: "IDLE" });
100
+ return res.json({ success: true, message: "Project Idle/Finished." });
101
  }
 
102
 
103
+ // 3. NUKE WORKER (Scope Isolation)
104
+ // We clear history and give the new standalone prompt
105
+ console.log(`[${projectId}] 🧹 Nuking Worker for next task...`);
106
+ const newWorkerHistory = [];
107
+ const newPrompt = `New Objective: ${nextWorkerInstruction}`;
108
+
109
+ const workerResponse = await AIEngine.callWorker(newWorkerHistory, newPrompt);
110
+ newWorkerHistory.push({ role: 'user', parts: [{ text: newPrompt }] });
111
+ newWorkerHistory.push({ role: 'model', parts: [{ text: workerResponse }] });
112
+
113
+ await StateManager.updateProject(projectId, {
114
+ pmHistory: project.pmHistory,
115
+ workerHistory: newWorkerHistory,
116
+ failureCount: 0
117
+ });
118
+
119
+ // Send Clean Console command + Code
120
+ StateManager.queueCommand(projectId, "CLEAR_CONSOLE");
121
+ StateManager.queueCommand(projectId, workerResponse);
122
+
123
+ return res.json({ success: true, message: "Next Task Assigned" });
124
  }
125
 
126
+ // --- B. ERROR HANDLING & PM INTERVENTION ---
127
+ let isFailure = false;
128
+ if (logContext?.logs && ["Error", "Exception", "failed"].some(k => logContext.logs.includes(k))) {
129
+ isFailure = true;
130
+ project.failureCount = (project.failureCount || 0) + 1;
131
+ }
132
 
133
+ // Standard Feedback Context
134
+ const fullInput = `USER: ${prompt || "Auto-Feedback"}` + formatContext({ hierarchyContext, scriptContext, logContext });
135
+
136
+ // Check for Escalation (Failure > 2)
137
+ if (project.failureCount > 2) {
138
+ console.log(`[${projectId}] 🚨 Escalating to PM...`);
139
 
140
+ // Ask PM for judgment
141
+ const pmGuidancePrompt = sysPrompts.pm_guidance_prompt.replace('{{LOGS}}', logContext?.logs);
142
+ const pmVerdict = await AIEngine.callPM(project.pmHistory, pmGuidancePrompt);
143
+
144
+ // CHECK FOR TERMINATION ORDER
145
+ if (pmVerdict.includes("[TERMINATE]")) {
146
+ console.log(`[${projectId}] 💀 PM Ordered Termination.`);
147
+
148
+ // Extract the fix from PM's verdict to use as the new 'Initial Prompt'
149
+ const fixInstruction = pmVerdict.replace("[TERMINATE]", "").trim();
150
+
151
+ // Reset Worker completely
152
+ const resetHistory = [];
153
+ const resetPrompt = `[SYSTEM]: You were terminated for incompetence. \nNew Objective: ${fixInstruction}`;
154
+
155
+ const workerResp = await AIEngine.callWorker(resetHistory, resetPrompt);
156
+ resetHistory.push({ role: 'user', parts: [{ text: resetPrompt }] });
157
+ resetHistory.push({ role: 'model', parts: [{ text: workerResp }] });
158
+
159
+ await StateManager.updateProject(projectId, {
160
+ workerHistory: resetHistory,
161
+ failureCount: 0
162
+ });
163
+
164
+ StateManager.queueCommand(projectId, "CLEAR_CONSOLE");
165
+ StateManager.queueCommand(projectId, workerResp);
166
+ return res.json({ success: true, message: "Worker Terminated & Replaced." });
167
+ } else {
168
+ // Standard PM Guidance (Injection)
169
+ const injection = `[PM GUIDANCE]: ${pmVerdict} \n\nFix your code immediately.`;
170
+ const workerResp = await AIEngine.callWorker(project.workerHistory, injection);
171
 
172
+ project.workerHistory.push({ role: 'user', parts: [{ text: injection }] });
173
+ project.workerHistory.push({ role: 'model', parts: [{ text: workerResp }] });
174
+
175
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory, failureCount: 0 }); // Reset count after help
176
+ await StateManager.queueCommand(projectId, workerResp);
177
+ return res.json({ success: true, message: "PM Guidance Sent." });
178
  }
179
+ }
180
 
181
+ // --- C. STANDARD LOOP (Worker iterates) ---
182
+ try {
183
+ const response = await AIEngine.callWorker(project.workerHistory, fullInput);
184
  project.workerHistory.push({ role: 'user', parts: [{ text: fullInput }] });
185
  project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
186
+
187
+ await StateManager.updateProject(projectId, {
188
+ workerHistory: project.workerHistory,
189
+ failureCount: project.failureCount
190
+ });
191
+
192
  await StateManager.queueCommand(projectId, response);
193
+ res.json({ success: true });
 
194
 
195
  } catch (err) {
196
+ console.error(err);
197
  res.status(500).json({ error: "AI Failed" });
198
  }
199
  });
200
+
201
+ // --- HUMAN OVERRIDE (Preserved) ---
202
+ app.post('/human/override', validateRequest, async (req, res) => {
203
+ const { projectId, instruction, pruneHistory } = req.body;
204
+ const project = await StateManager.getProject(projectId);
205
 
206
+ const overrideMsg = `[SYSTEM OVERRIDE]: ${instruction}`;
 
 
 
 
207
 
208
+ // If we prune, we remove the last error context so the AI isn't confused
209
+ if (pruneHistory && project.workerHistory.length > 2) {
210
+ project.workerHistory = project.workerHistory.slice(0, -2);
211
+ }
212
 
213
+ const response = await AIEngine.callWorker(project.workerHistory, overrideMsg);
214
+ project.workerHistory.push({ role: 'user', parts: [{ text: overrideMsg }] });
215
+ project.workerHistory.push({ role: 'model', parts: [{ text: response }] });
216
 
217
+ await StateManager.updateProject(projectId, { workerHistory: project.workerHistory });
218
+ await StateManager.queueCommand(projectId, response);
219
+ res.json({ success: true });
220
+ });
 
221
 
222
+ app.post('/project/ping', async (req, res) => {
223
+ const { projectId } = req.body;
224
+ const command = await StateManager.popCommand(projectId);
225
+ if (command) {
226
+ // If the command string is exactly "CLEAR_CONSOLE", send special action
227
+ if (command.payload === "CLEAR_CONSOLE") {
228
+ res.json({ action: "CLEAR_LOGS" });
229
+ } else {
230
+ res.json({
231
+ action: command.type,
232
+ target: command.payload,
233
+ code: command.type === 'EXECUTE' ? command.payload : null
234
+ });
235
+ }
236
  } else {
237
  res.json({ action: "IDLE" });
238
  }
239
  });
240
 
241
+ // Helper to extract "WORKER_PROMPT:" from PM text
242
+ function extractWorkerPrompt(text) {
243
+ const match = text.match(/WORKER_PROMPT:\s*(.*)/s);
244
+ return match ? match[1].trim() : null;
245
+ }
 
 
 
246
 
247
+ function formatContext({ hierarchyContext, scriptContext, logContext }) {
248
+ let out = "";
249
+ if (scriptContext) out += `\n[SCRIPT]: ${scriptContext.targetName}`;
250
+ if (logContext) out += `\n[LOGS]: ${logContext.logs}`;
251
+ return out;
252
+ }
 
 
 
 
 
 
253
 
254
+ app.listen(PORT, () => { console.log(`Backend Running on ${PORT}`); });