From f130bba8a8af39e33b76b8111a61f9e74e55502a Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:35:24 -0400 Subject: [PATCH] Add overseer tool result context --- .../overseerControlService/contextBuilder.js | 117 +----------------- 1 file changed, 1 insertion(+), 116 deletions(-) diff --git a/server/src/services/overseerControlService/contextBuilder.js b/server/src/services/overseerControlService/contextBuilder.js index 93ad4814..6ce35c51 100644 --- a/server/src/services/overseerControlService/contextBuilder.js +++ b/server/src/services/overseerControlService/contextBuilder.js @@ -9,119 +9,4 @@ function normalizeNeatoIssue(value) { function toStateUpdate({ mode, homeAssistantState, neatoState, liftState, roster, triggerReason }) { const lines = []; - lines.push(`trigger: ${triggerReason || 'heartbeat'}`); - 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(`lift: ${liftState?.connected ? 'connected' : 'offline'} busy=${liftState?.busy ? 'yes' : 'no'}`); - const neatoError = normalizeNeatoIssue(neatoState?.telemetry?.robotError); - const neatoAlert = normalizeNeatoIssue(neatoState?.telemetry?.robotAlert); - lines.push( - `neato: ${neatoState?.connected ? 'connected' : 'offline'} state=${neatoState?.telemetry?.robotState || 'unknown'} error=${neatoError} alert=${neatoAlert}`, - ); - const entities = Array.isArray(homeAssistantState?.entities) ? homeAssistantState.entities : []; - if (entities.length) { - lines.push('home_assistant_room_lights:'); - entities.slice(0, 24).forEach((entity) => { - const haType = String(entity.type || 'entity'); - const details = [ - 'kind=room_light', - `ha_domain=${haType}`, - `state=${entity.state || 'unknown'}`, - `available=${entity.available ? 'yes' : 'no'}`, - ]; - - // All configured Home Assistant controls in this list represent room - // lighting from the overseer's point of view, including outlet-backed - // lamps that Home Assistant exposes as switches. The original HA domain is - // still shown because only true light-domain entities can accept color - // payloads; switch-domain lamps remain valid on/off room lights. - details.push(`supports_color=${entity.supportsColor ? 'yes' : 'no'}`); - if (entity.colorHex) details.push(`color=${entity.colorHex}`); - - lines.push(`- ${entity.id} ${details.join(' ')}`); - }); - } - const roverLines = (Array.isArray(roster) ? roster : []).slice(0, 6).map((rover) => { - const roverId = rover?.id || 'unknown'; - const drivers = Array.isArray(rover?.drivers) ? rover.drivers.filter(Boolean) : []; - const driver = drivers.length ? drivers.join(',') : 'none'; - const status = rover?.statusTag || 'unknown'; - return `- ${roverId} status=${status} drivers=${driver}`; - }); - if (roverLines.length) { - lines.push('rovers:'); - lines.push(...roverLines); - } - return lines.join('\n'); -} - -function buildToolState({ mode, homeAssistantState, neatoState, liftState }) { - return evaluateTools({ mode, homeAssistantState, neatoState, liftState }); -} - -function buildConversation({ recentMessages, name }) { - const messages = []; - (recentMessages || []).forEach((entry) => { - const text = String(entry?.text || '').trim(); - if (!text) return; - const isAssistant = Boolean(entry?.bot); - const nickname = String(entry?.nickname || (isAssistant ? name || 'Overseer' : 'user')).trim(); - if (isAssistant) { - messages.push({ role: 'assistant', content: text }); - return; - } - messages.push({ role: 'user', content: `${nickname}: ${text}` }); - }); - return messages; -} - -function buildModelMessages({ - systemPrompt, - stateUpdate, - memorySummary, - recentEvents, - conversationMessages, - availableTools, - blockedTools, -}) { - const messages = []; - messages.push({ role: 'system', content: systemPrompt }); - messages.push({ role: 'system', content: `ROOM_SNAPSHOT\n${stateUpdate}` }); - if (memorySummary) { - messages.push({ role: 'system', content: `MEMORY_SUMMARY\n${memorySummary}` }); - } - if (recentEvents) { - messages.push({ role: 'system', content: `RECENT_EVENTS\n${recentEvents}` }); - } - messages.push({ - role: 'system', - content: `TOOL_CONSTRAINTS\n${blockedTools.map((entry) => `- blocked: ${entry.tool} reason=${entry.reason}`).join('\n') || '- none'}`, - }); - (conversationMessages || []).forEach((message) => { - if (!message || !message.role || !message.content) return; - const content = String(message.content || '') - // The overseer can sometimes write plain-text lines that look like tool - // calls instead of returning real structured tool_calls to Ollama. If - // those fake calls are replayed in later context, the model sees its own - // malformed pattern as chat history and tends to repeat it. This removes - // any whole line shaped like "(tool_name {"json": "payload"})" regardless - // of the invented tool name, while leaving normal prose and legitimate - // structured tool metadata untouched. - .replace(/^\s*\([a-zA-Z_][a-zA-Z0-9_]*\s+\{.*\}\)\s*$/gm, '') - .replace(/sourceMapping/g, '') - .replace(/SourceMapping/g, '') - .replace(/sourcemapping/g, '') - .trim(); - if (!content) return; - messages.push({ ...message, content }); - }); - return messages; -} - -module.exports = { - toStateUpdate, - buildToolState, - buildConversation, - buildModelMessages, -}; + lines.push(`trigger: ${triggerReason || ' \ No newline at end of file