Observe: per-agent drill-in from the World view
Clicking a neuron/pawn now focuses the Observe panel on that agent: a header with its role + live status + a "← System" back button, then that agent's filtered REASONING STREAM and TOOL CALLS. Feed items are tagged by agentId; the system view shows the agent id per line. Wired via worldSel in Dashboard. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c5daf5925c
commit
b9d3fc4f44
@@ -16,6 +16,7 @@ interface FeedItem {
|
||||
kind: "tool" | "think" | "door";
|
||||
text: string;
|
||||
color: string;
|
||||
agentId: string;
|
||||
}
|
||||
interface Door {
|
||||
doorId: string;
|
||||
@@ -35,7 +36,10 @@ function Section({ label, children }: { label: string; children: React.ReactNode
|
||||
);
|
||||
}
|
||||
|
||||
export function ObservePanel() {
|
||||
export function ObservePanel({
|
||||
focusAgent,
|
||||
onClearFocus,
|
||||
}: { focusAgent?: string | null; onClearFocus?: () => void } = {}) {
|
||||
const telemetry = useLiveState<"telemetry", { doorsPending?: number; loops?: number; costPerHr?: number; tokensPerMin?: number }>(
|
||||
"telemetry",
|
||||
(d) => d,
|
||||
@@ -56,15 +60,15 @@ export function ObservePanel() {
|
||||
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);
|
||||
const push = (kind: FeedItem["kind"], text: string, color: string, agentId: string) => {
|
||||
buf.current = [{ id: seq.current++, kind, text, color, agentId }, ...buf.current].slice(0, 60);
|
||||
};
|
||||
useLiveEvent("agent.tool.call", (d) =>
|
||||
push("tool", `${short(d.agentId)} → ${d.tool}${d.target ? ` ${d.target}` : ""}`, "#5ec8d8"),
|
||||
push("tool", `→ ${d.tool}${d.target ? ` ${d.target}` : ""}`, "#5ec8d8", d.agentId),
|
||||
);
|
||||
useLiveEvent("agent.reasoning.delta", (d) => {
|
||||
const t = d.text.trim();
|
||||
if (t) push("think", `${short(d.agentId)} · ${t}`, "#9a9aa2");
|
||||
if (t) push("think", t, "#9a9aa2", d.agentId);
|
||||
});
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setFeed(buf.current.slice()), 300);
|
||||
@@ -79,6 +83,63 @@ export function ObservePanel() {
|
||||
</div>
|
||||
);
|
||||
|
||||
// Per-agent drill-in: when a neuron/pawn is selected and we have live data for
|
||||
// it, focus the panel on that agent's reasoning + tools.
|
||||
const focused = focusAgent && (agents[focusAgent] || feed.some((f) => f.agentId === focusAgent)) ? focusAgent : null;
|
||||
if (focused) {
|
||||
const a = agents[focused];
|
||||
const items = feed.filter((f) => f.agentId === focused);
|
||||
const think = items.filter((f) => f.kind === "think");
|
||||
const tools = items.filter((f) => f.kind === "tool");
|
||||
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 }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearFocus}
|
||||
style={{ fontFamily: mono, fontSize: 10.5, padding: "4px 9px", borderRadius: 7, border: "1px solid rgba(255,255,255,.12)", background: "rgba(255,255,255,.04)", color: "#cfcfd5", cursor: "pointer" }}
|
||||
>
|
||||
← System
|
||||
</button>
|
||||
<span style={{ flex: 1 }} />
|
||||
<span style={{ width: 8, height: 8, borderRadius: "50%", background: statusColor(a?.status ?? "idle") }} />
|
||||
<span style={{ fontFamily: mono, fontSize: 10, color: statusColor(a?.status ?? "idle") }}>{a?.status ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: "#f3f3f5" }}>{a?.role || short(focused)}</div>
|
||||
<div style={{ fontFamily: mono, fontSize: 10, color: "#6a6a72" }}>observing · {short(focused)}</div>
|
||||
</div>
|
||||
<Section label="REASONING STREAM">
|
||||
{think.length === 0 ? (
|
||||
<span style={{ fontFamily: mono, fontSize: 11, color: "#3a3a40" }}>no reasoning yet…</span>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
{think.map((f) => (
|
||||
<div key={f.id} style={{ fontFamily: mono, fontSize: 11, color: "#cfcfd5", lineHeight: 1.5 }}>
|
||||
{f.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
<Section label="TOOL CALLS">
|
||||
{tools.length === 0 ? (
|
||||
<span style={{ fontFamily: mono, fontSize: 11, color: "#3a3a40" }}>no tool calls yet…</span>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
||||
{tools.map((f) => (
|
||||
<div key={f.id} style={{ display: "flex", gap: 8, fontFamily: mono, fontSize: 10.5 }}>
|
||||
<span style={{ color: "#5ec8d8" }}>⚙</span>
|
||||
<span style={{ color: "#cfcfd5" }}>{f.text}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const agentList = Object.entries(agents);
|
||||
|
||||
return (
|
||||
@@ -125,6 +186,7 @@ export function ObservePanel() {
|
||||
{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: "none", color: "#6a6a72" }}>{short(f.agentId)}</span>
|
||||
<span style={{ flex: 1, minWidth: 0, color: "#cfcfd5", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{f.text}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user