fix: send world state snapshot on WS client connect
When a Workshop client connects to /api/world/ws, immediately send the full world_state snapshot so the client never starts from a blank slate. Extracts _get_current_state() helper to share logic with GET /api/world/state. Fixes #374 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,17 +69,11 @@ def _build_world_state(presence: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@router.get("/state")
|
||||
async def get_world_state() -> JSONResponse:
|
||||
"""Return Timmy's current world state for Workshop bootstrap.
|
||||
|
||||
Reads from ``~/.timmy/presence.json`` if fresh, otherwise
|
||||
rebuilds live from cognitive state.
|
||||
"""
|
||||
def _get_current_state() -> dict:
|
||||
"""Build the current world-state dict from best available source."""
|
||||
presence = _read_presence_file()
|
||||
|
||||
if presence is None:
|
||||
# Fallback: build live from cognitive tracker
|
||||
try:
|
||||
from timmy.workshop_state import get_state_dict
|
||||
|
||||
@@ -96,8 +90,18 @@ async def get_world_state() -> JSONResponse:
|
||||
"concerns": [],
|
||||
}
|
||||
|
||||
return _build_world_state(presence)
|
||||
|
||||
|
||||
@router.get("/state")
|
||||
async def get_world_state() -> JSONResponse:
|
||||
"""Return Timmy's current world state for Workshop bootstrap.
|
||||
|
||||
Reads from ``~/.timmy/presence.json`` if fresh, otherwise
|
||||
rebuilds live from cognitive state.
|
||||
"""
|
||||
return JSONResponse(
|
||||
content=_build_world_state(presence),
|
||||
content=_get_current_state(),
|
||||
headers={"Cache-Control": "no-cache, no-store"},
|
||||
)
|
||||
|
||||
@@ -109,10 +113,21 @@ async def get_world_state() -> JSONResponse:
|
||||
|
||||
@router.websocket("/ws")
|
||||
async def world_ws(websocket: WebSocket) -> None:
|
||||
"""Accept a Workshop client and keep it alive for state broadcasts."""
|
||||
"""Accept a Workshop client and keep it alive for state broadcasts.
|
||||
|
||||
Sends a full ``world_state`` snapshot immediately on connect so the
|
||||
client never starts from a blank slate.
|
||||
"""
|
||||
await websocket.accept()
|
||||
_ws_clients.append(websocket)
|
||||
logger.info("World WS connected — %d clients", len(_ws_clients))
|
||||
|
||||
# Send full world-state snapshot so client bootstraps instantly
|
||||
try:
|
||||
snapshot = _get_current_state()
|
||||
await websocket.send_text(json.dumps({"type": "world_state", **snapshot}))
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to send WS snapshot: %s", exc)
|
||||
try:
|
||||
while True:
|
||||
await websocket.receive_text() # keep-alive
|
||||
|
||||
@@ -219,3 +219,29 @@ def test_world_ws_endpoint_accepts_connection(client):
|
||||
"""WebSocket endpoint at /api/world/ws accepts connections."""
|
||||
with client.websocket_connect("/api/world/ws"):
|
||||
pass # Connection accepted — just close it
|
||||
|
||||
|
||||
def test_world_ws_sends_snapshot_on_connect(client, tmp_path):
|
||||
"""WebSocket sends a world_state snapshot immediately on connect."""
|
||||
f = tmp_path / "presence.json"
|
||||
f.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"version": 1,
|
||||
"liveness": "2026-03-19T02:00:00Z",
|
||||
"mood": "exploring",
|
||||
"current_focus": "testing",
|
||||
"active_threads": [],
|
||||
"recent_events": [],
|
||||
"concerns": [],
|
||||
}
|
||||
)
|
||||
)
|
||||
with patch("dashboard.routes.world.PRESENCE_FILE", f):
|
||||
with client.websocket_connect("/api/world/ws") as ws:
|
||||
msg = json.loads(ws.receive_text())
|
||||
|
||||
assert msg["type"] == "world_state"
|
||||
assert msg["timmyState"]["mood"] == "exploring"
|
||||
assert msg["timmyState"]["activity"] == "testing"
|
||||
assert "updatedAt" in msg
|
||||
|
||||
Reference in New Issue
Block a user