feat: implement OpenClaw integration with new API endpoints, frontend page, client, and tests.
This commit is contained in:
@@ -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
|
||||
# ═════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
|
||||
def explore_qmd():
|
||||
foxy_home_str = os.environ.get("FOXY_HOME", "/home/foxy/.openclaw")
|
||||
# If running on windows local, this might resolve weirdly.
|
||||
# We will try the raw string first.
|
||||
home = Path(foxy_home_str)
|
||||
|
||||
# Alternatively try C:\home\foxy\.openclaw if windows
|
||||
if not home.exists() and os.name == 'nt':
|
||||
alt_home = Path("C:") / foxy_home_str
|
||||
if alt_home.exists():
|
||||
home = alt_home
|
||||
|
||||
workspace = home / "workspace"
|
||||
print("Exploring workspace:", workspace)
|
||||
print("Exists:", workspace.exists())
|
||||
|
||||
if not workspace.exists():
|
||||
print("Cannot find workspace.")
|
||||
return
|
||||
|
||||
sqlite_files = list(workspace.rglob("*.sqlite")) + list(workspace.rglob("*.db")) + list(workspace.rglob("*.qmd"))
|
||||
print("Found sqlite files:", len(sqlite_files))
|
||||
|
||||
for f in sqlite_files:
|
||||
print("\n--- SCHEMA FOR", f)
|
||||
try:
|
||||
conn = sqlite3.connect(f)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
for t in tables:
|
||||
table_name = t[0]
|
||||
print(f"Table: {table_name}")
|
||||
cursor.execute(f"PRAGMA table_info('{table_name}')")
|
||||
columns = cursor.fetchall()
|
||||
for c in columns:
|
||||
print(f" - {c[1]} ({c[2]})")
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print("Error reading db:", e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
explore_qmd()
|
||||
Reference in New Issue
Block a user