World: Phase D — Observe live mission-control panel
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

frontend/src/components/observe/ObservePanel.tsx — a live mission-control view
driven entirely by the taxonomy feed (telemetry strip, doors awaiting approval,
agent statuses, and a rolling activity stream of real reasoning + tool calls;
the feed is ref-buffered + flushed on an interval so reasoning bursts don't
re-render per token). Rendered as the World slide-out's system view (replaces
the empty-state placeholder). With the run_events normalization, this shows
REAL agent reasoning, tool calls, and doors.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-23 21:56:51 -07:00
co-authored by Claude Opus 4.8
parent 380b95da2a
commit b1d18504c3
2 changed files with 140 additions and 5 deletions
@@ -21,6 +21,7 @@ import { topologyById } from "@/lib/topologies";
import { panelParsers, type DeviceSize } from "@/lib/url/panel-params"; import { panelParsers, type DeviceSize } from "@/lib/url/panel-params";
import { type FlowItem } from "./flow/TopologyFlow"; import { type FlowItem } from "./flow/TopologyFlow";
import { WorldCanvas } from "../world/WorldCanvas"; import { WorldCanvas } from "../world/WorldCanvas";
import { ObservePanel } from "../observe/ObservePanel";
import { StructureTree, orgNode, clawNode, type TreeItem } from "./StructureTree"; import { StructureTree, orgNode, clawNode, type TreeItem } from "./StructureTree";
import { UserMenu } from "./UserMenu"; import { UserMenu } from "./UserMenu";
import { ToolPanel, type ToolKey } from "./ToolPanel"; import { ToolPanel, type ToolKey } from "./ToolPanel";
@@ -589,11 +590,7 @@ export function Dashboard({ user, orgs, claws }: { user?: { display_name?: strin
</div> </div>
{(() => { {(() => {
const loc = locateAgent(worldSel); const loc = locateAgent(worldSel);
if (!loc) return ( if (!loc) return <ObservePanel />;
<div style={{ flex: 1, minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
<span style={{ fontFamily: mono, fontSize: 12, color: "#3a3a40", textAlign: "center", lineHeight: 1.6 }}>Click an agent node to see its summary.</span>
</div>
);
const { org: o, company: co, team: t, agent: a } = loc; const { org: o, company: co, team: t, agent: a } = loc;
const statusCol = a.status === "running" ? "#5ec8d8" : a.status === "online" ? "#5fd08a" : "#5a5a62"; const statusCol = a.status === "running" ? "#5ec8d8" : a.status === "online" ? "#5fd08a" : "#5a5a62";
const chip = (text: string, col = "#9a9aa2") => <span style={{ fontFamily: mono, fontSize: 10, color: col, padding: "3px 9px", borderRadius: 999, background: `${col}1a`, border: `1px solid ${col}33` }}>{text}</span>; const chip = (text: string, col = "#9a9aa2") => <span style={{ fontFamily: mono, fontSize: 10, color: col, padding: "3px 9px", borderRadius: 999, background: `${col}1a`, border: `1px solid ${col}33` }}>{text}</span>;
@@ -0,0 +1,138 @@
"use client";
// Observe — live mission-control, driven entirely by the Clawmates taxonomy feed
// (so it's independent of the world graph's seed data). Shows the telemetry strip,
// doors awaiting approval, agent statuses, and a rolling activity stream of real
// reasoning + tool calls. Rendered in the World slide-out as its system view.
import { useEffect, useRef, useState } from "react";
import { useLiveEvent, useLiveState } from "@/lib/live/useClawmatesLive";
const mono = "'Geist Mono', ui-monospace, monospace";
interface FeedItem {
id: number;
kind: "tool" | "think" | "door";
text: string;
color: string;
}
interface Door {
doorId: string;
agentId: string;
action: string;
summary?: string;
}
const short = (id: string) => (id.length > 8 ? id.slice(0, 6) : id);
function Section({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
<span style={{ fontFamily: mono, fontSize: 9, letterSpacing: ".12em", color: "#5a5a62" }}>{label}</span>
{children}
</div>
);
}
export function ObservePanel() {
const telemetry = useLiveState<"telemetry", { doorsPending?: number; loops?: number; costPerHr?: number; tokensPerMin?: number }>(
"telemetry",
(d) => d,
{},
);
const [doors, setDoors] = useState<Door[]>([]);
useLiveEvent("door.request", (d) =>
setDoors((cur) => [{ doorId: d.doorId, agentId: d.agentId, action: d.action, summary: d.summary }, ...cur.filter((x) => x.doorId !== d.doorId)].slice(0, 6)),
);
useLiveEvent("door.resolve", (d) => setDoors((cur) => cur.filter((x) => x.doorId !== d.doorId)));
const [agents, setAgents] = useState<Record<string, { status: string; role?: string }>>({});
useLiveEvent("agent.status", (d) => setAgents((cur) => ({ ...cur, [d.agentId]: { status: d.status, role: d.role } })));
// Rolling activity feed — buffered in a ref and flushed on an interval so a
// burst of reasoning deltas doesn't trigger a re-render per token.
const buf = useRef<FeedItem[]>([]);
const seq = useRef(0);
const [feed, setFeed] = useState<FeedItem[]>([]);
const push = (kind: FeedItem["kind"], text: string, color: string) => {
buf.current = [{ id: seq.current++, kind, text, color }, ...buf.current].slice(0, 60);
};
useLiveEvent("agent.tool.call", (d) =>
push("tool", `${short(d.agentId)} → ${d.tool}${d.target ? ` ${d.target}` : ""}`, "#5ec8d8"),
);
useLiveEvent("agent.reasoning.delta", (d) => {
const t = d.text.trim();
if (t) push("think", `${short(d.agentId)} · ${t}`, "#9a9aa2");
});
useEffect(() => {
const id = setInterval(() => setFeed(buf.current.slice()), 300);
return () => clearInterval(id);
}, []);
const statusColor = (s: string) => (s === "working" ? "#5ec8d8" : s === "online" ? "#5fd08a" : "#5a5a62");
const pill = (label: string, value: string | number) => (
<div style={{ flex: 1, borderRadius: 10, background: "#101014", border: "1px solid rgba(255,255,255,.06)", padding: "9px 6px", textAlign: "center" }}>
<div style={{ fontSize: 17, fontWeight: 700, color: "#f3f3f5" }}>{value}</div>
<div style={{ fontFamily: mono, fontSize: 8.5, letterSpacing: ".06em", color: "#6a6a72", marginTop: 2 }}>{label}</div>
</div>
);
const agentList = Object.entries(agents);
return (
<div style={{ flex: 1, minHeight: 0, overflow: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span className="cm-blink" style={{ width: 7, height: 7, borderRadius: "50%", background: "#5ec8d8" }} />
<span style={{ fontFamily: mono, fontSize: 12, letterSpacing: ".06em", color: "#cfcfd5", fontWeight: 600 }}>LIVE OBSERVE</span>
</div>
<div style={{ display: "flex", gap: 8 }}>
{pill("DOORS", telemetry.doorsPending ?? 0)}
{pill("LOOPS", telemetry.loops ?? 0)}
{pill("AGENTS", agentList.length)}
</div>
{doors.length > 0 ? (
<Section label="DOORS AWAITING APPROVAL">
{doors.map((d) => (
<div key={d.doorId} style={{ borderRadius: 10, background: "rgba(232,180,101,.08)", border: "1px solid rgba(232,180,101,.3)", padding: "9px 11px" }}>
<div style={{ fontSize: 12.5, fontWeight: 600, color: "#f0d9a8" }}>{d.action}</div>
<div style={{ fontFamily: mono, fontSize: 10, color: "#9a9aa2", marginTop: 2 }}>{short(d.agentId)}{d.summary ? ` · ${d.summary}` : ""}</div>
</div>
))}
</Section>
) : null}
{agentList.length > 0 ? (
<Section label="AGENTS">
{agentList.map(([id, a]) => (
<div key={id} style={{ display: "flex", alignItems: "center", gap: 9, padding: "8px 11px", borderRadius: 10, background: "#101014", border: "1px solid rgba(255,255,255,.06)" }}>
<span style={{ width: 8, height: 8, flex: "none", borderRadius: "50%", background: statusColor(a.status) }} />
<span style={{ flex: 1, minWidth: 0, fontSize: 12.5, color: "#eaeaee", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{a.role || short(id)}</span>
<span style={{ fontFamily: mono, fontSize: 9.5, color: statusColor(a.status) }}>{a.status}</span>
</div>
))}
</Section>
) : null}
<Section label="LIVE ACTIVITY">
{feed.length === 0 ? (
<span style={{ fontFamily: mono, fontSize: 11, color: "#3a3a40" }}>waiting for agent activity…</span>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
{feed.map((f) => (
<div key={f.id} style={{ display: "flex", gap: 8, fontFamily: mono, fontSize: 10.5, lineHeight: 1.5 }}>
<span style={{ flex: "none", color: f.color }}>{f.kind === "tool" ? "⚙" : "›"}</span>
<span style={{ flex: 1, minWidth: 0, color: "#cfcfd5", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{f.text}</span>
</div>
))}
</div>
)}
</Section>
</div>
);
}
export default ObservePanel;