diff --git a/pi/roverd/config.go b/pi/roverd/config.go index a98b0077..212f8941 100644 --- a/pi/roverd/config.go +++ b/pi/roverd/config.go @@ -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:"-"` } diff --git a/pi/roverd/media_env.go b/pi/roverd/media_env.go index 66fa636b..7e881913 100644 --- a/pi/roverd/media_env.go +++ b/pi/roverd/media_env.go @@ -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 diff --git a/plans/dead-code-audit.md b/plans/dead-code-audit.md new file mode 100644 index 00000000..37494f7a --- /dev/null +++ b/plans/dead-code-audit.md @@ -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: {/* */} +webui/src/App.jsx:153: {/* {showTelemetry ? : null} */} +webui/src/App.jsx:183: {/* */} +webui/src/App.jsx:211: {/* */} +webui/src/components/QuickstartOverlay/index.jsx:82: {/* */} +webui/src/components/QuickstartOverlay/index.jsx:90: {/* {!isDesktop?
: null} */} +webui/src/components/QuickstartOverlay/index.jsx:116: {/* */} +webui/src/components/DriveDockAction/index.jsx:261: {/* {!isMobile && expanded ? ( */} +webui/src/components/UserListPanel/index.jsx:263: {/* {isSelf && YOU} */} +webui/src/components/AuthPanel/index.jsx:48: {/*
+webui/src/components/AuthPanel/index.jsx:55:
*/} +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: {/*

+webui/src/components/ModeGateOverlay/index.jsx:111:

*/} +webui/src/components/RoomCameraPanel/index.jsx:103: {/*
+webui/src/components/RoomCameraPanel/index.jsx:106:
*/} +webui/src/components/DriverVideoPanel/index.jsx:145: {/* colored button to visit the spectator page */} +webui/src/components/TelemetryPanel/index.jsx:43: {/*
+webui/src/components/TelemetryPanel/index.jsx:49:
*/} + +## 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 ``; 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. diff --git a/plans/to-do.md b/plans/to-do.md index fffb1f2f..fd70409a 100644 --- a/plans/to-do.md +++ b/plans/to-do.md @@ -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 diff --git a/server/assets/test-audio.mp3 b/server/assets/test-audio.mp3 deleted file mode 100644 index 5659c4e2..00000000 Binary files a/server/assets/test-audio.mp3 and /dev/null differ diff --git a/server/prompts/commentary_system_backup_pre_lobotomy.txt b/server/prompts/commentary_system_backup_pre_lobotomy.txt deleted file mode 100644 index ecf992eb..00000000 --- a/server/prompts/commentary_system_backup_pre_lobotomy.txt +++ /dev/null @@ -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. diff --git a/server/prompts/commentary_system_backup_small.txt b/server/prompts/commentary_system_backup_small.txt deleted file mode 100644 index 5e60ed6f..00000000 --- a/server/prompts/commentary_system_backup_small.txt +++ /dev/null @@ -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. diff --git a/server/src/helpers/dataPaths.js b/server/src/helpers/dataPaths.js index 0dc60afb..075dbedf 100644 --- a/server/src/helpers/dataPaths.js +++ b/server/src/helpers/dataPaths.js @@ -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 = { diff --git a/server/src/helpers/sensorDecoder.js b/server/src/helpers/sensorDecoder.js index 3a3159e7..144879a8 100644 --- a/server/src/helpers/sensorDecoder.js +++ b/server/src/helpers/sensorDecoder.js @@ -221,5 +221,4 @@ function validateChecksum(frame, checksum) { module.exports = { parseSensorFrame, - CHARGING_STATE, }; diff --git a/server/src/rewards/index.js b/server/src/rewards/index.js index 97f53c5a..1ceaf15c 100644 --- a/server/src/rewards/index.js +++ b/server/src/rewards/index.js @@ -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, }; diff --git a/server/src/services/adminLogService/index.js b/server/src/services/adminLogService/index.js index cc692615..7a82d5a6 100644 --- a/server/src/services/adminLogService/index.js +++ b/server/src/services/adminLogService/index.js @@ -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 = []; diff --git a/server/src/services/adminReasonService/index.js b/server/src/services/adminReasonService/index.js index 546a48dc..3112f678 100644 --- a/server/src/services/adminReasonService/index.js +++ b/server/src/services/adminReasonService/index.js @@ -94,5 +94,4 @@ module.exports = { getAdminReason, setAdminReason, clearAdminReason, - MAX_REASON_LENGTH, }; diff --git a/server/src/services/authService/index.js b/server/src/services/authService/index.js index cc5c491e..9e6c52fb 100644 --- a/server/src/services/authService/index.js +++ b/server/src/services/authService/index.js @@ -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 = {}; diff --git a/server/src/services/discordBotService/integrations/helpers.js b/server/src/services/discordBotService/integrations/helpers.js index 048e98e9..ebe4d616 100644 --- a/server/src/services/discordBotService/integrations/helpers.js +++ b/server/src/services/discordBotService/integrations/helpers.js @@ -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; } diff --git a/server/src/services/eventBus/index.js b/server/src/services/eventBus/index.js index 7bd404d8..07efa42c 100644 --- a/server/src/services/eventBus/index.js +++ b/server/src/services/eventBus/index.js @@ -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, }; diff --git a/server/src/services/globalObjectiveService/index.js b/server/src/services/globalObjectiveService/index.js index 148998f1..43875120 100644 --- a/server/src/services/globalObjectiveService/index.js +++ b/server/src/services/globalObjectiveService/index.js @@ -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, }; diff --git a/server/src/services/llmCommentaryService/runtimeHelpers.js b/server/src/services/llmCommentaryService/runtimeHelpers.js index 64ba34f4..e4d41207 100644 --- a/server/src/services/llmCommentaryService/runtimeHelpers.js +++ b/server/src/services/llmCommentaryService/runtimeHelpers.js @@ -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) { diff --git a/server/src/services/lockdownGuard/index.js b/server/src/services/lockdownGuard/index.js index 9d23a6d7..70a0e32f 100644 --- a/server/src/services/lockdownGuard/index.js +++ b/server/src/services/lockdownGuard/index.js @@ -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) { diff --git a/server/src/services/modeManager/index.js b/server/src/services/modeManager/index.js index 4af4b2d7..3c3ce755 100644 --- a/server/src/services/modeManager/index.js +++ b/server/src/services/modeManager/index.js @@ -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); diff --git a/server/src/services/overseerControlService/runtimeHelpers.js b/server/src/services/overseerControlService/runtimeHelpers.js index 75d1784a..499d7f6d 100644 --- a/server/src/services/overseerControlService/runtimeHelpers.js +++ b/server/src/services/overseerControlService/runtimeHelpers.js @@ -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, }; diff --git a/server/src/services/overseerControlService/tools/chatSay.js b/server/src/services/overseerControlService/tools/chatSay.js deleted file mode 100644 index b698535a..00000000 --- a/server/src/services/overseerControlService/tools/chatSay.js +++ /dev/null @@ -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 }; - }, -}; diff --git a/server/src/services/overseerControlService/tools/index.js b/server/src/services/overseerControlService/tools/index.js index 0f0a3a65..9222aed4 100644 --- a/server/src/services/overseerControlService/tools/index.js +++ b/server/src/services/overseerControlService/tools/index.js @@ -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, }; diff --git a/server/src/services/privateRoverAccessRequestService/core.js b/server/src/services/privateRoverAccessRequestService/core.js index b64b9e4e..2b02a12a 100644 --- a/server/src/services/privateRoverAccessRequestService/core.js +++ b/server/src/services/privateRoverAccessRequestService/core.js @@ -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; } diff --git a/server/src/services/privateRoverAccessRequestService/index.js b/server/src/services/privateRoverAccessRequestService/index.js index 6be61c13..9f483cfc 100644 --- a/server/src/services/privateRoverAccessRequestService/index.js +++ b/server/src/services/privateRoverAccessRequestService/index.js @@ -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, diff --git a/server/src/services/privateRoverAccessRequestService/state.js b/server/src/services/privateRoverAccessRequestService/state.js index 095c5df9..82087040 100644 --- a/server/src/services/privateRoverAccessRequestService/state.js +++ b/server/src/services/privateRoverAccessRequestService/state.js @@ -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, diff --git a/server/src/services/replayEngineV2/sidebarRenderer.js b/server/src/services/replayEngineV2/sidebarRenderer.js index 23f08aee..af6bbcbb 100644 --- a/server/src/services/replayEngineV2/sidebarRenderer.js +++ b/server/src/services/replayEngineV2/sidebarRenderer.js @@ -39,7 +39,6 @@ function createSidebarRenderer({ execFileAsync, ensureDir }) { switch (String(role)) { case 'admin': case 'lockdown': - case 'lockdown-admin': return '#FCD34D'; case 'spectator': return '#94A3B8'; diff --git a/server/src/services/replayEngineV2/sources.js b/server/src/services/replayEngineV2/sources.js index f139b668..fcde1e9d 100644 --- a/server/src/services/replayEngineV2/sources.js +++ b/server/src/services/replayEngineV2/sources.js @@ -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, diff --git a/server/src/services/roomCameraService/socketGateway.js b/server/src/services/roomCameraService/socketGateway.js index 834ae80f..aa01fd31 100644 --- a/server/src/services/roomCameraService/socketGateway.js +++ b/server/src/services/roomCameraService/socketGateway.js @@ -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)); }); diff --git a/server/src/services/roverManager/index.js b/server/src/services/roverManager/index.js index 0ad1e02a..3e9e5290 100644 --- a/server/src/services/roverManager/index.js +++ b/server/src/services/roverManager/index.js @@ -216,7 +216,6 @@ registerSocketHandlers({ tickPrivateAutoClose, removeSocket, enableSpectator, - getRosterForSocket, canRequestControl, canSwitchRover, getRoversForSocket, diff --git a/server/src/services/roverManager/rosterLifecycle.js b/server/src/services/roverManager/rosterLifecycle.js index a7bf80ce..9b833c1e 100644 --- a/server/src/services/roverManager/rosterLifecycle.js +++ b/server/src/services/roverManager/rosterLifecycle.js @@ -251,9 +251,6 @@ function createRosterLifecycle(deps) { function broadcastRoster() { syncSpectatorRooms(); - io.sockets.sockets.forEach((socket) => { - socket.emit('rovers', getRosterForSocket(socket)); - }); } function setNightVisionState(roverId, nightVisionOn) { diff --git a/server/src/services/roverManager/roverLifecycle.js b/server/src/services/roverManager/roverLifecycle.js index d4d969a8..fb435330 100644 --- a/server/src/services/roverManager/roverLifecycle.js +++ b/server/src/services/roverManager/roverLifecycle.js @@ -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, diff --git a/server/src/services/roverManager/socketHandlers.js b/server/src/services/roverManager/socketHandlers.js index f4c93c24..cbbc5561 100644 --- a/server/src/services/roverManager/socketHandlers.js +++ b/server/src/services/roverManager/socketHandlers.js @@ -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', () => { diff --git a/server/src/services/sessionService/constants.js b/server/src/services/sessionService/constants.js index 58ac014b..cb0f5549 100644 --- a/server/src/services/sessionService/constants.js +++ b/server/src/services/sessionService/constants.js @@ -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, }; diff --git a/server/src/services/sessionService/index.js b/server/src/services/sessionService/index.js index 08a38dec..ccbd7b62 100644 --- a/server/src/services/sessionService/index.js +++ b/server/src/services/sessionService/index.js @@ -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 = {}; diff --git a/server/src/services/verificationService/identity.js b/server/src/services/verificationService/identity.js index 80d4cd85..401b547a 100644 --- a/server/src/services/verificationService/identity.js +++ b/server/src/services/verificationService/identity.js @@ -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) { diff --git a/server/src/services/videoSessions/index.js b/server/src/services/videoSessions/index.js index a7cb9ff6..3c2cdaa0 100644 --- a/server/src/services/videoSessions/index.js +++ b/server/src/services/videoSessions/index.js @@ -68,6 +68,5 @@ module.exports = { createSession, getSession, revokeSession, - revokeBySocket, revokeWhere, }; diff --git a/server/src/services/videoSocketService/index.js b/server/src/services/videoSocketService/index.js index 429b3c21..4291e168 100644 --- a/server/src/services/videoSocketService/index.js +++ b/server/src/services/videoSocketService/index.js @@ -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) { diff --git a/webui/src/App.css b/webui/src/App.css deleted file mode 100644 index 2bf5bb83..00000000 --- a/webui/src/App.css +++ /dev/null @@ -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; -} diff --git a/webui/src/assets/react.svg b/webui/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/webui/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webui/src/components/AdminPanel/AdminPanelContent.jsx b/webui/src/components/AdminPanel/AdminPanelContent.jsx index 8c3bc79f..12f5b735 100644 --- a/webui/src/components/AdminPanel/AdminPanelContent.jsx +++ b/webui/src/components/AdminPanel/AdminPanelContent.jsx @@ -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'; diff --git a/webui/src/components/ChatMessageRow/index.jsx b/webui/src/components/ChatMessageRow/index.jsx index 7cdbb03a..419abc09 100644 --- a/webui/src/components/ChatMessageRow/index.jsx +++ b/webui/src/components/ChatMessageRow/index.jsx @@ -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' diff --git a/webui/src/components/ModeGateOverlay/index.jsx b/webui/src/components/ModeGateOverlay/index.jsx index 5e1054f3..f2965aa9 100644 --- a/webui/src/components/ModeGateOverlay/index.jsx +++ b/webui/src/components/ModeGateOverlay/index.jsx @@ -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') { diff --git a/webui/src/components/RawUserPilePanel/index.jsx b/webui/src/components/RawUserPilePanel/index.jsx index be93f7ad..7faea8a7 100644 --- a/webui/src/components/RawUserPilePanel/index.jsx +++ b/webui/src/components/RawUserPilePanel/index.jsx @@ -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'; diff --git a/webui/src/components/RoverQueuesPanel/index.jsx b/webui/src/components/RoverQueuesPanel/index.jsx index 1ebcf034..7221b13b 100644 --- a/webui/src/components/RoverQueuesPanel/index.jsx +++ b/webui/src/components/RoverQueuesPanel/index.jsx @@ -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( diff --git a/webui/src/components/UserListPanel/index.jsx b/webui/src/components/UserListPanel/index.jsx index 8e9eba4a..d6124820 100644 --- a/webui/src/components/UserListPanel/index.jsx +++ b/webui/src/components/UserListPanel/index.jsx @@ -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 (
({ diff --git a/webui/src/hooks/useVideoRequests.js b/webui/src/hooks/useVideoRequests.js index 04462a83..8e7a55dc 100644 --- a/webui/src/hooks/useVideoRequests.js +++ b/webui/src/hooks/useVideoRequests.js @@ -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 }));