Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions packages/client-runtime/src/state/subagentRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,62 @@ describe("deriveAgentPanelModel", () => {
expect(model.liveCount).toBe(0);
});

it("counts a running workflow coordinator between phases", () => {
const betweenPhases = fold([
activity("task.started", {
taskId: "wf-between-phases",
taskType: "local_workflow",
title: "review",
}),
activity("task.progress", {
taskId: "wf-between-phases:wf:0",
title: "review:a",
status: "completed",
parentAgentId: "wf-between-phases",
agentIndex: 0,
phaseIndex: 0,
}),
activity("task.completed", {
taskId: "wf-between-phases:wf:0",
status: "completed",
parentAgentId: "wf-between-phases",
}),
]);

const model = deriveAgentPanelModel({ agents: betweenPhases });

expect(model.runningCount).toBe(1);
expect(model.settledCount).toBe(1);
expect(model.liveCount).toBe(1);
expect(model.runningCount + model.waitingCount + model.idleCount + model.settledCount).toBe(
betweenPhases.length,
);
});

it("does not count a workflow coordinator while a member is idle", () => {
const idleMember = fold([
activity("task.started", {
taskId: "wf-idle-member",
taskType: "local_workflow",
title: "review",
}),
activity("task.progress", {
taskId: "wf-idle-member:wf:0",
title: "review:a",
status: "idle",
parentAgentId: "wf-idle-member",
agentIndex: 0,
phaseIndex: 0,
}),
]);

const model = deriveAgentPanelModel({ agents: idleMember });

expect(model.runningCount).toBe(0);
expect(model.idleCount).toBe(1);
expect(model.liveCount).toBe(0);
});

it("keeps direct spawns in first-seen order as their activity changes", () => {
const directRoster = fold([
activity("task.started", { taskId: "direct-a", title: "First" }, "2026-08-01T11:00:00.000Z"),
Expand Down
31 changes: 21 additions & 10 deletions packages/client-runtime/src/state/subagentRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,16 +826,27 @@ export function deriveAgentPanelModel({
let settledCount = 0;
let totalTokens = 0;
for (const agent of source) {
// A workflow coordinator with members is a container for those members, not
// work of its own: it reports running for the whole run and aggregates their
// usage upstream in some providers. Counting it would report one more agent
// working than there are, and double count tokens.
if (agent.kind === "workflow" && (members.get(agent.id) ?? []).length > 0) continue;
if (agent.status === "running" || agent.status === "pending") runningCount += 1;
else if (agent.status === "waiting") waitingCount += 1;
else if (agent.status === "idle") idleCount += 1;
else settledCount += 1;
totalTokens += agent.usage?.totalTokens ?? 0;
const workflowMembers = members.get(agent.id) ?? [];
const standsInForMembers =
agent.kind === "workflow" &&
workflowMembers.length > 0 &&
!(
isActiveSubagentStatus(agent.status) &&
workflowMembers.every((member) => isTerminalSubagentStatus(member.status))
);
Comment thread
cursor[bot] marked this conversation as resolved.

if (!standsInForMembers) {
if (agent.status === "running" || agent.status === "pending") runningCount += 1;
else if (agent.status === "waiting") waitingCount += 1;
else if (agent.status === "idle") idleCount += 1;
else settledCount += 1;
}

// Workflow usage may aggregate member usage upstream, including while the
// coordinator is the only live unit between phases.
if (agent.kind !== "workflow" || workflowMembers.length === 0) {
totalTokens += agent.usage?.totalTokens ?? 0;
}
}

return {
Expand Down
Loading