mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
overseer v2 5 (real tools)
This commit is contained in:
@@ -142,6 +142,32 @@ function createButtonBoxCore(deps) {
|
||||
return store.clone(button);
|
||||
}
|
||||
|
||||
async function addCount(buttonId, amount = 1) {
|
||||
const state = store.getState();
|
||||
const button = state.buttons.find((entry) => entry.id === buttonId);
|
||||
if (!button) {
|
||||
throw new Error('Unknown button');
|
||||
}
|
||||
const inc = Math.max(1, Math.floor(Number(amount) || 0));
|
||||
button.count += inc;
|
||||
button.lastIncrementAt = Date.now();
|
||||
store.writeState();
|
||||
|
||||
io.emit('buttonBox:increment', {
|
||||
buttonId,
|
||||
count: button.count,
|
||||
ts: button.lastIncrementAt,
|
||||
});
|
||||
|
||||
while (button.count >= button.goal) {
|
||||
await runRewardForButton(button);
|
||||
store.writeState();
|
||||
}
|
||||
|
||||
publishUpdated();
|
||||
return store.clone(button);
|
||||
}
|
||||
|
||||
async function recoverEffects() {
|
||||
const state = store.getState();
|
||||
const effects = state.effects && typeof state.effects === 'object' ? { ...state.effects } : {};
|
||||
@@ -165,6 +191,7 @@ function createButtonBoxCore(deps) {
|
||||
|
||||
return {
|
||||
applyPress,
|
||||
addCount,
|
||||
recoverEffects,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,4 +79,5 @@ core.recoverEffects().catch((err) => {
|
||||
|
||||
module.exports = {
|
||||
getButtonBoxState: store.getStateClone,
|
||||
addButtonBoxCount: core.addCount,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,13 @@ function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster
|
||||
lines.push(`lights_locked_on: ${homeAssistantState?.lightPolicy?.lockedOn ? 'yes' : 'no'}`);
|
||||
lines.push(`lift: ${liftState?.connected ? 'connected' : 'offline'} busy=${liftState?.busy ? 'yes' : 'no'}`);
|
||||
lines.push(`neato: ${neatoState?.connected ? 'connected' : 'offline'} state=${neatoState?.telemetry?.robotState || 'unknown'}`);
|
||||
const entities = Array.isArray(homeAssistantState?.entities) ? homeAssistantState.entities : [];
|
||||
if (entities.length) {
|
||||
lines.push('home_assistant_entities:');
|
||||
entities.slice(0, 24).forEach((entity) => {
|
||||
lines.push(`- ${entity.id} (${entity.type || 'entity'}) state=${entity.state || 'unknown'} available=${entity.available ? 'yes' : 'no'}`);
|
||||
});
|
||||
}
|
||||
const roverLines = (Array.isArray(roster) ? roster : []).slice(0, 6).map((rover) => {
|
||||
const roverId = rover?.id || 'unknown';
|
||||
const driver = rover?.driverNickname || 'none';
|
||||
@@ -60,7 +67,7 @@ function buildModelMessages({ systemPrompt, stateUpdate, conversationMessages, a
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content:
|
||||
'Respond with JSON only: {"decision":"SKIP|CHAT|ACTION|ACTION+CHAT","chat":"optional text","actions":[{"tool":"tool_id","args":{}}]}. Use action tool ids only.',
|
||||
'Use tool calls when needed. If no tool is needed, either respond with one short chat line or SKIP.',
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const { getMode } = require('../modeManager');
|
||||
const homeAssistantService = require('../homeAssistantService');
|
||||
const neatoService = require('../neatoService');
|
||||
const liftService = require('../liftService');
|
||||
const buttonBoxService = require('../buttonBoxService');
|
||||
const { getState: getHomeAssistantState, homeAssistantEvents } = homeAssistantService;
|
||||
const { getState: getNeatoState, neatoEvents } = neatoService;
|
||||
const { getState: getLiftState, liftEvents } = liftService;
|
||||
@@ -23,9 +24,10 @@ const {
|
||||
MAX_BOT_CONTEXT,
|
||||
normalizeMs,
|
||||
} = require('./constants');
|
||||
const { isAdminRole, buildAdminState, parseOverseerOutput, buildFailureInfo } = require('./runtimeHelpers');
|
||||
const { isAdminRole, buildAdminState, buildFailureInfo } = require('./runtimeHelpers');
|
||||
const { toStateUpdate, buildToolState, buildConversation, buildModelMessages } = require('./contextBuilder');
|
||||
const { executeToolAction } = require('./tools');
|
||||
const { buildOllamaTools, executeToolAction } = require('./tools');
|
||||
const { loadMemory, saveMemory, createDefaultMemory } = require('./memoryStore');
|
||||
|
||||
const config = loadConfig();
|
||||
const overseerConfig = config.overseerControl || {};
|
||||
@@ -46,7 +48,7 @@ const runtime = {
|
||||
generationCount: 0,
|
||||
generationTotalMs: 0,
|
||||
runHistory: [],
|
||||
memoryStore: ['', '', ''],
|
||||
memoryStore: loadMemory(),
|
||||
};
|
||||
|
||||
let status = {
|
||||
@@ -112,11 +114,14 @@ async function readPrompt() {
|
||||
}
|
||||
|
||||
function buildRosterSummary() {
|
||||
return roverManager.getRoster().map((rover) => ({
|
||||
id: rover?.id || 'unknown',
|
||||
statusTag: rover?.statusTag || 'unknown',
|
||||
driverNickname: rover?.driverNickname || null,
|
||||
}));
|
||||
return roverManager
|
||||
.getRoster()
|
||||
.filter((rover) => roverManager.canReplayRoverId(rover?.id))
|
||||
.map((rover) => ({
|
||||
id: rover?.id || 'unknown',
|
||||
statusTag: rover?.statusTag || 'unknown',
|
||||
driverNickname: rover?.driverNickname || null,
|
||||
}));
|
||||
}
|
||||
|
||||
function computeTriggerReason() {
|
||||
@@ -131,6 +136,36 @@ function computeTriggerReason() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeToolCalls(payload = null) {
|
||||
const calls = Array.isArray(payload?.message?.tool_calls) ? payload.message.tool_calls : [];
|
||||
return calls
|
||||
.map((call) => {
|
||||
const fn = call?.function || {};
|
||||
const tool = String(fn.name || '').trim();
|
||||
if (!tool) return null;
|
||||
let args = fn.arguments;
|
||||
if (typeof args === 'string') {
|
||||
try {
|
||||
args = JSON.parse(args);
|
||||
} catch {
|
||||
args = {};
|
||||
}
|
||||
}
|
||||
if (!args || typeof args !== 'object') args = {};
|
||||
return { tool, args };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function inferDecision({ toolCalls, chatText }) {
|
||||
const hasTools = (toolCalls || []).length > 0;
|
||||
const hasChat = Boolean(String(chatText || '').trim());
|
||||
if (hasTools && hasChat) return 'ACTION+CHAT';
|
||||
if (hasTools) return 'ACTION';
|
||||
if (hasChat) return 'CHAT';
|
||||
return 'SKIP';
|
||||
}
|
||||
|
||||
async function runDecision(triggerReason) {
|
||||
const runId = runtime.tickCount;
|
||||
updateStatus({ phase: 'context_build', currentRunId: runId, lastTriggerReason: triggerReason, lastError: null, lastErrorDetails: null });
|
||||
@@ -144,7 +179,12 @@ async function runDecision(triggerReason) {
|
||||
const stateUpdate = toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason });
|
||||
const toolState = buildToolState({ mode, homeAssistantState, neatoState, liftState });
|
||||
|
||||
const recentConversation = getRecentMessages(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT, { includeSystem: true });
|
||||
const recentConversation = getRecentMessages(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT + 20, { includeSystem: true })
|
||||
.filter((entry) => {
|
||||
if (!entry?.roverId) return true;
|
||||
return roverManager.canReplayRoverId(entry.roverId);
|
||||
})
|
||||
.slice(-(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT));
|
||||
const conversationMessages = buildConversation({ recentMessages: recentConversation, name });
|
||||
|
||||
const systemPrompt = await readPrompt();
|
||||
@@ -155,6 +195,7 @@ async function runDecision(triggerReason) {
|
||||
availableTools: toolState.available,
|
||||
blockedTools: toolState.blocked,
|
||||
});
|
||||
const ollamaTools = buildOllamaTools(toolState.availableIds);
|
||||
|
||||
updateStatus({
|
||||
phase: 'awaiting_model',
|
||||
@@ -167,19 +208,24 @@ async function runDecision(triggerReason) {
|
||||
lastModelInputAt: Date.now(),
|
||||
});
|
||||
|
||||
let parsed = { decision: 'SKIP', chat: null, actions: [], raw: '' };
|
||||
const generationStart = Date.now();
|
||||
let payload = null;
|
||||
if (ollamaClient && model) {
|
||||
const payload = await ollamaClient.chat({
|
||||
payload = await ollamaClient.chat({
|
||||
model,
|
||||
stream: false,
|
||||
keep_alive: -1,
|
||||
options: { temperature: 0.4, top_p: 0.9 },
|
||||
messages: modelMessages,
|
||||
tools: ollamaTools,
|
||||
});
|
||||
parsed = parseOverseerOutput(payload?.message?.content || '');
|
||||
}
|
||||
|
||||
const rawOutput = String(payload?.message?.content || '');
|
||||
const toolCalls = normalizeToolCalls(payload);
|
||||
const chatDraft = rawOutput.trim() || null;
|
||||
const decision = inferDecision({ toolCalls, chatText: chatDraft });
|
||||
|
||||
const generationMs = Math.max(0, Date.now() - generationStart);
|
||||
runtime.generationCount += 1;
|
||||
runtime.generationTotalMs += generationMs;
|
||||
@@ -187,33 +233,37 @@ async function runDecision(triggerReason) {
|
||||
runtime.lastModelAt = Date.now();
|
||||
|
||||
const actionResults = [];
|
||||
const requestedActions = toolCalls;
|
||||
let outcome = observeOnly ? 'observed' : 'executed';
|
||||
let reason = observeOnly ? 'observe-only mode' : null;
|
||||
const reason = observeOnly ? 'observe-only mode' : null;
|
||||
|
||||
if (!observeOnly) {
|
||||
if ((parsed.decision === 'CHAT' || parsed.decision === 'ACTION+CHAT') && parsed.chat) {
|
||||
sendSystemMessage(parsed.chat, { nickname: name });
|
||||
if ((decision === 'CHAT' || decision === 'ACTION+CHAT') && chatDraft) {
|
||||
sendSystemMessage(chatDraft, { nickname: name });
|
||||
actionResults.push({ kind: 'chat', ok: true });
|
||||
}
|
||||
|
||||
if (parsed.decision === 'ACTION' || parsed.decision === 'ACTION+CHAT') {
|
||||
for (const action of parsed.actions) {
|
||||
const isAvailable = toolState.available.some((signature) => signature.startsWith(`${action.tool}(`));
|
||||
if (!isAvailable) {
|
||||
if (decision === 'ACTION' || decision === 'ACTION+CHAT') {
|
||||
for (const action of requestedActions) {
|
||||
if (!toolState.availableIds.includes(action.tool)) {
|
||||
actionResults.push({ kind: 'tool', tool: action.tool, ok: false, error: 'tool unavailable or blocked' });
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const result = await executeToolAction(action, {
|
||||
const result = await executeToolAction(action.tool, action.args, {
|
||||
sendSystemMessage,
|
||||
name,
|
||||
memoryStore: runtime.memoryStore,
|
||||
neatoService,
|
||||
liftService,
|
||||
homeAssistantService,
|
||||
buttonBoxService,
|
||||
actor: 'overseerControl',
|
||||
});
|
||||
if (action.tool === 'memory_write' && Array.isArray(result?.slots)) {
|
||||
runtime.memoryStore = saveMemory(result.slots);
|
||||
}
|
||||
if (action.tool === 'memory_read' && Array.isArray(result?.slots)) {
|
||||
runtime.memoryStore = result.slots;
|
||||
}
|
||||
actionResults.push({ kind: 'tool', tool: action.tool, ok: true, result });
|
||||
@@ -227,10 +277,10 @@ async function runDecision(triggerReason) {
|
||||
updateStatus({
|
||||
phase: 'decision_recorded',
|
||||
lastModelOutputAt: Date.now(),
|
||||
lastModelRawOutput: parsed.raw,
|
||||
lastDecision: parsed.decision,
|
||||
lastChatDraft: parsed.chat,
|
||||
lastRequestedActions: parsed.actions,
|
||||
lastModelRawOutput: rawOutput,
|
||||
lastDecision: decision,
|
||||
lastChatDraft: chatDraft,
|
||||
lastRequestedActions: requestedActions,
|
||||
lastActionResults: actionResults,
|
||||
lastOutcome: outcome,
|
||||
lastReason: reason,
|
||||
@@ -243,9 +293,9 @@ async function runDecision(triggerReason) {
|
||||
runId,
|
||||
at: Date.now(),
|
||||
triggerReason,
|
||||
decision: parsed.decision,
|
||||
chatDraft: parsed.chat,
|
||||
requestedActions: parsed.actions,
|
||||
decision,
|
||||
chatDraft,
|
||||
requestedActions,
|
||||
actionResults,
|
||||
outcome,
|
||||
observeOnly,
|
||||
@@ -292,7 +342,7 @@ function clearHistory() {
|
||||
runtime.runHistory = [];
|
||||
runtime.generationCount = 0;
|
||||
runtime.generationTotalMs = 0;
|
||||
runtime.memoryStore = ['', '', ''];
|
||||
runtime.memoryStore = saveMemory(createDefaultMemory());
|
||||
updateStatus({ lastReason: 'admin requested clear history', lastOutcome: 'cleared' });
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
const fs = require('fs');
|
||||
const { resolveDataPath } = require('../../helpers/dataPaths');
|
||||
|
||||
const STORE_PATH = resolveDataPath('overseer-control-memory.json');
|
||||
|
||||
function createDefaultMemory() {
|
||||
return ['', '', ''];
|
||||
}
|
||||
|
||||
function sanitizeSlots(slots) {
|
||||
const next = Array.isArray(slots) ? slots.slice(0, 3) : [];
|
||||
while (next.length < 3) next.push('');
|
||||
return next.map((entry) => String(entry || '').slice(0, 180));
|
||||
}
|
||||
|
||||
function loadMemory() {
|
||||
try {
|
||||
const raw = JSON.parse(fs.readFileSync(STORE_PATH, 'utf8'));
|
||||
return sanitizeSlots(raw?.slots);
|
||||
} catch {
|
||||
return createDefaultMemory();
|
||||
}
|
||||
}
|
||||
|
||||
function saveMemory(slots) {
|
||||
const next = sanitizeSlots(slots);
|
||||
const payload = { updatedAt: Date.now(), slots: next };
|
||||
fs.writeFileSync(STORE_PATH, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
||||
return next;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadMemory,
|
||||
saveMemory,
|
||||
createDefaultMemory,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
id: 'button_box_add_count',
|
||||
signature: 'button_box_add_count(button_id, amount)',
|
||||
description: 'Add progress count to a button box button.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
button_id: { type: 'integer', minimum: 1, maximum: 4 },
|
||||
amount: { type: 'integer', minimum: 1, maximum: 25 },
|
||||
},
|
||||
required: ['button_id', 'amount'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
return { available: false, reason: `policy_lock:site_mode_${mode}` };
|
||||
}
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
async execute({ args = {}, buttonBoxService }) {
|
||||
const buttonId = Math.max(1, Math.min(4, Number(args?.button_id) || 0));
|
||||
const amount = Math.max(1, Math.min(25, Number(args?.amount) || 0));
|
||||
if (!buttonId) throw new Error('button_box_add_count requires args.button_id 1..4');
|
||||
if (!amount) throw new Error('button_box_add_count requires args.amount 1..25');
|
||||
const resp = await buttonBoxService.addButtonBoxCount(buttonId, amount);
|
||||
return { ok: true, button: resp };
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,15 @@
|
||||
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, maxLength: 220 },
|
||||
},
|
||||
required: ['text'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability() {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
module.exports = {
|
||||
id: 'ha_set_entity',
|
||||
signature: 'ha_set_entity(entity_id, state)',
|
||||
description: 'Set Home Assistant controllable entity on/off.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
entity_id: { type: 'string', minLength: 1 },
|
||||
state: { type: 'string', enum: ['on', 'off'] },
|
||||
},
|
||||
required: ['entity_id', 'state'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
@@ -15,6 +25,10 @@ module.exports = {
|
||||
async execute({ args = {}, homeAssistantService }) {
|
||||
const entityId = String(args?.entity_id || args?.entityId || '').trim();
|
||||
if (!entityId) throw new Error('ha_set_entity requires args.entity_id');
|
||||
const allowed = new Set(
|
||||
(homeAssistantService.getState()?.entities || []).map((entry) => String(entry?.id || '')).filter(Boolean),
|
||||
);
|
||||
if (!allowed.has(entityId)) throw new Error('ha_set_entity entity_id not configured');
|
||||
const state = String(args?.state || '').toLowerCase();
|
||||
if (state !== 'on' && state !== 'off') throw new Error('ha_set_entity requires args.state of on/off');
|
||||
await homeAssistantService.setEntityState(entityId, state, { source: 'overseerControl' });
|
||||
|
||||
@@ -8,6 +8,7 @@ const neatoSendHome = require('./neatoSendHome');
|
||||
const neatoLocate = require('./neatoLocate');
|
||||
const neatoClearErrors = require('./neatoClearErrors');
|
||||
const haSetEntity = require('./haSetEntity');
|
||||
const buttonBoxAddCount = require('./buttonBoxAddCount');
|
||||
|
||||
const TOOL_DEFINITIONS = [
|
||||
chatSay,
|
||||
@@ -20,37 +21,69 @@ const TOOL_DEFINITIONS = [
|
||||
neatoLocate,
|
||||
neatoClearErrors,
|
||||
haSetEntity,
|
||||
buttonBoxAddCount,
|
||||
];
|
||||
const TOOL_BY_ID = new Map(TOOL_DEFINITIONS.map((tool) => [tool.id, tool]));
|
||||
|
||||
function evaluateTools(context = {}) {
|
||||
const available = [];
|
||||
const availableIds = [];
|
||||
const blocked = [];
|
||||
TOOL_DEFINITIONS.forEach((tool) => {
|
||||
const result = typeof tool.availability === 'function' ? tool.availability(context) : { available: false, reason: 'unavailable' };
|
||||
if (result?.available) {
|
||||
available.push(tool.signature);
|
||||
availableIds.push(tool.id);
|
||||
return;
|
||||
}
|
||||
blocked.push({ tool: tool.signature, reason: result?.reason || 'unavailable' });
|
||||
blocked.push({ id: tool.id, tool: tool.signature, reason: result?.reason || 'unavailable' });
|
||||
});
|
||||
return { available, blocked };
|
||||
return { available, availableIds, blocked };
|
||||
}
|
||||
|
||||
async function executeToolAction(action = {}, context = {}) {
|
||||
const toolId = String(action?.tool || '').trim();
|
||||
const tool = TOOL_BY_ID.get(toolId);
|
||||
function buildOllamaTools(availableIds = []) {
|
||||
const allowed = new Set((availableIds || []).map((id) => String(id)));
|
||||
return TOOL_DEFINITIONS.filter((tool) => allowed.has(tool.id)).map((tool) => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.id,
|
||||
description: String(tool.description || tool.signature || tool.id),
|
||||
parameters: tool.parameters || {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
async function executeToolAction(toolId, args = {}, context = {}) {
|
||||
const key = String(toolId || '').trim();
|
||||
if (!key) throw new Error('Missing tool id');
|
||||
const tool = TOOL_BY_ID.get(key);
|
||||
if (!tool) {
|
||||
throw new Error(`Unknown tool: ${toolId}`);
|
||||
throw new Error(`Unknown tool: ${key}`);
|
||||
}
|
||||
if (typeof tool.execute !== 'function') {
|
||||
throw new Error(`Tool ${toolId} is not executable`);
|
||||
throw new Error(`Tool ${key} is not executable`);
|
||||
}
|
||||
return tool.execute({ ...context, args: action?.args || {} });
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'lift_down',
|
||||
signature: 'lift_down()',
|
||||
description: 'Move the lift downward.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'lift_up',
|
||||
signature: 'lift_up()',
|
||||
description: 'Move the lift upward.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
module.exports = {
|
||||
id: 'memory_read',
|
||||
signature: 'memory_read()',
|
||||
description: 'Read the Overseer persistent 3-slot memory.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability() {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
module.exports = {
|
||||
id: 'memory_write',
|
||||
signature: 'memory_write(slot, text)',
|
||||
description: 'Write one of 3 Overseer memory slots.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
slot: { type: 'integer', minimum: 1, maximum: 3 },
|
||||
text: { type: 'string', minLength: 1, maxLength: 180 },
|
||||
},
|
||||
required: ['slot', 'text'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
availability() {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'neato_clear_errors',
|
||||
signature: 'neato_clear_errors()',
|
||||
description: 'Clear Neato errors.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'neato_locate',
|
||||
signature: 'neato_locate()',
|
||||
description: 'Play Neato locate/chime action.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'neato_send_home',
|
||||
signature: 'neato_send_home()',
|
||||
description: 'Send Neato back to base.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module.exports = {
|
||||
id: 'neato_start',
|
||||
signature: 'neato_start()',
|
||||
description: 'Start Neato cleaning cycle.',
|
||||
parameters: { type: 'object', properties: {}, additionalProperties: false },
|
||||
availability(ctx = {}) {
|
||||
const mode = String(ctx.mode || '');
|
||||
if (mode === 'admin' || mode === 'lockdown') {
|
||||
|
||||
Reference in New Issue
Block a user