feat(boards): Store gateway config per board

Move gateway configuration into board settings and wire agent\nprovisioning, heartbeat templates, and gateway status lookups\nto use board-specific gateway settings. Adds board_id on agents\nand UI updates for board-scoped selection.\n\nCo-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Abhimanyu Saharan
2026-02-04 16:04:52 +05:30
parent 12698d0781
commit 4dea771545
20 changed files with 827 additions and 196 deletions

View File

@@ -9,7 +9,6 @@ from uuid import uuid4
import websockets
from app.core.config import settings
class OpenClawGatewayError(RuntimeError):
@@ -21,9 +20,17 @@ class OpenClawResponse:
payload: Any
def _build_gateway_url() -> str:
base_url = settings.openclaw_gateway_url or "ws://127.0.0.1:18789"
token = settings.openclaw_gateway_token
@dataclass(frozen=True)
class GatewayConfig:
url: str
token: str | None = None
def _build_gateway_url(config: GatewayConfig) -> str:
base_url = (config.url or "").strip()
if not base_url:
raise OpenClawGatewayError("Gateway URL is not configured for this board.")
token = config.token
if not token:
return base_url
parsed = urlparse(base_url)
@@ -58,7 +65,9 @@ async def _send_request(
async def _handle_challenge(
ws: websockets.WebSocketClientProtocol, first_message: str | bytes | None
ws: websockets.WebSocketClientProtocol,
first_message: str | bytes | None,
config: GatewayConfig,
) -> None:
if not first_message:
return
@@ -69,28 +78,35 @@ async def _handle_challenge(
return
connect_id = str(uuid4())
params: dict[str, Any] = {
"minProtocol": 3,
"maxProtocol": 3,
"client": {
"id": "gateway-client",
"version": "1.0.0",
"platform": "web",
"mode": "ui",
},
}
if config.token:
params["auth"] = {"token": config.token}
response = {
"type": "req",
"id": connect_id,
"method": "connect",
"params": {
"minProtocol": 3,
"maxProtocol": 3,
"client": {
"id": "gateway-client",
"version": "1.0.0",
"platform": "web",
"mode": "ui",
},
"auth": {"token": settings.openclaw_gateway_token},
},
"params": params,
}
await ws.send(json.dumps(response))
await _await_response(ws, connect_id)
async def openclaw_call(method: str, params: dict[str, Any] | None = None) -> Any:
gateway_url = _build_gateway_url()
async def openclaw_call(
method: str,
params: dict[str, Any] | None = None,
*,
config: GatewayConfig,
) -> Any:
gateway_url = _build_gateway_url(config)
try:
async with websockets.connect(gateway_url, ping_interval=None) as ws:
first_message = None
@@ -98,7 +114,7 @@ async def openclaw_call(method: str, params: dict[str, Any] | None = None) -> An
first_message = await asyncio.wait_for(ws.recv(), timeout=2)
except asyncio.TimeoutError:
first_message = None
await _handle_challenge(ws, first_message)
await _handle_challenge(ws, first_message, config)
return await _send_request(ws, method, params)
except OpenClawGatewayError:
raise
@@ -110,6 +126,7 @@ async def send_message(
message: str,
*,
session_key: str,
config: GatewayConfig,
deliver: bool = False,
) -> Any:
params: dict[str, Any] = {
@@ -118,23 +135,31 @@ async def send_message(
"deliver": deliver,
"idempotencyKey": str(uuid4()),
}
return await openclaw_call("chat.send", params)
return await openclaw_call("chat.send", params, config=config)
async def get_chat_history(session_key: str, limit: int | None = None) -> Any:
async def get_chat_history(
session_key: str,
config: GatewayConfig,
limit: int | None = None,
) -> Any:
params: dict[str, Any] = {"sessionKey": session_key}
if limit is not None:
params["limit"] = limit
return await openclaw_call("chat.history", params)
return await openclaw_call("chat.history", params, config=config)
async def delete_session(session_key: str) -> Any:
return await openclaw_call("sessions.delete", {"key": session_key})
async def delete_session(session_key: str, *, config: GatewayConfig) -> Any:
return await openclaw_call("sessions.delete", {"key": session_key}, config=config)
async def ensure_session(session_key: str, label: str | None = None) -> Any:
async def ensure_session(
session_key: str,
*,
config: GatewayConfig,
label: str | None = None,
) -> Any:
params: dict[str, Any] = {"key": session_key}
if label:
params["label"] = label
return await openclaw_call("sessions.patch", params)
return await openclaw_call("sessions.patch", params, config=config)