From d34c107b1a55038cef94e0c822f14e06035994c0 Mon Sep 17 00:00:00 2001 From: legop3 Date: Thu, 26 Feb 2026 03:32:31 -0500 Subject: [PATCH] gwuh --- server/src/services/chatService.js | 64 ++++++++++++++++++++- server/src/services/llmCommentaryService.js | 33 ++++++++--- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/server/src/services/chatService.js b/server/src/services/chatService.js index d96f1f76..1d910e58 100644 --- a/server/src/services/chatService.js +++ b/server/src/services/chatService.js @@ -105,12 +105,68 @@ function buildMessage(socket, text, meta = {}) { discordUserId: meta.discordUserId || null, discordUserName: meta.discordUserName || null, discordUserAvatarUrl: meta.discordUserAvatarUrl || null, + roverCtx: meta.roverCtx || null, text, tts: meta.tts || null, system: Boolean(meta.system), }; } +function isChargingFromSensors(sensors = {}) { + const label = String(sensors?.chargingState?.label || '').toLowerCase(); + if (label === 'waiting' || label === 'full charging' || label === 'trickle charging') { + return true; + } + const code = sensors?.chargingState?.code; + return code === 2 || code === 3 || code === 4; +} + +function buildRoverCtxSnapshot(roverId) { + if (!roverId) return null; + const key = String(roverId); + const record = roverManager.rovers.get(key); + if (!record) return null; + const sensors = record?.lastSensor?.decoded || {}; + const batteryState = record?.batteryState || null; + const { getActiveDrivers } = require('./turnService'); + const activeDrivers = getActiveDrivers(); + const driverSocketId = activeDrivers[key] || record?.drivers?.values?.().next?.().value || null; + const charging = isChargingFromSensors(sensors); + const docked = Boolean(sensors?.chargingSources?.homeBase); + const wheelsOffGround = Boolean( + sensors?.bumpsAndWheelDrops?.wheelDropLeft && sensors?.bumpsAndWheelDrops?.wheelDropRight, + ); + const latestDistanceM = Math.round((Math.abs(Number(sensors?.distanceMm) || 0) / 1000) * 10) / 10; + const latestTurnDeg = Math.round(Math.abs(Number(sensors?.angleDeg) || 0)); + const latestBumps = + (sensors?.bumpsAndWheelDrops?.bumpLeft ? 0.5 : 0) + + (sensors?.bumpsAndWheelDrops?.bumpRight ? 0.5 : 0); + const moving = latestDistanceM > 0.05 || latestTurnDeg > 10; + let statusTag = 'idle'; + if (charging) { + statusTag = 'charging'; + } else if (docked) { + statusTag = 'docked'; + } else if (driverSocketId && moving) { + statusTag = 'driving'; + } else if (driverSocketId) { + statusTag = 'active-idle'; + } + return { + id: key, + status_tag: statusTag, + battery_low: Boolean(batteryState?.warnActive || batteryState?.urgentActive), + docked, + charging, + wheels_off_ground: wheelsOffGround, + activity_30s: { + distance_m: latestDistanceM, + turn_deg: latestTurnDeg, + bumps: latestBumps, + }, + }; +} + function buildTypingPayload(socket, meta = {}) { const roverId = meta.roverId || resolveRoverId(socket?.id); const socketId = socket?.id || null; @@ -282,7 +338,12 @@ function handleIncoming({ text, tts } = {}, socket, cb = () => {}) { // } const roverId = resolveRoverId(socket?.id); const ttsOptions = normalizeTtsOptions(tts); - const message = buildMessage(socket, clean, { fromDiscord: false, roverId, tts: ttsOptions }); + const message = buildMessage(socket, clean, { + fromDiscord: false, + roverId, + roverCtx: buildRoverCtxSnapshot(roverId), + tts: ttsOptions, + }); logger.info('Chat message', { socket: socket.id, roverId: message.roverId }); playTypingNote(roverId, TYPING_SEND_NOTE, socket?.id); broadcastMessage(message); @@ -349,6 +410,7 @@ function sendExternalMessage({ nickname, role, roverId, + roverCtx: buildRoverCtxSnapshot(roverId), fromDiscord: true, discordGuildId, discordGuildName, diff --git a/server/src/services/llmCommentaryService.js b/server/src/services/llmCommentaryService.js index c5291987..1904c054 100644 --- a/server/src/services/llmCommentaryService.js +++ b/server/src/services/llmCommentaryService.js @@ -477,12 +477,8 @@ function compactRoverForContext(rover) { }; } -function buildSnapshot() { - const now = new Date(); - const nowMs = now.getTime(); +function buildRoversNow(nowMs = Date.now()) { const activeDrivers = getActiveDrivers(); - const driverEntries = collectActiveDriverEntries(); - const roster = roverManager.getRoster().slice(0, MAX_ROVERS); const nextRoverStateById = new Map(); const rovers = roster.map((entry) => { @@ -537,6 +533,14 @@ function buildSnapshot() { nextRoverStateById.forEach((value, roverId) => { lastRoverStateById.set(roverId, value); }); + return { rovers, activeDrivers, roster }; +} + +function buildSnapshot() { + const now = new Date(); + const nowMs = now.getTime(); + const driverEntries = collectActiveDriverEntries(); + const { rovers } = buildRoversNow(nowMs); const roverById = new Map(rovers.map((rover) => [String(rover.id), rover])); const allRecentMessages = getRecentMessages(300, { includeSystem: true }) .filter((entry) => Number(entry?.ts) >= contextResetAt); @@ -571,7 +575,7 @@ function buildSnapshot() { nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown', text: entry.text || '', rover_id: roverId, - rover_ctx: compactRoverForContext(rover), + rover_ctx: entry?.roverCtx || entry?.rover_ctx || compactRoverForContext(rover), }; }); const hasRecentChat = eventStream.some((event) => event.type === 'chat'); @@ -604,6 +608,17 @@ function buildSnapshot() { }; } +function refreshFinalSnapshotForSend(snapshot) { + const { rovers } = buildRoversNow(Date.now()); + return { + ...(snapshot || {}), + current_snapshot: { + ...(snapshot?.current_snapshot || {}), + rovers, + }, + }; +} + function normalizeCommentary(rawText) { if (typeof rawText !== 'string') return null; const trimmed = rawText.trim(); @@ -832,14 +847,15 @@ async function runTick() { lastInfoSnapshot: snapshot, }); const systemPrompt = await readSystemPrompt(); - const modelMessages = buildModelMessages(systemPrompt, snapshot); + const snapshotForSend = refreshFinalSnapshotForSend(snapshot); + const modelMessages = buildModelMessages(systemPrompt, snapshotForSend); const modelInputAt = Date.now(); patchCurrentRun({ phase: 'input_ready', input: { ...(currentRun?.input || {}), systemPrompt, - infoSnapshot: snapshot, + infoSnapshot: snapshotForSend, modelMessages, modelInputAt, }, @@ -848,6 +864,7 @@ async function runTick() { lastModelMessages: modelMessages, lastModelInputAt: modelInputAt, lastModelInputTickId: tickId, + lastInfoSnapshot: snapshotForSend, lastModelRawOutput: null, lastModelOutputAt: null, lastModelOutputTickId: null,