diff --git a/nexus/multi_user_bridge.py b/nexus/multi_user_bridge.py index df562e00..20f9d41b 100644 --- a/nexus/multi_user_bridge.py +++ b/nexus/multi_user_bridge.py @@ -275,6 +275,7 @@ class MultiUserBridge: self._app.router.add_get("/bridge/sessions", self.handle_sessions) self._app.router.add_get("/bridge/health", self.handle_health) self._app.router.add_get("/bridge/rooms", self.handle_rooms) + self._app.router.add_get("/bridge/stats", self.handle_stats) self._app.router.add_get("/bridge/room_events/{user_id}", self.handle_room_events) self._app.router.add_get("/bridge/ws/{user_id}", self.handle_ws) return self._app @@ -319,6 +320,23 @@ class MultiUserBridge: "total_users": self.sessions.active_count, }) + async def handle_stats(self, request: web.Request) -> web.Response: + """GET /bridge/stats — Aggregate bridge statistics.""" + uptime = time.time() - self._start_time + total_messages = sum(len(s.message_history) for s in self.sessions._sessions.values()) + total_commands = sum(s.command_count for s in self.sessions._sessions.values()) + rooms = {r: len(users) for r, users in self.sessions._room_occupants.items() if users} + ws_connections = sum(len(s.ws_connections) for s in self.sessions._sessions.values()) + return web.json_response({ + "uptime_seconds": round(uptime, 1), + "active_sessions": self.sessions.active_count, + "total_messages": total_messages, + "total_commands": total_commands, + "rooms": rooms, + "room_count": len(rooms), + "ws_connections": ws_connections, + }) + async def handle_room_events(self, request: web.Request) -> web.Response: """GET /bridge/room_events/{user_id} — Drain pending room events for a user.""" user_id = request.match_info["user_id"] @@ -512,7 +530,53 @@ class MultiUserBridge: other_session = self.sessions.get(other_id) if other_session: other_session.room_events.append(broadcast) - return f'You say: "{speech}"' + return f'You say: \"{speech}\"' + + if msg_lower.startswith("go ") or msg_lower.startswith("move "): + # Move to a new room (HTTP equivalent of WS move) + parts = message.split(None, 1) + if len(parts) < 2 or not parts[1].strip(): + return "Go where? Usage: go " + new_room = parts[1].strip() + old_room = session.room + if new_room == old_room: + return f"You're already in {new_room}." + # Update room tracking + self.sessions._room_occupants[old_room].discard(session.user_id) + session.room = new_room + self.sessions._room_occupants[new_room].add(session.user_id) + # Notify occupants in old room + old_occupants = self.sessions.get_room_occupants(old_room) + for other_id in old_occupants: + other_session = self.sessions.get(other_id) + if other_session: + other_session.room_events.append({ + "type": "room_broadcast", + "from_user": session.user_id, + "from_username": session.username, + "room": old_room, + "message": f"{session.username} leaves for {new_room}.", + }) + return f"You leave {old_room} and arrive in {new_room}." + + if msg_lower.startswith("emote ") or msg_lower.startswith("/me "): + # Emote — broadcast action to room + action = message.split(None, 1)[1] if len(message.split(None, 1)) > 1 else "" + if not action: + return "Emote what? Usage: emote " + occupants = self.sessions.get_room_occupants(session.room) + others = [o for o in occupants if o != session.user_id] + for other_id in others: + other_session = self.sessions.get(other_id) + if other_session: + other_session.room_events.append({ + "type": "room_broadcast", + "from_user": session.user_id, + "from_username": session.username, + "room": session.room, + "message": f"{session.username} {action}", + }) + return f"You {action}" if msg_lower == "who": all_sessions = self.sessions.list_sessions()