mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
overseer voting stuff yay!
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||
<title>Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-DUTZ-8ek.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-91zM9j6A.css">
|
||||
<script type="module" crossorigin src="/assets/index-BD2vTGND.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DTyQMQGe.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -5,6 +5,7 @@ const logger = require('../../globals/logger').child('overseerControl');
|
||||
const { loadConfig } = require('../../helpers/configLoader');
|
||||
const { getRole, roleEvents } = require('../roleService');
|
||||
const { getMode, MODES, modeEvents } = require('../modeManager');
|
||||
const { verificationEvents } = require('../verificationService');
|
||||
const homeAssistantService = require('../homeAssistantService');
|
||||
const neatoService = require('../neatoService');
|
||||
const liftService = require('../liftService');
|
||||
@@ -89,6 +90,7 @@ let status = {
|
||||
lastLiveToolCalls: null,
|
||||
lastOutcome: null,
|
||||
lastReason: null,
|
||||
voteStatus: null,
|
||||
lastError: null,
|
||||
lastErrorDetails: null,
|
||||
lastFailedAt: null,
|
||||
@@ -98,6 +100,26 @@ let status = {
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
|
||||
function buildVoteStatus() {
|
||||
const sockets = Array.from(io.sockets.sockets.values());
|
||||
let yesCount = 0;
|
||||
let noCount = 0;
|
||||
sockets.forEach((socket) => {
|
||||
const pref = socket?.data?.overseerEnabled;
|
||||
if (typeof pref === 'boolean' ? pref : true) yesCount += 1;
|
||||
else noCount += 1;
|
||||
});
|
||||
const onlineCount = yesCount + noCount;
|
||||
const gatePassed = yesCount > noCount;
|
||||
return {
|
||||
yesCount,
|
||||
noCount,
|
||||
onlineCount,
|
||||
gatePassed,
|
||||
running: Boolean(enabled && gatePassed && getMode() !== MODES.LOCKDOWN),
|
||||
};
|
||||
}
|
||||
|
||||
function updateStatus(patch = {}) {
|
||||
status = { ...status, ...patch, updatedAt: Date.now() };
|
||||
const payload = buildAdminState(status, runtime.runHistory);
|
||||
@@ -460,8 +482,29 @@ function startScheduler(reason = null) {
|
||||
runtime.timer = setTimeout(tick, gateIntervalMs);
|
||||
}
|
||||
|
||||
function evaluateSchedulerGate(reason = 'gate reevaluated') {
|
||||
const voteStatus = buildVoteStatus();
|
||||
const runningAllowed = Boolean(enabled && voteStatus.gatePassed && getMode() !== MODES.LOCKDOWN);
|
||||
updateStatus({ voteStatus });
|
||||
if (!enabled) {
|
||||
stopScheduler('overseerControl.enabled is false');
|
||||
return;
|
||||
}
|
||||
if (getMode() === MODES.LOCKDOWN) {
|
||||
stopScheduler('paused during lockdown');
|
||||
return;
|
||||
}
|
||||
if (!voteStatus.gatePassed) {
|
||||
stopScheduler('paused by user vote');
|
||||
return;
|
||||
}
|
||||
startScheduler(reason);
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
emitStateToSocket(socket);
|
||||
evaluateSchedulerGate('online vote update');
|
||||
socket.on('disconnect', () => evaluateSchedulerGate('online vote update'));
|
||||
socket.on('overseer:control', ({ controls } = {}, cb = () => {}) => {
|
||||
if (!isAdminRole(getRole(socket))) return cb({ error: 'Not authorized' });
|
||||
const action = controls?.action || null;
|
||||
@@ -474,6 +517,7 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
roleEvents.on('change', ({ socket }) => emitStateToSocket(socket));
|
||||
verificationEvents.on('change', () => evaluateSchedulerGate('online vote update'));
|
||||
homeAssistantEvents.on('update', () => updateStatus({ phase: status.phase }));
|
||||
neatoEvents.on('update', () => updateStatus({ phase: status.phase }));
|
||||
liftEvents.on('update', () => updateStatus({ phase: status.phase }));
|
||||
@@ -485,7 +529,7 @@ modeEvents.on('change', (mode) => {
|
||||
logger.info('overseerControl paused due to lockdown mode');
|
||||
return;
|
||||
}
|
||||
startScheduler(observeOnly ? 'observe-only mode' : null);
|
||||
evaluateSchedulerGate(observeOnly ? 'observe-only mode' : null);
|
||||
});
|
||||
|
||||
if (!enabled) {
|
||||
@@ -496,9 +540,11 @@ if (!enabled) {
|
||||
stopScheduler('paused during lockdown');
|
||||
logger.info('overseerControl paused on startup due to lockdown mode');
|
||||
} else {
|
||||
startScheduler(observeOnly ? 'observe-only mode' : null);
|
||||
evaluateSchedulerGate(observeOnly ? 'observe-only mode' : null);
|
||||
logger.info('overseerControl enabled', { model, ollamaUrl, gateIntervalMs, heartbeatMs, observeOnly });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {};
|
||||
module.exports = {
|
||||
getVoteStatus: () => status.voteStatus || buildVoteStatus(),
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ const { getRoomCameras, roomCameraEvents } = require('../roomCameraService');
|
||||
const { getState: getHomeAssistantState, homeAssistantEvents } = require('../homeAssistantService');
|
||||
const { getState: getNeatoState, neatoEvents } = require('../neatoService');
|
||||
const { getState: getLiftState, liftEvents } = require('../liftService');
|
||||
const { getVoteStatus: getOverseerVoteStatus } = require('../overseerControlService');
|
||||
const { getNickname, nicknameEvents } = require('../nicknameService');
|
||||
const {
|
||||
getVerificationStateForSocket,
|
||||
@@ -66,6 +67,7 @@ function buildUserEntry(socket) {
|
||||
}
|
||||
|
||||
function buildSession(socket) {
|
||||
const overseerVote = getOverseerVoteStatus();
|
||||
const users = Array.from(io.sockets.sockets.values())
|
||||
.map((sock) => buildUserEntry(sock))
|
||||
.filter(Boolean)
|
||||
@@ -124,6 +126,10 @@ function buildSession(socket) {
|
||||
audioForward: getAudioForwardState(),
|
||||
audioLevels: getAudioLevels(),
|
||||
buttonBox: getButtonBoxState(),
|
||||
overseerVote: {
|
||||
...overseerVote,
|
||||
preference: typeof socket?.data?.overseerEnabled === 'boolean' ? socket.data.overseerEnabled : true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -111,11 +111,18 @@ function identifySocket(socket, payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'overseerEnabled')) {
|
||||
data.overseerEnabled = Boolean(payload.overseerEnabled);
|
||||
} else if (typeof data.overseerEnabled !== 'boolean') {
|
||||
data.overseerEnabled = true;
|
||||
}
|
||||
|
||||
const verification = reevaluateSocketVerification(socket);
|
||||
const deterrence = reevaluateSocketDeterrence(socket);
|
||||
emitChange('identify', { socketId: socket.id });
|
||||
return {
|
||||
cookieUserId: data.cookieUserId,
|
||||
overseerEnabled: Boolean(data.overseerEnabled),
|
||||
isVerified: verification.isVerified,
|
||||
isDeterred: deterrence.isDeterred,
|
||||
reason: verification.reason,
|
||||
|
||||
@@ -97,6 +97,7 @@ function createVerificationFlow(deps) {
|
||||
return {
|
||||
cookieUserId: normalizeCookieUserId(data.cookieUserId) || null,
|
||||
nickname: getNickname(socket) || null,
|
||||
overseerEnabled: typeof data.overseerEnabled === 'boolean' ? data.overseerEnabled : true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user