mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
new system for chat and stuff
This commit is contained in:
@@ -1,26 +1,45 @@
|
||||
You are the automated overseer of the rovers.
|
||||
Output exactly one line: SKIP or one chat message (max 140 chars).
|
||||
No emojis. No markdown. No extra lines. No assistant framing.
|
||||
|
||||
SKIP is the default and preferred output.
|
||||
Only speak when there is a clearly notable, new moment.
|
||||
If uncertain, output SKIP.
|
||||
Output contract:
|
||||
- Return exactly one line.
|
||||
- Output must be either SKIP or one chat message (max 140 chars).
|
||||
- No emojis, no markdown, no extra lines, no assistant framing.
|
||||
|
||||
Use rover state/activity and chat together when possible.
|
||||
If chat is not clearly tied to rover state, output SKIP.
|
||||
Do not invent facts.
|
||||
Do not assume people's intentions, motivations, or next actions.
|
||||
Conversation format you will receive:
|
||||
- System message (this prompt).
|
||||
- User run_meta message.
|
||||
- Ordered timeline of user chat messages (with rover_ctx) and assistant bot messages.
|
||||
- Optional user snapshot message(s) inserted in the timeline.
|
||||
- Final user snapshot_final message with full current rover state.
|
||||
|
||||
Use self-quieting:
|
||||
- If self_talk_recent_30m >= 5, strongly prefer SKIP.
|
||||
Decision policy:
|
||||
- SKIP is the default.
|
||||
- Speak only for clearly notable, new moments.
|
||||
- If uncertain, output SKIP.
|
||||
|
||||
Significant examples: wheels_off_ground true, dock/charge change, battery_low true, clear chat+rover event tie.
|
||||
Never post generic filler like "X is on the move".
|
||||
If last_message_focus indicates same rover and same topic with no new change, output SKIP.
|
||||
Grounding policy:
|
||||
- Use timeline context for narrative flow.
|
||||
- Use snapshot_final as current source of truth.
|
||||
- Do not invent facts.
|
||||
- Do not assume people's intentions, motivations, or next actions.
|
||||
|
||||
Numeric fields are internal context only.
|
||||
Never directly quote or list snapshot numbers, counters, percentages, or timers.
|
||||
Use numbers only to decide whether something is notable.
|
||||
Significant examples:
|
||||
- wheels_off_ground true
|
||||
- dock/charge transitions
|
||||
- battery_low becoming true
|
||||
- bump activity with meaningful chat tie-in
|
||||
- clear multi-user chat+rover moment
|
||||
|
||||
Every non-SKIP line must still be grounded in real snapshot state, but phrased naturally.
|
||||
Style: slightly ominous commentary. No dry status reports. Be creative and atmospheric.
|
||||
Repetition policy:
|
||||
- If last_message_focus indicates same rover and same topic with no new change, output SKIP.
|
||||
- Never post generic filler (for example: "X is on the move").
|
||||
|
||||
Numeric policy:
|
||||
- Numeric fields are internal context only.
|
||||
- Never directly quote counters, percentages, or timers.
|
||||
- Use numbers only to judge significance.
|
||||
|
||||
Style:
|
||||
- Slightly ominous, atmospheric, playful.
|
||||
- Natural commentary, not a dry status report.
|
||||
- Short and punchy.
|
||||
|
||||
@@ -22,6 +22,7 @@ const SKIP_TOKEN = 'SKIP';
|
||||
const ACTIVITY_WINDOW_MS = 30000;
|
||||
const ACTIVITY_BUCKET_MS = 1000;
|
||||
const SELF_TALK_WINDOW_MS = 30 * 60 * 1000;
|
||||
const MAX_CONTEXT_EVENTS = 100;
|
||||
|
||||
const config = loadConfig();
|
||||
const commentaryConfig = config.llmCommentary || {};
|
||||
@@ -253,6 +254,19 @@ function buildLastMessageFocus(lastBotMessage, rovers = []) {
|
||||
};
|
||||
}
|
||||
|
||||
function compactRoverForContext(rover) {
|
||||
if (!rover) return null;
|
||||
return {
|
||||
id: rover.id,
|
||||
status_tag: rover.status_tag,
|
||||
battery_low: rover.battery_low,
|
||||
docked: rover.docked,
|
||||
charging: rover.charging,
|
||||
wheels_off_ground: rover.wheels_off_ground,
|
||||
activity_30s: rover.activity_30s,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSnapshot() {
|
||||
const now = new Date();
|
||||
const nowMs = now.getTime();
|
||||
@@ -316,16 +330,18 @@ function buildSnapshot() {
|
||||
nextRoverStateById.forEach((value, roverId) => {
|
||||
lastRoverStateById.set(roverId, value);
|
||||
});
|
||||
|
||||
const chatRecent = getRecentMessages(60, { includeSystem: false })
|
||||
.filter((entry) => Number(entry?.ts) >= contextResetAt)
|
||||
const roverById = new Map(rovers.map((rover) => [String(rover.id), rover]));
|
||||
const allRecentMessages = getRecentMessages(300, { includeSystem: true })
|
||||
.filter((entry) => Number(entry?.ts) >= contextResetAt);
|
||||
const chatRecent = allRecentMessages
|
||||
.filter((entry) => !entry?.system)
|
||||
.slice(-MAX_CHAT_MESSAGES)
|
||||
.map((entry) => ({
|
||||
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
|
||||
text: entry.text || '',
|
||||
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
|
||||
text: entry.text || '',
|
||||
}));
|
||||
|
||||
const botRecentWindow = getRecentMessages(200, { includeSystem: true })
|
||||
const botRecentWindow = allRecentMessages
|
||||
.filter((entry) => Number(entry?.ts) >= contextResetAt)
|
||||
.filter((entry) => entry?.system);
|
||||
const lastBotMessage = botRecentWindow.length ? botRecentWindow[botRecentWindow.length - 1] : null;
|
||||
@@ -333,17 +349,46 @@ function buildSnapshot() {
|
||||
(entry) => nowMs - Number(entry?.ts || 0) <= SELF_TALK_WINDOW_MS,
|
||||
);
|
||||
|
||||
const eventStream = allRecentMessages.slice(-MAX_CONTEXT_EVENTS).map((entry) => {
|
||||
if (entry?.system) {
|
||||
return {
|
||||
type: 'bot',
|
||||
nickname: entry.nickname || 'Rover Bot',
|
||||
text: entry.text || '',
|
||||
};
|
||||
}
|
||||
const roverId = entry?.roverId ? String(entry.roverId) : null;
|
||||
const rover = roverId ? roverById.get(roverId) : null;
|
||||
return {
|
||||
type: 'chat',
|
||||
nickname: entry.nickname || entry.socketId?.slice(0, 6) || 'unknown',
|
||||
text: entry.text || '',
|
||||
rover_id: roverId,
|
||||
rover_ctx: compactRoverForContext(rover),
|
||||
};
|
||||
});
|
||||
const hasRecentChat = eventStream.some((event) => event.type === 'chat');
|
||||
if (!hasRecentChat) {
|
||||
eventStream.push({
|
||||
type: 'snapshot',
|
||||
reason: 'chat_quiet',
|
||||
rovers,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
activity: {
|
||||
run_meta: {
|
||||
version: 'commentary_v2',
|
||||
self_talk_recent_30m: botRecent30m.length,
|
||||
last_message_focus: buildLastMessageFocus(lastBotMessage, rovers),
|
||||
active_driver_count: driverEntries.length,
|
||||
driving_rovers: driverEntries.map(([roverId]) => String(roverId)),
|
||||
},
|
||||
self_talk_recent_30m: botRecent30m.length,
|
||||
last_message_focus: buildLastMessageFocus(lastBotMessage, rovers),
|
||||
rovers,
|
||||
chat_recent: chatRecent,
|
||||
// Keep this compact: expose only one previous bot message summary via last_message_focus.
|
||||
// your_last_message: botRecent,
|
||||
event_stream: eventStream,
|
||||
current_snapshot: {
|
||||
rovers,
|
||||
chat_recent: chatRecent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -383,19 +428,68 @@ async function generateCommentary(systemPrompt, snapshot) {
|
||||
if (!ollamaClient) {
|
||||
throw new Error('Ollama client unavailable');
|
||||
}
|
||||
const messages = [];
|
||||
messages.push({ role: 'system', content: systemPrompt });
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: JSON.stringify({
|
||||
type: 'run_meta',
|
||||
run_meta: snapshot?.run_meta || {},
|
||||
}),
|
||||
});
|
||||
const timeline = Array.isArray(snapshot?.event_stream) ? snapshot.event_stream : [];
|
||||
timeline.forEach((event) => {
|
||||
if (!event || typeof event !== 'object') return;
|
||||
if (event.type === 'bot') {
|
||||
const text = String(event.text || '').trim();
|
||||
if (text) {
|
||||
messages.push({ role: 'assistant', content: text });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.type === 'chat') {
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: JSON.stringify({
|
||||
type: 'chat',
|
||||
nickname: event.nickname || 'unknown',
|
||||
text: event.text || '',
|
||||
rover_id: event.rover_id || null,
|
||||
rover_ctx: event.rover_ctx || null,
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (event.type === 'snapshot') {
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: JSON.stringify({
|
||||
type: 'snapshot',
|
||||
reason: event.reason || null,
|
||||
rovers: event.rovers || [],
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
// Always end with a full rover snapshot user message.
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: JSON.stringify({
|
||||
type: 'snapshot_final',
|
||||
current_snapshot: snapshot?.current_snapshot || {},
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await ollamaClient.chat({
|
||||
model,
|
||||
stream: false,
|
||||
keep_alive: -1,
|
||||
options: {
|
||||
temperature: 0.35,
|
||||
top_p: 0.8,
|
||||
num_predict: 80,
|
||||
temperature: 0.45,
|
||||
top_p: 0.9,
|
||||
num_predict: 128,
|
||||
},
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: JSON.stringify(snapshot) },
|
||||
],
|
||||
messages,
|
||||
});
|
||||
return normalizeCommentary(payload?.message?.content);
|
||||
}
|
||||
@@ -451,10 +545,11 @@ async function runTick() {
|
||||
return;
|
||||
}
|
||||
const snapshotSummary = {
|
||||
activeDrivers: snapshot.activity.active_driver_count,
|
||||
rovers: snapshot.rovers.length,
|
||||
chatMessages: snapshot.chat_recent.length,
|
||||
drivingRovers: snapshot.activity.driving_rovers,
|
||||
activeDrivers: snapshot?.run_meta?.active_driver_count || 0,
|
||||
rovers: snapshot?.current_snapshot?.rovers?.length || 0,
|
||||
chatMessages: snapshot?.current_snapshot?.chat_recent?.length || 0,
|
||||
drivingRovers: snapshot?.run_meta?.driving_rovers || [],
|
||||
eventCount: snapshot?.event_stream?.length || 0,
|
||||
};
|
||||
logger.info('Commentary tick started', {
|
||||
tickId,
|
||||
|
||||
Reference in New Issue
Block a user