Spaces:
Sleeping
Sleeping
Update app.js
Browse files
app.js
CHANGED
|
@@ -7,17 +7,17 @@ import { createClient } from '@supabase/supabase-js';
|
|
| 7 |
// ==========================================
|
| 8 |
// 1. CONFIGURATION & SETUP
|
| 9 |
// ==========================================
|
| 10 |
-
const PORT =
|
| 11 |
const SUPABASE_URL = process.env.SUPABASE_URL;
|
| 12 |
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY;
|
| 13 |
-
const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:11434"; // Defaulting to local
|
| 14 |
const FRONT_URL = process.env.FRONT_URL;
|
| 15 |
-
const CRON_REGISTRY_URL = process.env.CRON_REGISTRY_URL || "http://localhost:7861";
|
| 16 |
const CRON_SECRET = process.env.CRON_SECRET || "default_secret";
|
| 17 |
|
| 18 |
// --- MODEL CONFIGURATION ---
|
| 19 |
-
const SMART_MODEL_ID = "
|
| 20 |
-
const FAST_MODEL_ID = "gpt-5-
|
| 21 |
|
| 22 |
if (!SUPABASE_URL || !SUPABASE_KEY) {
|
| 23 |
console.error("FATAL: Missing Supabase URL or Service Key.");
|
|
@@ -34,7 +34,6 @@ app.use(cors());
|
|
| 34 |
// 2. PROMPT MANAGEMENT
|
| 35 |
// ==========================================
|
| 36 |
let prompts = {};
|
| 37 |
-
// Load synchronously on boot
|
| 38 |
try {
|
| 39 |
const promptsPath = path.resolve('./prompts.json');
|
| 40 |
if (fs.existsSync(promptsPath)) {
|
|
@@ -72,7 +71,7 @@ const StateManager = {
|
|
| 72 |
if (error) return [];
|
| 73 |
const fullHistory = (chunks || []).reverse().flatMap(c => c.payload || []);
|
| 74 |
|
| 75 |
-
// Initialize Cache with default frozen state (false)
|
| 76 |
activeProjects.set(projectId, { history: fullHistory, lastActive: Date.now(), isFrozen: false });
|
| 77 |
return fullHistory;
|
| 78 |
},
|
|
@@ -122,7 +121,6 @@ const StateManager = {
|
|
| 122 |
} catch (e) { console.error("History Sync Error", e); }
|
| 123 |
},
|
| 124 |
|
| 125 |
-
// Check frozen state from DB
|
| 126 |
isFrozen: async (projectId) => {
|
| 127 |
if (activeProjects.has(projectId)) return activeProjects.get(projectId).isFrozen;
|
| 128 |
const { data } = await supabase.from('leads').select('is_frozen').eq('id', projectId).single();
|
|
@@ -140,7 +138,6 @@ const StateManager = {
|
|
| 140 |
// ==========================================
|
| 141 |
|
| 142 |
const callAI = async (history, input, contextData, systemPrompt, projectContext, modelId) => {
|
| 143 |
-
// Construct Context
|
| 144 |
let contextStr = "";
|
| 145 |
try { contextStr = JSON.stringify(contextData, null, 2); } catch {}
|
| 146 |
|
|
@@ -166,7 +163,7 @@ const callAI = async (history, input, contextData, systemPrompt, projectContext,
|
|
| 166 |
};
|
| 167 |
|
| 168 |
// ==========================================
|
| 169 |
-
// 5.
|
| 170 |
// ==========================================
|
| 171 |
|
| 172 |
function extractCommands(text) {
|
|
@@ -186,25 +183,26 @@ function extractCommands(text) {
|
|
| 186 |
parse(/<notification>([\s\S]*?)<\/notification>/g, 'notification', false);
|
| 187 |
parse(/<update_requirements>([\s\S]*?)<\/update_requirements>/g, 'update_requirements', false);
|
| 188 |
parse(/<schedule_briefing>([\s\S]*?)<\/schedule_briefing>/g, 'schedule_briefing');
|
| 189 |
-
parse(/<freeze_project>([\s\S]*?)<\/freeze_project>/g, 'freeze_project', false);
|
| 190 |
-
parse(/<thrust_complete>([\s\S]*?)<\/thrust_complete>/g, 'thrust_complete', false);
|
| 191 |
|
| 192 |
return commands;
|
| 193 |
}
|
| 194 |
|
| 195 |
-
//
|
| 196 |
async function registerMorningCron(projectId, offset) {
|
| 197 |
-
// 1. Calculate time until next 6 AM
|
| 198 |
const now = new Date();
|
| 199 |
const target = new Date(now);
|
| 200 |
|
| 201 |
-
//
|
| 202 |
-
//
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
if (target <= now) {
|
| 206 |
-
|
| 207 |
-
target.setDate(target.getDate() + 1);
|
| 208 |
}
|
| 209 |
|
| 210 |
const delayMs = target.getTime() - now.getTime();
|
|
@@ -212,14 +210,6 @@ async function registerMorningCron(projectId, offset) {
|
|
| 212 |
|
| 213 |
console.log(`⏰ Scheduling Briefing for ${projectId}. Next run in ${(delayMs/1000/60).toFixed(1)} mins.`);
|
| 214 |
|
| 215 |
-
// 2. Call Cron Registry (One time set for specific start, then interval)
|
| 216 |
-
// Note: The simple registry provided uses setInterval. To sync to 6AM exactly,
|
| 217 |
-
// we register a job that fires in 'delayMs', which then executes logic and re-registers itself
|
| 218 |
-
// OR simply use 24h interval starting NOW if close enough.
|
| 219 |
-
|
| 220 |
-
// Better strategy for this specific "Morning" requirement with a simple setInterval registry:
|
| 221 |
-
// We register a job with delayMs. The payload tells the Core "Setup Daily Loop".
|
| 222 |
-
|
| 223 |
await fetch(`${CRON_REGISTRY_URL}/register`, {
|
| 224 |
method: 'POST',
|
| 225 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -227,7 +217,8 @@ async function registerMorningCron(projectId, offset) {
|
|
| 227 |
secret: CRON_SECRET,
|
| 228 |
jobId: `briefing_${projectId}`,
|
| 229 |
intervalMs: intervalMs,
|
| 230 |
-
|
|
|
|
| 231 |
payload: { projectId, timezoneOffset: offset }
|
| 232 |
})
|
| 233 |
}).catch(e => console.error("Cron Reg Error", e));
|
|
@@ -282,7 +273,7 @@ async function executeCommands(userId, projectId, commands) {
|
|
| 282 |
}
|
| 283 |
|
| 284 |
if (cmd.type === 'thrust_complete') {
|
| 285 |
-
flags.thrustComplete = true; //
|
| 286 |
}
|
| 287 |
|
| 288 |
if (cmd.type === 'notification' && FRONT_URL) {
|
|
@@ -306,14 +297,12 @@ async function executeCommands(userId, projectId, commands) {
|
|
| 306 |
app.post('/init-project', async (req, res) => {
|
| 307 |
const { userId, name, description, localPath } = req.body;
|
| 308 |
|
| 309 |
-
// Create DB Entry
|
| 310 |
const { data: lead } = await supabase.from('leads').insert({
|
| 311 |
user_id: userId, name, description, local_path: localPath, status: 'active', requirements_doc: "Init..."
|
| 312 |
}).select().single();
|
| 313 |
|
| 314 |
res.json({ success: true, leadId: lead.id });
|
| 315 |
|
| 316 |
-
// Background Process
|
| 317 |
setImmediate(async () => {
|
| 318 |
const initInput = `PROJECT: ${name}\nDESC: ${description}\nTask: Init PRD, First Thrust, Schedule Morning Briefing.`;
|
| 319 |
const aiResult = await callAI([], initInput, {}, prompts.init_system_prompt, "", SMART_MODEL_ID);
|
|
@@ -333,13 +322,12 @@ app.post('/process', async (req, res) => {
|
|
| 333 |
// 1. Unfreeze if user interacts
|
| 334 |
if (task_type === 'chat') await StateManager.setFrozen(projectId, false);
|
| 335 |
|
| 336 |
-
// 2. Select Model
|
| 337 |
-
// Logic: Log ingestion uses Fast model. If Fast model says "Thrust Complete", we switch to Smart.
|
| 338 |
let selectedModel = (task_type === 'log_ingestion') ? FAST_MODEL_ID : SMART_MODEL_ID;
|
| 339 |
let sysPrompt = (task_type === 'log_ingestion') ? prompts.log_analyst_prompt : prompts.director_system_prompt;
|
| 340 |
|
| 341 |
try {
|
| 342 |
-
//
|
| 343 |
const { data: lead } = await supabase.from('leads').select('requirements_doc').eq('id', projectId).single();
|
| 344 |
const { data: activeThrust } = await supabase.from('thrusts')
|
| 345 |
.select('title, tasks:thrust_tasks(title, status)')
|
|
@@ -348,46 +336,52 @@ app.post('/process', async (req, res) => {
|
|
| 348 |
.order('created_at', { ascending: false })
|
| 349 |
.limit(1)
|
| 350 |
.single();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
const projectContext = `
|
| 353 |
-
[PRD]: ${lead?.requirements_doc?.substring(0,
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
`;
|
| 356 |
|
| 357 |
const history = await StateManager.getHistory(projectId);
|
| 358 |
if (task_type === 'chat') await StateManager.addHistory(projectId, 'user', prompt);
|
| 359 |
|
| 360 |
-
//
|
| 361 |
let aiResult = await callAI(history, prompt, context, sysPrompt, projectContext, selectedModel);
|
| 362 |
-
|
| 363 |
-
// 4. Command Execution
|
| 364 |
let cmds = extractCommands(aiResult.text);
|
| 365 |
let flags = await executeCommands(userId, projectId, cmds);
|
| 366 |
|
| 367 |
// 5. AUTO-ESCALATION (Fast -> Smart)
|
| 368 |
-
//
|
| 369 |
if (flags.thrustComplete && task_type === 'log_ingestion') {
|
| 370 |
console.log(`🚀 Thrust Complete detected by Analyst. Escalating to Director...`);
|
| 371 |
|
| 372 |
const escalationPrompt = "The previous thrust is complete based on logs. Generate the next Thrust immediately to keep momentum.";
|
| 373 |
-
// Call Smart Model
|
| 374 |
const smartResult = await callAI(history, escalationPrompt, context, prompts.director_system_prompt, projectContext, SMART_MODEL_ID);
|
| 375 |
|
| 376 |
-
// Append Smart response
|
| 377 |
aiResult.text += `\n\n[DIRECTOR INTERVENTION]:\n${smartResult.text}`;
|
| 378 |
|
| 379 |
const smartCmds = extractCommands(smartResult.text);
|
| 380 |
await executeCommands(userId, projectId, smartCmds);
|
| 381 |
|
| 382 |
-
// Save history
|
| 383 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 384 |
} else {
|
| 385 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 386 |
}
|
| 387 |
|
| 388 |
-
// Clean output
|
| 389 |
const cleanText = aiResult.text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, '').trim();
|
| 390 |
-
|
| 391 |
res.json({ text: cleanText, should_reload: flags.shouldReload });
|
| 392 |
|
| 393 |
} catch (e) {
|
|
@@ -402,10 +396,8 @@ app.post('/automated-briefing', async (req, res) => {
|
|
| 402 |
console.log(`☀️ Morning Briefing Triggered: ${projectId}`);
|
| 403 |
|
| 404 |
try {
|
| 405 |
-
// 1. Check Freeze State
|
| 406 |
const isFrozen = await StateManager.isFrozen(projectId);
|
| 407 |
|
| 408 |
-
// Check when the last thrust was made. If made today, skip.
|
| 409 |
const { data: lastThrust } = await supabase.from('thrusts')
|
| 410 |
.select('created_at')
|
| 411 |
.eq('lead_id', projectId)
|
|
@@ -416,7 +408,7 @@ app.post('/automated-briefing', async (req, res) => {
|
|
| 416 |
const lastThrustDate = lastThrust ? new Date(lastThrust.created_at).getDate() : 0;
|
| 417 |
const todayDate = new Date().getDate();
|
| 418 |
|
| 419 |
-
//
|
| 420 |
if (isFrozen) {
|
| 421 |
console.log(`Skipping briefing: Project Frozen.`);
|
| 422 |
return res.json({ status: "skipped_frozen" });
|
|
@@ -426,24 +418,25 @@ app.post('/automated-briefing', async (req, res) => {
|
|
| 426 |
return res.json({ status: "skipped_exists" });
|
| 427 |
}
|
| 428 |
|
| 429 |
-
//
|
| 430 |
const prompt = "It is 6 AM. Generate the Morning Briefing (New Thrust). If the project has been idle, use <freeze_project>true</freeze_project> after this.";
|
| 431 |
|
| 432 |
-
//
|
| 433 |
-
// We create a fake "request" to process
|
| 434 |
-
// Ideally refactor logic out, but for now calling AI directly:
|
| 435 |
const { data: lead } = await supabase.from('leads').select('*').eq('id', projectId).single();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
const history = await StateManager.getHistory(projectId);
|
| 437 |
|
| 438 |
-
const aiResult = await callAI(history, prompt, {}, prompts.director_system_prompt,
|
| 439 |
|
| 440 |
-
// 3. Save & Execute
|
| 441 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 442 |
const cmds = extractCommands(aiResult.text);
|
| 443 |
await executeCommands(lead.user_id, projectId, cmds);
|
| 444 |
|
| 445 |
-
//
|
| 446 |
-
// This
|
| 447 |
await StateManager.setFrozen(projectId, true);
|
| 448 |
|
| 449 |
res.json({ success: true });
|
|
|
|
| 7 |
// ==========================================
|
| 8 |
// 1. CONFIGURATION & SETUP
|
| 9 |
// ==========================================
|
| 10 |
+
const PORT = 7862;
|
| 11 |
const SUPABASE_URL = process.env.SUPABASE_URL;
|
| 12 |
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY;
|
| 13 |
+
const REMOTE_SERVER_URL = process.env.REMOTE_AI_URL || "http://localhost:11434"; // Defaulting to local if env missing
|
| 14 |
const FRONT_URL = process.env.FRONT_URL;
|
| 15 |
+
const CRON_REGISTRY_URL = process.env.CRON_REGISTRY_URL || "http://localhost:7861";
|
| 16 |
const CRON_SECRET = process.env.CRON_SECRET || "default_secret";
|
| 17 |
|
| 18 |
// --- MODEL CONFIGURATION ---
|
| 19 |
+
const SMART_MODEL_ID = "gpt-4-turbo";
|
| 20 |
+
const FAST_MODEL_ID = "gpt-3.5-turbo";
|
| 21 |
|
| 22 |
if (!SUPABASE_URL || !SUPABASE_KEY) {
|
| 23 |
console.error("FATAL: Missing Supabase URL or Service Key.");
|
|
|
|
| 34 |
// 2. PROMPT MANAGEMENT
|
| 35 |
// ==========================================
|
| 36 |
let prompts = {};
|
|
|
|
| 37 |
try {
|
| 38 |
const promptsPath = path.resolve('./prompts.json');
|
| 39 |
if (fs.existsSync(promptsPath)) {
|
|
|
|
| 71 |
if (error) return [];
|
| 72 |
const fullHistory = (chunks || []).reverse().flatMap(c => c.payload || []);
|
| 73 |
|
| 74 |
+
// Initialize Cache with default frozen state (false)
|
| 75 |
activeProjects.set(projectId, { history: fullHistory, lastActive: Date.now(), isFrozen: false });
|
| 76 |
return fullHistory;
|
| 77 |
},
|
|
|
|
| 121 |
} catch (e) { console.error("History Sync Error", e); }
|
| 122 |
},
|
| 123 |
|
|
|
|
| 124 |
isFrozen: async (projectId) => {
|
| 125 |
if (activeProjects.has(projectId)) return activeProjects.get(projectId).isFrozen;
|
| 126 |
const { data } = await supabase.from('leads').select('is_frozen').eq('id', projectId).single();
|
|
|
|
| 138 |
// ==========================================
|
| 139 |
|
| 140 |
const callAI = async (history, input, contextData, systemPrompt, projectContext, modelId) => {
|
|
|
|
| 141 |
let contextStr = "";
|
| 142 |
try { contextStr = JSON.stringify(contextData, null, 2); } catch {}
|
| 143 |
|
|
|
|
| 163 |
};
|
| 164 |
|
| 165 |
// ==========================================
|
| 166 |
+
// 5. COMMAND & CRON LOGIC
|
| 167 |
// ==========================================
|
| 168 |
|
| 169 |
function extractCommands(text) {
|
|
|
|
| 183 |
parse(/<notification>([\s\S]*?)<\/notification>/g, 'notification', false);
|
| 184 |
parse(/<update_requirements>([\s\S]*?)<\/update_requirements>/g, 'update_requirements', false);
|
| 185 |
parse(/<schedule_briefing>([\s\S]*?)<\/schedule_briefing>/g, 'schedule_briefing');
|
| 186 |
+
parse(/<freeze_project>([\s\S]*?)<\/freeze_project>/g, 'freeze_project', false);
|
| 187 |
+
parse(/<thrust_complete>([\s\S]*?)<\/thrust_complete>/g, 'thrust_complete', false);
|
| 188 |
|
| 189 |
return commands;
|
| 190 |
}
|
| 191 |
|
| 192 |
+
// Logic: Calculate delay until 6AM user time, register Cron with that initial delay
|
| 193 |
async function registerMorningCron(projectId, offset) {
|
|
|
|
| 194 |
const now = new Date();
|
| 195 |
const target = new Date(now);
|
| 196 |
|
| 197 |
+
// Logic: 6 AM relative to user offset.
|
| 198 |
+
// E.g., Offset -5 (EST). UTC is 11 AM when EST is 6 AM.
|
| 199 |
+
// Target UTC hour = 6 - offset.
|
| 200 |
+
const targetUtcHour = 6 - offset;
|
| 201 |
+
|
| 202 |
+
target.setUTCHours(targetUtcHour, 0, 0, 0);
|
| 203 |
|
| 204 |
if (target <= now) {
|
| 205 |
+
target.setDate(target.getDate() + 1); // Move to tomorrow
|
|
|
|
| 206 |
}
|
| 207 |
|
| 208 |
const delayMs = target.getTime() - now.getTime();
|
|
|
|
| 210 |
|
| 211 |
console.log(`⏰ Scheduling Briefing for ${projectId}. Next run in ${(delayMs/1000/60).toFixed(1)} mins.`);
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
await fetch(`${CRON_REGISTRY_URL}/register`, {
|
| 214 |
method: 'POST',
|
| 215 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 217 |
secret: CRON_SECRET,
|
| 218 |
jobId: `briefing_${projectId}`,
|
| 219 |
intervalMs: intervalMs,
|
| 220 |
+
initialDelay: delayMs, // Send delay to Cron Server
|
| 221 |
+
webhookUrl: `http://localhost:${PORT}/automated-briefing`,
|
| 222 |
payload: { projectId, timezoneOffset: offset }
|
| 223 |
})
|
| 224 |
}).catch(e => console.error("Cron Reg Error", e));
|
|
|
|
| 273 |
}
|
| 274 |
|
| 275 |
if (cmd.type === 'thrust_complete') {
|
| 276 |
+
flags.thrustComplete = true; // Signal for escalation
|
| 277 |
}
|
| 278 |
|
| 279 |
if (cmd.type === 'notification' && FRONT_URL) {
|
|
|
|
| 297 |
app.post('/init-project', async (req, res) => {
|
| 298 |
const { userId, name, description, localPath } = req.body;
|
| 299 |
|
|
|
|
| 300 |
const { data: lead } = await supabase.from('leads').insert({
|
| 301 |
user_id: userId, name, description, local_path: localPath, status: 'active', requirements_doc: "Init..."
|
| 302 |
}).select().single();
|
| 303 |
|
| 304 |
res.json({ success: true, leadId: lead.id });
|
| 305 |
|
|
|
|
| 306 |
setImmediate(async () => {
|
| 307 |
const initInput = `PROJECT: ${name}\nDESC: ${description}\nTask: Init PRD, First Thrust, Schedule Morning Briefing.`;
|
| 308 |
const aiResult = await callAI([], initInput, {}, prompts.init_system_prompt, "", SMART_MODEL_ID);
|
|
|
|
| 322 |
// 1. Unfreeze if user interacts
|
| 323 |
if (task_type === 'chat') await StateManager.setFrozen(projectId, false);
|
| 324 |
|
| 325 |
+
// 2. Select Model (Log Ingestion = Fast, High Level = Smart)
|
|
|
|
| 326 |
let selectedModel = (task_type === 'log_ingestion') ? FAST_MODEL_ID : SMART_MODEL_ID;
|
| 327 |
let sysPrompt = (task_type === 'log_ingestion') ? prompts.log_analyst_prompt : prompts.director_system_prompt;
|
| 328 |
|
| 329 |
try {
|
| 330 |
+
// 3. Build Context (God View: PRD + Timeline + Active Thrust)
|
| 331 |
const { data: lead } = await supabase.from('leads').select('requirements_doc').eq('id', projectId).single();
|
| 332 |
const { data: activeThrust } = await supabase.from('thrusts')
|
| 333 |
.select('title, tasks:thrust_tasks(title, status)')
|
|
|
|
| 336 |
.order('created_at', { ascending: false })
|
| 337 |
.limit(1)
|
| 338 |
.single();
|
| 339 |
+
|
| 340 |
+
// FETCH TIMELINE (Restored functionality)
|
| 341 |
+
const { data: timeline } = await supabase.from('timeline_events')
|
| 342 |
+
.select('title, type, description, created_at')
|
| 343 |
+
.eq('lead_id', projectId)
|
| 344 |
+
.order('created_at', { ascending: false })
|
| 345 |
+
.limit(10); // Look at last 10 events for context
|
| 346 |
|
| 347 |
const projectContext = `
|
| 348 |
+
[PRD]: ${lead?.requirements_doc?.substring(0, 3000)}...
|
| 349 |
+
|
| 350 |
+
[CURRENT THRUST]:
|
| 351 |
+
${activeThrust ? JSON.stringify(activeThrust, null, 2) : "None"}
|
| 352 |
+
|
| 353 |
+
[RECENT TIMELINE]:
|
| 354 |
+
${JSON.stringify(timeline || [], null, 2)}
|
| 355 |
`;
|
| 356 |
|
| 357 |
const history = await StateManager.getHistory(projectId);
|
| 358 |
if (task_type === 'chat') await StateManager.addHistory(projectId, 'user', prompt);
|
| 359 |
|
| 360 |
+
// 4. AI Execution
|
| 361 |
let aiResult = await callAI(history, prompt, context, sysPrompt, projectContext, selectedModel);
|
|
|
|
|
|
|
| 362 |
let cmds = extractCommands(aiResult.text);
|
| 363 |
let flags = await executeCommands(userId, projectId, cmds);
|
| 364 |
|
| 365 |
// 5. AUTO-ESCALATION (Fast -> Smart)
|
| 366 |
+
// Logic: Analyst determines thrust is done via logs -> Triggers Director to set new Aim.
|
| 367 |
if (flags.thrustComplete && task_type === 'log_ingestion') {
|
| 368 |
console.log(`🚀 Thrust Complete detected by Analyst. Escalating to Director...`);
|
| 369 |
|
| 370 |
const escalationPrompt = "The previous thrust is complete based on logs. Generate the next Thrust immediately to keep momentum.";
|
| 371 |
+
// Call Smart Model
|
| 372 |
const smartResult = await callAI(history, escalationPrompt, context, prompts.director_system_prompt, projectContext, SMART_MODEL_ID);
|
| 373 |
|
|
|
|
| 374 |
aiResult.text += `\n\n[DIRECTOR INTERVENTION]:\n${smartResult.text}`;
|
| 375 |
|
| 376 |
const smartCmds = extractCommands(smartResult.text);
|
| 377 |
await executeCommands(userId, projectId, smartCmds);
|
| 378 |
|
|
|
|
| 379 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 380 |
} else {
|
| 381 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 382 |
}
|
| 383 |
|
|
|
|
| 384 |
const cleanText = aiResult.text.replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g, '').trim();
|
|
|
|
| 385 |
res.json({ text: cleanText, should_reload: flags.shouldReload });
|
| 386 |
|
| 387 |
} catch (e) {
|
|
|
|
| 396 |
console.log(`☀️ Morning Briefing Triggered: ${projectId}`);
|
| 397 |
|
| 398 |
try {
|
|
|
|
| 399 |
const isFrozen = await StateManager.isFrozen(projectId);
|
| 400 |
|
|
|
|
| 401 |
const { data: lastThrust } = await supabase.from('thrusts')
|
| 402 |
.select('created_at')
|
| 403 |
.eq('lead_id', projectId)
|
|
|
|
| 408 |
const lastThrustDate = lastThrust ? new Date(lastThrust.created_at).getDate() : 0;
|
| 409 |
const todayDate = new Date().getDate();
|
| 410 |
|
| 411 |
+
// Prevent generating if project is dead (frozen) or if generated recently
|
| 412 |
if (isFrozen) {
|
| 413 |
console.log(`Skipping briefing: Project Frozen.`);
|
| 414 |
return res.json({ status: "skipped_frozen" });
|
|
|
|
| 418 |
return res.json({ status: "skipped_exists" });
|
| 419 |
}
|
| 420 |
|
| 421 |
+
// Generate Briefing
|
| 422 |
const prompt = "It is 6 AM. Generate the Morning Briefing (New Thrust). If the project has been idle, use <freeze_project>true</freeze_project> after this.";
|
| 423 |
|
| 424 |
+
// Manually build context context for the automated call
|
|
|
|
|
|
|
| 425 |
const { data: lead } = await supabase.from('leads').select('*').eq('id', projectId).single();
|
| 426 |
+
const { data: timeline } = await supabase.from('timeline_events')
|
| 427 |
+
.select('*').eq('lead_id', projectId).order('created_at', { ascending: false }).limit(5);
|
| 428 |
+
|
| 429 |
+
const projectContext = `[PRD]: ${lead.requirements_doc}\n[TIMELINE]: ${JSON.stringify(timeline)}`;
|
| 430 |
const history = await StateManager.getHistory(projectId);
|
| 431 |
|
| 432 |
+
const aiResult = await callAI(history, prompt, {}, prompts.director_system_prompt, projectContext, SMART_MODEL_ID);
|
| 433 |
|
|
|
|
| 434 |
await StateManager.addHistory(projectId, 'model', aiResult.text);
|
| 435 |
const cmds = extractCommands(aiResult.text);
|
| 436 |
await executeCommands(lead.user_id, projectId, cmds);
|
| 437 |
|
| 438 |
+
// Morning briefings set the project to Frozen state until user returns
|
| 439 |
+
// This prevents the AI from generating 30 briefings for an abandoned project
|
| 440 |
await StateManager.setFrozen(projectId, true);
|
| 441 |
|
| 442 |
res.json({ success: true });
|