fix(subagents): correct duration display showing 5-6x inflated runtime (#57739)

Merged via squash.

Prepared head SHA: 018bbbca4d
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
This commit is contained in:
samzong 2026-03-30 23:44:36 +08:00 committed by GitHub
parent f011d0be28
commit 09bb93c6e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 8 additions and 22 deletions

View File

@ -103,6 +103,7 @@ Docs: https://docs.openclaw.ai
- Agents/MCP: dispose bundled MCP runtimes after one-shot `openclaw agent --local` runs finish, while preserving bundled MCP state across in-run retries so local JSON runs exit cleanly without restarting stateful MCP tools mid-run.
- Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit `x-openclaw-scopes`, so headless `/v1/chat/completions` and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.
- Gateway/attachments: offload large inbound images without leaking `media://` markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.
- Agents/subagents: fix interim subagent runtime display so `/subagents list` and `/subagents info` stop inflating short runtimes and show second-level durations correctly. (#57739) Thanks @samzong.
## 2026.3.28

View File

@ -345,7 +345,7 @@ export function buildSubagentList(params: {
}),
),
);
const runtime = formatDurationCompact(runtimeMs);
const runtime = formatDurationCompact(runtimeMs) ?? "n/a";
const label = truncateLine(resolveSubagentLabel(entry), 48);
const task = truncateLine(entry.task.trim(), params.taskMaxChars ?? 72);
const line = `${index}. ${label} (${resolveModelDisplay(sessionEntry, entry.model)}, ${runtime}${usageText ? `, ${usageText}` : ""}) ${status}${task.toLowerCase() !== label.toLowerCase() ? ` - ${task}` : ""}`;

View File

@ -148,7 +148,7 @@ export function formatSubagentListLine(params: {
const usageText = formatTokenUsageDisplay(params.sessionEntry);
const label = truncateLine(formatRunLabel(params.entry, { maxLength: 48 }), 48);
const task = formatTaskPreview(params.entry.task);
const runtime = formatDurationCompact(params.runtimeMs);
const runtime = formatDurationCompact(params.runtimeMs) ?? "n/a";
const status = resolveDisplayStatus(params.entry, {
pendingDescendants: params.pendingDescendants,
});

View File

@ -9,9 +9,10 @@ import {
} from "./subagents-format.js";
describe("shared/subagents-format", () => {
it("formats compact durations across minute, hour, and day buckets", () => {
expect(formatDurationCompact()).toBe("n/a");
expect(formatDurationCompact(30_000)).toBe("1m");
it("re-exports the canonical formatter with second-level precision", () => {
expect(formatDurationCompact()).toBeUndefined();
expect(formatDurationCompact(30_000)).toBe("30s");
expect(formatDurationCompact(90_000)).toBe("1m30s");
expect(formatDurationCompact(60 * 60_000)).toBe("1h");
expect(formatDurationCompact(61 * 60_000)).toBe("1h1m");
expect(formatDurationCompact(24 * 60 * 60_000)).toBe("1d");

View File

@ -1,20 +1,4 @@
export function formatDurationCompact(valueMs?: number) {
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
return "n/a";
}
const minutes = Math.max(1, Math.round(valueMs / 60_000));
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const minutesRemainder = minutes % 60;
if (hours < 24) {
return minutesRemainder > 0 ? `${hours}h${minutesRemainder}m` : `${hours}h`;
}
const days = Math.floor(hours / 24);
const hoursRemainder = hours % 24;
return hoursRemainder > 0 ? `${days}d${hoursRemainder}h` : `${days}d`;
}
export { formatDurationCompact } from "../infra/format-time/format-duration.ts";
export function formatTokenShort(value?: number) {
if (!value || !Number.isFinite(value) || value <= 0) {