Cyber Catalyst Team commited on
Commit
6a43d97
·
1 Parent(s): be32809

Implement multi-project queue management UI with active toggle and priority selector

Browse files
Files changed (1) hide show
  1. backend.py +369 -58
backend.py CHANGED
@@ -436,11 +436,14 @@ async def init_db():
436
  CREATE INDEX IF NOT EXISTS idx_session_key ON agent_session_entries (project_key, session_id, subpath, id);
437
  CREATE INDEX IF NOT EXISTS idx_project_session ON agent_session_entries (project_key, session_id);
438
 
439
- CREATE TABLE IF NOT EXISTS eternity_system_state (
440
  id SERIAL PRIMARY KEY,
 
441
  goal TEXT NOT NULL,
442
  deadline TIMESTAMPTZ NOT NULL,
443
  current_mode VARCHAR(20) NOT NULL DEFAULT 'build',
 
 
444
  roadmap JSONB DEFAULT '[]',
445
  latest_brief TEXT,
446
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
@@ -1053,6 +1056,7 @@ DASHBOARD_HTML = """
1053
  <div class="border-b border-gray-800 max-w-7xl w-full mx-auto px-6 mt-6 flex space-x-6 text-sm">
1054
  <button onclick="switchTab('models')" id="tab-btn-models" class="pb-3 border-b-2 border-blue-500 font-semibold text-blue-400 transition-all">NIM Models</button>
1055
  <button onclick="switchTab('logs')" id="tab-btn-logs" class="pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all">Live Logs</button>
 
1056
  <button onclick="switchTab('explorer')" id="tab-btn-explorer" class="pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold flex items-center space-x-1 transition-all">
1057
  <span>Workspace Explorer (IDE)</span>
1058
  <span class="px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 border border-blue-500/20 text-[10px] font-bold">VS Code View</span>
@@ -1131,8 +1135,59 @@ Select a file from the sidebar explorer on the left to read its code contents in
1131
  </div>
1132
  </div>
1133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
  </main>
1135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  <script>
1137
  let currentTab = 'models';
1138
 
@@ -1142,9 +1197,11 @@ Select a file from the sidebar explorer on the left to read its code contents in
1142
  document.getElementById('section-models').classList.add('hidden');
1143
  document.getElementById('section-logs').classList.add('hidden');
1144
  document.getElementById('section-explorer').classList.add('hidden');
 
1145
 
1146
  document.getElementById('tab-btn-models').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all';
1147
  document.getElementById('tab-btn-logs').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all';
 
1148
  document.getElementById('tab-btn-explorer').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold flex items-center space-x-1 transition-all';
1149
 
1150
  if (tabId === 'models') {
@@ -1157,6 +1214,10 @@ Select a file from the sidebar explorer on the left to read its code contents in
1157
  document.getElementById('section-explorer').classList.remove('hidden');
1158
  document.getElementById('tab-btn-explorer').className = 'pb-3 border-b-2 border-blue-500 font-semibold text-blue-400 flex items-center space-x-1 transition-all';
1159
  refreshFileTree();
 
 
 
 
1160
  }
1161
  }
1162
 
@@ -1294,9 +1355,159 @@ Select a file from the sidebar explorer on the left to read its code contents in
1294
  }
1295
  }
1296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1297
  setInterval(fetchSystemData, 3000);
1298
  setInterval(fetchModels, 3000);
1299
  setInterval(fetchLogs, 2000);
 
1300
 
1301
  fetchSystemData();
1302
  fetchModels();
@@ -1406,9 +1617,11 @@ async def delete_session(req: SessionDeleteRequest, authorization: str = Header(
1406
  from datetime import datetime, timedelta, timezone
1407
 
1408
  class EternityInitRequest(BaseModel):
 
1409
  goal: Optional[str] = None
1410
  problem_statement: Optional[str] = None
1411
  deadline_hours: float
 
1412
 
1413
  @app.post("/api/eternity/init")
1414
  async def init_eternity_system(req: EternityInitRequest, authorization: str = Header(None)):
@@ -1417,21 +1630,99 @@ async def init_eternity_system(req: EternityInitRequest, authorization: str = He
1417
  raise HTTPException(status_code=500, detail="Database not connected")
1418
  try:
1419
  goal_text = req.goal or req.problem_statement or "Build a calculator"
 
 
 
 
 
 
1420
  deadline = datetime.now(timezone.utc) + timedelta(hours=req.deadline_hours)
1421
  async with db_pool.acquire() as conn:
1422
- # Delete any old state
1423
- await conn.execute("DELETE FROM eternity_system_state")
1424
- # Insert new state
1425
- await conn.execute(
1426
- "INSERT INTO eternity_system_state (goal, deadline, current_mode, roadmap) VALUES ($1, $2, 'build', $3)",
1427
- goal_text, deadline, "[]"
1428
- )
1429
- log_activity(f"[Eternity Loop] System initialized with goal: '{goal_text}' | Deadline: {deadline}")
1430
- return {"status": "success", "deadline": deadline.isoformat()}
1431
  except Exception as e:
1432
  log_activity(f"[Eternity Loop Error] Init failed: {e}")
1433
  raise HTTPException(status_code=500, detail=str(e))
1434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1435
  @app.get("/api/eternity/status")
1436
  async def get_eternity_status(authorization: str = Header(None)):
1437
  auth(authorization)
@@ -1439,7 +1730,7 @@ async def get_eternity_status(authorization: str = Header(None)):
1439
  raise HTTPException(status_code=500, detail="Database not connected")
1440
  try:
1441
  async with db_pool.acquire() as conn:
1442
- row = await conn.fetchrow("SELECT goal, deadline, current_mode, roadmap, latest_brief FROM eternity_system_state ORDER BY id DESC LIMIT 1")
1443
  if not row:
1444
  return {"active": False}
1445
 
@@ -1449,6 +1740,7 @@ async def get_eternity_status(authorization: str = Header(None)):
1449
 
1450
  return {
1451
  "active": True,
 
1452
  "goal": row["goal"],
1453
  "deadline": deadline.isoformat(),
1454
  "current_mode": row["current_mode"],
@@ -1571,21 +1863,21 @@ SPACE4_URL = os.environ.get("SPACE4_URL", "https://shyota-mcp-cloud-host.hf.spac
1571
  SPACE5_URL = os.environ.get("SPACE5_URL", "https://augment17-better-chatbot.hf.space")
1572
  SPACE6_URL = os.environ.get("SPACE6_URL", "https://augment17-mcp-cloud-host.hf.space")
1573
 
1574
- async def get_db_state():
1575
  async with db_pool.acquire() as conn:
1576
- return await conn.fetchrow("SELECT goal, deadline, current_mode FROM eternity_system_state ORDER BY id DESC LIMIT 1")
1577
 
1578
- async def update_db_mode(mode: str):
1579
  async with db_pool.acquire() as conn:
1580
- await conn.execute("UPDATE eternity_system_state SET current_mode = $1", mode)
1581
 
1582
- async def update_db_brief(brief: str):
1583
  async with db_pool.acquire() as conn:
1584
- await conn.execute("UPDATE eternity_system_state SET latest_brief = $1", brief)
1585
 
1586
- async def update_db_roadmap(roadmap: list):
1587
  async with db_pool.acquire() as conn:
1588
- await conn.execute("UPDATE eternity_system_state SET roadmap = $1", json.dumps(roadmap))
1589
 
1590
  def post_json(url: str, payload: dict) -> dict:
1591
  headers = {
@@ -1605,8 +1897,8 @@ def post_json(url: str, payload: dict) -> dict:
1605
  print(f"[HTTP Error] POST to {url} failed: {e}")
1606
  return {"status": "error", "error": str(e)}
1607
 
1608
- async def execute_build_cycle(goal: str):
1609
- log_activity(f"[Build Mode] Initiating build cycle for goal: '{goal}'")
1610
  await rate_limiter.wait_for_nim()
1611
  prompt = f"We are building: '{goal}'. Write a JSON instruction for Space 3 (The Forge) to code the next milestone. Respond ONLY with JSON matching the contract: " + '{"prompt": "task description", "context_rules": "rules"}'
1612
  try:
@@ -1619,20 +1911,20 @@ async def execute_build_cycle(goal: str):
1619
  task_prompt = task.get("prompt")
1620
  context_rules = task.get("context_rules", "")
1621
  except Exception as e:
1622
- log_activity(f"[Build Mode Error] NIM planning failed: {e}")
1623
  return
1624
 
1625
- log_activity(f"[Build Mode] Dispatching task to Space 3: '{task_prompt}'")
1626
  forge_res = post_json(f"{SPACE3_URL}/api/forge/execute", {
1627
- "task_id": f"build_{int(time.time())}",
1628
  "action": "execute_code",
1629
  "prompt": task_prompt,
1630
  "context_rules": context_rules
1631
  })
1632
 
1633
  if forge_res.get("status") == "success":
1634
- log_activity(f"[Build Mode] Space 3 success: {forge_res.get('summary')}")
1635
- log_activity("[Build Mode] Triggering Space 6 (The Sandbox) UI verification...")
1636
  test_res = post_json(f"{SPACE6_URL}/api/sandbox/test", {
1637
  "test_cmd": "verify_ui",
1638
  "url": f"{SPACE3_URL}"
@@ -1644,13 +1936,13 @@ async def execute_build_cycle(goal: str):
1644
  else:
1645
  log_activity(f"[Build Mode Warning] Space 3 reported failure: {forge_res.get('error')}")
1646
 
1647
- async def execute_eternity_cycle(goal: str):
1648
- log_activity(f"[Eternity Mode] Initiating autonomous R&D cycle for goal: '{goal}'")
1649
- log_activity("[Eternity Mode] Querying Space 4 (The Library) for new feature research...")
1650
- research_res = post_json(f"{SPACE4_URL}/api/research", {"query": f"novel scientific or chemical calculation features and algorithms for {goal}"})
1651
  brief = research_res.get("brief", "No new features found.")
1652
- await update_db_brief(brief)
1653
- log_activity(f"[Eternity Mode] Received research brief: {brief[:100]}...")
1654
 
1655
  await rate_limiter.wait_for_nim()
1656
  prompt = f"Goal: '{goal}'. Research Brief: '{brief}'. Plan the next feature/optimization code. Respond ONLY with JSON: " + '{"prompt": "task description", "context_rules": "rules"}'
@@ -1664,12 +1956,12 @@ async def execute_eternity_cycle(goal: str):
1664
  task_prompt = task.get("prompt")
1665
  context_rules = task.get("context_rules", "")
1666
  except Exception as e:
1667
- log_activity(f"[Eternity Mode Error] NIM planning failed: {e}")
1668
  return
1669
 
1670
  log_activity(f"[Eternity Mode] Dispatching task to Space 3: '{task_prompt}'")
1671
  forge_res = post_json(f"{SPACE3_URL}/api/forge/execute", {
1672
- "task_id": f"eternity_{int(time.time())}",
1673
  "action": "execute_code",
1674
  "prompt": task_prompt,
1675
  "context_rules": context_rules
@@ -1681,8 +1973,11 @@ async def execute_eternity_cycle(goal: str):
1681
  post_json(f"{SPACE5_URL}/api/vault/push", {})
1682
 
1683
 
 
 
1684
  def run_eternity_loop():
1685
  log_activity("[Eternity Loop] Daemon thread started.")
 
1686
  while True:
1687
  try:
1688
  if not db_pool:
@@ -1690,37 +1985,53 @@ def run_eternity_loop():
1690
  continue
1691
 
1692
  loop = asyncio.new_event_loop()
1693
- state = loop.run_until_complete(get_db_state())
1694
  loop.close()
1695
 
1696
- if not state:
1697
  time.sleep(30)
1698
  continue
1699
 
1700
- goal = state["goal"]
1701
- deadline = state["deadline"]
1702
- current_mode = state["current_mode"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1703
 
1704
- now = datetime.now(timezone.utc)
1705
- if current_mode == "build" and now >= deadline:
1706
- loop = asyncio.new_event_loop()
1707
- loop.run_until_complete(update_db_mode("eternity"))
1708
- loop.close()
1709
- current_mode = "eternity"
1710
- log_activity("[Eternity Loop] Deadline reached. Transitioned to Eternity R&D Mode.")
1711
 
1712
- if current_mode == "build":
1713
- loop = asyncio.new_event_loop()
1714
- loop.run_until_complete(execute_build_cycle(goal))
1715
- loop.close()
1716
- time.sleep(300)
1717
- else:
1718
- loop = asyncio.new_event_loop()
1719
- loop.run_until_complete(execute_eternity_cycle(goal))
1720
- loop.close()
1721
- interval = int(os.environ.get("ETERNITY_LOOP_INTERVAL", "3600"))
1722
- time.sleep(interval)
1723
-
1724
  except Exception as e:
1725
  log_activity(f"[Eternity Loop Error] Loop crash: {e}")
1726
  time.sleep(60)
 
436
  CREATE INDEX IF NOT EXISTS idx_session_key ON agent_session_entries (project_key, session_id, subpath, id);
437
  CREATE INDEX IF NOT EXISTS idx_project_session ON agent_session_entries (project_key, session_id);
438
 
439
+ CREATE TABLE IF NOT EXISTS eternity_projects (
440
  id SERIAL PRIMARY KEY,
441
+ project_name VARCHAR(100) UNIQUE NOT NULL,
442
  goal TEXT NOT NULL,
443
  deadline TIMESTAMPTZ NOT NULL,
444
  current_mode VARCHAR(20) NOT NULL DEFAULT 'build',
445
+ is_active BOOLEAN NOT NULL DEFAULT true,
446
+ priority VARCHAR(20) NOT NULL DEFAULT 'low',
447
  roadmap JSONB DEFAULT '[]',
448
  latest_brief TEXT,
449
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
 
1056
  <div class="border-b border-gray-800 max-w-7xl w-full mx-auto px-6 mt-6 flex space-x-6 text-sm">
1057
  <button onclick="switchTab('models')" id="tab-btn-models" class="pb-3 border-b-2 border-blue-500 font-semibold text-blue-400 transition-all">NIM Models</button>
1058
  <button onclick="switchTab('logs')" id="tab-btn-logs" class="pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all">Live Logs</button>
1059
+ <button onclick="switchTab('eternity')" id="tab-btn-eternity" class="pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all">Eternity R&D Lab</button>
1060
  <button onclick="switchTab('explorer')" id="tab-btn-explorer" class="pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold flex items-center space-x-1 transition-all">
1061
  <span>Workspace Explorer (IDE)</span>
1062
  <span class="px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 border border-blue-500/20 text-[10px] font-bold">VS Code View</span>
 
1135
  </div>
1136
  </div>
1137
 
1138
+ <!-- SECTION: Eternity Lab -->
1139
+ <div id="section-eternity" class="hidden space-y-6">
1140
+ <div class="flex items-center justify-between border-b border-gray-800 pb-4">
1141
+ <div>
1142
+ <h2 class="text-xl font-bold tracking-tight text-gray-200">Eternity R&D Projects</h2>
1143
+ <p class="text-xs text-gray-500 mt-1">Autonomous multi-agent loop orchestrator panel</p>
1144
+ </div>
1145
+ <button onclick="openNewProjectModal()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-semibold transition-all shadow-lg glow-amber">
1146
+ ➕ New R&D Goal
1147
+ </button>
1148
+ </div>
1149
+
1150
+ <div id="eternity-projects-list" class="grid grid-cols-1 gap-6">
1151
+ <!-- Dynamically populated project cards -->
1152
+ </div>
1153
+ </div>
1154
+
1155
  </main>
1156
 
1157
+ <!-- MODAL: New Project -->
1158
+ <div id="new-project-modal" class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center hidden z-50">
1159
+ <div class="bg-gray-900 border border-gray-800 p-6 rounded-xl max-w-md w-full space-y-4">
1160
+ <h3 class="text-lg font-bold text-gray-100">Initialize R&D Goal</h3>
1161
+ <div class="space-y-3 text-sm">
1162
+ <div>
1163
+ <label class="block text-gray-400 mb-1">Project Name (Letters/Numbers/Dashes only)</label>
1164
+ <input id="proj-name" type="text" class="w-full bg-black border border-gray-800 rounded p-2 text-gray-200" placeholder="e.g. stoichiometry-solver">
1165
+ </div>
1166
+ <div>
1167
+ <label class="block text-gray-400 mb-1">Problem Statement / Goal</label>
1168
+ <textarea id="proj-goal" rows="4" class="w-full bg-black border border-gray-800 rounded p-2 text-gray-200" placeholder="Describe the goal in detail..."></textarea>
1169
+ </div>
1170
+ <div class="grid grid-cols-2 gap-4">
1171
+ <div>
1172
+ <label class="block text-gray-400 mb-1">Deadline (Hours)</label>
1173
+ <input id="proj-deadline" type="number" step="0.01" value="1.0" class="w-full bg-black border border-gray-800 rounded p-2 text-gray-200">
1174
+ </div>
1175
+ <div>
1176
+ <label class="block text-gray-400 mb-1">Priority</label>
1177
+ <select id="proj-priority" class="w-full bg-black border border-gray-800 rounded p-2 text-gray-200">
1178
+ <option value="supreme">Supreme Priority</option>
1179
+ <option value="low" selected>Low Priority</option>
1180
+ </select>
1181
+ </div>
1182
+ </div>
1183
+ </div>
1184
+ <div class="flex justify-end space-x-3 text-sm pt-2">
1185
+ <button onclick="closeNewProjectModal()" class="px-4 py-2 border border-gray-800 text-gray-400 rounded hover:bg-gray-800">Cancel</button>
1186
+ <button onclick="submitNewProject()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-semibold">Start R&D</button>
1187
+ </div>
1188
+ </div>
1189
+ </div>
1190
+
1191
  <script>
1192
  let currentTab = 'models';
1193
 
 
1197
  document.getElementById('section-models').classList.add('hidden');
1198
  document.getElementById('section-logs').classList.add('hidden');
1199
  document.getElementById('section-explorer').classList.add('hidden');
1200
+ document.getElementById('section-eternity').classList.add('hidden');
1201
 
1202
  document.getElementById('tab-btn-models').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all';
1203
  document.getElementById('tab-btn-logs').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all';
1204
+ document.getElementById('tab-btn-eternity').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold transition-all';
1205
  document.getElementById('tab-btn-explorer').className = 'pb-3 border-b-2 border-transparent text-gray-400 hover:text-gray-200 font-semibold flex items-center space-x-1 transition-all';
1206
 
1207
  if (tabId === 'models') {
 
1214
  document.getElementById('section-explorer').classList.remove('hidden');
1215
  document.getElementById('tab-btn-explorer').className = 'pb-3 border-b-2 border-blue-500 font-semibold text-blue-400 flex items-center space-x-1 transition-all';
1216
  refreshFileTree();
1217
+ } else if (tabId === 'eternity') {
1218
+ document.getElementById('section-eternity').classList.remove('hidden');
1219
+ document.getElementById('tab-btn-eternity').className = 'pb-3 border-b-2 border-blue-500 font-semibold text-blue-400 transition-all';
1220
+ fetchEternityProjects();
1221
  }
1222
  }
1223
 
 
1355
  }
1356
  }
1357
 
1358
+ // Eternity R&D UI Logic
1359
+ function openNewProjectModal() {
1360
+ document.getElementById('new-project-modal').classList.remove('hidden');
1361
+ }
1362
+
1363
+ function closeNewProjectModal() {
1364
+ document.getElementById('new-project-modal').classList.add('hidden');
1365
+ }
1366
+
1367
+ async function submitNewProject() {
1368
+ const name = document.getElementById('proj-name').value.trim();
1369
+ const goal = document.getElementById('proj-goal').value.trim();
1370
+ const deadline = parseFloat(document.getElementById('proj-deadline').value) || 1.0;
1371
+ const priority = document.getElementById('proj-priority').value;
1372
+
1373
+ if (!goal) {
1374
+ alert('Please enter a goal statement.');
1375
+ return;
1376
+ }
1377
+
1378
+ try {
1379
+ const res = await fetch('/api/eternity/init', {
1380
+ method: 'POST',
1381
+ headers: { 'Content-Type': 'application/json' },
1382
+ body: JSON.stringify({
1383
+ project_name: name,
1384
+ goal: goal,
1385
+ deadline_hours: deadline,
1386
+ priority: priority
1387
+ })
1388
+ });
1389
+ if (res.ok) {
1390
+ closeNewProjectModal();
1391
+ // Clear fields
1392
+ document.getElementById('proj-name').value = '';
1393
+ document.getElementById('proj-goal').value = '';
1394
+ fetchEternityProjects();
1395
+ } else {
1396
+ const data = await res.json();
1397
+ alert(`Failed: ${data.detail || 'Unknown error'}`);
1398
+ }
1399
+ } catch (e) {
1400
+ alert(`Error: ${e.message}`);
1401
+ }
1402
+ }
1403
+
1404
+ async function toggleActive(projName, isActive) {
1405
+ try {
1406
+ await fetch('/api/eternity/toggle', {
1407
+ method: 'POST',
1408
+ headers: { 'Content-Type': 'application/json' },
1409
+ body: JSON.stringify({ project_name: projName, is_active: isActive })
1410
+ });
1411
+ fetchEternityProjects();
1412
+ } catch (e) {
1413
+ console.error(e);
1414
+ }
1415
+ }
1416
+
1417
+ async function setPriority(projName, priorityVal) {
1418
+ try {
1419
+ await fetch('/api/eternity/set-priority', {
1420
+ method: 'POST',
1421
+ headers: { 'Content-Type': 'application/json' },
1422
+ body: JSON.stringify({ project_name: projName, priority: priorityVal })
1423
+ });
1424
+ fetchEternityProjects();
1425
+ } catch (e) {
1426
+ console.error(e);
1427
+ }
1428
+ }
1429
+
1430
+ async function fetchEternityProjects() {
1431
+ if (currentTab !== 'eternity') return;
1432
+ try {
1433
+ const res = await fetch('/api/eternity/list');
1434
+ if (!res.ok) return;
1435
+ const data = await res.json();
1436
+
1437
+ const container = document.getElementById('eternity-projects-list');
1438
+ if (data.projects.length === 0) {
1439
+ container.innerHTML = `
1440
+ <div class="text-center py-12 border border-dashed border-gray-800 rounded-lg text-gray-500">
1441
+ No active R&D goals configured. Click "New R&D Goal" to launch one!
1442
+ </div>
1443
+ `;
1444
+ return;
1445
+ }
1446
+
1447
+ container.innerHTML = data.projects.map(p => {
1448
+ const modeColor = p.current_mode === 'build' ? 'from-amber-500/20 to-orange-500/20 text-orange-400 border-orange-500/30' : 'from-green-500/20 to-emerald-500/20 text-green-400 border-green-500/30';
1449
+ const activeBadge = p.is_active ? '<span class="bg-green-500/10 text-green-400 border border-green-500/20 px-2 py-0.5 rounded text-[10px] font-bold">ACTIVE</span>' : '<span class="bg-gray-800 text-gray-500 border border-gray-700 px-2 py-0.5 rounded text-[10px] font-bold">PAUSED</span>';
1450
+ const priorityColor = p.priority === 'supreme' ? 'bg-red-500/10 text-red-400 border-red-500/20' : 'bg-blue-500/10 text-blue-400 border-blue-500/20';
1451
+
1452
+ return `
1453
+ <div class="bg-gray-950 border border-gray-800 rounded-xl p-6 space-y-4 hover:border-gray-700 transition-all">
1454
+ <div class="flex items-start justify-between">
1455
+ <div class="space-y-1">
1456
+ <div class="flex items-center space-x-2">
1457
+ <h3 class="text-lg font-bold text-gray-200 code-font">${p.project_name}</h3>
1458
+ ${activeBadge}
1459
+ <span class="px-2 py-0.5 rounded text-[10px] font-bold border ${priorityColor}">${p.priority.toUpperCase()}</span>
1460
+ </div>
1461
+ <p class="text-xs text-gray-500">Started on ${new Date(p.created_at).toLocaleString()}</p>
1462
+ </div>
1463
+ <div class="flex items-center space-x-3">
1464
+ <select onchange="setPriority('${p.project_name}', this.value)" class="bg-black border border-gray-850 rounded px-2.5 py-1 text-xs text-gray-300 focus:outline-none">
1465
+ <option value="supreme" ${p.priority === 'supreme' ? 'selected' : ''}>Supreme</option>
1466
+ <option value="low" ${p.priority === 'low' ? 'selected' : ''}>Low</option>
1467
+ </select>
1468
+
1469
+ <button onclick="toggleActive('${p.project_name}', ${!p.is_active})" class="text-xs px-3.5 py-1 rounded border ${p.is_active ? 'border-red-500/30 text-red-400 bg-red-500/5 hover:bg-red-500/10' : 'border-green-500/30 text-green-400 bg-green-500/5 hover:bg-green-500/10'} font-semibold transition-all">
1470
+ ${p.is_active ? 'Pause' : 'Resume'}
1471
+ </button>
1472
+ </div>
1473
+ </div>
1474
+
1475
+ <div class="space-y-2">
1476
+ <div class="text-sm text-gray-300 font-semibold">Goal Description:</div>
1477
+ <div class="text-sm text-gray-400 bg-black/40 p-3 rounded border border-gray-900 leading-relaxed">${p.goal}</div>
1478
+ </div>
1479
+
1480
+ <div class="grid grid-cols-2 gap-4 text-xs">
1481
+ <div class="bg-gray-900/50 p-3 rounded border border-gray-850 space-y-1">
1482
+ <div class="text-gray-500">Mode Status</div>
1483
+ <div class="font-bold bg-gradient-to-r ${modeColor} bg-clip-text text-transparent">${p.current_mode.toUpperCase()} MODE</div>
1484
+ </div>
1485
+ <div class="bg-gray-900/50 p-3 rounded border border-gray-850 space-y-1">
1486
+ <div class="text-gray-500">Deadline Countdown</div>
1487
+ <div class="font-bold text-gray-200 code-font">${p.time_remaining_str || 'Expired'}</div>
1488
+ </div>
1489
+ </div>
1490
+
1491
+ ${p.latest_brief ? `
1492
+ <div class="bg-blue-950/20 border border-blue-900/30 rounded p-4 text-xs text-blue-300 leading-relaxed space-y-1">
1493
+ <div class="font-bold text-blue-400 flex items-center space-x-1">
1494
+ <span>📘 Latest Research Brief Summary</span>
1495
+ </div>
1496
+ <div class="text-blue-300 mt-1">${p.latest_brief}</div>
1497
+ </div>
1498
+ ` : ''}
1499
+ </div>
1500
+ `;
1501
+ }).join('');
1502
+ } catch (e) {
1503
+ console.error(e);
1504
+ }
1505
+ }
1506
+
1507
  setInterval(fetchSystemData, 3000);
1508
  setInterval(fetchModels, 3000);
1509
  setInterval(fetchLogs, 2000);
1510
+ setInterval(fetchEternityProjects, 5000);
1511
 
1512
  fetchSystemData();
1513
  fetchModels();
 
1617
  from datetime import datetime, timedelta, timezone
1618
 
1619
  class EternityInitRequest(BaseModel):
1620
+ project_name: Optional[str] = None
1621
  goal: Optional[str] = None
1622
  problem_statement: Optional[str] = None
1623
  deadline_hours: float
1624
+ priority: Optional[str] = "low"
1625
 
1626
  @app.post("/api/eternity/init")
1627
  async def init_eternity_system(req: EternityInitRequest, authorization: str = Header(None)):
 
1630
  raise HTTPException(status_code=500, detail="Database not connected")
1631
  try:
1632
  goal_text = req.goal or req.problem_statement or "Build a calculator"
1633
+ # Generate slugified project name if not provided
1634
+ import re
1635
+ slug = req.project_name
1636
+ if not slug:
1637
+ slug = re.sub(r'[^a-z0-9]+', '-', goal_text.lower()).strip('-')[:30] or "project"
1638
+
1639
  deadline = datetime.now(timezone.utc) + timedelta(hours=req.deadline_hours)
1640
  async with db_pool.acquire() as conn:
1641
+ await conn.execute("""
1642
+ INSERT INTO eternity_projects (project_name, goal, deadline, current_mode, priority, is_active)
1643
+ VALUES ($1, $2, $3, 'build', $4, true)
1644
+ ON CONFLICT (project_name) DO UPDATE
1645
+ SET goal = EXCLUDED.goal, deadline = EXCLUDED.deadline, current_mode = 'build', priority = EXCLUDED.priority, is_active = true
1646
+ """, slug, goal_text, deadline, req.priority or "low")
1647
+ log_activity(f"[Eternity Loop] Project '{slug}' initialized | Goal: '{goal_text}' | Priority: {req.priority}")
1648
+ return {"status": "success", "project_name": slug, "deadline": deadline.isoformat()}
 
1649
  except Exception as e:
1650
  log_activity(f"[Eternity Loop Error] Init failed: {e}")
1651
  raise HTTPException(status_code=500, detail=str(e))
1652
 
1653
+ class EternityToggleRequest(BaseModel):
1654
+ project_name: str
1655
+ is_active: bool
1656
+
1657
+ @app.post("/api/eternity/toggle")
1658
+ async def toggle_project(req: EternityToggleRequest, authorization: str = Header(None)):
1659
+ auth(authorization)
1660
+ if not db_pool:
1661
+ raise HTTPException(status_code=500, detail="Database not connected")
1662
+ try:
1663
+ async with db_pool.acquire() as conn:
1664
+ await conn.execute("UPDATE eternity_projects SET is_active = $1 WHERE project_name = $2", req.is_active, req.project_name)
1665
+ log_activity(f"[Eternity Loop] Project '{req.project_name}' is_active set to {req.is_active}")
1666
+ return {"status": "success"}
1667
+ except Exception as e:
1668
+ raise HTTPException(status_code=500, detail=str(e))
1669
+
1670
+ class EternityPriorityRequest(BaseModel):
1671
+ project_name: str
1672
+ priority: str
1673
+
1674
+ @app.post("/api/eternity/set-priority")
1675
+ async def set_project_priority(req: EternityPriorityRequest, authorization: str = Header(None)):
1676
+ auth(authorization)
1677
+ if not db_pool:
1678
+ raise HTTPException(status_code=500, detail="Database not connected")
1679
+ try:
1680
+ if req.priority not in ["supreme", "low"]:
1681
+ raise HTTPException(status_code=400, detail="Invalid priority")
1682
+ async with db_pool.acquire() as conn:
1683
+ await conn.execute("UPDATE eternity_projects SET priority = $1 WHERE project_name = $2", req.priority, req.project_name)
1684
+ log_activity(f"[Eternity Loop] Project '{req.project_name}' priority set to {req.priority}")
1685
+ return {"status": "success"}
1686
+ except Exception as e:
1687
+ raise HTTPException(status_code=500, detail=str(e))
1688
+
1689
+ @app.get("/api/eternity/list")
1690
+ async def list_eternity_projects(authorization: str = Header(None)):
1691
+ auth(authorization)
1692
+ if not db_pool:
1693
+ raise HTTPException(status_code=500, detail="Database not connected")
1694
+ try:
1695
+ async with db_pool.acquire() as conn:
1696
+ rows = await conn.fetch("SELECT project_name, goal, deadline, current_mode, is_active, priority, latest_brief, created_at FROM eternity_projects ORDER BY created_at DESC")
1697
+ projects = []
1698
+ now = datetime.now(timezone.utc)
1699
+ for r in rows:
1700
+ deadline = r["deadline"]
1701
+ remaining = max(0.0, (deadline - now).total_seconds())
1702
+
1703
+ # Format remaining time
1704
+ if remaining > 0:
1705
+ h = int(remaining // 3600)
1706
+ m = int((remaining % 3600) // 60)
1707
+ rem_str = f"{h}h {m}m"
1708
+ else:
1709
+ rem_str = "Expired"
1710
+
1711
+ projects.append({
1712
+ "project_name": r["project_name"],
1713
+ "goal": r["goal"],
1714
+ "deadline": deadline.isoformat(),
1715
+ "current_mode": r["current_mode"],
1716
+ "is_active": r["is_active"],
1717
+ "priority": r["priority"],
1718
+ "latest_brief": r["latest_brief"],
1719
+ "created_at": r["created_at"].isoformat(),
1720
+ "time_remaining_str": rem_str
1721
+ })
1722
+ return {"projects": projects}
1723
+ except Exception as e:
1724
+ raise HTTPException(status_code=500, detail=str(e))
1725
+
1726
  @app.get("/api/eternity/status")
1727
  async def get_eternity_status(authorization: str = Header(None)):
1728
  auth(authorization)
 
1730
  raise HTTPException(status_code=500, detail="Database not connected")
1731
  try:
1732
  async with db_pool.acquire() as conn:
1733
+ row = await conn.fetchrow("SELECT project_name, goal, deadline, current_mode, roadmap, latest_brief FROM eternity_projects WHERE is_active = true ORDER BY id DESC LIMIT 1")
1734
  if not row:
1735
  return {"active": False}
1736
 
 
1740
 
1741
  return {
1742
  "active": True,
1743
+ "project_name": row["project_name"],
1744
  "goal": row["goal"],
1745
  "deadline": deadline.isoformat(),
1746
  "current_mode": row["current_mode"],
 
1863
  SPACE5_URL = os.environ.get("SPACE5_URL", "https://augment17-better-chatbot.hf.space")
1864
  SPACE6_URL = os.environ.get("SPACE6_URL", "https://augment17-mcp-cloud-host.hf.space")
1865
 
1866
+ async def get_active_projects():
1867
  async with db_pool.acquire() as conn:
1868
+ return await conn.fetch("SELECT project_name, goal, deadline, current_mode, priority FROM eternity_projects WHERE is_active = true ORDER BY id DESC")
1869
 
1870
+ async def update_db_mode(project_name: str, mode: str):
1871
  async with db_pool.acquire() as conn:
1872
+ await conn.execute("UPDATE eternity_projects SET current_mode = $1 WHERE project_name = $2", mode, project_name)
1873
 
1874
+ async def update_db_brief(project_name: str, brief: str):
1875
  async with db_pool.acquire() as conn:
1876
+ await conn.execute("UPDATE eternity_projects SET latest_brief = $1 WHERE project_name = $2", brief, project_name)
1877
 
1878
+ async def update_db_roadmap(project_name: str, roadmap: list):
1879
  async with db_pool.acquire() as conn:
1880
+ await conn.execute("UPDATE eternity_projects SET roadmap = $1 WHERE project_name = $2", json.dumps(roadmap), project_name)
1881
 
1882
  def post_json(url: str, payload: dict) -> dict:
1883
  headers = {
 
1897
  print(f"[HTTP Error] POST to {url} failed: {e}")
1898
  return {"status": "error", "error": str(e)}
1899
 
1900
+ async def execute_build_cycle(project_name: str, goal: str):
1901
+ log_activity(f"[Build Mode] Initiating build cycle for project '{project_name}' (goal: '{goal}')")
1902
  await rate_limiter.wait_for_nim()
1903
  prompt = f"We are building: '{goal}'. Write a JSON instruction for Space 3 (The Forge) to code the next milestone. Respond ONLY with JSON matching the contract: " + '{"prompt": "task description", "context_rules": "rules"}'
1904
  try:
 
1911
  task_prompt = task.get("prompt")
1912
  context_rules = task.get("context_rules", "")
1913
  except Exception as e:
1914
+ log_activity(f"[Build Mode Error] NIM planning failed for project '{project_name}': {e}")
1915
  return
1916
 
1917
+ log_activity(f"[Build Mode] Dispatching project '{project_name}' task to Space 3: '{task_prompt}'")
1918
  forge_res = post_json(f"{SPACE3_URL}/api/forge/execute", {
1919
+ "task_id": f"build_{project_name}_{int(time.time())}",
1920
  "action": "execute_code",
1921
  "prompt": task_prompt,
1922
  "context_rules": context_rules
1923
  })
1924
 
1925
  if forge_res.get("status") == "success":
1926
+ log_activity(f"[Build Mode] Space 3 success for project '{project_name}': {forge_res.get('summary')}")
1927
+ log_activity(f"[Build Mode] Triggering Space 6 (The Sandbox) UI verification for project '{project_name}'...")
1928
  test_res = post_json(f"{SPACE6_URL}/api/sandbox/test", {
1929
  "test_cmd": "verify_ui",
1930
  "url": f"{SPACE3_URL}"
 
1936
  else:
1937
  log_activity(f"[Build Mode Warning] Space 3 reported failure: {forge_res.get('error')}")
1938
 
1939
+ async def execute_eternity_cycle(project_name: str, goal: str):
1940
+ log_activity(f"[Eternity Mode] Initiating autonomous R&D cycle for project '{project_name}' (goal: '{goal}')")
1941
+ log_activity(f"[Eternity Mode] Querying Space 4 (The Library) for research on project '{project_name}'...")
1942
+ research_res = post_json(f"{SPACE4_URL}/api/research", {"query": f"novel algorithms and components for {goal}"})
1943
  brief = research_res.get("brief", "No new features found.")
1944
+ await update_db_brief(project_name, brief)
1945
+ log_activity(f"[Eternity Mode] Received research brief for '{project_name}': {brief[:100]}...")
1946
 
1947
  await rate_limiter.wait_for_nim()
1948
  prompt = f"Goal: '{goal}'. Research Brief: '{brief}'. Plan the next feature/optimization code. Respond ONLY with JSON: " + '{"prompt": "task description", "context_rules": "rules"}'
 
1956
  task_prompt = task.get("prompt")
1957
  context_rules = task.get("context_rules", "")
1958
  except Exception as e:
1959
+ log_activity(f"[Eternity Mode Error] NIM planning failed for project '{project_name}': {e}")
1960
  return
1961
 
1962
  log_activity(f"[Eternity Mode] Dispatching task to Space 3: '{task_prompt}'")
1963
  forge_res = post_json(f"{SPACE3_URL}/api/forge/execute", {
1964
+ "task_id": f"eternity_{project_name}_{int(time.time())}",
1965
  "action": "execute_code",
1966
  "prompt": task_prompt,
1967
  "context_rules": context_rules
 
1973
  post_json(f"{SPACE5_URL}/api/vault/push", {})
1974
 
1975
 
1976
+ last_run_times = {} # project_name -> timestamp
1977
+
1978
  def run_eternity_loop():
1979
  log_activity("[Eternity Loop] Daemon thread started.")
1980
+ loop_counter = 0
1981
  while True:
1982
  try:
1983
  if not db_pool:
 
1985
  continue
1986
 
1987
  loop = asyncio.new_event_loop()
1988
+ projects = loop.run_until_complete(get_active_projects())
1989
  loop.close()
1990
 
1991
+ if not projects:
1992
  time.sleep(30)
1993
  continue
1994
 
1995
+ loop_counter += 1
1996
+ for p in projects:
1997
+ name = p["project_name"]
1998
+ goal = p["goal"]
1999
+ deadline = p["deadline"]
2000
+ current_mode = p["current_mode"]
2001
+ priority = p["priority"]
2002
+
2003
+ # Priority gating: Low priority skips 3 out of 4 cycles to save token rate limits
2004
+ if priority == "low" and (loop_counter % 4) != 0:
2005
+ continue
2006
+
2007
+ now = datetime.now(timezone.utc)
2008
+ if current_mode == "build" and now >= deadline:
2009
+ loop = asyncio.new_event_loop()
2010
+ loop.run_until_complete(update_db_mode(name, "eternity"))
2011
+ loop.close()
2012
+ current_mode = "eternity"
2013
+ log_activity(f"[Eternity Loop] Project '{name}' deadline reached. Transitioned to Eternity R&D Mode.")
2014
+
2015
+ if current_mode == "build":
2016
+ loop = asyncio.new_event_loop()
2017
+ loop.run_until_complete(execute_build_cycle(name, goal))
2018
+ loop.close()
2019
+ else:
2020
+ # Enforce the sleep interval for Eternity Mode independently per project
2021
+ last_run = last_run_times.get(name, 0.0)
2022
+ now_ts = time.time()
2023
+ interval = int(os.environ.get("ETERNITY_LOOP_INTERVAL", "3600"))
2024
+ if now_ts - last_run < interval:
2025
+ continue
2026
+ last_run_times[name] = now_ts
2027
+
2028
+ loop = asyncio.new_event_loop()
2029
+ loop.run_until_complete(execute_eternity_cycle(name, goal))
2030
+ loop.close()
2031
 
2032
+ # Base check interval (5 minutes)
2033
+ time.sleep(300)
 
 
 
 
 
2034
 
 
 
 
 
 
 
 
 
 
 
 
 
2035
  except Exception as e:
2036
  log_activity(f"[Eternity Loop Error] Loop crash: {e}")
2037
  time.sleep(60)