import { resolveStoredSubagentCapabilities } from "../../../agents/subagent-capabilities.js"; import type { ResolvedSubagentController } from "../../../agents/subagent-control.js"; import { countPendingDescendantRuns, type SubagentRunRecord, } from "../../../agents/subagent-registry.js"; import { extractAssistantText, resolveInternalSessionKey, resolveMainSessionAlias, sanitizeTextContent, stripToolMessages, } from "../../../agents/tools/sessions-helpers.js"; import type { SessionEntry, loadSessionStore as loadSessionStoreFn, resolveStorePath as resolveStorePathFn, } from "../../../config/sessions.js"; import { parseDiscordTarget } from "../../../discord/targets.js"; import { callGateway } from "../../../gateway/call.js"; import { formatTimeAgo } from "../../../infra/format-time/format-relative.ts"; import { parseAgentSessionKey } from "../../../routing/session-key.js"; import { isSubagentSessionKey } from "../../../routing/session-key.js"; import { looksLikeSessionId } from "../../../sessions/session-id.js"; import { extractTextFromChatContent } from "../../../shared/chat-content.js"; import { formatDurationCompact, formatTokenUsageDisplay, truncateLine, } from "../../../shared/subagents-format.js"; import { isDiscordSurface, isTelegramSurface, resolveCommandSurfaceChannel, resolveDiscordAccountId, resolveChannelAccountId, } from "../channel-context.js"; import type { CommandHandler, CommandHandlerResult } from "../commands-types.js"; import { formatRunLabel, formatRunStatus, resolveSubagentTargetFromRuns, type SubagentTargetResolution, } from "../subagents-utils.js"; import { resolveTelegramConversationId } from "../telegram-context.js"; export { extractAssistantText, stripToolMessages }; export { isDiscordSurface, isTelegramSurface, resolveCommandSurfaceChannel, resolveDiscordAccountId, resolveChannelAccountId, resolveTelegramConversationId, }; export const COMMAND = "/subagents"; export const COMMAND_KILL = "/kill"; export const COMMAND_STEER = "/steer"; export const COMMAND_TELL = "/tell"; export const COMMAND_FOCUS = "/focus"; export const COMMAND_UNFOCUS = "/unfocus"; export const COMMAND_AGENTS = "/agents"; export const ACTIONS = new Set([ "list", "kill", "log", "send", "steer", "info", "spawn", "focus", "unfocus", "agents", "help", ]); export const RECENT_WINDOW_MINUTES = 30; const SUBAGENT_TASK_PREVIEW_MAX = 110; export const STEER_ABORT_SETTLE_TIMEOUT_MS = 5_000; function compactLine(value: string) { return value.replace(/\s+/g, " ").trim(); } function formatTaskPreview(value: string) { return truncateLine(compactLine(value), SUBAGENT_TASK_PREVIEW_MAX); } function resolveModelDisplay( entry?: { model?: unknown; modelProvider?: unknown; modelOverride?: unknown; providerOverride?: unknown; }, fallbackModel?: string, ) { const model = typeof entry?.model === "string" ? entry.model.trim() : ""; const provider = typeof entry?.modelProvider === "string" ? entry.modelProvider.trim() : ""; let combined = model.includes("/") ? model : model && provider ? `${provider}/${model}` : model; if (!combined) { const overrideModel = typeof entry?.modelOverride === "string" ? entry.modelOverride.trim() : ""; const overrideProvider = typeof entry?.providerOverride === "string" ? entry.providerOverride.trim() : ""; combined = overrideModel.includes("/") ? overrideModel : overrideModel && overrideProvider ? `${overrideProvider}/${overrideModel}` : overrideModel; } if (!combined) { combined = fallbackModel?.trim() || ""; } if (!combined) { return "model n/a"; } const slash = combined.lastIndexOf("/"); if (slash >= 0 && slash < combined.length - 1) { return combined.slice(slash + 1); } return combined; } export function resolveDisplayStatus( entry: SubagentRunRecord, options?: { pendingDescendants?: number }, ) { const pendingDescendants = Math.max(0, options?.pendingDescendants ?? 0); if (pendingDescendants > 0) { const childLabel = pendingDescendants === 1 ? "child" : "children"; return `active (waiting on ${pendingDescendants} ${childLabel})`; } const status = formatRunStatus(entry); return status === "error" ? "failed" : status; } export function formatSubagentListLine(params: { entry: SubagentRunRecord; index: number; runtimeMs: number; sessionEntry?: SessionEntry; pendingDescendants?: number; }) { 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 status = resolveDisplayStatus(params.entry, { pendingDescendants: params.pendingDescendants, }); return `${params.index}. ${label} (${resolveModelDisplay(params.sessionEntry, params.entry.model)}, ${runtime}${usageText ? `, ${usageText}` : ""}) ${status}${task.toLowerCase() !== label.toLowerCase() ? ` - ${task}` : ""}`; } function formatTimestamp(valueMs?: number) { if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { return "n/a"; } return new Date(valueMs).toISOString(); } export function formatTimestampWithAge(valueMs?: number) { if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) { return "n/a"; } return `${formatTimestamp(valueMs)} (${formatTimeAgo(Date.now() - valueMs, { fallback: "n/a" })})`; } export type SubagentsAction = | "list" | "kill" | "log" | "send" | "steer" | "info" | "spawn" | "focus" | "unfocus" | "agents" | "help"; export type SubagentsCommandParams = Parameters[0]; export type SubagentsCommandContext = { params: SubagentsCommandParams; handledPrefix: string; requesterKey: string; runs: SubagentRunRecord[]; restTokens: string[]; }; export function stopWithText(text: string): CommandHandlerResult { return { shouldContinue: false, reply: { text } }; } export function stopWithUnknownTargetError(error?: string): CommandHandlerResult { return stopWithText(`⚠️ ${error ?? "Unknown subagent."}`); } export function resolveSubagentTarget( runs: SubagentRunRecord[], token: string | undefined, ): SubagentTargetResolution { return resolveSubagentTargetFromRuns({ runs, token, recentWindowMinutes: RECENT_WINDOW_MINUTES, label: (entry) => formatRunLabel(entry), isActive: (entry) => !entry.endedAt || Math.max(0, countPendingDescendantRuns(entry.childSessionKey)) > 0, errors: { missingTarget: "Missing subagent id.", invalidIndex: (value) => `Invalid subagent index: ${value}`, unknownSession: (value) => `Unknown subagent session: ${value}`, ambiguousLabel: (value) => `Ambiguous subagent label: ${value}`, ambiguousLabelPrefix: (value) => `Ambiguous subagent label prefix: ${value}`, ambiguousRunIdPrefix: (value) => `Ambiguous run id prefix: ${value}`, unknownTarget: (value) => `Unknown subagent id: ${value}`, }, }); } export function resolveSubagentEntryForToken( runs: SubagentRunRecord[], token: string | undefined, ): { entry: SubagentRunRecord } | { reply: CommandHandlerResult } { const resolved = resolveSubagentTarget(runs, token); if (!resolved.entry) { return { reply: stopWithUnknownTargetError(resolved.error) }; } return { entry: resolved.entry }; } export function resolveRequesterSessionKey( params: SubagentsCommandParams, opts?: { preferCommandTarget?: boolean }, ): string | undefined { const commandTarget = params.ctx.CommandTargetSessionKey?.trim(); const commandSession = params.sessionKey?.trim(); const shouldPreferCommandTarget = opts?.preferCommandTarget ?? params.ctx.CommandSource === "native"; const raw = shouldPreferCommandTarget ? commandTarget || commandSession : commandSession || commandTarget; if (!raw) { return undefined; } const { mainKey, alias } = resolveMainSessionAlias(params.cfg); return resolveInternalSessionKey({ key: raw, alias, mainKey }); } export function resolveCommandSubagentController( params: SubagentsCommandParams, requesterKey: string, ): ResolvedSubagentController { if (!isSubagentSessionKey(requesterKey)) { return { controllerSessionKey: requesterKey, callerSessionKey: requesterKey, callerIsSubagent: false, controlScope: "children", }; } const capabilities = resolveStoredSubagentCapabilities(requesterKey, { cfg: params.cfg, }); return { controllerSessionKey: requesterKey, callerSessionKey: requesterKey, callerIsSubagent: true, controlScope: capabilities.controlScope, }; } export function resolveHandledPrefix(normalized: string): string | null { return normalized.startsWith(COMMAND) ? COMMAND : normalized.startsWith(COMMAND_KILL) ? COMMAND_KILL : normalized.startsWith(COMMAND_STEER) ? COMMAND_STEER : normalized.startsWith(COMMAND_TELL) ? COMMAND_TELL : normalized.startsWith(COMMAND_FOCUS) ? COMMAND_FOCUS : normalized.startsWith(COMMAND_UNFOCUS) ? COMMAND_UNFOCUS : normalized.startsWith(COMMAND_AGENTS) ? COMMAND_AGENTS : null; } export function resolveSubagentsAction(params: { handledPrefix: string; restTokens: string[]; }): SubagentsAction | null { if (params.handledPrefix === COMMAND) { const [actionRaw] = params.restTokens; const action = (actionRaw?.toLowerCase() || "list") as SubagentsAction; if (!ACTIONS.has(action)) { return null; } params.restTokens.splice(0, 1); return action; } if (params.handledPrefix === COMMAND_KILL) { return "kill"; } if (params.handledPrefix === COMMAND_FOCUS) { return "focus"; } if (params.handledPrefix === COMMAND_UNFOCUS) { return "unfocus"; } if (params.handledPrefix === COMMAND_AGENTS) { return "agents"; } return "steer"; } export type FocusTargetResolution = { targetKind: "subagent" | "acp"; targetSessionKey: string; agentId: string; label?: string; }; export function resolveDiscordChannelIdForFocus( params: SubagentsCommandParams, ): string | undefined { const toCandidates = [ typeof params.ctx.OriginatingTo === "string" ? params.ctx.OriginatingTo.trim() : "", typeof params.command.to === "string" ? params.command.to.trim() : "", typeof params.ctx.To === "string" ? params.ctx.To.trim() : "", ].filter(Boolean); for (const candidate of toCandidates) { try { const target = parseDiscordTarget(candidate, { defaultKind: "channel" }); if (target?.kind === "channel" && target.id) { return target.id; } } catch { // Ignore parse failures and try the next candidate. } } return undefined; } export async function resolveFocusTargetSession(params: { runs: SubagentRunRecord[]; token: string; }): Promise { const subagentMatch = resolveSubagentTarget(params.runs, params.token); if (subagentMatch.entry) { const key = subagentMatch.entry.childSessionKey; const parsed = parseAgentSessionKey(key); return { targetKind: "subagent", targetSessionKey: key, agentId: parsed?.agentId ?? "main", label: formatRunLabel(subagentMatch.entry), }; } const token = params.token.trim(); if (!token) { return null; } const attempts: Array> = []; attempts.push({ key: token }); if (looksLikeSessionId(token)) { attempts.push({ sessionId: token }); } attempts.push({ label: token }); for (const attempt of attempts) { try { const resolved = await callGateway<{ key?: string }>({ method: "sessions.resolve", params: attempt, }); const key = typeof resolved?.key === "string" ? resolved.key.trim() : ""; if (!key) { continue; } const parsed = parseAgentSessionKey(key); return { targetKind: key.includes(":subagent:") ? "subagent" : "acp", targetSessionKey: key, agentId: parsed?.agentId ?? "main", label: token, }; } catch { // Try the next resolution strategy. } } return null; } export function buildSubagentsHelp() { return [ "Subagents", "Usage:", "- /subagents list", "- /subagents kill ", "- /subagents log [limit] [tools]", "- /subagents info ", "- /subagents send ", "- /subagents steer ", "- /subagents spawn [--model ] [--thinking ]", "- /focus ", "- /unfocus", "- /agents", "- /session idle ", "- /session max-age ", "- /kill ", "- /steer ", "- /tell ", "", "Ids: use the list index (#), runId/session prefix, label, or full session key.", ].join("\n"); } export type ChatMessage = { role?: unknown; content?: unknown; }; export function extractMessageText(message: ChatMessage): { role: string; text: string } | null { const role = typeof message.role === "string" ? message.role : ""; const shouldSanitize = role === "assistant"; const text = extractTextFromChatContent(message.content, { sanitizeText: shouldSanitize ? sanitizeTextContent : undefined, }); return text ? { role, text } : null; } export function formatLogLines(messages: ChatMessage[]) { const lines: string[] = []; for (const msg of messages) { const extracted = extractMessageText(msg); if (!extracted) { continue; } const label = extracted.role === "assistant" ? "Assistant" : "User"; lines.push(`${label}: ${extracted.text}`); } return lines; } export type SessionStoreCache = Map>; export function loadSubagentSessionEntry( params: SubagentsCommandParams, childKey: string, loaders: { loadSessionStore: typeof loadSessionStoreFn; resolveStorePath: typeof resolveStorePathFn; }, storeCache?: SessionStoreCache, ) { const parsed = parseAgentSessionKey(childKey); const storePath = loaders.resolveStorePath(params.cfg.session?.store, { agentId: parsed?.agentId, }); let store = storeCache?.get(storePath); if (!store) { store = loaders.loadSessionStore(storePath); storeCache?.set(storePath, store); } return { storePath, store, entry: store[childKey] }; }