diff --git a/src/auto-reply/reply/commands-subagents/action-agents.test.ts b/src/auto-reply/reply/commands-subagents/action-agents.test.ts index de3d1d2e32d..1a3332073fe 100644 --- a/src/auto-reply/reply/commands-subagents/action-agents.test.ts +++ b/src/auto-reply/reply/commands-subagents/action-agents.test.ts @@ -75,4 +75,71 @@ describe("handleSubagentsAgentsAction", () => { expect(result.reply?.text).toContain("current worker label"); expect(result.reply?.text).not.toContain("stale worker label"); }); + + it("keeps /agents numbering aligned with target resolution when hidden recent rows exist", () => { + const hiddenSessionKey = "agent:main:subagent:hidden-recent"; + const visibleSessionKey = "agent:main:subagent:visible-bound"; + listBySessionMock.mockImplementation((sessionKey: string) => + sessionKey === visibleSessionKey + ? [ + { + bindingId: "binding-visible", + targetSessionKey: visibleSessionKey, + targetKind: "subagent", + conversation: { + channel: "discord", + accountId: "default", + conversationId: "thread-visible", + }, + status: "active", + boundAt: Date.now() - 20_000, + }, + ] + : [], + ); + + const result = handleSubagentsAgentsAction({ + params: { + ctx: { + Provider: "discord", + Surface: "discord", + }, + command: { + channel: "discord", + }, + }, + requesterKey: "agent:main:main", + runs: [ + { + runId: "run-hidden-recent", + childSessionKey: hiddenSessionKey, + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "hidden recent worker", + cleanup: "keep", + createdAt: Date.now() - 10_000, + startedAt: Date.now() - 10_000, + endedAt: Date.now() - 5_000, + outcome: { status: "ok" }, + }, + { + runId: "run-visible-bound", + childSessionKey: visibleSessionKey, + requesterSessionKey: "agent:main:main", + requesterDisplayKey: "main", + task: "visible bound worker", + cleanup: "keep", + createdAt: Date.now() - 20_000, + startedAt: Date.now() - 20_000, + endedAt: Date.now() - 15_000, + outcome: { status: "ok" }, + }, + ], + restTokens: [], + } as never); + + expect(result.reply?.text).toContain("2. visible bound worker"); + expect(result.reply?.text).not.toContain("1. visible bound worker"); + expect(result.reply?.text).not.toContain("hidden recent worker"); + }); }); diff --git a/src/auto-reply/reply/commands-subagents/action-agents.ts b/src/auto-reply/reply/commands-subagents/action-agents.ts index cb415fe8553..8ec91aa5829 100644 --- a/src/auto-reply/reply/commands-subagents/action-agents.ts +++ b/src/auto-reply/reply/commands-subagents/action-agents.ts @@ -3,6 +3,7 @@ import { getSessionBindingService } from "../../../infra/outbound/session-bindin import type { CommandHandlerResult } from "../commands-types.js"; import { formatRunLabel, sortSubagentRuns } from "../subagents-utils.js"; import { + RECENT_WINDOW_MINUTES, type SubagentsCommandContext, resolveChannelAccountId, resolveCommandSurfaceChannel, @@ -46,12 +47,34 @@ export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): Comma return resolved; }; - const visibleRuns: typeof runs = []; + const dedupedRuns: typeof runs = []; const seenChildSessionKeys = new Set(); for (const entry of sortSubagentRuns(runs)) { if (seenChildSessionKeys.has(entry.childSessionKey)) { continue; } + seenChildSessionKeys.add(entry.childSessionKey); + dedupedRuns.push(entry); + } + + const recentCutoff = Date.now() - RECENT_WINDOW_MINUTES * 60_000; + const numericOrder = [ + ...dedupedRuns.filter( + (entry) => !entry.endedAt || countPendingDescendantRuns(entry.childSessionKey) > 0, + ), + ...dedupedRuns.filter( + (entry) => + entry.endedAt && + countPendingDescendantRuns(entry.childSessionKey) === 0 && + entry.endedAt >= recentCutoff, + ), + ]; + const indexByChildSessionKey = new Map( + numericOrder.map((entry, idx) => [entry.childSessionKey, idx + 1] as const), + ); + + const visibleRuns: typeof dedupedRuns = []; + for (const entry of dedupedRuns) { const visible = !entry.endedAt || countPendingDescendantRuns(entry.childSessionKey) > 0 || @@ -59,7 +82,6 @@ export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): Comma if (!visible) { continue; } - seenChildSessionKeys.add(entry.childSessionKey); visibleRuns.push(entry); } @@ -67,7 +89,6 @@ export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): Comma if (visibleRuns.length === 0) { lines.push("(none)"); } else { - let index = 1; for (const entry of visibleRuns) { const binding = resolveSessionBindings(entry.childSessionKey)[0]; const bindingText = binding @@ -78,8 +99,9 @@ export function handleSubagentsAgentsAction(ctx: SubagentsCommandContext): Comma : channel === "discord" || channel === "telegram" ? "unbound" : "bindings available on discord/telegram"; - lines.push(`${index}. ${formatRunLabel(entry)} (${bindingText})`); - index += 1; + const resolvedIndex = indexByChildSessionKey.get(entry.childSessionKey); + const prefix = resolvedIndex ? `${resolvedIndex}.` : "-"; + lines.push(`${prefix} ${formatRunLabel(entry)} (${bindingText})`); } }