fix: send world state snapshot on WS client connect
All checks were successful
Tests / lint (pull_request) Successful in 4s
Tests / test (pull_request) Successful in 58s

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:
kimi
2026-03-19 01:24:08 -04:00
parent 858264be0d
commit b6868c6100
2 changed files with 51 additions and 10 deletions

View File

@@ -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

View File

@@ -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