refactor: extract shared session status runtime

This commit is contained in:
Onur
2026-04-13 11:06:46 +02:00
committed by Onur Solmaz
parent 79a681cf75
commit 9f7ff4dd58
12 changed files with 386 additions and 395 deletions

View File

@@ -249,7 +249,7 @@ vi.mock("../plugins/providers.runtime.js", () => ({
vi.mock("../agents/auth-profiles.js", createAuthProfilesModuleMock);
vi.mock("../agents/model-auth.js", createModelAuthModuleMock);
vi.mock("../infra/provider-usage.js", createProviderUsageModuleMock);
vi.mock("../auto-reply/reply/commands-status.runtime.js", createCommandsStatusRuntimeModuleMock);
vi.mock("./tools/session-status.runtime.js", createCommandsStatusRuntimeModuleMock);
vi.mock("../auto-reply/group-activation.js", () => ({
normalizeGroupActivation: (value: unknown) => value ?? "always",
}));

View File

@@ -87,7 +87,7 @@ let commandsStatusRuntimePromise: Promise<CommandsStatusRuntimeModule> | null =
function loadCommandsStatusRuntime(): Promise<CommandsStatusRuntimeModule> {
commandsStatusRuntimePromise ??=
import("../../auto-reply/reply/commands-status.runtime.js") as Promise<CommandsStatusRuntimeModule>;
import("./session-status.runtime.js") as Promise<CommandsStatusRuntimeModule>;
return commandsStatusRuntimePromise;
}

View File

@@ -0,0 +1 @@
export { buildStatusText } from "../../status/status-text.js";

View File

@@ -1,19 +1,18 @@
import { formatRawAssistantErrorForUi } from "../agents/pi-embedded-helpers.js";
import type { SessionEntry } from "../config/sessions.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { FallbackNoticeState } from "../status/fallback-notice-state.js";
import { formatProviderModelRef } from "./model-runtime.js";
import type { RuntimeFallbackAttempt } from "./reply/agent-runner-execution.js";
export {
resolveActiveFallbackState,
type FallbackNoticeState,
} from "../status/fallback-notice-state.js";
const FALLBACK_REASON_PART_MAX = 80;
const TRANSIENT_FALLBACK_REASONS = new Set(["rate_limit", "overloaded", "timeout"]);
const TRANSIENT_ERROR_DETAIL_HINT_RE =
/\b(?:429|5\d\d|too many requests|usage limit|quota|try again in|retry[- ]after|seconds?|minutes?|hours?|temporarily unavailable|overloaded|service unavailable|throttl)\b/i;
export type FallbackNoticeState = Pick<
SessionEntry,
"fallbackNoticeSelectedModel" | "fallbackNoticeActiveModel" | "fallbackNoticeReason"
>;
function truncateFallbackReasonPart(value: string, max = FALLBACK_REASON_PART_MAX): string {
const text = value.replace(/\s+/g, " ").trim();
if (text.length <= max) {
@@ -110,24 +109,6 @@ export function buildFallbackClearedNotice(params: {
return `↪️ Model Fallback cleared: ${selected}`;
}
export function resolveActiveFallbackState(params: {
selectedModelRef: string;
activeModelRef: string;
state?: FallbackNoticeState;
}): { active: boolean; reason?: string } {
const selected = normalizeOptionalString(params.state?.fallbackNoticeSelectedModel);
const active = normalizeOptionalString(params.state?.fallbackNoticeActiveModel);
const reason = normalizeOptionalString(params.state?.fallbackNoticeReason);
const fallbackActive =
params.selectedModelRef !== params.activeModelRef &&
selected === params.selectedModelRef &&
active === params.activeModelRef;
return {
active: fallbackActive,
reason: fallbackActive ? reason : undefined,
};
}
export type ResolvedFallbackTransition = {
selectedModelRef: string;
activeModelRef: string;

View File

@@ -1 +1,2 @@
export { buildStatusReply, buildStatusText } from "./commands-status.js";
export { buildStatusReply } from "./commands-status.js";
export { buildStatusText } from "../../status/status-text.js";

View File

@@ -1,153 +1,16 @@
import {
resolveAgentConfig,
resolveAgentDir,
resolveDefaultAgentId,
resolveSessionAgentId,
resolveAgentModelFallbacksOverride,
} from "../../agents/agent-scope.js";
import { resolveFastModeState } from "../../agents/fast-mode.js";
import { resolveModelAuthLabel } from "../../agents/model-auth-label.js";
import {
resolveInternalSessionKey,
resolveMainSessionAlias,
} from "../../agents/tools/sessions-helpers.js";
import { toAgentModelListLike } from "../../config/model-input.js";
import type { SessionEntry, SessionScope } from "../../config/sessions.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { logVerbose } from "../../globals.js";
import {
formatUsageWindowSummary,
loadProviderUsageSummary,
resolveUsageProviderId,
} from "../../infra/provider-usage.js";
import type { MediaUnderstandingDecision } from "../../media-understanding/types.js";
import { importRuntimeModule } from "../../shared/runtime-import.js";
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
import {
listTasksForAgentIdForStatus,
listTasksForSessionKeyForStatus,
} from "../../tasks/task-status-access.js";
import {
buildTaskStatusSnapshot,
formatTaskStatusDetail,
formatTaskStatusTitle,
} from "../../tasks/task-status.js";
import { normalizeGroupActivation } from "../group-activation.js";
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
import { buildStatusText, type BuildStatusTextParams } from "../../status/status-text.js";
import type { ReplyPayload } from "../types.js";
import type { CommandContext } from "./commands-types.js";
import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js";
export { buildStatusText } from "../../status/status-text.js";
// Some usage endpoints only work with CLI/session OAuth tokens, not API keys.
// Skip those probes when the active auth mode cannot satisfy the endpoint.
const USAGE_OAUTH_ONLY_PROVIDERS = new Set([
"anthropic",
"github-copilot",
"google-gemini-cli",
"openai-codex",
]);
type StatusRuntimeModule = {
buildStatusMessage: (args: Record<string, unknown>) => string;
};
type CommandsStatusSubagentsModule = {
buildSubagentsStatusLine: (params: {
runs: Array<{ childSessionKey: string; endedAt?: number | null }>;
verboseEnabled: boolean;
pendingDescendantsForRun: (entry: { childSessionKey: string }) => number;
}) => string | undefined;
countPendingDescendantRuns: (rootSessionKey: string) => number;
listControlledSubagentRuns: (
controllerSessionKey: string,
) => Array<{ childSessionKey: string; endedAt?: number | null }>;
};
const STATUS_RUNTIME_SPEC = ["../status.runtime", ".js"] as const;
const COMMANDS_STATUS_DEPS_RUNTIME_SPEC = ["./commands-status-deps.runtime", ".js"] as const;
let statusRuntimePromise: Promise<StatusRuntimeModule> | null = null;
let commandsStatusDepsRuntimePromise: Promise<CommandsStatusSubagentsModule> | null = null;
function loadStatusRuntime(): Promise<StatusRuntimeModule> {
statusRuntimePromise ??= importRuntimeModule<StatusRuntimeModule>(
import.meta.url,
STATUS_RUNTIME_SPEC,
);
return statusRuntimePromise;
}
function loadCommandsStatusDepsRuntime(): Promise<CommandsStatusSubagentsModule> {
commandsStatusDepsRuntimePromise ??= importRuntimeModule<CommandsStatusSubagentsModule>(
import.meta.url,
COMMANDS_STATUS_DEPS_RUNTIME_SPEC,
);
return commandsStatusDepsRuntimePromise;
}
function shouldLoadUsageSummary(params: {
provider?: string;
selectedModelAuth?: string;
}): boolean {
if (!params.provider) {
return false;
}
if (!USAGE_OAUTH_ONLY_PROVIDERS.has(params.provider)) {
return true;
}
const auth = normalizeOptionalLowercaseString(params.selectedModelAuth);
return Boolean(auth?.startsWith("oauth") || auth?.startsWith("token"));
}
function formatSessionTaskLine(sessionKey: string): string | undefined {
const snapshot = buildTaskStatusSnapshot(listTasksForSessionKeyForStatus(sessionKey));
const task = snapshot.focus;
if (!task) {
return undefined;
}
const headline =
snapshot.activeCount > 0
? `${snapshot.activeCount} active · ${snapshot.totalCount} total`
: snapshot.recentFailureCount > 0
? `${snapshot.recentFailureCount} recent failure${snapshot.recentFailureCount === 1 ? "" : "s"}`
: "recently finished";
const title = formatTaskStatusTitle(task);
const detail = formatTaskStatusDetail(task);
const parts = [headline, task.runtime, title, detail].filter(Boolean);
return parts.length ? `📌 Tasks: ${parts.join(" · ")}` : undefined;
}
function formatAgentTaskCountsLine(agentId: string): string | undefined {
const snapshot = buildTaskStatusSnapshot(listTasksForAgentIdForStatus(agentId));
if (snapshot.totalCount === 0) {
return undefined;
}
return `📌 Tasks: ${snapshot.activeCount} active · ${snapshot.totalCount} total · agent-local`;
}
export async function buildStatusReply(params: {
cfg: OpenClawConfig;
type BuildStatusReplyParams = Omit<BuildStatusTextParams, "statusChannel"> & {
command: CommandContext;
sessionEntry?: SessionEntry;
sessionKey: string;
parentSessionKey?: string;
sessionScope?: SessionScope;
storePath?: string;
provider: string;
model: string;
contextTokens: number;
resolvedThinkLevel?: ThinkLevel;
resolvedFastMode?: boolean;
resolvedVerboseLevel: VerboseLevel;
resolvedReasoningLevel: ReasoningLevel;
resolvedElevatedLevel?: ElevatedLevel;
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
isGroup: boolean;
defaultGroupActivation: () => "always" | "mention";
mediaDecisions?: MediaUnderstandingDecision[];
modelAuthOverride?: string;
activeModelAuthOverride?: string;
}): Promise<ReplyPayload | undefined> {
};
export async function buildStatusReply(
params: BuildStatusReplyParams,
): Promise<ReplyPayload | undefined> {
const { command } = params;
if (!command.isAuthorizedSender) {
logVerbose(`Ignoring /status from unauthorized sender: ${command.senderId || "<unknown>"}`);
@@ -161,225 +24,3 @@ export async function buildStatusReply(params: {
}),
};
}
export async function buildStatusText(params: {
cfg: OpenClawConfig;
sessionEntry?: SessionEntry;
sessionKey: string;
parentSessionKey?: string;
sessionScope?: SessionScope;
storePath?: string;
statusChannel: string;
provider: string;
model: string;
contextTokens?: number;
resolvedThinkLevel?: ThinkLevel;
resolvedFastMode?: boolean;
resolvedVerboseLevel: VerboseLevel;
resolvedReasoningLevel: ReasoningLevel;
resolvedElevatedLevel?: ElevatedLevel;
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
isGroup: boolean;
defaultGroupActivation: () => "always" | "mention";
mediaDecisions?: MediaUnderstandingDecision[];
taskLineOverride?: string;
skipDefaultTaskLookup?: boolean;
primaryModelLabelOverride?: string;
modelAuthOverride?: string;
activeModelAuthOverride?: string;
includeTranscriptUsage?: boolean;
}): Promise<string> {
const {
cfg,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
storePath,
statusChannel,
provider,
model,
contextTokens,
resolvedThinkLevel,
resolvedFastMode,
resolvedVerboseLevel,
resolvedReasoningLevel,
resolvedElevatedLevel,
resolveDefaultThinkingLevel,
isGroup,
defaultGroupActivation,
} = params;
const statusAgentId = sessionKey
? resolveSessionAgentId({ sessionKey, config: cfg })
: resolveDefaultAgentId(cfg);
const statusAgentDir = resolveAgentDir(cfg, statusAgentId);
const modelRefs = resolveSelectedAndActiveModel({
selectedProvider: provider,
selectedModel: model,
sessionEntry,
});
const selectedModelAuth = Object.hasOwn(params, "modelAuthOverride")
? params.modelAuthOverride
: resolveModelAuthLabel({
provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
});
const activeModelAuth = Object.hasOwn(params, "activeModelAuthOverride")
? params.activeModelAuthOverride
: modelRefs.activeDiffers
? resolveModelAuthLabel({
provider: modelRefs.active.provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
})
: selectedModelAuth;
const currentUsageProvider = (() => {
try {
return resolveUsageProviderId(provider);
} catch {
return undefined;
}
})();
let usageLine: string | null = null;
if (
currentUsageProvider &&
shouldLoadUsageSummary({
provider: currentUsageProvider,
selectedModelAuth,
})
) {
try {
const usageSummaryTimeoutMs = 3500;
let usageTimeout: NodeJS.Timeout | undefined;
const usageSummary = await Promise.race([
loadProviderUsageSummary({
timeoutMs: usageSummaryTimeoutMs,
providers: [currentUsageProvider],
agentDir: statusAgentDir,
}),
new Promise<never>((_, reject) => {
usageTimeout = setTimeout(
() => reject(new Error("usage summary timeout")),
usageSummaryTimeoutMs,
);
}),
]).finally(() => {
if (usageTimeout) {
clearTimeout(usageTimeout);
}
});
const usageEntry = usageSummary.providers[0];
if (usageEntry && !usageEntry.error && usageEntry.windows.length > 0) {
const summaryLine = formatUsageWindowSummary(usageEntry, {
now: Date.now(),
maxWindows: 2,
includeResets: true,
});
if (summaryLine) {
usageLine = `📊 Usage: ${summaryLine}`;
}
}
} catch {
usageLine = null;
}
}
const queueSettings = resolveQueueSettings({
cfg,
channel: statusChannel,
sessionEntry,
});
const queueKey = sessionKey ?? sessionEntry?.sessionId;
const queueDepth = queueKey ? getFollowupQueueDepth(queueKey) : 0;
const queueOverrides = Boolean(
sessionEntry?.queueDebounceMs ?? sessionEntry?.queueCap ?? sessionEntry?.queueDrop,
);
let subagentsLine: string | undefined;
let taskLine: string | undefined;
if (sessionKey) {
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const requesterKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
taskLine = params.skipDefaultTaskLookup
? params.taskLineOverride
: (params.taskLineOverride ?? formatSessionTaskLine(requesterKey));
if (!taskLine && !params.skipDefaultTaskLookup) {
taskLine = formatAgentTaskCountsLine(statusAgentId);
}
const { buildSubagentsStatusLine, countPendingDescendantRuns, listControlledSubagentRuns } =
await loadCommandsStatusDepsRuntime();
const runs = listControlledSubagentRuns(requesterKey);
const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off";
subagentsLine = buildSubagentsStatusLine({
runs,
verboseEnabled,
pendingDescendantsForRun: (entry) => countPendingDescendantRuns(entry.childSessionKey),
});
}
const groupActivation = isGroup
? (normalizeGroupActivation(sessionEntry?.groupActivation) ?? defaultGroupActivation())
: undefined;
const agentDefaults = cfg.agents?.defaults ?? {};
const agentConfig = resolveAgentConfig(cfg, statusAgentId);
const effectiveFastMode =
resolvedFastMode ??
resolveFastModeState({
cfg,
provider,
model,
agentId: statusAgentId,
sessionEntry,
}).enabled;
const agentFallbacksOverride = resolveAgentModelFallbacksOverride(cfg, statusAgentId);
const { buildStatusMessage } = await loadStatusRuntime();
const statusText = buildStatusMessage({
config: cfg,
agent: {
...agentDefaults,
model: {
...toAgentModelListLike(agentDefaults.model),
primary: params.primaryModelLabelOverride ?? `${provider}/${model}`,
...(agentFallbacksOverride === undefined ? {} : { fallbacks: agentFallbacksOverride }),
},
...(typeof contextTokens === "number" && contextTokens > 0 ? { contextTokens } : {}),
thinkingDefault: agentConfig?.thinkingDefault ?? agentDefaults.thinkingDefault,
verboseDefault: agentDefaults.verboseDefault,
elevatedDefault: agentDefaults.elevatedDefault,
},
agentId: statusAgentId,
explicitConfiguredContextTokens:
typeof agentDefaults.contextTokens === "number" && agentDefaults.contextTokens > 0
? agentDefaults.contextTokens
: undefined,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
sessionStorePath: storePath,
groupActivation,
resolvedThink: resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
resolvedFast: effectiveFastMode,
resolvedVerbose: resolvedVerboseLevel,
resolvedReasoning: resolvedReasoningLevel,
resolvedElevated: resolvedElevatedLevel,
modelAuth: selectedModelAuth,
activeModelAuth,
usageLine: usageLine ?? undefined,
queue: {
mode: queueSettings.mode,
depth: queueDepth,
debounceMs: queueSettings.debounceMs,
cap: queueSettings.cap,
dropPolicy: queueSettings.dropPolicy,
showDetails: queueOverrides,
},
subagentsLine,
taskLine,
mediaDecisions: params.mediaDecisions,
includeTranscriptUsage: params.includeTranscriptUsage ?? true,
});
return statusText;
}

View File

@@ -53,7 +53,7 @@ export {
type CommandsMessageOptions,
type CommandsMessageResult,
} from "./command-status-builders.js";
import { resolveActiveFallbackState } from "./fallback-state.js";
import { resolveActiveFallbackState } from "../status/fallback-notice-state.js";
import { formatProviderModelRef, resolveSelectedAndActiveModel } from "./model-runtime.js";
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js";

View File

@@ -0,0 +1,25 @@
import type { SessionEntry } from "../config/sessions.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type FallbackNoticeState = Pick<
SessionEntry,
"fallbackNoticeSelectedModel" | "fallbackNoticeActiveModel" | "fallbackNoticeReason"
>;
export function resolveActiveFallbackState(params: {
selectedModelRef: string;
activeModelRef: string;
state?: FallbackNoticeState;
}): { active: boolean; reason?: string } {
const selected = normalizeOptionalString(params.state?.fallbackNoticeSelectedModel);
const active = normalizeOptionalString(params.state?.fallbackNoticeActiveModel);
const reason = normalizeOptionalString(params.state?.fallbackNoticeReason);
const fallbackActive =
params.selectedModelRef !== params.activeModelRef &&
selected === params.selectedModelRef &&
active === params.activeModelRef;
return {
active: fallbackActive,
reason: fallbackActive ? reason : undefined,
};
}

View File

@@ -0,0 +1,3 @@
export async function loadStatusMessageRuntimeModule() {
return await import("../auto-reply/status.runtime.js");
}

View File

@@ -0,0 +1 @@
export { getFollowupQueueDepth, resolveQueueSettings } from "../auto-reply/reply/queue.js";

View File

@@ -0,0 +1,3 @@
export { listControlledSubagentRuns } from "../agents/subagent-control.js";
export { countPendingDescendantRuns } from "../agents/subagent-registry.js";
export { buildSubagentsStatusLine } from "../auto-reply/reply/commands-status-subagents.js";

335
src/status/status-text.ts Normal file
View File

@@ -0,0 +1,335 @@
import {
resolveAgentConfig,
resolveAgentDir,
resolveDefaultAgentId,
resolveSessionAgentId,
resolveAgentModelFallbacksOverride,
} from "../agents/agent-scope.js";
import { resolveFastModeState } from "../agents/fast-mode.js";
import { resolveModelAuthLabel } from "../agents/model-auth-label.js";
import {
resolveInternalSessionKey,
resolveMainSessionAlias,
} from "../agents/tools/sessions-helpers.js";
import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
import { resolveSelectedAndActiveModel } from "../auto-reply/model-runtime.js";
import type {
ElevatedLevel,
ReasoningLevel,
ThinkLevel,
VerboseLevel,
} from "../auto-reply/thinking.js";
import { toAgentModelListLike } from "../config/model-input.js";
import type { SessionEntry, SessionScope } from "../config/sessions.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import {
formatUsageWindowSummary,
loadProviderUsageSummary,
resolveUsageProviderId,
} from "../infra/provider-usage.js";
import type { MediaUnderstandingDecision } from "../media-understanding/types.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import {
listTasksForAgentIdForStatus,
listTasksForSessionKeyForStatus,
} from "../tasks/task-status-access.js";
import {
buildTaskStatusSnapshot,
formatTaskStatusDetail,
formatTaskStatusTitle,
} from "../tasks/task-status.js";
export type BuildStatusTextParams = {
cfg: OpenClawConfig;
sessionEntry?: SessionEntry;
sessionKey: string;
parentSessionKey?: string;
sessionScope?: SessionScope;
storePath?: string;
statusChannel: string;
provider: string;
model: string;
contextTokens?: number;
resolvedThinkLevel?: ThinkLevel;
resolvedFastMode?: boolean;
resolvedVerboseLevel: VerboseLevel;
resolvedReasoningLevel: ReasoningLevel;
resolvedElevatedLevel?: ElevatedLevel;
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
isGroup: boolean;
defaultGroupActivation: () => "always" | "mention";
mediaDecisions?: MediaUnderstandingDecision[];
taskLineOverride?: string;
skipDefaultTaskLookup?: boolean;
primaryModelLabelOverride?: string;
modelAuthOverride?: string;
activeModelAuthOverride?: string;
includeTranscriptUsage?: boolean;
};
const USAGE_OAUTH_ONLY_PROVIDERS = new Set([
"anthropic",
"github-copilot",
"google-gemini-cli",
"openai-codex",
]);
let statusMessageRuntimePromise: Promise<typeof import("../auto-reply/status.runtime.js")> | null =
null;
let statusQueueRuntimePromise: Promise<typeof import("./status-queue.runtime.js")> | null = null;
let statusSubagentsRuntimePromise: Promise<typeof import("./status-subagents.runtime.js")> | null =
null;
function loadStatusMessageRuntime(): Promise<typeof import("../auto-reply/status.runtime.js")> {
const runtimePromise = (statusMessageRuntimePromise ??=
import("./status-message.runtime.js").then((module) =>
module.loadStatusMessageRuntimeModule(),
));
return runtimePromise;
}
function loadStatusSubagentsRuntime(): Promise<typeof import("./status-subagents.runtime.js")> {
const runtimePromise = (statusSubagentsRuntimePromise ??=
import("./status-subagents.runtime.js"));
return runtimePromise;
}
function loadStatusQueueRuntime(): Promise<typeof import("./status-queue.runtime.js")> {
const runtimePromise = (statusQueueRuntimePromise ??= import("./status-queue.runtime.js"));
return runtimePromise;
}
function shouldLoadUsageSummary(params: {
provider?: string;
selectedModelAuth?: string;
}): boolean {
if (!params.provider) {
return false;
}
if (!USAGE_OAUTH_ONLY_PROVIDERS.has(params.provider)) {
return true;
}
const auth = normalizeOptionalLowercaseString(params.selectedModelAuth);
return Boolean(auth?.startsWith("oauth") || auth?.startsWith("token"));
}
function formatSessionTaskLine(sessionKey: string): string | undefined {
const snapshot = buildTaskStatusSnapshot(listTasksForSessionKeyForStatus(sessionKey));
const task = snapshot.focus;
if (!task) {
return undefined;
}
const headline =
snapshot.activeCount > 0
? `${snapshot.activeCount} active · ${snapshot.totalCount} total`
: snapshot.recentFailureCount > 0
? `${snapshot.recentFailureCount} recent failure${snapshot.recentFailureCount === 1 ? "" : "s"}`
: "recently finished";
const title = formatTaskStatusTitle(task);
const detail = formatTaskStatusDetail(task);
const parts = [headline, task.runtime, title, detail].filter(Boolean);
return parts.length ? `📌 Tasks: ${parts.join(" · ")}` : undefined;
}
function formatAgentTaskCountsLine(agentId: string): string | undefined {
const snapshot = buildTaskStatusSnapshot(listTasksForAgentIdForStatus(agentId));
if (snapshot.totalCount === 0) {
return undefined;
}
return `📌 Tasks: ${snapshot.activeCount} active · ${snapshot.totalCount} total · agent-local`;
}
export async function buildStatusText(params: BuildStatusTextParams): Promise<string> {
const {
cfg,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
storePath,
statusChannel,
provider,
model,
contextTokens,
resolvedThinkLevel,
resolvedFastMode,
resolvedVerboseLevel,
resolvedReasoningLevel,
resolvedElevatedLevel,
resolveDefaultThinkingLevel,
isGroup,
defaultGroupActivation,
} = params;
const statusAgentId = sessionKey
? resolveSessionAgentId({ sessionKey, config: cfg })
: resolveDefaultAgentId(cfg);
const statusAgentDir = resolveAgentDir(cfg, statusAgentId);
const modelRefs = resolveSelectedAndActiveModel({
selectedProvider: provider,
selectedModel: model,
sessionEntry,
});
const selectedModelAuth = Object.hasOwn(params, "modelAuthOverride")
? params.modelAuthOverride
: resolveModelAuthLabel({
provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
});
const activeModelAuth = Object.hasOwn(params, "activeModelAuthOverride")
? params.activeModelAuthOverride
: modelRefs.activeDiffers
? resolveModelAuthLabel({
provider: modelRefs.active.provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
})
: selectedModelAuth;
const currentUsageProvider = (() => {
try {
return resolveUsageProviderId(provider);
} catch {
return undefined;
}
})();
let usageLine: string | null = null;
if (
currentUsageProvider &&
shouldLoadUsageSummary({
provider: currentUsageProvider,
selectedModelAuth,
})
) {
try {
const usageSummaryTimeoutMs = 3500;
let usageTimeout: NodeJS.Timeout | undefined;
const usageSummary = await Promise.race([
loadProviderUsageSummary({
timeoutMs: usageSummaryTimeoutMs,
providers: [currentUsageProvider],
agentDir: statusAgentDir,
}),
new Promise<never>((_, reject) => {
usageTimeout = setTimeout(
() => reject(new Error("usage summary timeout")),
usageSummaryTimeoutMs,
);
}),
]).finally(() => {
if (usageTimeout) {
clearTimeout(usageTimeout);
}
});
const usageEntry = usageSummary.providers[0];
if (usageEntry && !usageEntry.error && usageEntry.windows.length > 0) {
const summaryLine = formatUsageWindowSummary(usageEntry, {
now: Date.now(),
maxWindows: 2,
includeResets: true,
});
if (summaryLine) {
usageLine = `📊 Usage: ${summaryLine}`;
}
}
} catch {
usageLine = null;
}
}
const { getFollowupQueueDepth, resolveQueueSettings } = await loadStatusQueueRuntime();
const queueSettings = resolveQueueSettings({
cfg,
channel: statusChannel,
sessionEntry,
});
const queueKey = sessionKey ?? sessionEntry?.sessionId;
const queueDepth = queueKey ? getFollowupQueueDepth(queueKey) : 0;
const queueOverrides = Boolean(
sessionEntry?.queueDebounceMs ?? sessionEntry?.queueCap ?? sessionEntry?.queueDrop,
);
let subagentsLine: string | undefined;
let taskLine: string | undefined;
if (sessionKey) {
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const requesterKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
taskLine = params.skipDefaultTaskLookup
? params.taskLineOverride
: (params.taskLineOverride ?? formatSessionTaskLine(requesterKey));
if (!taskLine && !params.skipDefaultTaskLookup) {
taskLine = formatAgentTaskCountsLine(statusAgentId);
}
const { buildSubagentsStatusLine, countPendingDescendantRuns, listControlledSubagentRuns } =
await loadStatusSubagentsRuntime();
const runs = listControlledSubagentRuns(requesterKey);
const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off";
subagentsLine = buildSubagentsStatusLine({
runs,
verboseEnabled,
pendingDescendantsForRun: (entry) => countPendingDescendantRuns(entry.childSessionKey),
});
}
const groupActivation = isGroup
? (normalizeGroupActivation(sessionEntry?.groupActivation) ?? defaultGroupActivation())
: undefined;
const agentDefaults = cfg.agents?.defaults ?? {};
const agentConfig = resolveAgentConfig(cfg, statusAgentId);
const effectiveFastMode =
resolvedFastMode ??
resolveFastModeState({
cfg,
provider,
model,
agentId: statusAgentId,
sessionEntry,
}).enabled;
const agentFallbacksOverride = resolveAgentModelFallbacksOverride(cfg, statusAgentId);
const { buildStatusMessage } = await loadStatusMessageRuntime();
return buildStatusMessage({
config: cfg,
agent: {
...agentDefaults,
model: {
...toAgentModelListLike(agentDefaults.model),
primary: params.primaryModelLabelOverride ?? `${provider}/${model}`,
...(agentFallbacksOverride === undefined ? {} : { fallbacks: agentFallbacksOverride }),
},
...(typeof contextTokens === "number" && contextTokens > 0 ? { contextTokens } : {}),
thinkingDefault: agentConfig?.thinkingDefault ?? agentDefaults.thinkingDefault,
verboseDefault: agentDefaults.verboseDefault,
elevatedDefault: agentDefaults.elevatedDefault,
},
agentId: statusAgentId,
explicitConfiguredContextTokens:
typeof agentDefaults.contextTokens === "number" && agentDefaults.contextTokens > 0
? agentDefaults.contextTokens
: undefined,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
sessionStorePath: storePath,
groupActivation,
resolvedThink: resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
resolvedFast: effectiveFastMode,
resolvedVerbose: resolvedVerboseLevel,
resolvedReasoning: resolvedReasoningLevel,
resolvedElevated: resolvedElevatedLevel,
modelAuth: selectedModelAuth,
activeModelAuth,
usageLine: usageLine ?? undefined,
queue: {
mode: queueSettings.mode,
depth: queueDepth,
debounceMs: queueSettings.debounceMs,
cap: queueSettings.cap,
dropPolicy: queueSettings.dropPolicy,
showDetails: queueOverrides,
},
subagentsLine,
taskLine,
mediaDecisions: params.mediaDecisions,
includeTranscriptUsage: params.includeTranscriptUsage ?? true,
});
}