diff --git a/src/features/office/components/OfficeFloorNav.tsx b/src/features/office/components/OfficeFloorNav.tsx index 67a9084..0e1c22c 100644 --- a/src/features/office/components/OfficeFloorNav.tsx +++ b/src/features/office/components/OfficeFloorNav.tsx @@ -1,9 +1,10 @@ import { OFFICE_FLOORS, getAdjacentEnabledOfficeFloorId, - listOfficeFloorsForZone, + listAvailableFloorsForAdapter, type FloorDefinition, type FloorId, + type FloorProvider, } from "@/lib/office/floors"; import type { FloorRosterState } from "@/lib/office/floorRoster"; @@ -11,12 +12,15 @@ type OfficeFloorNavProps = { activeFloorId: FloorId; floorRosterCache: Record; onSelectFloor: (floorId: FloorId) => void; + /** The currently selected adapter — controls which runtime floors are shown */ + activeAdapterType?: FloorProvider | null; }; -const PROVIDER_LABEL: Record = { +const PROVIDER_LABEL: Record = { demo: "Demo", openclaw: "OpenClaw", hermes: "Hermes", + paperclip: "Paperclip", custom: "Custom", local: "Local", claw3d: "Claw3D", @@ -86,10 +90,18 @@ export function OfficeFloorNav({ activeFloorId, floorRosterCache, onSelectFloor, + activeAdapterType, }: OfficeFloorNavProps) { - const buildingFloors = listOfficeFloorsForZone("building"); - const outsideFloors = listOfficeFloorsForZone("outside"); - const activeFloor = OFFICE_FLOORS.find((floor) => floor.id === activeFloorId) ?? OFFICE_FLOORS[0]; + const availableFloors = listAvailableFloorsForAdapter(activeAdapterType ?? null); + const buildingFloors = availableFloors.filter((f) => f.zone === "building"); + const outsideFloors = availableFloors.filter((f) => f.zone === "outside"); + + // Active floor — fall back to lobby if current floor is no longer available + const activeIsAvailable = availableFloors.some((f) => f.id === activeFloorId); + const displayActiveFloorId = activeIsAvailable ? activeFloorId : "lobby"; + + const activeFloor = + OFFICE_FLOORS.find((floor) => floor.id === displayActiveFloorId) ?? OFFICE_FLOORS[0]; const activeRoster = floorRosterCache[activeFloor.id]; return ( @@ -121,7 +133,7 @@ export function OfficeFloorNav({ Building {buildingFloors.map((floor) => - renderFloorButton({ floor, activeFloorId, floorRosterCache, onSelectFloor }), + renderFloorButton({ floor, activeFloorId: displayActiveFloorId, floorRosterCache, onSelectFloor }), )} {outsideFloors.length > 0 ? ( @@ -130,7 +142,7 @@ export function OfficeFloorNav({ Outside {outsideFloors.map((floor) => - renderFloorButton({ floor, activeFloorId, floorRosterCache, onSelectFloor }), + renderFloorButton({ floor, activeFloorId: displayActiveFloorId, floorRosterCache, onSelectFloor }), )} ) : null} diff --git a/src/features/office/screens/OfficeScreen.tsx b/src/features/office/screens/OfficeScreen.tsx index 6198fa1..c04c138 100644 --- a/src/features/office/screens/OfficeScreen.tsx +++ b/src/features/office/screens/OfficeScreen.tsx @@ -80,6 +80,7 @@ import { listOfficeFloorsForProvider, resolveActiveOfficeFloorId, type FloorId, + type FloorProvider, } from "@/lib/office/floors"; import { AgentEditorModal, @@ -1481,6 +1482,15 @@ export function OfficeScreen({ console.error("Failed to resolve floor runtime profile.", error); } + // Guard: if this is a runtime floor and there's no gateway URL to connect to, + // bail back to lobby rather than entering a connect-hang limbo state. + if (floor.kind === "runtime" && !nextGatewayUrl.trim()) { + setActiveFloorId("lobby"); + settingsCoordinator.schedulePatch({ activeFloorId: "lobby" }, 0); + setAgentsLoaded(true); + return; + } + setSelectedAdapterType(adapterType); setGatewayUrl(nextGatewayUrl); setToken(nextToken); @@ -4741,6 +4751,7 @@ export function OfficeScreen({ onSelectFloor={(floorId) => { void handleSelectFloor(floorId); }} + activeAdapterType={(selectedAdapterType as FloorProvider) ?? null} />
{ + return OFFICE_FLOORS.filter((floor) => { + if (!floor.enabled) return false; + if (floor.kind === "lobby") return true; + if (floor.kind === "runtime") { + return ( + Boolean(activeAdapterType) && + activeAdapterType !== "demo" && + floor.provider === activeAdapterType + ); + } + // training / market / campus + return true; + }); +}; + export const getAdjacentEnabledOfficeFloorId = ( floorId: FloorId, direction: 1 | -1,