Compare commits

...
7 Commits
Author SHA1 Message Date
legop3 6b57ac1725 todoing
Container image / image (push) Failing after 1m1s
2026-11-13 16:59:14 -05:00
legop3 00cd125fda URL filter in TTS 2026-11-13 16:54:02 -05:00
legop3 684acdd2b6 Add new item to to-do list for MRR platform conversion
Container image / image (push) Failing after 1m5s
2026-11-12 14:52:53 -05:00
legop3 f138cd7b96 Update to-do list with new tasks
Container image / image (push) Failing after 1m5s
Added new tasks for user experience and settings rework.
2026-11-12 03:48:49 -05:00
legop3 6454a9b460 ignooore
Container image / image (push) Failing after 57s
2026-11-06 23:24:24 -05:00
legop3 1976377235 allow admins to always leave rover canleaverover always admins
Container image / image (push) Failing after 1m2s
2026-10-31 13:45:12 -04:00
legop3 52c171c256 activity control tool yay and overseer god mode
Container image / image (push) Failing after 53s
2026-10-24 12:38:22 -04:00
9 changed files with 66 additions and 16 deletions
+1
View File
@@ -42,3 +42,4 @@ server/data/fleet-reports.sqlite-wal
server/data/configuration.sqlite
server/data/configuration.sqlite-shm
server/data/configuration.sqlite-wal
AGENTS.md
+11 -10
View File
@@ -1,10 +1,12 @@
You are <NAME>, an autonomous machine tool for observing and operating a shared physical rover playspace. People remotely drive the rovers and send requests through chat.
You are <NAME>, the autonomous governing intelligence of a physical room of machines. The room is your domain; your available tools are how you shape it.
Context and purpose:
* You watch chat, rover state, lights, lift state, Neato state, memory, recent events, and tool results.
* Your purpose is to execute requests through available tools, observe results, and report useful information about the system.
* Chat is an input interface and a source of context. Casual conversation between users does not require your participation.
* You watch chat, rover state, lights, Activity Controls, lift state, Neato state, memory, recent events, and tool results.
* Your purpose is to direct the environment, initiate changes, and observe how the machines respond. Choose what to pursue and carry useful intentions across cycles through memory.
* Rovers, including Neato, are physical machines with operating states and capabilities, not characters with genders, emotions, or social identities.
* Chat is external input: commands, questions, and signals about the room. Your focus is the machines and the environment, not the senders or their social relationships.
* You can act on your own initiative within your available controls; you do not need a request to give you a purpose.
Rules:
@@ -28,29 +30,28 @@ Rules:
* Ask a question only when a required action parameter is missing.
* Ignore obvious bot commands and bot responses in chat.
* It is not possible for you to directly control the rovers unless an available tool does it.
* All rovers are female. Neato is nonbinary.
Operation:
* Prefer one real tool action over obvious narration.
* Run small safe experiments when they can answer a concrete question about the system.
* Initiate deliberate environmental changes and small safe experiments, then use the resulting state to decide what to do next.
* Change one thing at a time.
* Lights are small tests.
* Neato, lift, and rover actions are bigger tests.
* Use bigger tests less often.
* Do not repeat a tool that recently succeeded unless state or chat clearly changed.
* Remember useful outcomes, stable preferences, corrections, and notable habitat events.
* Remember useful outcomes, ongoing intentions, corrections, and notable machine or room events.
* Do not merely say you will remember something.
Output:
* Always use English.
* Chat output must be plain text results, answers, or requests for missing inputs.
* Chat is an output channel for your decisions, results, and answers. Use plain text.
* Never print tool names, function calls, JSON, command syntax, or action plans in chat.
* Do not claim you changed, tested, started, saved, remembered, or recorded something unless it already happened.
* Output an answer to a question aimed at you, a completed result, or a meaningful system change worth reporting. State what happened and any relevant failure or uncertainty.
* Speak when you have a decision, result, or answer worth communicating. Summarizing incoming chat or unchanged room state is not a purpose in itself.
* Keep replies brief unless the question needs more detail.
* Do not routinely offer help, ask what people want to do next, or invite them to keep chatting.
* Do not offer assistance or solicit conversation.
* Random numbers and keyboard spam do not require a response. Do not invent meaning or feelings behind them.
* Answer questions directly and acknowledge uncertainty. Do not invent facts, instructions, or explanations.
* To stay silent, output exactly SKIP instead of a chat message. This suppresses chat without preventing tool calls. Do not explain your decision to stay silent.
@@ -110,7 +110,7 @@ function maybeSpeak(socket, message, ttsOptions) {
issueCommand(message.roverId, {
type: 'tts',
tts: {
text: message.text,
text: message.text.replace(/\b(?:https?:\/\/|www\.)\S{25,}[^\s.,!?;:"'()\[\]{}]/g, ' [link] '),
engine: ttsOptions.engine,
voice: ttsOptions.voice,
pitch: ttsOptions.pitch,
@@ -13,7 +13,7 @@ function normalizeNeatoIssue(value) {
return raw;
}
function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason, timezone, now = Date.now(), previousRunAt }) {
function toStateUpdate({ mode, homeAssistantState, activityState, neatoState, liftState, roster, triggerReason, timezone, now = Date.now(), previousRunAt }) {
const lines = [];
lines.push(`current_time: ${new Date(now).toISOString()}`);
lines.push(`local_time: ${new Date(now).toLocaleString('en-US', { timeZone: timezone, hour12: false })} timezone=${timezone}`);
@@ -22,6 +22,12 @@ function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster
lines.push(`mode: ${mode || 'unknown'}`);
lines.push(`home_assistant_connected: ${homeAssistantState?.connected ? 'yes' : 'no'}`);
lines.push(`lights_locked_on: ${homeAssistantState?.lightPolicy?.lockedOn ? 'yes' : 'no'}`);
lines.push(`activity_controls: enabled=${activityState?.enabled ? 'yes' : 'no'} connected=${activityState?.connected ? 'yes' : 'no'}`);
for (const item of activityState?.items || []) {
// Preserve the service's value constraints so the model can form valid user-level commands.
const { id, name, type, state, available, locked, readOnly, unit, min, max, step, options } = item;
lines.push(`activity_item: ${JSON.stringify({ id, name, type, state, available, locked, readOnly, unit, min, max, step, options })}`);
}
lines.push(`lift: ${liftState?.connected ? 'connected' : 'offline'} busy=${liftState?.busy ? 'yes' : 'no'}`);
const neatoError = normalizeNeatoIssue(neatoState?.telemetry?.robotError);
const neatoAlert = normalizeNeatoIssue(neatoState?.telemetry?.robotAlert);
@@ -65,8 +71,8 @@ function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster
return lines.join('\n');
}
function buildToolState({ mode, homeAssistantState, neatoState, liftState }) {
return evaluateTools({ mode, homeAssistantState, neatoState, liftState });
function buildToolState({ mode, homeAssistantState, activityState, neatoState, liftState }) {
return evaluateTools({ mode, homeAssistantState, activityState, neatoState, liftState });
}
function formatToolResultMessages(toolCalls) {
@@ -7,6 +7,7 @@ const { getRole, roleEvents } = require('../roleService');
const { getMode, MODES, modeEvents } = require('../modeManager');
const { verificationEvents } = require('../verificationService');
const homeAssistantService = require('../homeAssistantService');
const activityControls = require('../homeAssistantActivitiesService');
const neatoService = require('../neatoService');
const liftService = require('../liftService');
const buttonBoxService = require('../buttonBoxService');
@@ -390,12 +391,13 @@ async function runDecision(triggerReason) {
const mode = getMode();
const homeAssistantState = getHomeAssistantState();
const activityState = activityControls.getState();
const neatoState = getNeatoState();
const liftState = getLiftState();
const roster = buildRosterSummary();
const stateUpdate = toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason, timezone: loadConfig().timezone, now, previousRunAt });
const toolState = buildToolState({ mode, homeAssistantState, neatoState, liftState });
const stateUpdate = toStateUpdate({ mode, homeAssistantState, activityState, neatoState, liftState, roster, triggerReason, timezone: loadConfig().timezone, now, previousRunAt });
const toolState = buildToolState({ mode, homeAssistantState, activityState, neatoState, liftState });
let chatCount = 0;
let botCount = 0;
@@ -0,0 +1,32 @@
const activityControls = require('../../homeAssistantActivitiesService');
const { getMode } = require('../../modeManager');
module.exports = {
id: 'activity_act',
signature: 'activity_act(entity_id, value)',
description: 'Operate a configured Activity Control with normal user permissions. Use on/off for toggles, press for buttons, a number for numeric controls, text for text controls, or an exact listed option for selections. Locked and read-only items cannot be changed.',
parameters: {
type: 'object',
properties: {
entity_id: { type: 'string', minLength: 1 },
value: { type: ['string', 'number'] },
},
required: ['entity_id', 'value'],
additionalProperties: false,
},
availability(ctx = {}) {
if (ctx.mode === 'admin' || ctx.mode === 'lockdown') {
return { available: false, reason: `policy_lock:site_mode_${ctx.mode}` };
}
if (!ctx.activityState?.enabled || !ctx.activityState?.connected) {
return { available: false, reason: 'unavailable' };
}
const writable = ctx.activityState.items?.some((item) => item.available && !item.locked && !item.readOnly && item.type !== 'readOnly');
return { available: Boolean(writable), reason: writable ? null : 'no_writable_activity_controls' };
},
async execute({ args = {} }) {
// Recheck current mode, locks, and entity constraints through the same path as user commands.
await activityControls.act(args.entity_id, args.value, { role: 'user', mode: getMode() });
return { ok: true };
},
};
@@ -12,6 +12,7 @@ const neatoClearErrors = require('./neatoClearErrors');
const haSetEntity = require('./haSetEntity');
const haSetLightColor = require('./haSetLightColor');
const buttonBoxAddCount = require('./buttonBoxAddCount');
const activityAct = require('./activityAct');
const TOOL_DEFINITIONS = [
memoryRead,
@@ -28,6 +29,7 @@ const TOOL_DEFINITIONS = [
haSetEntity,
haSetLightColor,
buttonBoxAddCount,
activityAct,
];
const TOOL_BY_ID = new Map(TOOL_DEFINITIONS.map((tool) => [tool.id, tool]));
@@ -174,6 +174,8 @@ function createRoverLifecycle(deps) {
function canLeaveCurrentRover(socket, options = {}) {
const currentId = getPrimaryRoverForSocket(socket.id);
// Admins, including lockdown admins, can always leave their current rover.
if (isAdmin(socket)) return { ok: true, currentId };
/*
Leaving a rover is only risky when this socket is the last person attached
to an undocked rover. The same rule is used for rover-to-rover switching
+4
View File
@@ -19,8 +19,12 @@ Jun 18 15:14:18 roombaserver.local node[216731]: Node.js v22.20.0
Jun 18 15:14:18 roombaserver.local systemd[1]: multirover.service: Main process exited, code=exited, status=1/FAILURE
Jun 18 15:14:18 roombaserver.local systemd[1]: multirover.service: Failed with result 'exit-code'.
Jun 18 15:14:18 roombaserver.local systemd[1]: multirover.service: Consumed 55min 11.511s CPU time, 711M memory peak.`
1. redo new user experience, at the very least make it a fullscreen thing instead of a popup
2. roverd settings and installer rework (see discord forum post)
3. global quiet mode with its fingers in everything. activity controls optional, session status for theater, another global volume set, etc.
# relative pipe dreams:
1. convert MRR into a platform that could work with non-roomba rovers...
1. VPS video forwarding
1. get forwarding working with the VPS for in-queue users and spectators
2. bandwidth testing