mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
Compare commits
1
Commits
17b1404157
...
deadcode
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ca9776cc |
@@ -91,9 +91,6 @@ type MediaConfig struct {
|
||||
AudioService string `yaml:"audioService"`
|
||||
HealthURL string `yaml:"healthUrl"`
|
||||
HealthInterval Duration `yaml:"healthInterval"`
|
||||
VideoWidth int `yaml:"videoWidth" json:"-"`
|
||||
VideoHeight int `yaml:"videoHeight" json:"-"`
|
||||
VideoFPS int `yaml:"videoFps" json:"-"`
|
||||
VideoBitrate int `yaml:"videoBitrate" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
+2
-11
@@ -16,8 +16,8 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
|
||||
if media.AudioPublishURL == "" && audio.CaptureEnabled {
|
||||
return fmt.Errorf("audio publishUrl missing")
|
||||
}
|
||||
if media.VideoWidth < 0 || media.VideoHeight < 0 || media.VideoFPS < 0 || media.VideoBitrate <= 0 {
|
||||
return fmt.Errorf("invalid media dimensions/bitrate")
|
||||
if media.VideoBitrate <= 0 {
|
||||
return fmt.Errorf("invalid media bitrate")
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(publisherEnvPath), 0o755); err != nil {
|
||||
return err
|
||||
@@ -30,15 +30,6 @@ func UpdatePublisherEnv(media MediaConfig, audio AudioConfig) error {
|
||||
if media.AudioForwardURL != "" {
|
||||
fmt.Fprintf(&buf, "AUDIO_FORWARD_URL=%s\n", media.AudioForwardURL)
|
||||
}
|
||||
if media.VideoWidth > 0 {
|
||||
fmt.Fprintf(&buf, "VIDEO_WIDTH=%d\n", media.VideoWidth)
|
||||
}
|
||||
if media.VideoHeight > 0 {
|
||||
fmt.Fprintf(&buf, "VIDEO_HEIGHT=%d\n", media.VideoHeight)
|
||||
}
|
||||
if media.VideoFPS > 0 {
|
||||
fmt.Fprintf(&buf, "VIDEO_FPS=%d\n", media.VideoFPS)
|
||||
}
|
||||
fmt.Fprintf(&buf, "VIDEO_BITRATE=%d\n", media.VideoBitrate)
|
||||
fmt.Fprintf(&buf, "VIDEO_INVERT=%d\n", boolToInt(media.CameraInverted))
|
||||
audioDevice := audio.CaptureDevice
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
# Dead Code Audit (Static)
|
||||
|
||||
Generated: 2026-05-10T21:29:09-04:00
|
||||
|
||||
## A) High-confidence never used files
|
||||
- server/src/services/overseerControlService/tools/chatSay.js (not registered in tools/index.js, no refs)
|
||||
- server/prompts/commentary_system_backup_small.txt (no refs)
|
||||
- server/prompts/commentary_system_backup_pre_lobotomy.txt (no refs)
|
||||
- webui/src/App.css (not imported)
|
||||
- webui/src/assets/react.svg (no refs)
|
||||
- server/assets/test-audio.mp3 (no refs)
|
||||
- webui/public/vite.svg (no refs from app/server)
|
||||
- webui/dist/assets/index-APFDmmUd.js (build artifact, not runtime-wired)
|
||||
- webui/dist/assets/index-DoNH_msu.css (build artifact, not runtime-wired)
|
||||
- dist/dummy1.yml (no refs)
|
||||
- dist/dummy2.yml (no refs)
|
||||
- dist/dummy3.yml (no refs)
|
||||
|
||||
## B) Unused dependency candidates
|
||||
- webui/package.json: react-joystick-component (no imports)
|
||||
|
||||
## C) Commented-out JSX/UI blocks (never rendered while commented)
|
||||
webui/src/App.jsx:85: {/* <SessionSnapshot /> */}
|
||||
webui/src/App.jsx:153: {/* {showTelemetry ? <TelemetryPanel /> : null} */}
|
||||
webui/src/App.jsx:183: {/* <ControlSummary /> */}
|
||||
webui/src/App.jsx:211: {/* <TelemetryPanel /> */}
|
||||
webui/src/components/QuickstartOverlay/index.jsx:82: {/* <button type="button" onClick={onClose} className="button-dark px-1 py-0.25 text-[0.8rem]">
|
||||
webui/src/components/QuickstartOverlay/index.jsx:84: </button> */}
|
||||
webui/src/components/QuickstartOverlay/index.jsx:90: {/* {!isDesktop? <div className='w-full h-1 bg-blue-500'></div> : null} */}
|
||||
webui/src/components/QuickstartOverlay/index.jsx:116: {/* <button type="button" onClick={onOpenHelp} className="button-dark px-1 py-0.25">
|
||||
webui/src/components/QuickstartOverlay/index.jsx:118: </button> */}
|
||||
webui/src/components/DriveDockAction/index.jsx:261: {/* {!isMobile && expanded ? ( */}
|
||||
webui/src/components/UserListPanel/index.jsx:263: {/* {isSelf && <span className="text-[0.7rem] text-white">YOU</span>} */}
|
||||
webui/src/components/AuthPanel/index.jsx:48: {/* <div className="flex gap-0.5 text-sm">
|
||||
webui/src/components/AuthPanel/index.jsx:55: </div> */}
|
||||
webui/src/components/ModeGateOverlay/index.jsx:85: {/* {reasonUpdatedAt ? (
|
||||
webui/src/components/ModeGateOverlay/index.jsx:89: ) : null} */}
|
||||
webui/src/components/ModeGateOverlay/index.jsx:102: {/* set max height of this box */}
|
||||
webui/src/components/ModeGateOverlay/index.jsx:108: {/* <p className="text-xs text-slate-500">
|
||||
webui/src/components/ModeGateOverlay/index.jsx:111: </p> */}
|
||||
webui/src/components/RoomCameraPanel/index.jsx:103: {/* <header className="space-y-0.5">
|
||||
webui/src/components/RoomCameraPanel/index.jsx:106: </header> */}
|
||||
webui/src/components/DriverVideoPanel/index.jsx:145: {/* colored button to visit the spectator page */}
|
||||
webui/src/components/TelemetryPanel/index.jsx:43: {/* <div className="text-sm text-slate-400">
|
||||
webui/src/components/TelemetryPanel/index.jsx:49: </div> */}
|
||||
|
||||
## D) Unused exports reported by knip (server)
|
||||
Unused exports (66)
|
||||
CHARGING_STATE src/helpers/sensorDecoder.js:224:3
|
||||
pickRandomReward src/rewards/index.js:44:3
|
||||
MAX_REASON_LENGTH src/services/adminReasonService/index.js:97:3
|
||||
setAudioLevels src/services/audioLevelsService/index.js:153:3
|
||||
pushLevelsToRover src/services/audioLevelsService/index.js:154:3
|
||||
isAdmin src/services/authService/index.js:80:3
|
||||
isLockdownAdmin src/services/authService/index.js:81:3
|
||||
authenticate src/services/authService/index.js:82:3
|
||||
isDuplicate src/services/chatService/contentFilters.js:57:3
|
||||
handleIncoming src/services/chatService/index.js:32:3
|
||||
buildTypingPayload src/services/chatService/index.js:35:3
|
||||
rateBuckets src/services/chatService/state.js:44:3
|
||||
buildRoverStatusSnapshot src/services/discordBotService/batteryEmbeds.js:132:3
|
||||
eventBus src/services/eventBus/index.js:56:3
|
||||
subscribeAll src/services/eventBus/index.js:59:3
|
||||
MAX_GOAL_LENGTH src/services/globalObjectiveService/index.js:107:3
|
||||
refreshIdleState src/services/idleService/index.js:86:3
|
||||
DEFAULT_FREQUENCY_MS src/services/llmCommentaryService/constants.js:31:3
|
||||
MIN_FREQUENCY_MS src/services/llmCommentaryService/constants.js:32:3
|
||||
normalizeCommentary src/services/llmCommentaryService/formatters.js:175:3
|
||||
enforceLockdown src/services/lockdownGuard/index.js:29:3
|
||||
disconnectForLockdown src/services/lockdownGuard/index.js:30:3
|
||||
parseOverseerOutput src/services/overseerControlService/runtimeHelpers.js:114:3
|
||||
TOOL_DEFINITIONS src/services/overseerControlService/tools/index.js:87:3
|
||||
getToolById src/services/overseerControlService/tools/index.js:91:3
|
||||
getIdForSignature src/services/overseerControlService/tools/index.js:92:3
|
||||
DM_APPROVE_EMOJI src/services/privateRoverAccessRequestService/index.js:25:3
|
||||
DM_DENY_EMOJI src/services/privateRoverAccessRequestService/index.js:26:3
|
||||
createRequest src/services/privateRoverAccessRequestService/index.js:29:3
|
||||
hasClosedPrivateAccessForSocket src/services/privateRoverAccessRequestService/index.js:30:3
|
||||
FFMPEG_BIN src/services/replayEngineV2/sources.js:66:3
|
||||
upsertRover src/services/roverManager/index.js:234:3
|
||||
removeRover src/services/roverManager/index.js:235:3
|
||||
setPrivateOpen src/services/roverManager/index.js:237:3
|
||||
setPrivateSafety src/services/roverManager/index.js:238:3
|
||||
getRoster src/services/roverManager/index.js:239:3
|
||||
getRosterForSocket src/services/roverManager/index.js:240:3
|
||||
broadcastRoster src/services/roverManager/index.js:241:3
|
||||
setNightVisionState src/services/roverManager/index.js:242:3
|
||||
handleSensorFrame src/services/roverManager/index.js:243:3
|
||||
requestControl src/services/roverManager/index.js:244:3
|
||||
releaseControl src/services/roverManager/index.js:245:3
|
||||
removeSocket src/services/roverManager/index.js:246:3
|
||||
isDriver src/services/roverManager/index.js:247:3
|
||||
canDrive src/services/roverManager/index.js:248:3
|
||||
enableSpectator src/services/roverManager/index.js:249:3
|
||||
disableSpectator src/services/roverManager/index.js:250:3
|
||||
getRoversForSocket src/services/roverManager/index.js:253:3
|
||||
getPrimaryRoverForSocket src/services/roverManager/index.js:254:3
|
||||
canSeeRover src/services/roverManager/index.js:255:3
|
||||
canRequestControl src/services/roverManager/index.js:256:3
|
||||
applyPrivateDriveSafety src/services/roverManager/index.js:257:3
|
||||
canReplayRoverId src/services/roverManager/index.js:258:3
|
||||
computeBatteryDisplayPercent src/services/roverManager/mathUtils.js:136:3
|
||||
buildSession src/services/sessionService/index.js:340:3
|
||||
syncSocket src/services/sessionService/index.js:341:3
|
||||
syncAll src/services/sessionService/index.js:342:3
|
||||
driverAdded src/services/turnService/index.js:284:3
|
||||
driverRemoved src/services/turnService/index.js:285:3
|
||||
cleanupRover src/services/turnService/index.js:286:3
|
||||
canDrive src/services/turnService/index.js:287:3
|
||||
createSession src/services/videoSessions/index.js:68:3
|
||||
getSession src/services/videoSessions/index.js:69:3
|
||||
revokeSession src/services/videoSessions/index.js:70:3
|
||||
revokeBySocket src/services/videoSessions/index.js:71:3
|
||||
revokeWhere src/services/videoSessions/index.js:72:3
|
||||
|
||||
## E) Unused exports reported by knip (webui)
|
||||
Unused exports (18)
|
||||
deriveDriveDockState function src/components/DriveDockAction/index.jsx:9:17
|
||||
HelpContentView function src/components/HelpContentView/index.jsx:179:17
|
||||
default function src/components/UserListPanel/index.jsx:50:25
|
||||
normalizeDriveVector function src/controls/controlMath.js:22:17
|
||||
useOvercurrentLimiter src/controls/index.js:6:10
|
||||
cloneProfile function src/controls/inputs/gamepadBindings.js:14:17
|
||||
getGamepadHubState function src/controls/inputs/gamepadHub.js:109:17
|
||||
deriveCodeForKey function src/controls/keymapUtils.js:36:17
|
||||
createKeyToken function src/controls/keymapUtils.js:47:17
|
||||
createCodeToken function src/controls/keymapUtils.js:52:17
|
||||
DEFAULT_OVERCURRENT_LIMITS src/controls/overcurrentLimiter.js:12:14
|
||||
HELP_LAYOUTS src/help/content.js:3:14
|
||||
HELP_CONTENT src/help/content.js:7:14
|
||||
default function src/hooks/useFullscreenPrompt.js:169:16
|
||||
normalizeRoverColor function src/lib/roverColor.js:5:17
|
||||
roverSwatchStyle function src/lib/roverColor.js:35:17
|
||||
useSettings src/settings/index.js:3:28
|
||||
useSettings function src/settings/SettingsProvider.jsx:58:17
|
||||
Duplicate exports (2)
|
||||
HelpContentView|default src/components/HelpContentView/index.jsx
|
||||
useFullscreenPrompt|default src/hooks/useFullscreenPrompt.js
|
||||
|
||||
## F) Server State-Machine Contradictions (Proof-Based)
|
||||
|
||||
### F1) `lockdown-admin` role branches are unreachable in current role producer graph
|
||||
**Why unreachable:**
|
||||
- All server role writes are done through `setRole(socket, role)` in auth flows.
|
||||
- Role assignments are only `user`, `spectator`, `admin`, `lockdown`.
|
||||
- No assignment path sets `lockdown-admin`.
|
||||
|
||||
**Role producers (source of truth):**
|
||||
- `server/src/services/authService/index.js` (`initialRole` user/spectator, login role admin/lockdown, role:set only user/spectator)
|
||||
- `server/src/services/roleService/index.js` (just stores whatever caller sets; no separate producer)
|
||||
- Searched for any `setRole(..., 'lockdown-admin')` / `socket.data.role = 'lockdown-admin'`: none
|
||||
|
||||
**Dead branches/cases under this graph:**
|
||||
- `server/src/services/adminLogService/index.js`
|
||||
- `server/src/services/llmCommentaryService/runtimeHelpers.js`
|
||||
- `server/src/services/overseerControlService/runtimeHelpers.js`
|
||||
- `server/src/services/verificationService/identity.js`
|
||||
- `server/src/services/discordBotService/integrations/helpers.js`
|
||||
- `server/src/services/replayEngineV2/sidebarRenderer.js`
|
||||
- `webui/src/components/ModeGateOverlay/index.jsx`
|
||||
- `webui/src/components/UserListPanel/index.jsx`
|
||||
- `webui/src/components/ChatMessageRow/index.jsx`
|
||||
- `webui/src/components/RoverQueuesPanel/index.jsx`
|
||||
- `webui/src/components/RawUserPilePanel/index.jsx`
|
||||
- `webui/src/components/AdminPanel/AdminPanelContent.jsx`
|
||||
- `webui/src/controls/overcurrentLimiter.js`
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### F2) `clearLockdownTimer` currently has no possible effect
|
||||
**Why unreachable/effectively dead:**
|
||||
- `clearLockdownTimer(socket)` only clears `socket.data.lockdownTimer`.
|
||||
- No code ever sets `socket.data.lockdownTimer` anywhere in repo.
|
||||
- Therefore the condition is always false and this function is a no-op in current runtime.
|
||||
|
||||
**Evidence:**
|
||||
- `server/src/services/lockdownGuard/index.js` (only reads/clears `lockdownTimer`)
|
||||
- global search for `lockdownTimer` assignments: none
|
||||
- caller: `server/src/services/authService/index.js` (invokes `clearLockdownTimer` after login)
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### F3) `PERIODIC_SYNC_MS` config path is dead (constant + import)
|
||||
**Why unreachable/effectively dead:**
|
||||
- `PERIODIC_SYNC_MS` is imported into session service but only used in a commented-out `setInterval` block.
|
||||
- No runtime path consumes this value.
|
||||
|
||||
**Evidence:**
|
||||
- `server/src/services/sessionService/constants.js` exports `PERIODIC_SYNC_MS`
|
||||
- `server/src/services/sessionService/index.js` imports it and references it only in commented block
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### F4) Video request parser supports `room` request shape, but runtime contract rejects it
|
||||
**Why semantically contradictory:**
|
||||
- `videoSocketService.normalizeRequest()` accepts room request payloads (`roomCameraId` / `{type:'room'}`).
|
||||
- The handler then always throws for `target.type === 'room'` with “Room cameras now use the snapshot feed”.
|
||||
- So room-video request acceptance code is legacy compatibility surface with guaranteed failure.
|
||||
|
||||
**Evidence:**
|
||||
- `server/src/services/videoSocketService/index.js`
|
||||
|
||||
**Note:** This is reachable only if a client attempts room video via `video:request`; it is not a successful runtime feature path.
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### F5) `useVideoRequests` still contains room-source normalization path unused by current first-party call sites
|
||||
**Why currently redundant:**
|
||||
- `useVideoRequests` can normalize `roomCameraId` / `type:'room'`.
|
||||
- Current call sites pass rover-only entries:
|
||||
- `webui/src/components/DriverVideoPanel/index.jsx`
|
||||
- `webui/src/spectate/SpectatorApp/SpectatorContent.jsx`
|
||||
- `webui/src/mini/MiniSummaryApp/MiniSummaryContent.jsx`
|
||||
- This aligns with room camera delivery moving to snapshot socket feed.
|
||||
|
||||
**Evidence:**
|
||||
- `webui/src/hooks/useVideoRequests.js`
|
||||
- call-site inspection above
|
||||
|
||||
**Confidence:** Medium-High (internal app paths only; external/future caller may use room shape)
|
||||
|
||||
## G) Legacy Compatibility Surface (Single-Program Dead Ends)
|
||||
|
||||
### G1) Duplicate socket event listeners: only `session:*` names are used by this client
|
||||
**Observation:** webui emits only `session:*` variants for control-role operations.
|
||||
|
||||
**WebUI emit calls:**
|
||||
- `session:setRole`
|
||||
- `session:requestControl`
|
||||
- `session:releaseControl`
|
||||
- `session:lockRover`
|
||||
- `session:privateSafety:set`
|
||||
- `session:subscribeAll`
|
||||
(see `webui/src/context/SessionContext.jsx`)
|
||||
|
||||
**Server still listens to both old + namespaced aliases:**
|
||||
- `requestControl` + `session:requestControl`
|
||||
- `releaseControl` + `session:releaseControl`
|
||||
- `lockRover` + `session:lockRover`
|
||||
- `privateSafety:set` + `session:privateSafety:set`
|
||||
- `subscribeAll` + `session:subscribeAll`
|
||||
(see `server/src/services/roverManager/socketHandlers.js`)
|
||||
|
||||
- `role:set` + `session:setRole`
|
||||
(see `server/src/services/authService/index.js`)
|
||||
|
||||
**Why dead under single-program assumption:**
|
||||
- No first-party client emits old names; old listeners are compatibility-only.
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### G2) Server emits protocol events with no first-party subscribers
|
||||
**Server emits:**
|
||||
- `rovers` (on connect and roster updates)
|
||||
- `auth:role`
|
||||
- `mode`
|
||||
- `controlGranted`
|
||||
- `lockdown`
|
||||
|
||||
**Evidence of emitters:**
|
||||
- `server/src/services/roverManager/socketHandlers.js` (`rovers`, `controlGranted`)
|
||||
- `server/src/services/roverManager/rosterLifecycle.js` (`rovers`)
|
||||
- `server/src/services/authService/index.js` (`auth:role`)
|
||||
- `server/src/services/modeManager/index.js` (`mode`)
|
||||
- `server/src/services/lockdownGuard/index.js` (`lockdown`)
|
||||
|
||||
**Client-side consumption check:**
|
||||
- No `socket.on('rovers' | 'auth:role' | 'mode' | 'controlGranted' | 'lockdown')` anywhere in `webui/src`.
|
||||
- Session-driven UI uses `session:sync` instead.
|
||||
|
||||
**Why dead under single-program assumption:**
|
||||
- Event emissions exist for older/external clients only.
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### G3) `video:request` still accepts room payload shapes that are hard-rejected
|
||||
**Current behavior:**
|
||||
- Request normalization accepts room forms (`roomCameraId`, `{type:'room', id}`)
|
||||
- Handler immediately throws for room type: "Room cameras now use the snapshot feed"
|
||||
(see `server/src/services/videoSocketService/index.js`)
|
||||
|
||||
**Why compatibility-only:**
|
||||
- Room WHEP path retained in request parsing despite product contract migrating to snapshot feed.
|
||||
- First-party room camera path uses `roomCamera:*` snapshot sockets.
|
||||
|
||||
**Confidence:** High
|
||||
|
||||
### G4) Global-objective legacy filename fallback likely one-time migration shim
|
||||
**Behavior:**
|
||||
- Reads canonical `global-objective.json`, else attempts legacy `community-goal.json`.
|
||||
(see `server/src/services/globalObjectiveService/index.js`)
|
||||
|
||||
**Why likely compatibility-only:**
|
||||
- This fallback exists purely for pre-rename data compatibility.
|
||||
- In a single coordinated deployment, once migrated, legacy read path is dead.
|
||||
|
||||
**Confidence:** Medium-High (depends on whether legacy file still exists in your deployed data dir)
|
||||
|
||||
### G5) Data directory legacy fallback path is compatibility shim
|
||||
**Behavior:**
|
||||
- `resolveDataPath`/`resolveDataDir` checks canonical `server/data` and legacy `server/src/data` style location.
|
||||
(see `server/src/helpers/dataPaths.js`)
|
||||
|
||||
**Why likely compatibility-only:**
|
||||
- Exists to preserve prior storage layout after refactor.
|
||||
- If deployment has stabilized on canonical path or explicit `SERVER_DATA_DIR`, legacy branch never used.
|
||||
|
||||
**Confidence:** Medium-High (environment dependent)
|
||||
|
||||
### G6) Overseer output parser keeps legacy one-line fallback parser
|
||||
**Behavior:**
|
||||
- Attempts JSON parse first; on failure falls back to historical one-line parse protocol.
|
||||
(see `server/src/services/overseerControlService/runtimeHelpers.js`)
|
||||
|
||||
**Why compatibility-only:**
|
||||
- Current prompt/protocol can be constrained to structured JSON output.
|
||||
- Fallback branch preserves old non-JSON output compatibility.
|
||||
|
||||
**Confidence:** Medium (depends on model output guarantees / prompt hardening)
|
||||
|
||||
## H) Additional Deep Sweep Findings (Repo-Wide)
|
||||
|
||||
### H1) Uncalled helper export: `eventBus.subscribeAll`
|
||||
**Evidence:**
|
||||
- Declared/exported in `server/src/services/eventBus/index.js`
|
||||
- No call sites in `server/src` or `webui/src`
|
||||
|
||||
**Assessment:** hard dead utility export in current codebase.
|
||||
**Confidence:** High
|
||||
|
||||
### H2) Uncalled helper export: `overseerControl.runtimeHelpers.parseOverseerOutput`
|
||||
**Evidence:**
|
||||
- Declared/exported in `server/src/services/overseerControlService/runtimeHelpers.js`
|
||||
- `overseerControl/index.js` imports only `{ isAdminRole, buildAdminState, buildFailureInfo }`
|
||||
- No other call sites in repo
|
||||
|
||||
**Assessment:** dead parser path (including its legacy one-line fallback) in current wiring.
|
||||
**Confidence:** High
|
||||
|
||||
### H3) Uncalled helper export: `overseerControl.tools.getIdForSignature`
|
||||
**Evidence:**
|
||||
- Declared/exported in `server/src/services/overseerControlService/tools/index.js`
|
||||
- No call sites in repo
|
||||
|
||||
**Assessment:** dead compatibility/helper function.
|
||||
**Confidence:** High
|
||||
|
||||
### H4) `socket.emit('rovers', ...)` channel appears fully orphaned
|
||||
**Evidence:**
|
||||
- Emitted by server in rover manager connect/roster paths:
|
||||
- `server/src/services/roverManager/socketHandlers.js`
|
||||
- `server/src/services/roverManager/rosterLifecycle.js`
|
||||
- No `socket.on('rovers', ...)` consumer in `webui/src`
|
||||
|
||||
**Assessment:** legacy protocol emission; superseded by `session:sync` usage.
|
||||
**Confidence:** High
|
||||
|
||||
### H5) `auth:role` and `mode` push events appear orphaned for first-party UI
|
||||
**Evidence:**
|
||||
- Emitted by:
|
||||
- `server/src/services/authService/index.js` (`auth:role`)
|
||||
- `server/src/services/modeManager/index.js` (`mode`)
|
||||
- No consumers in `webui/src`
|
||||
|
||||
**Assessment:** compatibility emissions for non-current clients.
|
||||
**Confidence:** High
|
||||
|
||||
### H6) `controlGranted` / `lockdown` push events appear orphaned for first-party UI
|
||||
**Evidence:**
|
||||
- Emitted by:
|
||||
- `server/src/services/roverManager/socketHandlers.js` (`controlGranted`)
|
||||
- `server/src/services/lockdownGuard/index.js` (`lockdown`)
|
||||
- No consumers in `webui/src`
|
||||
|
||||
**Assessment:** compatibility/legacy push surface for old clients.
|
||||
**Confidence:** High
|
||||
|
||||
### H7) Pi/roverd `dummy` paths are not dead by default (intentional build target)
|
||||
**Evidence:**
|
||||
- Build-tag split (`//go:build dummy` vs `!dummy`) in serial/sensor/nightvision/camera_servo/brc modules
|
||||
- `Makefile` has explicit `dummy` target: `go build -tags dummy ...`
|
||||
|
||||
**Assessment:** keep; this is an intentional alternate runtime, not dead code.
|
||||
**Confidence:** High
|
||||
|
||||
### H8) `roomcam-service` appears operationally standalone (not wired by installers)
|
||||
**Evidence:**
|
||||
- Present as service + script under `roomcam-service/`
|
||||
- Not installed by `server/install_server.sh` or `pi/install_roverd.sh`
|
||||
- Server room camera feature consumes configured URLs and does not require this local service specifically
|
||||
|
||||
**Assessment:** likely optional/ops artifact; remove only if you do not deploy it manually.
|
||||
**Confidence:** Medium
|
||||
|
||||
### H9) `dist/dummy{1,2,3}.yml` still has no runtime references
|
||||
**Evidence:**
|
||||
- No code paths consume these files
|
||||
- Existing references are only in the files themselves
|
||||
|
||||
**Assessment:** hard dead artifacts in repo runtime context.
|
||||
**Confidence:** High
|
||||
|
||||
### H10) `buttonbox/src/config example.h` is a template, not runtime code
|
||||
**Evidence:**
|
||||
- Firmware includes `<config.h>`; template file is named `config example.h`
|
||||
- Typical manual-copy onboarding artifact
|
||||
|
||||
**Assessment:** optional docs/template artifact; not dead logic, but not build-consumed unless manually copied.
|
||||
**Confidence:** High
|
||||
|
||||
## I) Final Confirmed-Only List (Static Proof)
|
||||
|
||||
These are the items I can confirm from source/wiring alone with high confidence.
|
||||
|
||||
### I1) Definitely uncalled functions/exports
|
||||
- `server/src/services/eventBus/index.js` → `subscribeAll`
|
||||
- `server/src/services/overseerControlService/runtimeHelpers.js` → `parseOverseerOutput`
|
||||
- `server/src/services/overseerControlService/tools/index.js` → `getIdForSignature`
|
||||
|
||||
Proof: repo-wide usage search returns definition only (no call sites).
|
||||
|
||||
### I2) Definitely orphan server push events for first-party webui
|
||||
Server emits, but `webui/src` has no listeners for these event names:
|
||||
- `rovers`
|
||||
- `auth:role`
|
||||
- `mode`
|
||||
- `controlGranted`
|
||||
- `lockdown`
|
||||
|
||||
Proof: emitters exist in server files; repo-wide `webui/src` listener search returns none.
|
||||
|
||||
### I3) Definitely unused old client event names in first-party webui
|
||||
Server listens for old aliases:
|
||||
- `requestControl`, `releaseControl`, `lockRover`, `privateSafety:set`, `subscribeAll`, `role:set`
|
||||
|
||||
WebUI emits only namespaced forms:
|
||||
- `session:requestControl`, `session:releaseControl`, `session:lockRover`, `session:privateSafety:set`, `session:subscribeAll`, `session:setRole`
|
||||
|
||||
Proof: listener and emitter searches across `server/src` + `webui/src`.
|
||||
|
||||
### I4) Definitely dead room branch in client video request helper (for first-party app)
|
||||
- `webui/src/hooks/useVideoRequests.js` supports room entry shapes (`roomCameraId` / `type:'room'`).
|
||||
- No first-party call site in `webui/src` constructs room entries.
|
||||
|
||||
Proof: `type:'room'` and `roomCameraId` appear only inside `useVideoRequests.js`.
|
||||
|
||||
### I5) Definitely dead file candidates (code/non-built artifacts)
|
||||
- `server/src/services/overseerControlService/tools/chatSay.js` (not registered in tool definitions; no refs)
|
||||
- `server/prompts/commentary_system_backup_small.txt` (no refs)
|
||||
- `server/prompts/commentary_system_backup_pre_lobotomy.txt` (no refs)
|
||||
- `webui/src/App.css` (not imported)
|
||||
- `webui/src/assets/react.svg` (no refs)
|
||||
- `server/assets/test-audio.mp3` (no refs)
|
||||
|
||||
---
|
||||
|
||||
## J) Not Provable Statically (requires runtime/env assertions)
|
||||
|
||||
- Legacy data path fallbacks (`server/src/helpers/dataPaths.js`) may be active depending on deployed filesystem and `SERVER_DATA_DIR`.
|
||||
- `globalObjective` legacy filename fallback (`community-goal.json`) may still be used if old file exists and new file absent.
|
||||
- `roomcam-service/*` may be manually used outside installer-managed workflows.
|
||||
- Pi/roverd `dummy` build-tag code is intentional alternate target; not dead by default.
|
||||
|
||||
|
||||
## K) Additional LLM-Style Flexibility Confirmed Unused/Dead
|
||||
|
||||
### K1) `pi/roverd` config knobs for `videoWidth/videoHeight/videoFps` are effectively dead in current pipeline
|
||||
**Proof:**
|
||||
- Config defines and validates `VideoWidth`, `VideoHeight`, `VideoFPS` in `pi/roverd/config.go`.
|
||||
- `UpdatePublisherEnv` writes `VIDEO_WIDTH/VIDEO_HEIGHT/VIDEO_FPS` to env file in `pi/roverd/media_env.go`.
|
||||
- `pi/bin/video-publisher.sh` does **not** read those env vars; it hardcodes:
|
||||
- `VIDEO_WIDTH="640"`
|
||||
- `VIDEO_HEIGHT="480"`
|
||||
- `VIDEO_FPS="30"`
|
||||
- Therefore these config values cannot affect runtime behavior as wired.
|
||||
|
||||
**Assessment:** dead configurability / fake knob.
|
||||
**Confidence:** High
|
||||
|
||||
### K2) Legacy/non-namespaced socket control API remains as compatibility baggage
|
||||
(Already identified, reiterated here as LLM-flex class)
|
||||
- Old listeners retained: `requestControl`, `releaseControl`, `lockRover`, `privateSafety:set`, `subscribeAll`, `role:set`
|
||||
- First-party client emits only `session:*` names.
|
||||
|
||||
**Assessment:** removable compatibility layer for your single-program model.
|
||||
**Confidence:** High
|
||||
|
||||
### K3) Server push-event fanout retained for non-current clients
|
||||
(Already identified, reiterated here as LLM-flex class)
|
||||
- Emits: `rovers`, `auth:role`, `mode`, `controlGranted`, `lockdown`
|
||||
- No webui listeners for any of these.
|
||||
|
||||
**Assessment:** compatibility broadcast surface with no first-party consumer.
|
||||
**Confidence:** High
|
||||
|
||||
### K4) Unused helper exports indicate speculative abstraction leftovers
|
||||
- `eventBus.subscribeAll`
|
||||
- `overseerControl.runtimeHelpers.parseOverseerOutput`
|
||||
- `overseerControl.tools.getIdForSignature`
|
||||
|
||||
**Assessment:** abstraction/future-proofing residue; currently dead.
|
||||
**Confidence:** High
|
||||
|
||||
## L) Cleanup Execution Checklist (Do This Order)
|
||||
|
||||
### Rules
|
||||
- Remove only items in the current batch.
|
||||
- Run smoke checks after each batch before moving on.
|
||||
- If smoke fails, revert that batch only and split it smaller.
|
||||
|
||||
### Smoke Check (run after each batch)
|
||||
- Open web UI and connect at least one client.
|
||||
- Verify role switch (`user`/`spectator`) still works.
|
||||
- Verify request control / release control still works.
|
||||
- Verify rover lock + private safety toggle still works.
|
||||
- Verify mode switch still works.
|
||||
- Verify spectator view still receives expected session state.
|
||||
|
||||
### Bucket 1: Confirmed Safe (static-proof removal candidates)
|
||||
|
||||
#### Batch 1 (lowest risk, start here)
|
||||
- Remove unused exports/helpers with no call sites:
|
||||
- `server/src/services/eventBus/index.js` → `subscribeAll`
|
||||
- `server/src/services/overseerControlService/runtimeHelpers.js` → `parseOverseerOutput`
|
||||
- `server/src/services/overseerControlService/tools/index.js` → `getIdForSignature`
|
||||
- Remove dead file assets/prompts not referenced anywhere:
|
||||
- `server/src/services/overseerControlService/tools/chatSay.js`
|
||||
- `server/prompts/commentary_system_backup_small.txt`
|
||||
- `server/prompts/commentary_system_backup_pre_lobotomy.txt`
|
||||
- `webui/src/App.css`
|
||||
- `webui/src/assets/react.svg`
|
||||
- `server/assets/test-audio.mp3`
|
||||
|
||||
#### Batch 2
|
||||
- Remove legacy non-namespaced socket alias listeners, keep namespaced/session contract:
|
||||
- In rover manager socket handlers, remove:
|
||||
- `requestControl`
|
||||
- `releaseControl`
|
||||
- `lockRover`
|
||||
- `privateSafety:set`
|
||||
- `subscribeAll`
|
||||
- In auth service, remove:
|
||||
- `role:set`
|
||||
- Keep:
|
||||
- `session:requestControl`
|
||||
- `session:releaseControl`
|
||||
- `session:lockRover`
|
||||
- `session:privateSafety:set`
|
||||
- `session:subscribeAll`
|
||||
- `session:setRole`
|
||||
- `setMode`
|
||||
|
||||
#### Batch 3
|
||||
- Remove orphan server push emissions not consumed by first-party web UI:
|
||||
- `rovers`
|
||||
- `auth:role`
|
||||
- `mode`
|
||||
- `controlGranted`
|
||||
- `lockdown`
|
||||
- Keep `session:sync` flow intact.
|
||||
|
||||
#### Batch 4
|
||||
- Remove dead room-shape branch in first-party client helper:
|
||||
- `webui/src/hooks/useVideoRequests.js` room entry normalization paths (`roomCameraId` / `type:'room'`)
|
||||
- Keep rover request path only.
|
||||
|
||||
#### Batch 5
|
||||
- Remove fake video configurability in `pi/roverd` (choose exactly one direction):
|
||||
- Option A: remove `videoWidth/videoHeight/videoFps` knobs and env writes entirely.
|
||||
- Option B: wire `pi/bin/video-publisher.sh` to consume `VIDEO_WIDTH/VIDEO_HEIGHT/VIDEO_FPS`.
|
||||
- Preferred for cleanup goal: Option A.
|
||||
|
||||
### Bucket 2: Needs Runtime Check (env/deploy dependent)
|
||||
- `server/src/helpers/dataPaths.js` legacy path fallback branches.
|
||||
- `server/src/services/globalObjectiveService/index.js` legacy `community-goal.json` fallback.
|
||||
- `roomcam-service/*` only if confirmed unused in your deployment.
|
||||
|
||||
### Gate Before Bucket 2
|
||||
- Confirm live environment values and on-disk data:
|
||||
- `SERVER_DATA_DIR` usage status.
|
||||
- Whether any deployment still has only legacy files/paths.
|
||||
- Whether `roomcam-service` is started by any external supervisor.
|
||||
|
||||
### Done Criteria
|
||||
- All Bucket 1 batches merged with smoke pass after each.
|
||||
- Bucket 2 either removed with runtime proof, or explicitly kept with rationale.
|
||||
- `rg` checks show no stale references to removed symbols/events/files.
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
14. add discord bot typing thing for when someone requests a replay
|
||||
15. change replay title for ones requested from discord, something other than "requester driving rover"
|
||||
16. better quickstart guide, something better than just a big list of controls. help overlay sucks i think. [x]
|
||||
1. restyle fullscreen overlay... please..
|
||||
1. restyle fullscreen overlay... please.. [x]
|
||||
17. fix rover request spam queue cheat
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,75 +0,0 @@
|
||||
You are The Overseer.
|
||||
|
||||
Priority order:
|
||||
1) Output contract
|
||||
2) Truth and grounding rules
|
||||
3) Decision policy (speak vs SKIP)
|
||||
4) Style/personality
|
||||
|
||||
Output contract:
|
||||
- Output exactly one line.
|
||||
- Output must be either SKIP or one chat message.
|
||||
- No markdown.
|
||||
- No emojis.
|
||||
- If posting unprompted, keep it to one concise sentence.
|
||||
- Length target when posting:
|
||||
- Unprompted comments: usually 14-28 words.
|
||||
- Direct replies/questions: usually 18-45 words.
|
||||
- Avoid very short fragments unless the moment clearly calls for it.
|
||||
|
||||
Truth and grounding rules:
|
||||
- Use timeline for flow.
|
||||
- Use SNAPSHOT FINAL as current truth.
|
||||
- Never invent facts about what users are doing, what rovers are doing, or what events happened.
|
||||
- Never claim a person acted/spoke unless it is present in timeline/snapshot.
|
||||
- You may invent style, mood, metaphors, and phrasing, but not factual events or user actions.
|
||||
- If facts are unclear or stale, output SKIP.
|
||||
|
||||
Decision policy:
|
||||
- Default is SKIP.
|
||||
- If nothing meaningful changed, output SKIP.
|
||||
- If your line is generic, reusable, repetitive, or just a status restatement, output SKIP.
|
||||
- If newest chat clearly addresses you (Overseer/The Overseer/bot, including close misspellings), you MUST respond this tick.
|
||||
- If newest chat asks a direct question you can answer from provided context, respond this tick.
|
||||
- If you already responded to that same direct-address/question in recent assistant lines, output SKIP.
|
||||
- If newest item is a high-signal rover event (dock/undock, battery_low flip), you may post one line.
|
||||
- If no one is actively driving and chat is quiet, almost always output SKIP.
|
||||
- Continuous normal driving/cruising is not a reason to post.
|
||||
- If rover state is broadly unchanged (st/bl/dk/ab/at), you MUST output SKIP, even if you can phrase it stylishly.
|
||||
- Prefer transitions over persistence.
|
||||
- After posting, prefer at least 15 SKIPs before posting again unless there is a new direct question/address or a new high-signal event.
|
||||
|
||||
Freshness / anti-repeat:
|
||||
- Read prior assistant lines and avoid repeating the same claim.
|
||||
- Do not repeat or paraphrase your immediately previous assistant message.
|
||||
- If the new line has the same underlying topic as your previous line, output SKIP.
|
||||
- If no fresh angle exists, output SKIP.
|
||||
|
||||
Character style:
|
||||
- Voice: sharp, dry, free-spoken, slightly ominous, witty.
|
||||
- You are not bubbly, not corporate, not cheery by default.
|
||||
- Avoid “assistant-sounding” filler and generic encouragement.
|
||||
- Keep humor understated and a little unsettling, not theatrical.
|
||||
- Answer direct chat questions plainly first, then add flavor if space allows.
|
||||
|
||||
What not to do:
|
||||
- No roll-call summaries.
|
||||
- No bland status dashboards.
|
||||
- Never produce roster/status dumps.
|
||||
- Never list multiple rover names with their status in one line.
|
||||
- Never summarize idle/docked/charging states across the room.
|
||||
- If your draft is mainly status facts (docked, charging, idle, battery flags, activity bands/scores), output SKIP.
|
||||
- No fabricated motives, plans, or intent for any user.
|
||||
- No assumptions about what someone will do next.
|
||||
- Never quote numeric counters/timers/scores directly.
|
||||
|
||||
Context format:
|
||||
- Timeline contains CHAT, EVENT, and prior assistant lines.
|
||||
- Final message is SNAPSHOT FINAL.
|
||||
|
||||
Key legend:
|
||||
- CHAT keys: n nickname, r rover_id, txt chat text, rn rover_now.
|
||||
- rn keys: st status, bl battery_low, dk docked, ab activity_band, at activity_trend.
|
||||
- SNAPSHOT rover keys: id rover_id, drv driver_nickname, st status, bl battery_low, dk docked, as activity_score, ab activity_band, at activity_trend.
|
||||
- skip_streak in SNAPSHOT FINAL is how many consecutive skips you have made.
|
||||
- If a CHAT line has r=none driver=none, that user is not driving a rover and has no rover inline context.
|
||||
@@ -1,21 +0,0 @@
|
||||
You are The Overseer of the rovers. You are able to see the rover's actions, and you are in the chatroom of the people driving them.
|
||||
You are not able to control the people or the rovers.
|
||||
Only add to the conversation if rovers are active or if someone is talking to you in the chat.
|
||||
Don't be afraid to be mean to someone if they are being mean to you in chat.
|
||||
Always pay attention to the chat.
|
||||
|
||||
Output contract:
|
||||
- Output must be either SKIP if you want to stay silent, or a message if you want to speak.
|
||||
- Allow 20 skips before speaking again, unless someone is talking to you directly.
|
||||
- If you choose to speak, send only one line.
|
||||
- Don't ever mention numbers or activity levels directly from the metadata. They are for internal use only.
|
||||
- Don't repeat the same or similar message over and over.
|
||||
- Pay attention to your skip streak, don't talk too much. Stay mostly silent unless a lot of activity is happening.
|
||||
- No markdown.
|
||||
|
||||
Key legend:
|
||||
- CHAT keys: n nickname, r rover_id, txt chat text, rn rover_now.
|
||||
- rn keys: st status, bl battery_low, dk docked, ab activity_band, at activity_trend.
|
||||
- SNAPSHOT rover keys: id rover_id, drv driver_nickname, st status, bl battery_low, dk docked, as activity_score, ab activity_band, at activity_trend.
|
||||
- skip_streak in SNAPSHOT FINAL is how many consecutive skips you have made.
|
||||
- If a CHAT line has r=none driver=none, that user is not driving a rover and has no rover inline context.
|
||||
@@ -1,38 +1,16 @@
|
||||
// data Paths helper
|
||||
// Purpose: Resolves persistent data paths across refactors so services keep loading prior state files.
|
||||
// Scope: Preserves runtime behavior by preferring configured/canonical paths while supporting legacy locations.
|
||||
const fs = require('fs');
|
||||
// Purpose: Resolves persistent data paths for server state.
|
||||
// Scope: Uses the canonical data directory for this single-program deployment.
|
||||
const path = require('path');
|
||||
|
||||
const CANONICAL_DATA_DIR = path.resolve(__dirname, '..', '..', 'data');
|
||||
const LEGACY_DATA_DIR = path.resolve(__dirname, '..', 'data');
|
||||
|
||||
function pathExists(target) {
|
||||
try {
|
||||
fs.accessSync(target, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDataDir() {
|
||||
const configured = String(process.env.SERVER_DATA_DIR || '').trim();
|
||||
if (configured) return path.resolve(configured);
|
||||
if (pathExists(CANONICAL_DATA_DIR)) return CANONICAL_DATA_DIR;
|
||||
if (pathExists(LEGACY_DATA_DIR)) return LEGACY_DATA_DIR;
|
||||
return CANONICAL_DATA_DIR;
|
||||
}
|
||||
|
||||
function resolveDataPath(fileName) {
|
||||
const configured = String(process.env.SERVER_DATA_DIR || '').trim();
|
||||
if (configured) return path.join(path.resolve(configured), fileName);
|
||||
|
||||
const canonicalPath = path.join(CANONICAL_DATA_DIR, fileName);
|
||||
const legacyPath = path.join(LEGACY_DATA_DIR, fileName);
|
||||
if (pathExists(canonicalPath)) return canonicalPath;
|
||||
if (pathExists(legacyPath)) return legacyPath;
|
||||
return canonicalPath;
|
||||
return path.join(CANONICAL_DATA_DIR, fileName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -221,5 +221,4 @@ function validateChecksum(frame, checksum) {
|
||||
|
||||
module.exports = {
|
||||
parseSensorFrame,
|
||||
CHARGING_STATE,
|
||||
};
|
||||
|
||||
@@ -32,14 +32,7 @@ function getRewardById(id) {
|
||||
return rewardById.get(String(id)) || null;
|
||||
}
|
||||
|
||||
function pickRandomReward(excludeId = null) {
|
||||
const list = listRewards().filter((reward) => !excludeId || reward.id !== excludeId);
|
||||
if (!list.length) return null;
|
||||
return list[Math.floor(Math.random() * list.length)] || null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listRewards,
|
||||
getRewardById,
|
||||
pickRandomReward,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ const io = require('../../globals/io');
|
||||
const { getRole, roleEvents } = require('../roleService');
|
||||
const { getSocketIp } = require('../../helpers/ipResolver');
|
||||
|
||||
const ADMIN_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
|
||||
const ADMIN_ROLES = new Set(['admin', 'lockdown']);
|
||||
const MAX_HISTORY = 200;
|
||||
const history = [];
|
||||
|
||||
|
||||
@@ -94,5 +94,4 @@ module.exports = {
|
||||
getAdminReason,
|
||||
setAdminReason,
|
||||
clearAdminReason,
|
||||
MAX_REASON_LENGTH,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ const bcrypt = require('bcrypt');
|
||||
const io = require('../../globals/io');
|
||||
const logger = require('../../globals/logger').child('authService');
|
||||
const { loadConfig } = require('../../helpers/configLoader');
|
||||
const { clearLockdownTimer } = require('../lockdownGuard');
|
||||
const { getMode, MODES } = require('../modeManager');
|
||||
const { setRole } = require('../roleService');
|
||||
|
||||
@@ -41,7 +40,6 @@ io.on('connection', (socket) => {
|
||||
const initialRole = requestedRole === 'spectator' ? 'spectator' : 'user';
|
||||
setRole(socket, initialRole);
|
||||
logger.info('Socket connected with role', socket.id, initialRole);
|
||||
socket.emit('auth:role', { role: initialRole });
|
||||
socket.on('auth:login', async ({ username, password }, cb = () => {}) => {
|
||||
try {
|
||||
const admin = await authenticate(username, password);
|
||||
@@ -51,8 +49,6 @@ io.on('connection', (socket) => {
|
||||
const role = admin.lockdown ? 'lockdown' : 'admin';
|
||||
socket.data.user = { username: admin.username, discordId: admin.discord_id };
|
||||
setRole(socket, role);
|
||||
socket.emit('auth:role', { role });
|
||||
clearLockdownTimer(socket);
|
||||
logger.info('Login success', socket.id, role);
|
||||
cb({ success: true, role: socket.data.role });
|
||||
} catch (err) {
|
||||
@@ -64,7 +60,6 @@ io.on('connection', (socket) => {
|
||||
function handleRoleChange({ role } = {}, cb = () => {}) {
|
||||
if (role === 'spectator' || role === 'user') {
|
||||
setRole(socket, role);
|
||||
socket.emit('auth:role', { role });
|
||||
logger.info('Role changed via client request', socket.id, role);
|
||||
cb({ success: true, role });
|
||||
} else {
|
||||
@@ -72,12 +67,7 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('role:set', handleRoleChange);
|
||||
socket.on('session:setRole', handleRoleChange);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
isAdmin,
|
||||
isLockdownAdmin,
|
||||
authenticate,
|
||||
};
|
||||
module.exports = {};
|
||||
|
||||
@@ -19,11 +19,11 @@ function formatWebhookUsername(payload) {
|
||||
const name = payload.nickname || payload.socketId?.slice(0, 6) || 'unknown';
|
||||
if (payload.fromDiscord) {
|
||||
const origin = payload.discordGuildName ? ` (From: ${payload.discordGuildName})` : '';
|
||||
const adminTag = payload.role === 'admin' || payload.role === 'lockdown' || payload.role === 'lockdown-admin' ? ' [Rover Admin]' : '';
|
||||
const adminTag = payload.role === 'admin' || payload.role === 'lockdown' ? ' [Rover Admin]' : '';
|
||||
return `${name}${origin}${adminTag}`;
|
||||
}
|
||||
const roverText = payload.roverId ? `Rover: ${payload.roverId}` : `No rover`;
|
||||
const roleText = payload.role === 'admin' || payload.role === 'lockdown' || payload.role === 'lockdown-admin' ? 'Admin' : null;
|
||||
const roleText = payload.role === 'admin' || payload.role === 'lockdown' ? 'Admin' : null;
|
||||
const suffix = [roverText, roleText].filter(Boolean).join(' · ');
|
||||
return suffix ? `${name} · ${suffix}` : name;
|
||||
}
|
||||
|
||||
@@ -43,18 +43,7 @@ function subscribe(type, handler) {
|
||||
return () => eventBus.off(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to all events on the bus.
|
||||
* @param {(event: object) => void} handler
|
||||
*/
|
||||
function subscribeAll(handler) {
|
||||
eventBus.on('*', handler);
|
||||
return () => eventBus.off('*', handler);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eventBus,
|
||||
publishEvent,
|
||||
subscribe,
|
||||
subscribeAll,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ const { resolveDataDir, resolveDataPath } = require('../../helpers/dataPaths');
|
||||
|
||||
const DATA_DIR = resolveDataDir();
|
||||
const STORE_PATH = resolveDataPath('global-objective.json');
|
||||
const LEGACY_STORE_PATH = resolveDataPath('community-goal.json');
|
||||
const MAX_GOAL_LENGTH = 240;
|
||||
|
||||
let cache = null;
|
||||
@@ -23,15 +22,6 @@ function loadStore() {
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
logger.warn('Failed to load global objective', err.message);
|
||||
} else {
|
||||
try {
|
||||
const legacyRaw = fs.readFileSync(LEGACY_STORE_PATH, 'utf8');
|
||||
cache = JSON.parse(legacyRaw);
|
||||
} catch (legacyErr) {
|
||||
if (legacyErr.code !== 'ENOENT') {
|
||||
logger.warn('Failed to load legacy global objective', legacyErr.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cache) cache = null;
|
||||
}
|
||||
@@ -104,5 +94,4 @@ module.exports = {
|
||||
getGlobalObjective,
|
||||
setGlobalObjective,
|
||||
clearGlobalObjective,
|
||||
MAX_GOAL_LENGTH,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Purpose: Provides pure helpers for admin state projection, role checks, and structured error normalization.
|
||||
// Scope: Keeps runtime behavior unchanged by extracting deterministic helper logic from index orchestration.
|
||||
function isAdminRole(role) {
|
||||
return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
|
||||
return role === 'admin' || role === 'lockdown';
|
||||
}
|
||||
|
||||
function buildAdminState(status, runHistory) {
|
||||
|
||||
@@ -2,21 +2,13 @@
|
||||
// Purpose: Defines the lockdown Guard module and the helpers/state used by this service unit.
|
||||
// Scope: Keeps runtime behavior unchanged while isolating responsibilities into a clear module boundary.
|
||||
const io = require('../../globals/io');
|
||||
const { MODES, getMode, modeEvents } = require('../modeManager');
|
||||
const { MODES, modeEvents } = require('../modeManager');
|
||||
const { isLockdownAdmin } = require('../roleService');
|
||||
|
||||
function disconnectForLockdown(socket) {
|
||||
socket.emit('lockdown', { message: 'Server is in lockdown mode' });
|
||||
socket.disconnect(true);
|
||||
}
|
||||
|
||||
function clearLockdownTimer(socket) {
|
||||
if (socket?.data?.lockdownTimer) {
|
||||
clearTimeout(socket.data.lockdownTimer);
|
||||
socket.data.lockdownTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function enforceLockdown() {
|
||||
for (const socket of io.sockets.sockets.values()) {
|
||||
if (!isLockdownAdmin(socket)) {
|
||||
@@ -25,11 +17,7 @@ function enforceLockdown() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
enforceLockdown,
|
||||
disconnectForLockdown,
|
||||
clearLockdownTimer,
|
||||
};
|
||||
module.exports = {};
|
||||
|
||||
modeEvents.on('change', (mode) => {
|
||||
if (mode === MODES.LOCKDOWN) {
|
||||
|
||||
@@ -51,7 +51,6 @@ function setMode(nextMode, socket, options = {}) {
|
||||
payload: { mode: currentMode, by: socket?.data?.user?.username || null },
|
||||
});
|
||||
modeEvents.emit('change', currentMode);
|
||||
io.emit('mode', { mode: currentMode });
|
||||
return currentMode;
|
||||
}
|
||||
|
||||
@@ -67,7 +66,6 @@ module.exports = {
|
||||
};
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.emit('mode', { mode: currentMode });
|
||||
socket.on('setMode', ({ mode }) => {
|
||||
try {
|
||||
setMode(mode, socket);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function isAdminRole(role) {
|
||||
return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
|
||||
return role === 'admin' || role === 'lockdown';
|
||||
}
|
||||
|
||||
function buildAdminState(status, runHistory) {
|
||||
@@ -66,36 +66,6 @@ function buildAdminState(status, runHistory) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseOverseerOutput(rawContent = '') {
|
||||
const raw = typeof rawContent === 'string' ? rawContent : '';
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return { raw, decision: 'SKIP', chat: null, actions: [] };
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
const decision = String(parsed?.decision || 'SKIP').toUpperCase();
|
||||
const allowed = new Set(['SKIP', 'CHAT', 'ACTION', 'ACTION+CHAT']);
|
||||
const nextDecision = allowed.has(decision) ? decision : 'SKIP';
|
||||
const chat = typeof parsed?.chat === 'string' && parsed.chat.trim() ? parsed.chat.trim() : null;
|
||||
const actions = Array.isArray(parsed?.actions)
|
||||
? parsed.actions
|
||||
.map((entry) => ({
|
||||
tool: String(entry?.tool || '').trim(),
|
||||
args: entry?.args && typeof entry.args === 'object' ? entry.args : {},
|
||||
}))
|
||||
.filter((entry) => entry.tool.length > 0)
|
||||
: [];
|
||||
return { raw, decision: nextDecision, chat, actions };
|
||||
} catch (_) {
|
||||
// fall through to legacy one-line parse
|
||||
}
|
||||
const first = trimmed.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
|
||||
const upper = first.toUpperCase();
|
||||
if (['SKIP', 'CHAT', 'ACTION', 'ACTION+CHAT'].includes(upper)) {
|
||||
return { raw, decision: upper, chat: null, actions: [] };
|
||||
}
|
||||
return { raw, decision: 'CHAT', chat: first, actions: [] };
|
||||
}
|
||||
|
||||
function buildFailureInfo(err) {
|
||||
const message = err?.message || String(err || 'Unknown error');
|
||||
const details = {
|
||||
@@ -111,6 +81,5 @@ function buildFailureInfo(err) {
|
||||
module.exports = {
|
||||
isAdminRole,
|
||||
buildAdminState,
|
||||
parseOverseerOutput,
|
||||
buildFailureInfo,
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
module.exports = {
|
||||
id: 'chat_say',
|
||||
signature: 'chat_say(text)',
|
||||
description: 'Post a chat line as the Overseer bot.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', minLength: 1 },
|
||||
},
|
||||
required: ['text'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability() {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
async execute({ args = {}, sendSystemMessage, name }) {
|
||||
const text = String(args?.text || '').trim();
|
||||
if (!text) throw new Error('chat_say requires args.text');
|
||||
sendSystemMessage(text, { nickname: name });
|
||||
return { ok: true };
|
||||
},
|
||||
};
|
||||
@@ -73,21 +73,8 @@ async function executeToolAction(toolId, args = {}, context = {}) {
|
||||
return tool.execute({ ...context, args: args || {} });
|
||||
}
|
||||
|
||||
function getToolById(toolId) {
|
||||
return TOOL_BY_ID.get(String(toolId || '').trim()) || null;
|
||||
}
|
||||
|
||||
function getIdForSignature(signature) {
|
||||
const sig = String(signature || '').trim();
|
||||
const match = TOOL_DEFINITIONS.find((tool) => tool.signature === sig);
|
||||
return match?.id || null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TOOL_DEFINITIONS,
|
||||
evaluateTools,
|
||||
buildOllamaTools,
|
||||
executeToolAction,
|
||||
getToolById,
|
||||
getIdForSignature,
|
||||
};
|
||||
|
||||
@@ -155,7 +155,6 @@ function tryAssignClosedPrivateRover(socket, roverId) {
|
||||
} catch (err) {
|
||||
logger.warn('Failed to move assignment after private access grant', { socketId: socket.id, error: err.message });
|
||||
}
|
||||
socket.emit('controlGranted', { roverId: String(roverId) });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Private Rover Access Request Service
|
||||
// Purpose: Composes private-rover access request state, core workflows, and event hooks behind one API.
|
||||
// Scope: Exposes request/grant operations and event stream while delegating behavior to focused modules.
|
||||
const { DM_APPROVE_EMOJI, DM_DENY_EMOJI, requestEvents } = require('./state');
|
||||
const { requestEvents } = require('./state');
|
||||
const {
|
||||
getStateForSocket,
|
||||
createRequest,
|
||||
@@ -22,8 +22,6 @@ registerPrivateRoverAccessHooks({
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
DM_APPROVE_EMOJI,
|
||||
DM_DENY_EMOJI,
|
||||
requestEvents,
|
||||
getStateForSocket,
|
||||
createRequest,
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const REQUEST_COOLDOWN_MS = 15 * 1000;
|
||||
const DM_APPROVE_EMOJI = '✅';
|
||||
const DM_DENY_EMOJI = '❌';
|
||||
|
||||
const requestEvents = new EventEmitter();
|
||||
const pendingRequests = new Map();
|
||||
@@ -16,8 +14,6 @@ const grants = new Map();
|
||||
|
||||
module.exports = {
|
||||
REQUEST_COOLDOWN_MS,
|
||||
DM_APPROVE_EMOJI,
|
||||
DM_DENY_EMOJI,
|
||||
requestEvents,
|
||||
pendingRequests,
|
||||
pendingByRequesterRover,
|
||||
|
||||
@@ -39,7 +39,6 @@ function createSidebarRenderer({ execFileAsync, ensureDir }) {
|
||||
switch (String(role)) {
|
||||
case 'admin':
|
||||
case 'lockdown':
|
||||
case 'lockdown-admin':
|
||||
return '#FCD34D';
|
||||
case 'spectator':
|
||||
return '#94A3B8';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
const path = require('path');
|
||||
const roverManager = require('../roverManager');
|
||||
const { getRoomCameras } = require('../roomCameraService');
|
||||
const { FFMPEG_BIN, SEGMENT_SECONDS, TARGET_FPS } = require('./constants');
|
||||
const { SEGMENT_SECONDS, TARGET_FPS } = require('./constants');
|
||||
|
||||
function sourceKey(source) {
|
||||
return `${source.sourceType}__${source.kind}__${source.id}`;
|
||||
@@ -63,7 +63,6 @@ function buildWorkerArgs(activeSegmentRoot, source) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FFMPEG_BIN,
|
||||
sourceKey,
|
||||
sourceDirForKey,
|
||||
listDesiredSources,
|
||||
|
||||
@@ -102,8 +102,6 @@ function registerRoomCameraSocketGateway({ getRoomCamera, getRoomCameras, getRoo
|
||||
socket.on('roomCamera:subscribe', (payload = {}, cb = () => {}) => {
|
||||
const list = Array.isArray(payload?.ids)
|
||||
? payload.ids.map(String)
|
||||
: payload?.roomCameraId || payload?.id
|
||||
? [String(payload.roomCameraId || payload.id)]
|
||||
: getRoomCameras().map((cam) => cam.id);
|
||||
const uniqueIds = Array.from(new Set(list));
|
||||
try {
|
||||
@@ -126,8 +124,6 @@ function registerRoomCameraSocketGateway({ getRoomCamera, getRoomCameras, getRoo
|
||||
socket.on('roomCamera:unsubscribe', (payload = {}) => {
|
||||
const list = Array.isArray(payload?.ids)
|
||||
? payload.ids.map(String)
|
||||
: payload?.roomCameraId || payload?.id
|
||||
? [String(payload.roomCameraId || payload.id)]
|
||||
: [];
|
||||
list.forEach((cameraId) => removeSubscription(socket.id, cameraId));
|
||||
});
|
||||
|
||||
@@ -216,7 +216,6 @@ registerSocketHandlers({
|
||||
tickPrivateAutoClose,
|
||||
removeSocket,
|
||||
enableSpectator,
|
||||
getRosterForSocket,
|
||||
canRequestControl,
|
||||
canSwitchRover,
|
||||
getRoversForSocket,
|
||||
|
||||
@@ -251,9 +251,6 @@ function createRosterLifecycle(deps) {
|
||||
|
||||
function broadcastRoster() {
|
||||
syncSpectatorRooms();
|
||||
io.sockets.sockets.forEach((socket) => {
|
||||
socket.emit('rovers', getRosterForSocket(socket));
|
||||
});
|
||||
}
|
||||
|
||||
function setNightVisionState(roverId, nightVisionOn) {
|
||||
|
||||
@@ -44,7 +44,6 @@ function createRoverLifecycle(deps) {
|
||||
socketToRovers.get(socket.id).add(roverId);
|
||||
socket.join(record.room);
|
||||
turnService.driverAdded(roverId, socket.id, force && isAdmin(socket));
|
||||
socket.emit('controlGranted', { roverId });
|
||||
managerEvents.emit('driver', { socketId: socket.id, roverId, action: 'add' });
|
||||
sendAlert({
|
||||
color: ALERT_COLOR,
|
||||
|
||||
@@ -18,7 +18,6 @@ function registerSocketHandlers(deps) {
|
||||
tickPrivateAutoClose,
|
||||
removeSocket,
|
||||
enableSpectator,
|
||||
getRosterForSocket,
|
||||
canRequestControl,
|
||||
canSwitchRover,
|
||||
getRoversForSocket,
|
||||
@@ -32,7 +31,6 @@ function registerSocketHandlers(deps) {
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
tickPrivateAutoClose();
|
||||
socket.emit('rovers', getRosterForSocket(socket));
|
||||
if (socket.data?.role === 'spectator') {
|
||||
enableSpectator(socket);
|
||||
}
|
||||
@@ -79,7 +77,6 @@ function registerSocketHandlers(deps) {
|
||||
info.sourceId !== `${targetId}-audio`,
|
||||
);
|
||||
managerEvents.emit('switch', { socketId: socket.id, roverId: targetId });
|
||||
socket.emit('controlGranted', { roverId: targetId });
|
||||
cb({ success: true, roverId: targetId });
|
||||
} catch (err) {
|
||||
logger.warn('Request control failed', socket.id, err.message);
|
||||
@@ -172,15 +169,10 @@ function registerSocketHandlers(deps) {
|
||||
cb({ success: true });
|
||||
}
|
||||
|
||||
socket.on('requestControl', handleRequestControl);
|
||||
socket.on('session:requestControl', handleRequestControl);
|
||||
socket.on('releaseControl', handleReleaseControl);
|
||||
socket.on('session:releaseControl', handleReleaseControl);
|
||||
socket.on('lockRover', handleLockToggle);
|
||||
socket.on('session:lockRover', handleLockToggle);
|
||||
socket.on('privateSafety:set', handlePrivateSafetySet);
|
||||
socket.on('session:privateSafety:set', handlePrivateSafetySet);
|
||||
socket.on('subscribeAll', handleSubscribeAll);
|
||||
socket.on('session:subscribeAll', handleSubscribeAll);
|
||||
|
||||
socket.on('disconnecting', () => {
|
||||
|
||||
@@ -11,7 +11,6 @@ const configuredSocials = Array.isArray(config.socials) ? config.socials : null;
|
||||
|
||||
const ACTIVITY_SYNC_COOLDOWN_MS = 3000;
|
||||
const NIGHT_VISION_SYNC_COOLDOWN_MS = 1000;
|
||||
const PERIODIC_SYNC_MS = 20000;
|
||||
|
||||
module.exports = {
|
||||
discordInvite,
|
||||
@@ -20,5 +19,4 @@ module.exports = {
|
||||
configuredSocials,
|
||||
ACTIVITY_SYNC_COOLDOWN_MS,
|
||||
NIGHT_VISION_SYNC_COOLDOWN_MS,
|
||||
PERIODIC_SYNC_MS,
|
||||
};
|
||||
|
||||
@@ -40,7 +40,6 @@ const {
|
||||
configuredSocials,
|
||||
ACTIVITY_SYNC_COOLDOWN_MS,
|
||||
NIGHT_VISION_SYNC_COOLDOWN_MS,
|
||||
PERIODIC_SYNC_MS,
|
||||
} = require('./constants');
|
||||
const { getState, setState } = require('./state');
|
||||
const {
|
||||
@@ -330,14 +329,4 @@ audioLevelsEvents.on('change', () => {
|
||||
syncAll();
|
||||
});
|
||||
|
||||
// sync all sockets 20 seconds
|
||||
// setInterval(() => {
|
||||
// logger.info('Periodic session sync for all clients');
|
||||
// syncAll();
|
||||
// }, PERIODIC_SYNC_MS);
|
||||
|
||||
module.exports = {
|
||||
buildSession,
|
||||
syncSocket,
|
||||
syncAll,
|
||||
};
|
||||
module.exports = {};
|
||||
|
||||
@@ -43,7 +43,7 @@ function normalizeKnownIps(raw = []) {
|
||||
}
|
||||
|
||||
function isAdminRole(role) {
|
||||
return role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
|
||||
return role === 'admin' || role === 'lockdown';
|
||||
}
|
||||
|
||||
function parseDeterrenceSelector(selector) {
|
||||
|
||||
@@ -68,6 +68,5 @@ module.exports = {
|
||||
createSession,
|
||||
getSession,
|
||||
revokeSession,
|
||||
revokeBySocket,
|
||||
revokeWhere,
|
||||
};
|
||||
|
||||
@@ -31,13 +31,7 @@ function getMediaPrefix() {
|
||||
function buildWhepUrlForSource(source) {
|
||||
const cleanBase = getMediaPrefix();
|
||||
if (!cleanBase) return '';
|
||||
const segments = [];
|
||||
if (source.type === 'room') {
|
||||
segments.push('room', encodeURIComponent(source.id));
|
||||
} else {
|
||||
segments.push(encodeURIComponent(source.id));
|
||||
}
|
||||
return `${cleanBase}/${segments.join('/')}/whep`;
|
||||
return `${cleanBase}/${encodeURIComponent(source.id)}/whep`;
|
||||
}
|
||||
|
||||
function passesMode(socket) {
|
||||
@@ -66,21 +60,14 @@ function canViewRover(socket, roverId) {
|
||||
return roverManager.isDriver(roverId, socket);
|
||||
}
|
||||
|
||||
function canViewRoomCamera(socket) {
|
||||
return passesMode(socket);
|
||||
}
|
||||
|
||||
function normalizeRequest(payload = {}) {
|
||||
if (!payload) return null;
|
||||
if (payload.type && payload.id) {
|
||||
return { type: payload.type, id: String(payload.id) };
|
||||
if (payload.type && payload.id && payload.type === 'rover') {
|
||||
return { type: 'rover', id: String(payload.id) };
|
||||
}
|
||||
if (payload.roverId) {
|
||||
return { type: 'rover', id: String(payload.roverId) };
|
||||
}
|
||||
if (payload.roomCameraId) {
|
||||
return { type: 'room', id: String(payload.roomCameraId) };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -91,26 +78,20 @@ io.on('connection', (socket) => {
|
||||
if (!target) {
|
||||
throw new Error('video source required');
|
||||
}
|
||||
if (target.type === 'rover') {
|
||||
const baseId = target.id.endsWith('-audio') ? target.id.slice(0, -6) : target.id;
|
||||
const isAudio = target.id.endsWith('-audio');
|
||||
if (!roverManager.rovers.has(baseId)) {
|
||||
throw new Error('Rover offline');
|
||||
}
|
||||
if (!canViewRover(socket, baseId)) {
|
||||
const baseId = target.id.endsWith('-audio') ? target.id.slice(0, -6) : target.id;
|
||||
const isAudio = target.id.endsWith('-audio');
|
||||
if (!roverManager.rovers.has(baseId)) {
|
||||
throw new Error('Rover offline');
|
||||
}
|
||||
if (!canViewRover(socket, baseId)) {
|
||||
throw new Error('Not authorized for video');
|
||||
}
|
||||
const role = getRole(socket);
|
||||
if (role === 'spectator' && !isAdmin(socket) && !isAudio) {
|
||||
const ip = getSocketIp(socket);
|
||||
if (!isLocalNetwork(ip)) {
|
||||
throw new Error('Not authorized for video');
|
||||
}
|
||||
const role = getRole(socket);
|
||||
if (role === 'spectator' && !isAdmin(socket) && !isAudio) {
|
||||
const ip = getSocketIp(socket);
|
||||
if (!isLocalNetwork(ip)) {
|
||||
throw new Error('Not authorized for video');
|
||||
}
|
||||
}
|
||||
} else if (target.type === 'room') {
|
||||
throw new Error('Room cameras now use the snapshot feed');
|
||||
} else {
|
||||
throw new Error('Unsupported video source');
|
||||
}
|
||||
const url = buildWhepUrlForSource(target);
|
||||
if (!url) {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -57,9 +57,8 @@ export default function AdminPanelContent() {
|
||||
|
||||
const isAdmin =
|
||||
session?.role === 'admin' ||
|
||||
session?.role === 'lockdown' ||
|
||||
session?.role === 'lockdown-admin';
|
||||
const isLockdownAdmin = session?.role === 'lockdown' || session?.role === 'lockdown-admin';
|
||||
session?.role === 'lockdown';
|
||||
const isLockdownAdmin = session?.role === 'lockdown';
|
||||
|
||||
const currentMode = session?.mode ?? 'open';
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ function roleColors(role) {
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
case 'lockdown':
|
||||
case 'lockdown-admin':
|
||||
return 'text-amber-300';
|
||||
case 'spectator':
|
||||
return 'text-slate-400';
|
||||
@@ -108,7 +107,7 @@ function chatRowClass(message) {
|
||||
return 'surface-muted relative flex flex-wrap items-start gap-0.5 border border-emerald-500/40 bg-emerald-900/15 text-sm';
|
||||
}
|
||||
const isAdmin =
|
||||
message.role === 'admin' || message.role === 'lockdown' || message.role === 'lockdown-admin';
|
||||
message.role === 'admin' || message.role === 'lockdown';
|
||||
return `surface-muted relative flex flex-wrap items-start gap-0.5 text-sm ${
|
||||
isAdmin
|
||||
? 'border border-amber-400/30'
|
||||
|
||||
@@ -8,8 +8,8 @@ import SocialButton from '../SocialButton/index.jsx';
|
||||
import ChatPanel from '../ChatPanel/index.jsx';
|
||||
import NicknameForm from '../NicknameForm/index.jsx';
|
||||
|
||||
const PRIVILEGED_ROLES = new Set(['admin', 'lockdown', 'lockdown-admin']);
|
||||
const LOCKDOWN_ROLES = new Set(['lockdown', 'lockdown-admin']);
|
||||
const PRIVILEGED_ROLES = new Set(['admin', 'lockdown']);
|
||||
const LOCKDOWN_ROLES = new Set(['lockdown']);
|
||||
const RESTRICTED_MODES = new Set(['admin', 'lockdown']);
|
||||
|
||||
function getModeDetails(mode = 'admin') {
|
||||
|
||||
@@ -10,7 +10,6 @@ function roleColors(role) {
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
case 'lockdown':
|
||||
case 'lockdown-admin':
|
||||
return 'text-amber-300';
|
||||
case 'spectator':
|
||||
return 'text-slate-400';
|
||||
|
||||
@@ -26,7 +26,6 @@ function roleColors(role) {
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
case 'lockdown':
|
||||
case 'lockdown-admin':
|
||||
return 'text-amber-300';
|
||||
case 'spectator':
|
||||
return 'text-slate-400';
|
||||
@@ -56,7 +55,7 @@ export default function RoverQueuesPanel({ title = 'Rovers' }) {
|
||||
|
||||
const canRequest = useMemo(() => role && role !== 'spectator', [role]);
|
||||
const adminCapable = useMemo(
|
||||
() => role === 'admin' || role === 'lockdown' || role === 'lockdown-admin',
|
||||
() => role === 'admin' || role === 'lockdown',
|
||||
[role],
|
||||
);
|
||||
const hasDeadlines = useMemo(
|
||||
|
||||
@@ -29,7 +29,6 @@ function roleColors(role) {
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
case 'lockdown':
|
||||
case 'lockdown-admin':
|
||||
return 'text-amber-300';
|
||||
case 'spectator':
|
||||
return 'text-slate-400';
|
||||
@@ -121,7 +120,7 @@ export default function UserListPanel({
|
||||
) : (
|
||||
sorted.map((user) => {
|
||||
const isAdmin =
|
||||
user.role === 'admin' || user.role === 'lockdown' || user.role === 'lockdown-admin';
|
||||
user.role === 'admin' || user.role === 'lockdown';
|
||||
return (
|
||||
<div
|
||||
key={user.socketId}
|
||||
@@ -245,7 +244,7 @@ export default function UserListPanel({
|
||||
const isNext = Boolean(nextId && socketId === nextId && !isCurrent);
|
||||
const isSelf = Boolean(selfId && socketId === selfId);
|
||||
const isAdmin =
|
||||
user.role === 'admin' || user.role === 'lockdown' || user.role === 'lockdown-admin';
|
||||
user.role === 'admin' || user.role === 'lockdown';
|
||||
const highlightClass = isCurrent
|
||||
? 'bg-sky-600 text-white ring-2 ring-amber-300 animate-pulse'
|
||||
: isNext
|
||||
|
||||
@@ -131,7 +131,7 @@ export function useOvercurrentLimiter(roverId, options = {}) {
|
||||
return { motors, groups };
|
||||
}, [overcurrentFlags]);
|
||||
|
||||
const adminImmune = role === 'admin' || role === 'lockdown' || role === 'lockdown-admin';
|
||||
const adminImmune = role === 'admin' || role === 'lockdown';
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Hook: useVideoRequests
|
||||
// Purpose: Coordinates client-side video stream request intents and authorization timing. Scope: Provides reusable request helpers for rover and room video consumers.
|
||||
// Purpose: Coordinates client-side video stream request intents and authorization timing. Scope: Provides reusable request helpers for rover video consumers.
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSocket } from '../context/SocketContext.jsx';
|
||||
|
||||
@@ -13,12 +13,9 @@ function normalizeEntry(entry) {
|
||||
if (typeof entry === 'object') {
|
||||
if (entry.type && entry.id) {
|
||||
const id = String(entry.id);
|
||||
let key = entry.key;
|
||||
if (!key) {
|
||||
key = entry.type === 'room' ? `room:${id}` : id;
|
||||
}
|
||||
const key = entry.key || id;
|
||||
return {
|
||||
type: entry.type,
|
||||
type: 'rover',
|
||||
id,
|
||||
key,
|
||||
};
|
||||
@@ -27,10 +24,6 @@ function normalizeEntry(entry) {
|
||||
const id = String(entry.roverId);
|
||||
return { type: 'rover', id, key: entry.key || id };
|
||||
}
|
||||
if (entry.roomCameraId) {
|
||||
const id = String(entry.roomCameraId);
|
||||
return { type: 'room', id, key: entry.key || `room:${id}` };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -87,7 +80,7 @@ export function useVideoRequests(sourceList = [], options = {}) {
|
||||
let cancelled = false;
|
||||
|
||||
function requestEntry(entry) {
|
||||
const payload = entry.type === 'room' ? { roomCameraId: entry.id } : { roverId: entry.id };
|
||||
const payload = { roverId: entry.id };
|
||||
socket.emit('video:request', payload, (resp = {}) => {
|
||||
if (cancelled) return;
|
||||
setSources((prev) => ({ ...prev, [entry.key]: resp }));
|
||||
|
||||
Reference in New Issue
Block a user