overseer v2 1

This commit is contained in:
legop3
2026-05-03 22:51:50 -04:00
parent 32e4afdbe8
commit 850f89c8ed
26 changed files with 929 additions and 128 deletions
@@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useSession } from '../../context/SessionContext.jsx';
import RoverRoster from '../RoverRoster/index.jsx';
import LlmCommentaryPanel from './LlmCommentaryPanel.jsx';
import OverseerControlPanel from './OverseerControlPanel.jsx';
import ReplaySnapshotHealth from './ReplaySnapshotHealth.jsx';
import AdminIpLogPanel from './AdminIpLogPanel.jsx';
@@ -28,14 +29,17 @@ export default function AdminPanelContent() {
setAudioLevels,
setPrivateSafety,
llmControl,
overseerControl,
adminLogs,
llmCommentaryState,
overseerControlState,
} = useSession();
const roster = useMemo(() => session?.roster ?? [], [session?.roster]);
const [lockStates, setLockStates] = useState({});
const [rebootStates, setRebootStates] = useState({});
const [serverRebooting, setServerRebooting] = useState(false);
const [clearingLlmHistory, setClearingLlmHistory] = useState(false);
const [clearingOverseerHistory, setClearingOverseerHistory] = useState(false);
const health = session?.health || null;
const currentGoal = session?.globalObjective?.text || '';
const goalUpdatedAt = session?.globalObjective?.updatedAt || null;
@@ -126,6 +130,19 @@ export default function AdminPanelContent() {
}
};
const handleClearOverseerHistory = async () => {
const ok = window.confirm('Clear Overseer Control history now?');
if (!ok) return;
setClearingOverseerHistory(true);
try {
await overseerControl('clearHistory');
} catch (err) {
alert(err.message);
} finally {
setClearingOverseerHistory(false);
}
};
const handleGoalSave = async () => {
try {
await setGlobalObjective(goalDraft);
@@ -491,6 +508,11 @@ export default function AdminPanelContent() {
onClearHistory={handleClearLlmHistory}
clearingHistory={clearingLlmHistory}
/>
<OverseerControlPanel
state={overseerControlState}
onClearHistory={handleClearOverseerHistory}
clearingHistory={clearingOverseerHistory}
/>
<AdminIpLogPanel entries={adminLogs} />
</section>
);
@@ -0,0 +1,79 @@
import { useState } from 'react';
export default function OverseerControlPanel({ state, onClearHistory, clearingHistory }) {
const [showPayload, setShowPayload] = useState(false);
if (!state) {
return (
<div className="space-y-0.5">
<div className="panel-muted text-xs uppercase">Overseer Control</div>
<div className="surface text-xs text-slate-300">No status received yet.</div>
</div>
);
}
const runtime = state.runtime || {};
const cfg = state.config || {};
const output = state.output || {};
const timings = state.timings || {};
const input = state.input || {};
const errors = state.errors || {};
return (
<div className="space-y-0.5">
<div className="panel-muted text-xs uppercase">Overseer Control</div>
<div className="flex gap-0.5 text-xs">
<button
type="button"
onClick={onClearHistory}
disabled={Boolean(clearingHistory)}
className="button-danger disabled:cursor-not-allowed disabled:opacity-60"
>
{clearingHistory ? 'Clearing...' : 'Clear Overseer History'}
</button>
</div>
<div className="surface flex flex-wrap gap-0.5 text-xs">
<span className="surface-muted">running: {runtime.running ? 'yes' : 'no'}</span>
<span className="surface-muted">phase: {runtime.phase || '--'}</span>
<span className="surface-muted">tick: {runtime.tickCount ?? 0}</span>
<span className="surface-muted">trigger: {runtime.lastTriggerReason || '--'}</span>
<span className="surface-muted">name: {cfg.name || '--'}</span>
<span className="surface-muted">model: {cfg.model || '--'}</span>
<span className="surface-muted">observeOnly: {cfg.observeOnly ? 'yes' : 'no'}</span>
<span className="surface-muted">decision: {output.normalized || '--'}</span>
<span className="surface-muted">outcome: {output.outcome || '--'}</span>
<span className="surface-muted">reason: {output.reason || '--'}</span>
<span className="surface-muted">last gen: {timings.lastGenerationMs != null ? `${timings.lastGenerationMs}ms` : '--'}</span>
</div>
<details className="surface text-xs text-slate-200" open>
<summary className="cursor-pointer select-none text-slate-300">Latest Context</summary>
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
{input.stateUpdate || '<none>'}
</pre>
</details>
<details className="surface text-xs text-slate-200">
<summary className="cursor-pointer select-none text-slate-300">Tool Availability</summary>
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
{JSON.stringify({ available: input.availableTools, blocked: input.blockedTools }, null, 2)}
</pre>
</details>
{errors.message ? <div className="surface text-xs text-red-300">Error: {errors.message}</div> : null}
<details className="surface text-xs text-slate-200">
<summary className="cursor-pointer select-none text-slate-300">Recent Runs</summary>
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
{JSON.stringify(state.history || [], null, 2)}
</pre>
</details>
<button type="button" className="button-dark text-xs" onClick={() => setShowPayload((v) => !v)}>
{showPayload ? 'Hide Full Payload' : 'Show Full Payload'}
</button>
{showPayload ? (
<pre className="surface whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">{JSON.stringify(state, null, 2)}</pre>
) : null}
</div>
);
}
+9
View File
@@ -13,6 +13,7 @@ const INITIAL_STATE = {
adminLogs: [],
llmCommentaryState: null,
llmCommentaryStatus: null,
overseerControlState: null,
alerts: [],
};
@@ -128,6 +129,10 @@ export function SessionProvider({ children }) {
],
}));
}
function handleOverseerState(payload = null) {
const state = payload && typeof payload === 'object' ? payload : null;
setState((prev) => ({ ...prev, overseerControlState: state }));
}
function handleNeatoLidar(payload = null) {
const next = payload && typeof payload === 'object' ? payload : null;
setState((prev) => ({ ...prev, neatoLidar: next }));
@@ -139,6 +144,7 @@ export function SessionProvider({ children }) {
socket.on('adminlog:init', handleAdminLogInit);
socket.on('adminlog:entry', handleAdminLogEntry);
socket.on('llm:state', handleLlmState);
socket.on('overseer:state', handleOverseerState);
socket.on('alert:new', handleAlertNew);
return () => {
socket.off('session:sync', handleSession);
@@ -148,6 +154,7 @@ export function SessionProvider({ children }) {
socket.off('adminlog:init', handleAdminLogInit);
socket.off('adminlog:entry', handleAdminLogEntry);
socket.off('llm:state', handleLlmState);
socket.off('overseer:state', handleOverseerState);
socket.off('alert:new', handleAlertNew);
};
}, [setState, socket]);
@@ -204,6 +211,8 @@ export function SessionProvider({ children }) {
emitWithAck('session:privateSafety:set', { roverId, safety }),
llmControl: (action, controls = {}) =>
emitWithAck('llm:control', { controls: { action, ...controls } }),
overseerControl: (action, controls = {}) =>
emitWithAck('overseer:control', { controls: { action, ...controls } }),
pushAlert: (alert) =>
setState((prev) => ({
...prev,