From a4ccd75ff37878f7001f9b980e07a11ec3538379 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:18:03 -0500 Subject: [PATCH] fix: dedupe verbose subagent status counts --- src/auto-reply/reply/commands-status.test.ts | 56 ++++++++++++++++++++ src/auto-reply/reply/commands-status.ts | 11 +++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/reply/commands-status.test.ts b/src/auto-reply/reply/commands-status.test.ts index 13bcb7e7acb..da710750afa 100644 --- a/src/auto-reply/reply/commands-status.test.ts +++ b/src/auto-reply/reply/commands-status.test.ts @@ -70,4 +70,60 @@ describe("buildStatusReply subagent summary", () => { expect(reply?.text).toContain("🤖 Subagents: 1 active"); }); + + it("dedupes stale rows in the verbose subagent status summary", async () => { + const childSessionKey = "agent:main:subagent:status-dedupe-worker"; + addSubagentRunForTests({ + runId: "run-status-current", + childSessionKey, + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "current status worker", + cleanup: "keep", + createdAt: Date.now() - 60_000, + startedAt: Date.now() - 60_000, + }); + addSubagentRunForTests({ + runId: "run-status-stale", + childSessionKey, + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "stale status worker", + cleanup: "keep", + createdAt: Date.now() - 120_000, + startedAt: Date.now() - 120_000, + endedAt: Date.now() - 90_000, + outcome: { status: "ok" }, + }); + + const cfg = { + commands: { text: true }, + channels: { whatsapp: { allowFrom: ["*"] } }, + session: { mainKey: "main", scope: "per-sender" }, + } as OpenClawConfig; + const params = buildCommandTestParams("/status", cfg); + const reply = await buildStatusReply({ + cfg, + command: params.command, + sessionEntry: params.sessionEntry, + sessionKey: params.sessionKey, + parentSessionKey: params.sessionKey, + sessionScope: params.sessionScope, + storePath: params.storePath, + provider: "anthropic", + model: "claude-opus-4-5", + contextTokens: 0, + resolvedThinkLevel: params.resolvedThinkLevel, + resolvedFastMode: false, + resolvedVerboseLevel: "on", + resolvedReasoningLevel: params.resolvedReasoningLevel, + resolvedElevatedLevel: params.resolvedElevatedLevel, + resolveDefaultThinkingLevel: params.resolveDefaultThinkingLevel, + isGroup: params.isGroup, + defaultGroupActivation: params.defaultGroupActivation, + }); + + expect(reply?.text).toContain("🤖 Subagents: 1 active"); + expect(reply?.text).not.toContain("· 1 done"); + }); }); diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 9e3d3e08927..5766fdc4423 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -30,7 +30,7 @@ import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from ".. import type { ReplyPayload } from "../types.js"; import type { CommandContext } from "./commands-types.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js"; -import { resolveSubagentLabel } from "./subagents-utils.js"; +import { resolveSubagentLabel, sortSubagentRuns } from "./subagents-utils.js"; // Some usage endpoints only work with CLI/session OAuth tokens, not API keys. // Skip those probes when the active auth mode cannot satisfy the endpoint. @@ -188,7 +188,14 @@ export async function buildStatusReply(params: { if (sessionKey) { const { mainKey, alias } = resolveMainSessionAlias(cfg); const requesterKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey }); - const runs = listSubagentRunsForRequester(requesterKey); + const seenChildSessionKeys = new Set(); + const runs = sortSubagentRuns(listSubagentRunsForRequester(requesterKey)).filter((entry) => { + if (seenChildSessionKeys.has(entry.childSessionKey)) { + return false; + } + seenChildSessionKeys.add(entry.childSessionKey); + return true; + }); const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off"; if (runs.length > 0) { const active = runs.filter(