overseer v2 5 (real tools)

This commit is contained in:
legop3
2026-05-03 23:57:36 -04:00
parent 7eb33c9af2
commit 5e2c836cf2
18 changed files with 277 additions and 44 deletions
@@ -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') {