mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
wording and overseer tool embeds
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-3_2nPjxD.js"></script>
|
<script type="module" crossorigin src="/assets/index-BWk3SALU.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-LJWnKvT4.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BtUMEmG6.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -100,6 +100,21 @@ function buildRoverCtxSnapshot(roverId) {
|
|||||||
function buildMessage(socket, text, meta = {}) {
|
function buildMessage(socket, text, meta = {}) {
|
||||||
const roverId = meta.roverId || resolveRoverId(socket?.id);
|
const roverId = meta.roverId || resolveRoverId(socket?.id);
|
||||||
const roverColor = meta.roverColor ?? resolveRoverColor(roverId);
|
const roverColor = meta.roverColor ?? resolveRoverColor(roverId);
|
||||||
|
const toolCalls = Array.isArray(meta.toolCalls)
|
||||||
|
? meta.toolCalls
|
||||||
|
.map((entry) => {
|
||||||
|
if (!entry || typeof entry !== 'object') return null;
|
||||||
|
return {
|
||||||
|
tool: String(entry.tool || '').trim() || 'unknown',
|
||||||
|
status: String(entry.status || '').trim() || 'unknown',
|
||||||
|
args: entry.args && typeof entry.args === 'object' ? entry.args : {},
|
||||||
|
result: entry.result && typeof entry.result === 'object' ? entry.result : null,
|
||||||
|
error: entry.error ? String(entry.error) : null,
|
||||||
|
durationMs: Number.isFinite(entry.durationMs) ? Math.max(0, Math.round(entry.durationMs)) : null,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
: null;
|
||||||
return {
|
return {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
@@ -119,6 +134,7 @@ function buildMessage(socket, text, meta = {}) {
|
|||||||
profileImage: normalizeProfileImageUrl(meta.profileImage),
|
profileImage: normalizeProfileImageUrl(meta.profileImage),
|
||||||
roverCtx: meta.roverCtx || null,
|
roverCtx: meta.roverCtx || null,
|
||||||
text,
|
text,
|
||||||
|
toolCalls,
|
||||||
tts: meta.tts || null,
|
tts: meta.tts || null,
|
||||||
bot: Boolean(meta.bot),
|
bot: Boolean(meta.bot),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const { registerChatSocketHooks } = require('./socketHooks');
|
|||||||
function sendSystemMessage(text, options = {}) {
|
function sendSystemMessage(text, options = {}) {
|
||||||
const normalized = normalizeUserText(text);
|
const normalized = normalizeUserText(text);
|
||||||
const clean = normalized.trim();
|
const clean = normalized.trim();
|
||||||
if (!clean) return null;
|
const hasToolCalls = Array.isArray(options.toolCalls) && options.toolCalls.length > 0;
|
||||||
|
if (!clean && !hasToolCalls) return null;
|
||||||
const safe = clean;
|
const safe = clean;
|
||||||
const message = buildMessage(null, safe, {
|
const message = buildMessage(null, safe, {
|
||||||
nickname: String(options.nickname || 'The Overseer'),
|
nickname: String(options.nickname || 'The Overseer'),
|
||||||
@@ -19,6 +20,7 @@ function sendSystemMessage(text, options = {}) {
|
|||||||
fromDiscord: false,
|
fromDiscord: false,
|
||||||
bot: options.bot !== false,
|
bot: options.bot !== false,
|
||||||
profileImage: options.profileImage || null,
|
profileImage: options.profileImage || null,
|
||||||
|
toolCalls: hasToolCalls ? options.toolCalls : null,
|
||||||
});
|
});
|
||||||
broadcastMessage(message);
|
broadcastMessage(message);
|
||||||
return message;
|
return message;
|
||||||
|
|||||||
@@ -3,6 +3,21 @@
|
|||||||
// Scope: Handles inbound Discord messages plus outbound webhook and typing relay.
|
// Scope: Handles inbound Discord messages plus outbound webhook and typing relay.
|
||||||
const { WebhookClient } = require('discord.js');
|
const { WebhookClient } = require('discord.js');
|
||||||
|
|
||||||
|
function summarizeToolCall(entry = {}) {
|
||||||
|
const tool = String(entry?.tool || 'unknown');
|
||||||
|
const status = String(entry?.status || 'unknown').toLowerCase();
|
||||||
|
if (status === 'ok') return `✅ ${tool}`;
|
||||||
|
if (status === 'blocked') return `⛔ ${tool} — ${String(entry?.error || 'blocked')}`;
|
||||||
|
if (status === 'error') return `❌ ${tool} — ${String(entry?.error || 'failed')}`;
|
||||||
|
return `• ${tool}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToolCallsCodeBlock(toolCalls = []) {
|
||||||
|
const rows = (toolCalls || []).map((entry) => summarizeToolCall(entry));
|
||||||
|
if (!rows.length) return '';
|
||||||
|
return `\`\`\`txt\nTool calls:\n${rows.join('\n')}\n\`\`\``;
|
||||||
|
}
|
||||||
|
|
||||||
function createChatBridgeHandlers(deps) {
|
function createChatBridgeHandlers(deps) {
|
||||||
const {
|
const {
|
||||||
logger,
|
logger,
|
||||||
@@ -48,7 +63,15 @@ function createChatBridgeHandlers(deps) {
|
|||||||
const guildConfigs = listGuildConfigs();
|
const guildConfigs = listGuildConfigs();
|
||||||
if (!guildConfigs.length) return;
|
if (!guildConfigs.length) return;
|
||||||
|
|
||||||
const text = payload.text?.length > 1900 ? `${payload.text.slice(0, 1897)}...` : payload.text;
|
const baseText = String(payload.text || '');
|
||||||
|
const toolCalls = Array.isArray(payload.toolCalls) ? payload.toolCalls : [];
|
||||||
|
const toolsBlock = formatToolCallsCodeBlock(toolCalls);
|
||||||
|
let text = baseText;
|
||||||
|
if (toolsBlock) {
|
||||||
|
text = text ? `${text}\n\n${toolsBlock}` : `Overseer ran tools (no chat line).\n\n${toolsBlock}`;
|
||||||
|
}
|
||||||
|
if (text.length > 1900) text = `${text.slice(0, 1897)}...`;
|
||||||
|
if (!text.trim()) return;
|
||||||
const username = formatWebhookUsername(payload);
|
const username = formatWebhookUsername(payload);
|
||||||
const avatarURL = payload.profileImage || (payload.fromDiscord ? payload.discordUserAvatarUrl || null : client.user?.displayAvatarURL?.({ extension: 'png', size: 128 }) || null);
|
const avatarURL = payload.profileImage || (payload.fromDiscord ? payload.discordUserAvatarUrl || null : client.user?.displayAvatarURL?.({ extension: 'png', size: 128 }) || null);
|
||||||
const typingId = getTypingId(payload);
|
const typingId = getTypingId(payload);
|
||||||
|
|||||||
@@ -207,6 +207,44 @@ function normalizeChatDraft(text) {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summarizeResult(result) {
|
||||||
|
if (!result || typeof result !== 'object') return null;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(result, 'ok')) return { ok: Boolean(result.ok) };
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildToolCallFeedEntries(requestedActions = [], actionResults = []) {
|
||||||
|
const resultsByTool = new Map();
|
||||||
|
(actionResults || []).forEach((entry) => {
|
||||||
|
if (!entry || entry.kind !== 'tool') return;
|
||||||
|
const key = String(entry.tool || '');
|
||||||
|
if (!key) return;
|
||||||
|
if (!resultsByTool.has(key)) resultsByTool.set(key, []);
|
||||||
|
resultsByTool.get(key).push(entry);
|
||||||
|
});
|
||||||
|
return (requestedActions || []).map((action) => {
|
||||||
|
const tool = String(action?.tool || '').trim() || 'unknown';
|
||||||
|
const bucket = resultsByTool.get(tool) || [];
|
||||||
|
const resultEntry = bucket.length ? bucket.shift() : null;
|
||||||
|
const ok = Boolean(resultEntry?.ok);
|
||||||
|
const errText = resultEntry?.error ? String(resultEntry.error) : '';
|
||||||
|
const status = resultEntry
|
||||||
|
? ok
|
||||||
|
? 'ok'
|
||||||
|
: errText.includes('blocked') || errText.includes('unavailable')
|
||||||
|
? 'blocked'
|
||||||
|
: 'error'
|
||||||
|
: 'started';
|
||||||
|
return {
|
||||||
|
tool,
|
||||||
|
status,
|
||||||
|
args: action?.args && typeof action.args === 'object' ? action.args : {},
|
||||||
|
result: summarizeResult(resultEntry?.result),
|
||||||
|
error: errText || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function runDecision(triggerReason) {
|
async function runDecision(triggerReason) {
|
||||||
const runId = runtime.tickCount;
|
const runId = runtime.tickCount;
|
||||||
updateStatus({ phase: 'context_build', currentRunId: runId, lastTriggerReason: triggerReason, lastError: null, lastErrorDetails: null });
|
updateStatus({ phase: 'context_build', currentRunId: runId, lastTriggerReason: triggerReason, lastError: null, lastErrorDetails: null });
|
||||||
@@ -280,11 +318,6 @@ async function runDecision(triggerReason) {
|
|||||||
const reason = observeOnly ? 'observe-only mode' : null;
|
const reason = observeOnly ? 'observe-only mode' : null;
|
||||||
|
|
||||||
if (!observeOnly) {
|
if (!observeOnly) {
|
||||||
if ((decision === 'CHAT' || decision === 'ACTION+CHAT') && chatDraft) {
|
|
||||||
sendSystemMessage(chatDraft, { nickname: name, bot: true, profileImage: profileImageUrl });
|
|
||||||
actionResults.push({ kind: 'chat', ok: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decision === 'ACTION' || decision === 'ACTION+CHAT') {
|
if (decision === 'ACTION' || decision === 'ACTION+CHAT') {
|
||||||
for (const action of requestedActions) {
|
for (const action of requestedActions) {
|
||||||
pushLiveToolCall({ phase: 'start', tool: action.tool, args: action.args });
|
pushLiveToolCall({ phase: 'start', tool: action.tool, args: action.args });
|
||||||
@@ -315,6 +348,17 @@ async function runDecision(triggerReason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolCallFeed = buildToolCallFeedEntries(requestedActions, actionResults);
|
||||||
|
if (toolCallFeed.length > 0) {
|
||||||
|
if ((decision === 'CHAT' || decision === 'ACTION+CHAT') && chatDraft) {
|
||||||
|
sendSystemMessage(chatDraft, { nickname: name, bot: true, profileImage: profileImageUrl, toolCalls: toolCallFeed });
|
||||||
|
} else {
|
||||||
|
sendSystemMessage('', { nickname: name, bot: true, profileImage: profileImageUrl, toolCalls: toolCallFeed });
|
||||||
|
}
|
||||||
|
} else if ((decision === 'CHAT' || decision === 'ACTION+CHAT') && chatDraft) {
|
||||||
|
sendSystemMessage(chatDraft, { nickname: name, bot: true, profileImage: profileImageUrl });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus({
|
updateStatus({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Chat Message Row
|
// Chat Message Row
|
||||||
// Purpose: Defines the Chat Message Row module and the local helpers/components used in this file.
|
// Purpose: Defines the Chat Message Row module and the local helpers/components used in this file.
|
||||||
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
||||||
|
import { useState } from 'react';
|
||||||
import { FaDiscord } from 'react-icons/fa';
|
import { FaDiscord } from 'react-icons/fa';
|
||||||
import { roverBadgeStyle } from '../../lib/roverColor.js';
|
import { roverBadgeStyle } from '../../lib/roverColor.js';
|
||||||
|
|
||||||
@@ -133,12 +134,40 @@ function chatRowClass(message) {
|
|||||||
|
|
||||||
export default function ChatMessageRow({ message }) {
|
export default function ChatMessageRow({ message }) {
|
||||||
const isBot = isBotMessage(message);
|
const isBot = isBotMessage(message);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const toolCalls = Array.isArray(message?.toolCalls) ? message.toolCalls : [];
|
||||||
|
const hasText = Boolean(String(message?.text || '').trim());
|
||||||
return (
|
return (
|
||||||
<div className={chatRowClass(message)}>
|
<div className={chatRowClass(message)}>
|
||||||
<span
|
<span
|
||||||
className={`min-w-0 flex-1 break-words leading-tight whitespace-pre-wrap ${isBot ? 'text-emerald-100' : 'text-slate-100'}`}
|
className={`min-w-0 flex-1 break-words leading-tight whitespace-pre-wrap ${isBot ? 'text-emerald-100' : 'text-slate-100'}`}
|
||||||
>
|
>
|
||||||
<ChatIdentity message={message} /> {message.text}
|
<ChatIdentity message={message} />{hasText ? ` ${message.text}` : ''}
|
||||||
|
{toolCalls.length > 0 ? (
|
||||||
|
<span className="mt-0.5 block">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded border border-slate-600/70 bg-slate-800/60 px-1 py-[1px] text-[0.65rem] text-slate-200 hover:bg-slate-700/70"
|
||||||
|
onClick={() => setOpen((v) => !v)}
|
||||||
|
>
|
||||||
|
{open ? '▼' : '▶'} Tools ({toolCalls.length})
|
||||||
|
</button>
|
||||||
|
{open ? (
|
||||||
|
<div className="mt-0.5 rounded border border-slate-700/70 bg-slate-900/70 p-0.5 text-[0.68rem] text-slate-200">
|
||||||
|
{toolCalls.map((entry, idx) => {
|
||||||
|
const status = String(entry?.status || 'unknown');
|
||||||
|
const icon = status === 'ok' ? '✅' : status === 'blocked' ? '⛔' : status === 'error' ? '❌' : '•';
|
||||||
|
return (
|
||||||
|
<div key={`${entry?.tool || 'tool'}-${idx}`} className="mb-0.5 last:mb-0">
|
||||||
|
<div>{icon} {entry?.tool || 'unknown'}</div>
|
||||||
|
{entry?.error ? <div className="text-rose-300">error: {String(entry.error)}</div> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-auto shrink-0 text-[0.65rem] text-slate-400/60">
|
<span className="ml-auto shrink-0 text-[0.65rem] text-slate-400/60">
|
||||||
{formatTime(message.ts)}
|
{formatTime(message.ts)}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export default function DriveDockAction({
|
|||||||
steps: [
|
steps: [
|
||||||
'Line up the center dot of your rover with the center of the dock',
|
'Line up the center dot of your rover with the center of the dock',
|
||||||
'The UI will indicate when you are successfully docked',
|
'The UI will indicate when you are successfully docked',
|
||||||
'When good contact is made, charging will start in about 5 seconds.',
|
'When good contact is made, the dock will stop flashing.',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user