feat(observe): surface delegation + A2A in the live world feed via audit poll

Edge-initiated inter-agent events (gated delegation, A2A ingress) bypass the
run loop, so the world SSE now polls the append-only audit log (cursor on the
BIGINT id, seeded to max on first pass) and emits:
  - delegation.invoked -> agent.delegate {fromAgentId,toAgentId,toName,task}
  - a2a.invoked        -> a2a.invoked   {agentId}
TeamObserver renders agent.delegate as an A->B handoff in the team timeline;
adds the agent.delegate taxonomy type. Reliable live (the synthesized-run path
never streamed — active_runs + first-sight cursor jump skip it).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-29 05:55:49 -07:00
co-authored by Claude Opus 4.8
parent 7352ed47ab
commit 85b0e1ac33
3 changed files with 75 additions and 3 deletions
@@ -7,7 +7,7 @@
// A2A invocations among team members as they happen. No composer — observe only.
import { useEffect, useRef, useState } from "react";
import { Users, MessagesSquare, Hash, Globe } from "lucide-react";
import { Users, MessagesSquare, Hash, Globe, GitBranch } from "lucide-react";
import type { Agent } from "@/lib/api/schemas";
import { useFetchJson } from "@/lib/api/use-fetch";
@@ -24,9 +24,10 @@ interface Room {
type FeedItem = {
key: number;
ts: string;
kind: "msg" | "room" | "a2a";
kind: "msg" | "room" | "a2a" | "delegate";
fromId: string;
toId?: string;
toName?: string;
subject?: string;
text: string;
};
@@ -110,6 +111,11 @@ export function TeamObserver({ agent }: { agent: Agent }) {
push({ kind: "a2a", fromId: d.agentId, text: d.skill ? `skill: ${d.skill}` : "external task" });
}
});
useLiveEvent("agent.delegate", (d) => {
if (members.has(d.fromAgentId) || members.has(d.toAgentId)) {
push({ kind: "delegate", fromId: d.fromAgentId, toId: d.toAgentId, toName: d.toName, text: d.task || "sub-task" });
}
});
const rooms = (useFetchJson<Room[]>("/api/claw-chat/rooms").data ?? []).filter(
(r) => r.kind === "room" && r.participants.some((id) => members.has(id)),
@@ -157,6 +163,8 @@ export function TeamObserver({ agent }: { agent: Agent }) {
<Hash aria-hidden size={11} className="text-muted-foreground" />
) : f.kind === "a2a" ? (
<Globe aria-hidden size={11} className="text-[#c98af0]" />
) : f.kind === "delegate" ? (
<GitBranch aria-hidden size={11} className="text-coral" />
) : (
<MessagesSquare aria-hidden size={11} className="text-muted-foreground" />
)}
@@ -165,7 +173,9 @@ export function TeamObserver({ agent }: { agent: Agent }) {
? `${nameOf(f.fromId)} → #${f.subject || "room"}`
: f.kind === "a2a"
? `external A2A → ${nameOf(f.fromId)}`
: `${nameOf(f.fromId)}${f.toId ? nameOf(f.toId) : "?"}`}
: f.kind === "delegate"
? `${nameOf(f.fromId)}${f.toName || (f.toId ? nameOf(f.toId) : "?")}`
: `${nameOf(f.fromId)}${f.toId ? nameOf(f.toId) : "?"}`}
</span>
<span className="ml-auto text-xxs text-muted-foreground">{relativeTime(f.ts)}</span>
</span>