mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
sloptimization.. maybe not worth it
This commit is contained in:
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) {
|
||||
|
||||
Reference in New Issue
Block a user