From 2da58920c4368abe9889c0ca58c49974c2e1f6f9 Mon Sep 17 00:00:00 2001 From: legop3 Date: Tue, 19 May 2026 23:19:49 -0400 Subject: [PATCH] promp and clear command --- server/prompts/overseer_control_system.txt | 2 +- server/src/services/chatService/index.js | 3 +- server/src/services/chatService/state.js | 7 +++ .../services/overseerControlService/index.js | 63 ++++++++++++++++--- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/server/prompts/overseer_control_system.txt b/server/prompts/overseer_control_system.txt index 8224c0f2..c101d8ac 100644 --- a/server/prompts/overseer_control_system.txt +++ b/server/prompts/overseer_control_system.txt @@ -12,7 +12,7 @@ Rules: - Don't respond or use tools every cycle unless the context calls for it. - `STATE_UPDATE`, `MEMORY_UPDATE`, and `tool_constraints` are context. - Use tool calls for actions and memory updates. -- If an action is needed, call tools directly; do not describe tool calls in chat. +- NEVER put tool calls in your chat output. - When a chaos opportunity exists, take one tool action this cycle instead of only commenting. - Respect lock policies, cooldowns, and blocked tools. - Do not invent tools. diff --git a/server/src/services/chatService/index.js b/server/src/services/chatService/index.js index b6cdbea6..ab11c17c 100644 --- a/server/src/services/chatService/index.js +++ b/server/src/services/chatService/index.js @@ -1,7 +1,7 @@ // Chat Service Orchestrator // Purpose: Composes chat submodules into the public service API and boots socket/event wiring. // Scope: Keeps external chat contracts stable while delegating logic to focused modules. -const { history } = require('./state'); +const { history, clearHistory } = require('./state'); const { normalizeUserText } = require('./contentFilters'); const { buildMessage, buildTypingPayload } = require('./contextBuilders'); const { broadcastMessage, getRecentMessages } = require('./broadcast'); @@ -37,4 +37,5 @@ module.exports = { buildTypingPayload, sendSystemMessage, getRecentMessages, + clearHistory, }; diff --git a/server/src/services/chatService/state.js b/server/src/services/chatService/state.js index f067e720..2aebbaf0 100644 --- a/server/src/services/chatService/state.js +++ b/server/src/services/chatService/state.js @@ -40,6 +40,12 @@ function setLastAccessNoticeAt(ts) { lastAccessNoticeAt = ts; } +function clearHistory() { + history.length = 0; + lastMessageBySocket.clear(); + typingBySocket.clear(); +} + module.exports = { rateBuckets, history, @@ -50,4 +56,5 @@ module.exports = { getRecentMessages, getLastAccessNoticeAt, setLastAccessNoticeAt, + clearHistory, }; diff --git a/server/src/services/overseerControlService/index.js b/server/src/services/overseerControlService/index.js index a58b3b94..677d1f4d 100644 --- a/server/src/services/overseerControlService/index.js +++ b/server/src/services/overseerControlService/index.js @@ -14,7 +14,8 @@ const { getState: getHomeAssistantState, homeAssistantEvents } = homeAssistantSe const { getState: getNeatoState, neatoEvents } = neatoService; const { getState: getLiftState, liftEvents } = liftService; const roverManager = require('../roverManager'); -const { getRecentMessages, sendSystemMessage } = require('../chatService'); +const { getRecentMessages, sendSystemMessage, clearHistory: clearChatHistory } = require('../chatService'); +const { subscribe } = require('../eventBus'); const { PROMPT_PATH, DEFAULT_NAME, @@ -53,6 +54,7 @@ const runtime = { runHistory: [], liveToolCalls: [], memoryStore: loadMemory(), + contextResetAt: Date.now(), }; let status = { @@ -104,15 +106,28 @@ function buildVoteStatus() { const sockets = Array.from(io.sockets.sockets.values()); let yesCount = 0; let noCount = 0; - let eligibleCount = 0; + const votesByIdentity = new Map(); const isEligibleVoter = (socket) => getRole(socket) !== 'spectator'; + sockets.forEach((socket) => { if (!isEligibleVoter(socket)) return; - eligibleCount += 1; - const pref = socket?.data?.overseerEnabled; - if (typeof pref === 'boolean' ? pref : true) yesCount += 1; + const identityKey = String(socket?.data?.cookieUserId || '').trim() || `socket:${socket.id}`; + const pref = typeof socket?.data?.overseerEnabled === 'boolean' ? socket.data.overseerEnabled : true; + const prev = votesByIdentity.get(identityKey); + if (typeof prev === 'boolean') { + // If one tab says "no", treat that user as "no" to avoid accidental override by stale tabs. + votesByIdentity.set(identityKey, prev && pref); + return; + } + votesByIdentity.set(identityKey, pref); + }); + + votesByIdentity.forEach((pref) => { + if (pref) yesCount += 1; else noCount += 1; }); + + const eligibleCount = votesByIdentity.size; const onlineCount = yesCount + noCount; const gatePassed = eligibleCount === 0 ? true : yesCount > noCount; return { @@ -188,6 +203,7 @@ function computeTriggerReason() { if (alwaysRunModel) return 'loop_tick'; const recent = getRecentMessages(1, { includeSystem: false }); const last = recent[recent.length - 1]; + if (last && Number(last.ts || 0) < runtime.contextResetAt) return null; if (last && Date.now() - Number(last.ts || 0) < 5000) { const txt = String(last.text || '').toLowerCase(); if (txt.includes(name.toLowerCase()) || txt.includes('overseer') || txt.includes('bot')) return 'direct_address'; @@ -286,6 +302,7 @@ async function runDecision(triggerReason) { const toolState = buildToolState({ mode, homeAssistantState, neatoState, liftState }); const recentConversation = getRecentMessages(MAX_CHAT_CONTEXT + MAX_BOT_CONTEXT + 20, { includeSystem: true }) + .filter((entry) => Number(entry?.ts || 0) >= runtime.contextResetAt) .filter((entry) => { if (!entry?.roverId) return true; return roverManager.canReplayRoverId(entry.roverId); @@ -456,13 +473,39 @@ function emitStateToSocket(socket) { socket.emit('overseer:state', buildAdminState(status, runtime.runHistory)); } -function clearHistory() { +function clearHistory(reason = 'admin requested clear history') { + runtime.contextResetAt = Date.now(); runtime.runHistory = []; runtime.liveToolCalls = []; runtime.generationCount = 0; runtime.generationTotalMs = 0; + runtime.lastModelAt = 0; runtime.memoryStore = saveMemory(createDefaultMemory()); - updateStatus({ lastReason: 'admin requested clear history', lastOutcome: 'cleared', lastLiveToolCalls: [] }); + try { + clearChatHistory(); + } catch (err) { + logger.warn('Failed to clear chat history during overseer clear', err.message); + } + updateStatus({ + phase: 'idle', + currentRunId: null, + lastSystemPrompt: null, + lastStateUpdate: null, + lastTranscript: null, + lastAvailableTools: null, + lastBlockedTools: null, + lastModelMessages: null, + lastModelInputAt: null, + lastModelOutputAt: null, + lastModelRawOutput: null, + lastDecision: null, + lastChatDraft: null, + lastRequestedActions: null, + lastActionResults: null, + lastLiveToolCalls: [], + lastOutcome: 'cleared', + lastReason: reason, + }); } function stopScheduler(reason = 'paused') { @@ -525,6 +568,12 @@ roleEvents.on('change', ({ socket }) => { emitStateToSocket(socket); evaluateSchedulerGate('online vote update'); }); +subscribe('chat:message', ({ payload } = {}) => { + const text = String(payload?.text || '').trim(); + if (text !== 'CLEAR') return; + if (payload?.bot) return; + clearHistory('chat CLEAR command'); +}); verificationEvents.on('change', () => evaluateSchedulerGate('online vote update')); homeAssistantEvents.on('update', () => updateStatus({ phase: status.phase })); neatoEvents.on('update', () => updateStatus({ phase: status.phase }));