- Migrate OpenClaw from HTTP (port 8004) to WebSocket (port 18789) - Add workspace file list and content preview handlers - Add OpenClawStatus component with agent/skills view - Add OpenClawView panel in trader interface - Add Zustand store for OpenClaw state management - Fix gateway logging noise (yfinance, websockets) - Fix RunWorkspaceManager.get_agent_asset_dir attribute error - Handle missing workspace files gracefully in preview Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
7.6 KiB
Python
180 lines
7.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""OpenClaw WebSocket handlers — gateway calls OpenClaw Gateway via WebSocket."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from backend.services.gateway import Gateway
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_ws_client(gateway) -> "OpenClawWebSocketClient":
|
|
"""Get the OpenClaw WebSocket client from gateway."""
|
|
from shared.client.openclaw_websocket_client import OpenClawWebSocketClient
|
|
client = gateway._openclaw_ws
|
|
if client is None:
|
|
raise RuntimeError("OpenClaw Gateway not connected")
|
|
return client
|
|
|
|
|
|
async def _ws_call(gateway, method: str, params: dict | None = None) -> dict:
|
|
"""Call OpenClaw Gateway via WebSocket and return result."""
|
|
try:
|
|
client = _get_ws_client(gateway)
|
|
return await client.call_method(method, params)
|
|
except Exception as exc:
|
|
logger.warning("OpenClaw Gateway call failed for %s: %s", method, exc)
|
|
return {"error": str(exc)[:200]}
|
|
|
|
|
|
async def handle_get_openclaw_status(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "status")
|
|
await websocket.send(json.dumps({"type": "openclaw_status_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_sessions(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "sessions.list", {"limit": 50, "includeLastMessage": True})
|
|
await websocket.send(json.dumps({"type": "openclaw_sessions_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_session_detail(gateway, websocket, data: dict) -> None:
|
|
session_key = data.get("session_key", "")
|
|
result = await _ws_call(gateway, "sessions.list", {"limit": 1, "agentId": session_key.split(":")[1] if session_key else None})
|
|
await websocket.send(json.dumps({
|
|
"type": "openclaw_session_detail_loaded",
|
|
"data": result,
|
|
"session_key": session_key,
|
|
}))
|
|
|
|
|
|
async def handle_get_openclaw_session_history(gateway, websocket, data: dict) -> None:
|
|
session_key = data.get("session_key", "")
|
|
limit = data.get("limit", 20)
|
|
result = await _ws_call(gateway, "sessions.list", {"limit": limit})
|
|
await websocket.send(json.dumps({
|
|
"type": "openclaw_session_history_loaded",
|
|
"data": result,
|
|
"session_key": session_key,
|
|
}))
|
|
|
|
|
|
async def handle_get_openclaw_cron(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "cron.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_cron_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_approvals(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "exec.approvals.get")
|
|
await websocket.send(json.dumps({"type": "openclaw_approvals_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_agents(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "agents.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_agents_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_agents_presence(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "node.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_agents_presence_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_skills(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "skills.status")
|
|
await websocket.send(json.dumps({"type": "openclaw_skills_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_models(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "models.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_models_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_hooks(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "tools.catalog")
|
|
await websocket.send(json.dumps({"type": "openclaw_hooks_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_plugins(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "config.get")
|
|
await websocket.send(json.dumps({"type": "openclaw_plugins_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_secrets_audit(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "secrets.reload")
|
|
await websocket.send(json.dumps({"type": "openclaw_secrets_audit_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_security_audit(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "gateway.identity.get")
|
|
await websocket.send(json.dumps({"type": "openclaw_security_audit_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_daemon_status(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "doctor.memory.status")
|
|
await websocket.send(json.dumps({"type": "openclaw_daemon_status_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_pairing(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "device.pair.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_pairing_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_qr(gateway, websocket, data: dict) -> None:
|
|
await websocket.send(json.dumps({"type": "openclaw_qr_loaded", "data": {"error": "QR code not available via WebSocket"}}))
|
|
|
|
|
|
async def handle_get_openclaw_update_status(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "update.run")
|
|
await websocket.send(json.dumps({"type": "openclaw_update_status_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_models_aliases(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "models.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_models_aliases_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_models_fallbacks(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "models.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_models_fallbacks_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_models_image_fallbacks(gateway, websocket, data: dict) -> None:
|
|
result = await _ws_call(gateway, "models.list")
|
|
await websocket.send(json.dumps({"type": "openclaw_models_image_fallbacks_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_skill_update(gateway, websocket, data: dict) -> None:
|
|
slug = data.get("slug")
|
|
all_flag = data.get("all", False)
|
|
params = {}
|
|
if slug is not None:
|
|
params["slug"] = slug
|
|
if all_flag:
|
|
params["all"] = "true"
|
|
result = await _ws_call(gateway, "skills.update", params)
|
|
await websocket.send(json.dumps({"type": "openclaw_skill_update_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_workspace_files(gateway, websocket, data: dict) -> None:
|
|
raw_workspace = data.get("workspace", "")
|
|
# Use the workspace param (which is actually the agent.id from frontend) as agent_id
|
|
agent_id = raw_workspace or "main"
|
|
result = await _ws_call(gateway, "agents.files.list", {"agentId": agent_id})
|
|
if isinstance(result, dict):
|
|
result["workspace"] = agent_id
|
|
await websocket.send(json.dumps({"type": "openclaw_workspace_files_loaded", "data": result}))
|
|
|
|
|
|
async def handle_get_openclaw_workspace_file(gateway, websocket, data: dict) -> None:
|
|
agent_id = data.get("agent_id", "main")
|
|
file_name = data.get("file_name", "")
|
|
if not file_name:
|
|
await websocket.send(json.dumps({"type": "openclaw_workspace_file_loaded", "data": {"error": "file_name is required"}}))
|
|
return
|
|
result = await _ws_call(gateway, "agents.files.get", {"agentId": agent_id, "name": file_name})
|
|
await websocket.send(json.dumps({"type": "openclaw_workspace_file_loaded", "data": result}))
|