Spaces:
Sleeping
Sleeping
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- 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
|
| 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 |
-
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
| 1426 |
-
|
| 1427 |
-
|
| 1428 |
-
|
| 1429 |
-
|
| 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
|
| 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
|
| 1575 |
async with db_pool.acquire() as conn:
|
| 1576 |
-
return await conn.
|
| 1577 |
|
| 1578 |
-
async def update_db_mode(mode: str):
|
| 1579 |
async with db_pool.acquire() as conn:
|
| 1580 |
-
await conn.execute("UPDATE
|
| 1581 |
|
| 1582 |
-
async def update_db_brief(brief: str):
|
| 1583 |
async with db_pool.acquire() as conn:
|
| 1584 |
-
await conn.execute("UPDATE
|
| 1585 |
|
| 1586 |
-
async def update_db_roadmap(roadmap: list):
|
| 1587 |
async with db_pool.acquire() as conn:
|
| 1588 |
-
await conn.execute("UPDATE
|
| 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
|
| 1650 |
-
research_res = post_json(f"{SPACE4_URL}/api/research", {"query": f"novel
|
| 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 |
-
|
| 1694 |
loop.close()
|
| 1695 |
|
| 1696 |
-
if not
|
| 1697 |
time.sleep(30)
|
| 1698 |
continue
|
| 1699 |
|
| 1700 |
-
|
| 1701 |
-
|
| 1702 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1703 |
|
| 1704 |
-
|
| 1705 |
-
|
| 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)
|