fix: subagent announce shows correct runtime, tokens, and output

Three fixes for subagent announce displaying "runtime 0s", "tokens n/a",
and "(no output)" even when the sub-agent completed successfully:

1. formatDurationShort: allow 0ms as valid (sub-second tasks are real),
   display as "<1s" instead of treating as invalid
2. waitForSessionUsage: increase polling from 4×200ms (800ms) to
   10×500ms (5s) so token data has time to persist
3. readLatestAssistantReply: add retry loop (5×500ms) for transcript
   readback when session data hasn't flushed yet

Fixes both copies of formatDurationShort (subagent-announce.ts and
subagents-utils.ts) for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Milofax 2026-01-30 16:24:23 +01:00
parent da71eaebd2
commit cf8c66d675
No known key found for this signature in database
3 changed files with 25 additions and 7 deletions

View File

@ -23,8 +23,10 @@ import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-que
import { readLatestAssistantReply } from "./tools/agent-step.js"; import { readLatestAssistantReply } from "./tools/agent-step.js";
function formatDurationShort(valueMs?: number) { function formatDurationShort(valueMs?: number) {
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return undefined; if (valueMs === undefined || valueMs === null || !Number.isFinite(valueMs) || valueMs < 0)
return undefined;
const totalSeconds = Math.round(valueMs / 1000); const totalSeconds = Math.round(valueMs / 1000);
if (totalSeconds === 0) return "<1s";
const hours = Math.floor(totalSeconds / 3600); const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60); const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60; const seconds = totalSeconds % 60;
@ -79,8 +81,8 @@ async function waitForSessionUsage(params: { sessionKey: string }) {
typeof entry.inputTokens === "number" || typeof entry.inputTokens === "number" ||
typeof entry.outputTokens === "number"); typeof entry.outputTokens === "number");
if (hasTokens()) return { entry, storePath }; if (hasTokens()) return { entry, storePath };
for (let attempt = 0; attempt < 4; attempt += 1) { for (let attempt = 0; attempt < 10; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 200)); await new Promise((resolve) => setTimeout(resolve, 500));
entry = loadSessionStore(storePath)[params.sessionKey]; entry = loadSessionStore(storePath)[params.sessionKey];
if (hasTokens()) break; if (hasTokens()) break;
} }
@ -359,10 +361,14 @@ export async function runSubagentAnnounceFlow(params: {
} }
if (!reply) { if (!reply) {
// Retry with backoff — transcript may not be flushed yet
for (let attempt = 0; attempt < 5 && !reply; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 500));
reply = await readLatestAssistantReply({ reply = await readLatestAssistantReply({
sessionKey: params.childSessionKey, sessionKey: params.childSessionKey,
}); });
} }
}
if (!outcome) outcome = { status: "unknown" }; if (!outcome) outcome = { status: "unknown" };

View File

@ -59,4 +59,14 @@ describe("subagents utils", () => {
expect(formatDurationShort(45_000)).toBe("45s"); expect(formatDurationShort(45_000)).toBe("45s");
expect(formatDurationShort(65_000)).toBe("1m5s"); expect(formatDurationShort(65_000)).toBe("1m5s");
}); });
it("formats zero duration as <1s instead of n/a", () => {
expect(formatDurationShort(0)).toBe("<1s");
expect(formatDurationShort(499)).toBe("<1s");
});
it("returns n/a for undefined/negative durations", () => {
expect(formatDurationShort(undefined)).toBe("n/a");
expect(formatDurationShort(-1)).toBe("n/a");
});
}); });

View File

@ -2,8 +2,10 @@ import type { SubagentRunRecord } from "../../agents/subagent-registry.js";
import { truncateUtf16Safe } from "../../utils.js"; import { truncateUtf16Safe } from "../../utils.js";
export function formatDurationShort(valueMs?: number) { export function formatDurationShort(valueMs?: number) {
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; if (valueMs === undefined || valueMs === null || !Number.isFinite(valueMs) || valueMs < 0)
return "n/a";
const totalSeconds = Math.round(valueMs / 1000); const totalSeconds = Math.round(valueMs / 1000);
if (totalSeconds === 0) return "<1s";
const hours = Math.floor(totalSeconds / 3600); const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60); const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60; const seconds = totalSeconds % 60;