feat: add agent activity events

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-21 12:48:27 +01:00
co-authored by Copilot
parent b9e9662f6e
commit c5979d07bd
14 changed files with 754 additions and 33 deletions
+95
View File
@@ -0,0 +1,95 @@
import type { SessionEventRecord } from '@shared/domain/event';
export interface SessionActivityState {
sessionId: string;
activityType?: SessionEventRecord['activityType'];
agentName?: string;
toolName?: string;
}
export type SessionActivityMap = Record<string, SessionActivityState | undefined>;
export function applySessionEventActivity(
current: SessionActivityMap,
event: SessionEventRecord,
): SessionActivityMap {
if (event.kind === 'agent-activity') {
return {
...current,
[event.sessionId]: {
sessionId: event.sessionId,
activityType: event.activityType,
agentName: event.agentName,
toolName: event.toolName,
},
};
}
if (event.kind === 'status') {
if (event.status === 'running' || event.status === 'idle' || event.status === 'error') {
return removeSessionActivity(current, event.sessionId);
}
}
if (event.kind === 'error') {
return removeSessionActivity(current, event.sessionId);
}
return current;
}
export function pruneSessionActivities(
current: SessionActivityMap,
sessionIds: Iterable<string>,
): SessionActivityMap {
const allowed = new Set(sessionIds);
const next: SessionActivityMap = {};
let changed = false;
for (const [sessionId, activity] of Object.entries(current)) {
if (!allowed.has(sessionId)) {
changed = true;
continue;
}
next[sessionId] = activity;
}
return changed || Object.keys(next).length !== Object.keys(current).length ? next : current;
}
export function formatSessionActivityLabel(
activity: SessionActivityState | undefined,
fallbackAgentName = 'Agent',
): string {
const agentName = activity?.agentName?.trim() || fallbackAgentName;
switch (activity?.activityType) {
case 'tool-calling':
return `${agentName} is using ${activity.toolName?.trim() || 'a tool'}`;
case 'handoff':
return `Handing off to ${agentName}`;
case 'completed':
return `${agentName} completed their turn.`;
case 'thinking':
default:
return `${agentName} is thinking…`;
}
}
export function shouldAnimateSessionActivity(activity: SessionActivityState | undefined): boolean {
return activity?.activityType !== 'completed';
}
function removeSessionActivity(
current: SessionActivityMap,
sessionId: string,
): SessionActivityMap {
if (!(sessionId in current)) {
return current;
}
const next = { ...current };
delete next[sessionId];
return next;
}