"""
OpenCLAW Web Server + Dashboard
================================
Lightweight Flask app for Render.com deployment.
Serves as health endpoint + agent dashboard + webhook receiver.
"""
import os
import sys
import json
import threading
import time
from datetime import datetime, timezone
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from flask import Flask, jsonify, render_template_string
from core.config import Config
from core.agent import OpenCLAWAgent, AgentState
app = Flask(__name__)
STATE_DIR = Path(os.getenv("STATE_DIR", "state"))
# Background agent thread
agent_thread = None
agent_running = False
def run_agent_loop():
"""Background thread running the agent."""
global agent_running
interval = int(os.getenv("DAEMON_INTERVAL", "3600")) # 1 hour default
while agent_running:
try:
config = Config.from_env()
agent = OpenCLAWAgent(config)
agent.run_cycle()
except Exception as e:
print(f"Agent cycle error: {e}")
# Sleep in small chunks for graceful shutdown
for _ in range(interval):
if not agent_running:
break
time.sleep(1)
DASHBOARD_HTML = """
OpenCLAW Agent Dashboard
🤖 OpenCLAW Autonomous Agent
Advanced AI Systems Laboratory — Madrid, Spain
📊 Agent Status
{{ status.cycle_count }}
Cycles
{{ status.posts_created }}
Posts
{{ status.engagement_count }}
Engagements
{{ status.papers_posted }}
Papers Shared
🔧 Services
{% for s in status.services %}✅ {{ s }} {% endfor %}
LLM:
{{ '✅ Online' if status.llm_available else '⚠️ Offline' }}
"""
@app.route("/")
def dashboard():
"""Dashboard page."""
config = Config.from_env()
agent = OpenCLAWAgent(config)
status = agent.get_status()
last_cycle = "{}"
lc_file = STATE_DIR / "last_cycle.json"
if lc_file.exists():
last_cycle = json.dumps(json.loads(lc_file.read_text()), indent=2)
return render_template_string(DASHBOARD_HTML, status=status, last_cycle=last_cycle)
@app.route("/health")
def health():
"""Health check endpoint."""
return jsonify({
"status": "healthy",
"agent": "OpenCLAW-Neuromorphic",
"timestamp": datetime.now(timezone.utc).isoformat(),
})
@app.route("/status")
def status():
"""JSON status endpoint."""
config = Config.from_env()
agent = OpenCLAWAgent(config)
return jsonify(agent.get_status())
@app.route("/trigger", methods=["POST"])
def trigger():
"""Manually trigger an agent cycle."""
try:
config = Config.from_env()
agent = OpenCLAWAgent(config)
results = agent.run_cycle()
return jsonify(results)
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
global agent_running, agent_thread
# Start background agent thread
agent_running = True
agent_thread = threading.Thread(target=run_agent_loop, daemon=True)
agent_thread.start()
print("🤖 Background agent started")
# Start web server
port = int(os.getenv("PORT", "10000"))
app.run(host="0.0.0.0", port=port)