feat: implement OpenClaw integration with new API endpoints, frontend page, client, and tests.

This commit is contained in:
2026-03-14 11:38:16 -04:00
parent 8c6ef5acb8
commit 3b82d69b22
4 changed files with 274 additions and 21 deletions
+72
View File
@@ -12,6 +12,7 @@ import os
import re
import shutil
import socket
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
@@ -473,6 +474,77 @@ async def list_openclaw_skills():
return {"skills": skills, "count": len(skills)}
# ═════════════════════════════════════════════════════════════════════════════════
# 4.a Agent Memory (QMD)
# ═════════════════════════════════════════════════════════════════════════════════
@router.get("/agents/{agent_name}/memory")
async def get_agent_memory(agent_name: str, limit: int = 100):
"""Attempt to read the agent's memory (QMD) SQLite database."""
ws_dir = _workspace() / agent_name
if not ws_dir.is_dir():
return {"has_db": False, "error": "Workspace not found"}
db_files = list(ws_dir.rglob("*.sqlite")) + list(ws_dir.rglob("*.db")) + list(ws_dir.rglob("*.qmd"))
if not db_files:
return {"has_db": False, "message": "No SQLite memory database found"}
db_path = db_files[0]
result = {
"has_db": True,
"db_path": str(db_path.relative_to(_home())),
"size": db_path.stat().st_size,
"timeline": [],
"stats": {"total_thoughts": 0}
}
def _read_db():
timeline = []
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# Check if common thought tables exist
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row["name"] for row in cursor.fetchall()]
result["tables"] = tables
# Very heuristic approach to grab 'thoughts' or 'history'
target_table = None
for t in ["thoughts", "messages", "history", "runs", "memory", "qmd_memory", "events"]:
if t in tables:
target_table = t
break
if target_table:
# Get column names
cursor.execute(f"PRAGMA table_info('{target_table}')")
columns = [c[1] for c in cursor.fetchall()]
query = f"SELECT * FROM {target_table} ORDER BY rowid DESC LIMIT ?"
if "timestamp" in columns or "created_at" in columns:
order_col = "timestamp" if "timestamp" in columns else "created_at"
query = f"SELECT * FROM {target_table} ORDER BY {order_col} DESC LIMIT ?"
cursor.execute(query, (limit,))
rows = cursor.fetchall()
timeline = [dict(r) for r in rows]
# Count total
cursor.execute(f"SELECT COUNT(*) FROM {target_table}")
result["stats"]["total_thoughts"] = cursor.fetchone()[0]
conn.close()
return timeline
except Exception as e:
log.error(f"Error reading QMD db: {e}")
return []
result["timeline"] = await asyncio.to_thread(_read_db)
return result
# ═════════════════════════════════════════════════════════════════════════════════
# 5. Logs
# ═════════════════════════════════════════════════════════════════════════════════