Files
paseo/packages/app/src/utils/workspace-agent-activity.ts
T
Mohamed Boudra 1f5283f5a3 Surface cross-workspace subagents in their workspaces (#2061)
* fix(workspaces): show provider subagent activity

Provider subagent lifecycle lives outside managed agent snapshots, so workspace aggregation dropped it after the parent turn finished. Count running provider subagents against the delegation root and emit workspace updates when their status changes.

* fix(workspaces): surface cross-workspace subagents
2026-07-14 14:22:14 +02:00

74 lines
2.4 KiB
TypeScript

import type { Agent, WorkspaceDescriptor } from "@/stores/session-store";
import { isWorkspaceRootAgent } from "@/subagents/policies";
import { deriveSidebarStateBucket } from "./sidebar-agent-state";
export interface WorkspaceAgentActivity {
agentId: string;
status: WorkspaceDescriptor["status"];
enteredAt: Date | null;
}
export function buildWorkspaceAgentActivityIndex(
agents: ReadonlyMap<string, Agent>,
previous?: ReadonlyMap<string, WorkspaceAgentActivity>,
): Map<string, WorkspaceAgentActivity> {
const activityByWorkspaceId = new Map<string, WorkspaceAgentActivity>();
const latestActivityAtByWorkspaceId = new Map<string, Date>();
for (const agent of agents.values()) {
const parentAgent = agent.parentAgentId ? agents.get(agent.parentAgentId) : undefined;
if (agent.archivedAt || !agent.workspaceId || !isWorkspaceRootAgent(agent, parentAgent)) {
continue;
}
const enteredAt = agent.attentionTimestamp ?? agent.updatedAt;
const latestActivityAt = latestActivityAtByWorkspaceId.get(agent.workspaceId);
if (latestActivityAt && enteredAt <= latestActivityAt) {
continue;
}
latestActivityAtByWorkspaceId.set(agent.workspaceId, enteredAt);
const status = deriveSidebarStateBucket({
status: agent.status,
pendingPermissionCount: agent.pendingPermissions.length,
requiresAttention: agent.requiresAttention,
attentionReason: agent.attentionReason,
});
activityByWorkspaceId.set(agent.workspaceId, {
agentId: agent.id,
status,
enteredAt,
});
}
for (const [workspaceId, activity] of activityByWorkspaceId) {
const previousActivity = previous?.get(workspaceId);
if (
previousActivity?.agentId === activity.agentId &&
previousActivity.status === activity.status
) {
activityByWorkspaceId.set(workspaceId, previousActivity);
}
}
if (previous && areWorkspaceAgentActivityIndexesIdentical(previous, activityByWorkspaceId)) {
return previous instanceof Map ? previous : new Map(previous);
}
return activityByWorkspaceId;
}
function areWorkspaceAgentActivityIndexesIdentical(
previous: ReadonlyMap<string, WorkspaceAgentActivity>,
next: ReadonlyMap<string, WorkspaceAgentActivity>,
): boolean {
if (previous.size !== next.size) {
return false;
}
for (const [workspaceId, activity] of next) {
if (previous.get(workspaceId) !== activity) {
return false;
}
}
return true;
}