loki-mode 6.30.1 → 6.30.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web-app/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-BTtIHGw7.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-CxHyjUh7.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="bg-background text-charcoal font-sans antialiased">
|
package/web-app/server.py
CHANGED
|
@@ -502,6 +502,105 @@ async def get_template_content(filename: str) -> JSONResponse:
|
|
|
502
502
|
# ---------------------------------------------------------------------------
|
|
503
503
|
|
|
504
504
|
|
|
505
|
+
async def _push_state_to_client(ws: WebSocket) -> None:
|
|
506
|
+
"""Background task: push state snapshots to a single WebSocket client.
|
|
507
|
+
|
|
508
|
+
Pushes every 2s when a session is running, every 30s when idle.
|
|
509
|
+
"""
|
|
510
|
+
while True:
|
|
511
|
+
is_running = (
|
|
512
|
+
session.process is not None
|
|
513
|
+
and session.running
|
|
514
|
+
and session.process.poll() is None
|
|
515
|
+
)
|
|
516
|
+
interval = 2.0 if is_running else 30.0
|
|
517
|
+
|
|
518
|
+
# Build status payload (same logic as GET /api/session/status)
|
|
519
|
+
loki_dir = _loki_dir()
|
|
520
|
+
phase = "idle"
|
|
521
|
+
iteration = 0
|
|
522
|
+
complexity = "standard"
|
|
523
|
+
current_task = ""
|
|
524
|
+
pending_tasks = 0
|
|
525
|
+
|
|
526
|
+
state_file = loki_dir / "state" / "session.json"
|
|
527
|
+
if state_file.exists():
|
|
528
|
+
try:
|
|
529
|
+
with open(state_file) as f:
|
|
530
|
+
state_data = json.load(f)
|
|
531
|
+
phase = state_data.get("phase", phase)
|
|
532
|
+
iteration = state_data.get("iteration", iteration)
|
|
533
|
+
complexity = state_data.get("complexity", complexity)
|
|
534
|
+
current_task = state_data.get("current_task", current_task)
|
|
535
|
+
pending_tasks = state_data.get("pending_tasks", pending_tasks)
|
|
536
|
+
except (json.JSONDecodeError, OSError):
|
|
537
|
+
pass
|
|
538
|
+
|
|
539
|
+
uptime = time.time() - session.start_time if is_running else 0
|
|
540
|
+
status_payload = {
|
|
541
|
+
"running": session.running,
|
|
542
|
+
"paused": False,
|
|
543
|
+
"phase": phase,
|
|
544
|
+
"iteration": iteration,
|
|
545
|
+
"complexity": complexity,
|
|
546
|
+
"mode": "autonomous",
|
|
547
|
+
"provider": session.provider,
|
|
548
|
+
"current_task": current_task,
|
|
549
|
+
"pending_tasks": pending_tasks,
|
|
550
|
+
"running_agents": 0,
|
|
551
|
+
"uptime": round(uptime),
|
|
552
|
+
"version": "",
|
|
553
|
+
"pid": str(session.process.pid) if session.process else "",
|
|
554
|
+
"projectDir": session.project_dir,
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# Build agents payload
|
|
558
|
+
agents_payload: list = []
|
|
559
|
+
agents_file = loki_dir / "state" / "agents.json"
|
|
560
|
+
if agents_file.exists():
|
|
561
|
+
try:
|
|
562
|
+
with open(agents_file) as f:
|
|
563
|
+
agents_data = json.load(f)
|
|
564
|
+
if isinstance(agents_data, list):
|
|
565
|
+
agents_payload = agents_data
|
|
566
|
+
except (json.JSONDecodeError, OSError):
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
# Build logs payload (last 50 lines)
|
|
570
|
+
recent = session.log_lines[-50:] if session.log_lines else []
|
|
571
|
+
logs_payload = []
|
|
572
|
+
for line in recent:
|
|
573
|
+
level = "info"
|
|
574
|
+
lower = line.lower()
|
|
575
|
+
if "error" in lower or "fail" in lower:
|
|
576
|
+
level = "error"
|
|
577
|
+
elif "warn" in lower:
|
|
578
|
+
level = "warning"
|
|
579
|
+
elif "debug" in lower:
|
|
580
|
+
level = "debug"
|
|
581
|
+
logs_payload.append({
|
|
582
|
+
"timestamp": "",
|
|
583
|
+
"level": level,
|
|
584
|
+
"message": line,
|
|
585
|
+
"source": "loki",
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
try:
|
|
589
|
+
await ws.send_json({
|
|
590
|
+
"type": "state_update",
|
|
591
|
+
"data": {
|
|
592
|
+
"status": status_payload,
|
|
593
|
+
"agents": agents_payload,
|
|
594
|
+
"logs": logs_payload,
|
|
595
|
+
},
|
|
596
|
+
})
|
|
597
|
+
except Exception:
|
|
598
|
+
# Client disconnected; exit task
|
|
599
|
+
return
|
|
600
|
+
|
|
601
|
+
await asyncio.sleep(interval)
|
|
602
|
+
|
|
603
|
+
|
|
505
604
|
@app.websocket("/ws")
|
|
506
605
|
async def websocket_endpoint(ws: WebSocket) -> None:
|
|
507
606
|
"""Real-time stream of loki output and events."""
|
|
@@ -521,6 +620,9 @@ async def websocket_endpoint(ws: WebSocket) -> None:
|
|
|
521
620
|
"data": {"line": line, "timestamp": ""},
|
|
522
621
|
}))
|
|
523
622
|
|
|
623
|
+
# Start server-push state task for this connection
|
|
624
|
+
push_task = asyncio.create_task(_push_state_to_client(ws))
|
|
625
|
+
|
|
524
626
|
try:
|
|
525
627
|
while True:
|
|
526
628
|
# Keep connection alive; handle client messages if needed
|
|
@@ -535,6 +637,7 @@ async def websocket_endpoint(ws: WebSocket) -> None:
|
|
|
535
637
|
except WebSocketDisconnect:
|
|
536
638
|
pass
|
|
537
639
|
finally:
|
|
640
|
+
push_task.cancel()
|
|
538
641
|
session.ws_clients.discard(ws)
|
|
539
642
|
|
|
540
643
|
|
|
@@ -574,7 +677,7 @@ def main() -> None:
|
|
|
574
677
|
host = os.environ.get("PURPLE_LAB_HOST", HOST)
|
|
575
678
|
port = int(os.environ.get("PURPLE_LAB_PORT", str(PORT)))
|
|
576
679
|
print(f"Purple Lab starting on http://{host}:{port}")
|
|
577
|
-
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
680
|
+
uvicorn.run(app, host=host, port=port, log_level="info", timeout_keep_alive=30)
|
|
578
681
|
|
|
579
682
|
|
|
580
683
|
if __name__ == "__main__":
|