mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 19:58:43 +02:00
fix: scope turn-activity panel metrics to per-agent events in sequential workflows
In sequential workflows, multiple collapsible turn-activity panels shared the same SessionRunRecord and displayed identical aggregate metrics (tool calls, approvals, etc.) instead of per-agent counts. Root cause: ChatPane grouped thinking messages correctly per agent, but passed the full unfiltered run object to every TurnActivityPanel, so summarizeActivity() counted all events from the entire run for each panel. Changes: - Extract filterEventsByAgent() and summarizeActivity() to runTimelineFormatting.ts as pure, testable helpers - Add agentNames (derived from thinking message authors) and isLastRunPanel to the DisplayItem turn-activity variant in ChatPane - TurnActivityPanel now filters run.events through filterEventsByAgent() so each panel only counts events belonging to its agents - Git summary / discard actions only render on the last panel of a run - Per-agent timing derived from scoped events instead of run-level start Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -168,3 +168,68 @@ export function truncateContent(content: string | undefined, maxLength = 80): st
|
||||
export function findLatestRun(runs: readonly SessionRunRecord[]): SessionRunRecord | undefined {
|
||||
return runs.length > 0 ? runs[0] : undefined;
|
||||
}
|
||||
|
||||
/* ── Agent-scoped event filtering ──────────────────────────── */
|
||||
|
||||
/** Run-level events that are not scoped to any specific agent. */
|
||||
const RUN_LEVEL_EVENT_KINDS = new Set<RunTimelineEventKind>([
|
||||
'run-started',
|
||||
'run-completed',
|
||||
'run-cancelled',
|
||||
'run-failed',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Filter run events to only those belonging to the given agents.
|
||||
* When `agentNames` is undefined or empty (single-agent run), all events pass through.
|
||||
* Run-level events (start/complete/fail) are excluded from agent-scoped panels
|
||||
* because they represent the entire run, not a specific agent's work.
|
||||
*/
|
||||
export function filterEventsByAgent(
|
||||
events: readonly RunTimelineEventRecord[],
|
||||
agentNames: ReadonlySet<string> | undefined,
|
||||
): RunTimelineEventRecord[] {
|
||||
if (!agentNames || agentNames.size === 0) return events.slice();
|
||||
|
||||
return events.filter((e) => {
|
||||
if (RUN_LEVEL_EVENT_KINDS.has(e.kind)) return false;
|
||||
if (e.kind === 'handoff') {
|
||||
return (e.sourceAgentName != null && agentNames.has(e.sourceAgentName))
|
||||
|| (e.targetAgentName != null && agentNames.has(e.targetAgentName));
|
||||
}
|
||||
if (e.agentName) return agentNames.has(e.agentName);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/** Counts of different activity types within a set of run timeline events. */
|
||||
export interface ActivitySummary {
|
||||
thinkingSteps: number;
|
||||
toolCalls: number;
|
||||
handoffs: number;
|
||||
approvals: number;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize activity from thinking messages and (already-filtered) run events.
|
||||
*/
|
||||
export function summarizeActivity(
|
||||
thinkingMessages: ReadonlyArray<{ content: string }>,
|
||||
events: readonly RunTimelineEventRecord[],
|
||||
): ActivitySummary {
|
||||
const thinkingSteps = thinkingMessages.filter((m) => m.content).length;
|
||||
let toolCalls = 0;
|
||||
let handoffs = 0;
|
||||
let approvals = 0;
|
||||
let hasError = false;
|
||||
|
||||
for (const e of events) {
|
||||
if (e.kind === 'tool-call') toolCalls++;
|
||||
else if (e.kind === 'handoff') handoffs++;
|
||||
else if (e.kind === 'approval') approvals++;
|
||||
else if (e.kind === 'run-failed') hasError = true;
|
||||
}
|
||||
|
||||
return { thinkingSteps, toolCalls, handoffs, approvals, hasError };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user