refactor: organize ActiveAgentsPanel feed by run, fix SKILL.md formatting
Switches feed aggregation from by-agent to by-run for more accurate streaming output attribution. Adds newline note for plan tags in SKILL.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,10 +125,14 @@ After:
|
|||||||
pls show the costs in either token or dollars on the /issues/{id} page. Make a plan first.
|
pls show the costs in either token or dollars on the /issues/{id} page. Make a plan first.
|
||||||
|
|
||||||
<plan>
|
<plan>
|
||||||
|
|
||||||
[your plan here]
|
[your plan here]
|
||||||
|
|
||||||
</plan>
|
</plan>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
\*make sure to have a newline after/before your <plan/> tags
|
||||||
|
|
||||||
## Key Endpoints (Quick Reference)
|
## Key Endpoints (Quick Reference)
|
||||||
|
|
||||||
| Action | Endpoint |
|
| Action | Endpoint |
|
||||||
|
|||||||
@@ -140,14 +140,8 @@ interface ActiveAgentsPanelProps {
|
|||||||
companyId: string;
|
companyId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AgentRunGroup {
|
|
||||||
agentId: string;
|
|
||||||
agentName: string;
|
|
||||||
runs: LiveRunForIssue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
||||||
const [feedByAgent, setFeedByAgent] = useState<Map<string, FeedItem[]>>(new Map());
|
const [feedByRun, setFeedByRun] = useState<Map<string, FeedItem[]>>(new Map());
|
||||||
const seenKeysRef = useRef(new Set<string>());
|
const seenKeysRef = useRef(new Set<string>());
|
||||||
const pendingByRunRef = useRef(new Map<string, string>());
|
const pendingByRunRef = useRef(new Map<string, string>());
|
||||||
const nextIdRef = useRef(1);
|
const nextIdRef = useRef(1);
|
||||||
@@ -161,19 +155,6 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
const runById = useMemo(() => new Map(runs.map((r) => [r.id, r])), [runs]);
|
const runById = useMemo(() => new Map(runs.map((r) => [r.id, r])), [runs]);
|
||||||
const activeRunIds = useMemo(() => new Set(runs.map((r) => r.id)), [runs]);
|
const activeRunIds = useMemo(() => new Set(runs.map((r) => r.id)), [runs]);
|
||||||
|
|
||||||
const agentGroups = useMemo(() => {
|
|
||||||
const map = new Map<string, AgentRunGroup>();
|
|
||||||
for (const run of runs) {
|
|
||||||
let group = map.get(run.agentId);
|
|
||||||
if (!group) {
|
|
||||||
group = { agentId: run.agentId, agentName: run.agentName, runs: [] };
|
|
||||||
map.set(run.agentId, group);
|
|
||||||
}
|
|
||||||
group.runs.push(run);
|
|
||||||
}
|
|
||||||
return Array.from(map.values());
|
|
||||||
}, [runs]);
|
|
||||||
|
|
||||||
// Clean up pending buffers for runs that ended
|
// Clean up pending buffers for runs that ended
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stillActive = new Set<string>();
|
const stillActive = new Set<string>();
|
||||||
@@ -196,12 +177,12 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
let reconnectTimer: number | null = null;
|
let reconnectTimer: number | null = null;
|
||||||
let socket: WebSocket | null = null;
|
let socket: WebSocket | null = null;
|
||||||
|
|
||||||
const appendItems = (agentId: string, items: FeedItem[]) => {
|
const appendItems = (runId: string, items: FeedItem[]) => {
|
||||||
if (items.length === 0) return;
|
if (items.length === 0) return;
|
||||||
setFeedByAgent((prev) => {
|
setFeedByRun((prev) => {
|
||||||
const next = new Map(prev);
|
const next = new Map(prev);
|
||||||
const existing = next.get(agentId) ?? [];
|
const existing = next.get(runId) ?? [];
|
||||||
next.set(agentId, [...existing, ...items].slice(-MAX_FEED_ITEMS));
|
next.set(runId, [...existing, ...items].slice(-MAX_FEED_ITEMS));
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -246,7 +227,7 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
if (seenKeysRef.current.size > 2000) seenKeysRef.current.clear();
|
if (seenKeysRef.current.size > 2000) seenKeysRef.current.clear();
|
||||||
const tone = eventType === "error" ? "error" : eventType === "lifecycle" ? "warn" : "info";
|
const tone = eventType === "error" ? "error" : eventType === "lifecycle" ? "warn" : "info";
|
||||||
const item = createFeedItem(run, event.createdAt, messageText, tone, nextIdRef.current++);
|
const item = createFeedItem(run, event.createdAt, messageText, tone, nextIdRef.current++);
|
||||||
if (item) appendItems(run.agentId, [item]);
|
if (item) appendItems(run.id, [item]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +239,7 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
if (seenKeysRef.current.size > 2000) seenKeysRef.current.clear();
|
if (seenKeysRef.current.size > 2000) seenKeysRef.current.clear();
|
||||||
const tone = status === "failed" || status === "timed_out" ? "error" : "warn";
|
const tone = status === "failed" || status === "timed_out" ? "error" : "warn";
|
||||||
const item = createFeedItem(run, event.createdAt, `run ${status}`, tone, nextIdRef.current++);
|
const item = createFeedItem(run, event.createdAt, `run ${status}`, tone, nextIdRef.current++);
|
||||||
if (item) appendItems(run.agentId, [item]);
|
if (item) appendItems(run.id, [item]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,10 +248,10 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
if (!chunk) return;
|
if (!chunk) return;
|
||||||
const stream = readString(payload["stream"]) === "stderr" ? "stderr" : "stdout";
|
const stream = readString(payload["stream"]) === "stderr" ? "stderr" : "stdout";
|
||||||
if (stream === "stderr") {
|
if (stream === "stderr") {
|
||||||
appendItems(run.agentId, parseStderrChunk(run, chunk, event.createdAt, pendingByRunRef.current, nextIdRef));
|
appendItems(run.id, parseStderrChunk(run, chunk, event.createdAt, pendingByRunRef.current, nextIdRef));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
appendItems(run.agentId, parseStdoutChunk(run, chunk, event.createdAt, pendingByRunRef.current, nextIdRef));
|
appendItems(run.id, parseStdoutChunk(run, chunk, event.createdAt, pendingByRunRef.current, nextIdRef));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,7 +278,7 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
};
|
};
|
||||||
}, [activeRunIds, companyId, runById]);
|
}, [activeRunIds, companyId, runById]);
|
||||||
|
|
||||||
if (agentGroups.length === 0) return null;
|
if (runs.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -305,11 +286,11 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
Active Agents
|
Active Agents
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
{agentGroups.map((group) => (
|
{runs.map((run) => (
|
||||||
<AgentRunCard
|
<AgentRunCard
|
||||||
key={group.agentId}
|
key={run.id}
|
||||||
group={group}
|
run={run}
|
||||||
feed={feedByAgent.get(group.agentId) ?? []}
|
feed={feedByRun.get(run.id) ?? []}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -317,10 +298,9 @@ export function ActiveAgentsPanel({ companyId }: ActiveAgentsPanelProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AgentRunCard({ group, feed }: { group: AgentRunGroup; feed: FeedItem[] }) {
|
function AgentRunCard({ run, feed }: { run: LiveRunForIssue; feed: FeedItem[] }) {
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
const recent = feed.slice(-20);
|
const recent = feed.slice(-20);
|
||||||
const primaryRun = group.runs[0];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = bodyRef.current;
|
const body = bodyRef.current;
|
||||||
@@ -336,23 +316,19 @@ function AgentRunCard({ group, feed }: { group: AgentRunGroup; feed: FeedItem[]
|
|||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75" />
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500" />
|
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-500" />
|
||||||
</span>
|
</span>
|
||||||
<Identity name={group.agentName} size="sm" />
|
<Identity name={run.agentName} size="sm" />
|
||||||
<span className="text-[11px] font-medium text-blue-400">Live</span>
|
<span className="text-[11px] font-medium text-blue-400">Live</span>
|
||||||
{group.runs.length > 1 && (
|
<span className="text-[10px] text-muted-foreground font-mono">
|
||||||
<span className="text-[10px] text-muted-foreground">
|
{run.id.slice(0, 8)}
|
||||||
({group.runs.length} runs)
|
</span>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{primaryRun && (
|
<Link
|
||||||
<Link
|
to={`/agents/${run.agentId}/runs/${run.id}`}
|
||||||
to={`/agents/${primaryRun.agentId}/runs/${primaryRun.id}`}
|
className="inline-flex items-center gap-1 text-[10px] text-blue-400 hover:text-blue-300"
|
||||||
className="inline-flex items-center gap-1 text-[10px] text-blue-400 hover:text-blue-300"
|
>
|
||||||
>
|
Open run
|
||||||
Open run
|
<ExternalLink className="h-2.5 w-2.5" />
|
||||||
<ExternalLink className="h-2.5 w-2.5" />
|
</Link>
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={bodyRef} className="max-h-[180px] overflow-y-auto p-2 font-mono text-[11px] space-y-1">
|
<div ref={bodyRef} className="max-h-[180px] overflow-y-auto p-2 font-mono text-[11px] space-y-1">
|
||||||
@@ -381,21 +357,6 @@ function AgentRunCard({ group, feed }: { group: AgentRunGroup; feed: FeedItem[]
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{group.runs.length > 1 && (
|
|
||||||
<div className="border-t border-border/50 px-3 py-1.5 flex flex-wrap gap-2">
|
|
||||||
{group.runs.map((run) => (
|
|
||||||
<Link
|
|
||||||
key={run.id}
|
|
||||||
to={`/agents/${run.agentId}/runs/${run.id}`}
|
|
||||||
className="inline-flex items-center gap-1 text-[10px] text-blue-400 hover:text-blue-300"
|
|
||||||
>
|
|
||||||
{run.id.slice(0, 8)}
|
|
||||||
<ExternalLink className="h-2.5 w-2.5" />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user