From 97cef49046243c3aa502ae1df9a096af229cb9f4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 05:14:23 +0000 Subject: [PATCH] refactor: share subagent helpers --- src/auto-reply/reply/commands-status.ts | 3 +- src/auto-reply/reply/commands-subagents.ts | 53 ++++------------------ src/auto-reply/reply/subagents-utils.ts | 53 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 src/auto-reply/reply/subagents-utils.ts diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 7fb5df45f..55219a2c1 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -27,6 +27,7 @@ import type { ReplyPayload } from "../types.js"; import type { CommandContext } from "./commands-types.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js"; import type { MediaUnderstandingDecision } from "../../media-understanding/types.js"; +import { resolveSubagentLabel } from "./subagents-utils.js"; function formatApiKeySnippet(apiKey: string): string { const compact = apiKey.replace(/\s+/g, ""); @@ -187,7 +188,7 @@ export async function buildStatusReply(params: { const done = runs.length - active.length; if (verboseEnabled) { const labels = active - .map((entry) => entry.label?.trim() || entry.task?.trim() || "") + .map((entry) => resolveSubagentLabel(entry, "")) .filter(Boolean) .slice(0, 3); const labelText = labels.length ? ` (${labels.join(", ")})` : ""; diff --git a/src/auto-reply/reply/commands-subagents.ts b/src/auto-reply/reply/commands-subagents.ts index 892232bb3..434c30ef7 100644 --- a/src/auto-reply/reply/commands-subagents.ts +++ b/src/auto-reply/reply/commands-subagents.ts @@ -15,7 +15,13 @@ import { callGateway } from "../../gateway/call.js"; import { logVerbose } from "../../globals.js"; import { parseAgentSessionKey } from "../../routing/session-key.js"; import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js"; -import { truncateUtf16Safe } from "../../utils.js"; +import { + formatAgeShort, + formatDurationShort, + formatRunLabel, + formatRunStatus, + sortSubagentRuns, +} from "./subagents-utils.js"; import { stopSubagentsForRequester } from "./abort.js"; import type { CommandHandler } from "./commands-types.js"; import { clearSessionQueues } from "./queue.js"; @@ -28,28 +34,6 @@ type SubagentTargetResolution = { const COMMAND = "/subagents"; const ACTIONS = new Set(["list", "stop", "log", "send", "info", "help"]); -function formatDurationShort(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; - const totalSeconds = Math.round(valueMs / 1000); - const hours = Math.floor(totalSeconds / 3600); - const minutes = Math.floor((totalSeconds % 3600) / 60); - const seconds = totalSeconds % 60; - if (hours > 0) return `${hours}h${minutes}m`; - if (minutes > 0) return `${minutes}m${seconds}s`; - return `${seconds}s`; -} - -function formatAgeShort(valueMs?: number) { - if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; - const minutes = Math.round(valueMs / 60_000); - if (minutes < 1) return "just now"; - if (minutes < 60) return `${minutes}m ago`; - const hours = Math.round(minutes / 60); - if (hours < 48) return `${hours}h ago`; - const days = Math.round(hours / 24); - return `${days}d ago`; -} - function formatTimestamp(valueMs?: number) { if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; return new Date(valueMs).toISOString(); @@ -60,25 +44,6 @@ function formatTimestampWithAge(valueMs?: number) { return `${formatTimestamp(valueMs)} (${formatAgeShort(Date.now() - valueMs)})`; } -function formatRunStatus(entry: SubagentRunRecord) { - if (!entry.endedAt) return "running"; - const status = entry.outcome?.status ?? "done"; - return status === "ok" ? "done" : status; -} - -function formatRunLabel(entry: SubagentRunRecord) { - const raw = entry.label?.trim() || entry.task?.trim() || "subagent"; - return raw.length > 72 ? `${truncateUtf16Safe(raw, 72).trimEnd()}…` : raw; -} - -function sortRuns(runs: SubagentRunRecord[]) { - return [...runs].sort((a, b) => { - const aTime = a.startedAt ?? a.createdAt ?? 0; - const bTime = b.startedAt ?? b.createdAt ?? 0; - return bTime - aTime; - }); -} - function resolveRequesterSessionKey(params: Parameters[0]): string | undefined { const raw = params.ctx.CommandTargetSessionKey?.trim() || params.sessionKey; if (!raw) return undefined; @@ -93,7 +58,7 @@ function resolveSubagentTarget( const trimmed = token?.trim(); if (!trimmed) return { error: "Missing subagent id." }; if (trimmed === "last") { - const sorted = sortRuns(runs); + const sorted = sortSubagentRuns(runs); return { entry: sorted[0] }; } const sorted = sortRuns(runs); @@ -212,7 +177,7 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo if (runs.length === 0) { return { shouldContinue: false, reply: { text: "🧭 Subagents: none for this session." } }; } - const sorted = sortRuns(runs); + const sorted = sortSubagentRuns(runs); const active = sorted.filter((entry) => !entry.endedAt); const done = sorted.length - active.length; const lines = [ diff --git a/src/auto-reply/reply/subagents-utils.ts b/src/auto-reply/reply/subagents-utils.ts new file mode 100644 index 000000000..238f3b8c7 --- /dev/null +++ b/src/auto-reply/reply/subagents-utils.ts @@ -0,0 +1,53 @@ +import type { SubagentRunRecord } from "../../agents/subagent-registry.js"; +import { truncateUtf16Safe } from "../../utils.js"; + +export function formatDurationShort(valueMs?: number) { + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + const totalSeconds = Math.round(valueMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + if (hours > 0) return `${hours}h${minutes}m`; + if (minutes > 0) return `${minutes}m${seconds}s`; + return `${seconds}s`; +} + +export function formatAgeShort(valueMs?: number) { + if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) return "n/a"; + const minutes = Math.round(valueMs / 60_000); + if (minutes < 1) return "just now"; + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.round(minutes / 60); + if (hours < 48) return `${hours}h ago`; + const days = Math.round(hours / 24); + return `${days}d ago`; +} + +export function resolveSubagentLabel(entry: SubagentRunRecord, fallback = "subagent") { + const raw = entry.label?.trim() || entry.task?.trim() || ""; + return raw || fallback; +} + +export function formatRunLabel( + entry: SubagentRunRecord, + options?: { maxLength?: number }, +) { + const raw = resolveSubagentLabel(entry); + const maxLength = options?.maxLength ?? 72; + if (!Number.isFinite(maxLength) || maxLength <= 0) return raw; + return raw.length > maxLength ? `${truncateUtf16Safe(raw, maxLength).trimEnd()}…` : raw; +} + +export function formatRunStatus(entry: SubagentRunRecord) { + if (!entry.endedAt) return "running"; + const status = entry.outcome?.status ?? "done"; + return status === "ok" ? "done" : status; +} + +export function sortSubagentRuns(runs: SubagentRunRecord[]) { + return [...runs].sort((a, b) => { + const aTime = a.startedAt ?? a.createdAt ?? 0; + const bTime = b.startedAt ?? b.createdAt ?? 0; + return bTime - aTime; + }); +}