fix: WebSocket 403 spam and missing /swarm endpoints

- CSRF middleware now skips WebSocket upgrade requests (they don't carry tokens)
- Added /swarm/live WebSocket endpoint wired to ws_manager singleton
- Added /swarm/agents/sidebar HTMX partial (was 404 on every dashboard poll)

Stops hundreds of 403 Forbidden + 404 log lines per minute.
This commit is contained in:
2026-03-14 16:29:35 -04:00
parent b30b5c6b57
commit f0b0e2f202
2 changed files with 40 additions and 0 deletions

View File

@@ -500,6 +500,42 @@ async def ws_redirect(websocket: WebSocket):
await websocket.send({"type": "websocket.close", "code": 1008})
@app.websocket("/swarm/live")
async def swarm_live(websocket: WebSocket):
"""Swarm live event stream via WebSocket."""
from infrastructure.ws_manager.handler import ws_manager as ws_mgr
await ws_mgr.connect(websocket)
try:
while True:
# Keep connection alive; events are pushed via ws_mgr.broadcast()
await websocket.receive_text()
except Exception:
ws_mgr.disconnect(websocket)
@app.get("/swarm/agents/sidebar", response_class=HTMLResponse)
async def swarm_agents_sidebar():
"""HTMX partial: list active swarm agents for the dashboard sidebar."""
try:
from config import settings
agents_yaml = settings.agents_config
agents = agents_yaml.get("agents", {})
lines = []
for name, cfg in agents.items():
model = cfg.get("model", "default")
lines.append(
f'<div class="mc-agent-row">'
f'<span class="mc-agent-name">{name}</span>'
f'<span class="mc-agent-model">{model}</span>'
f"</div>"
)
return "\n".join(lines) if lines else '<div class="mc-muted">No agents configured</div>'
except Exception:
return '<div class="mc-muted">Agents unavailable</div>'
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
"""Serve the main dashboard page."""

View File

@@ -134,6 +134,10 @@ class CSRFMiddleware(BaseHTTPMiddleware):
if settings.timmy_disable_csrf:
return await call_next(request)
# WebSocket upgrades don't carry CSRF tokens — skip them entirely
if request.headers.get("upgrade", "").lower() == "websocket":
return await call_next(request)
# Get existing CSRF token from cookie
csrf_cookie = request.cookies.get(self.cookie_name)