Files
openclaw/src/auto-reply/reply/commands-status.ts
2026-02-20 19:26:25 -06:00

204 lines
6.6 KiB
TypeScript

import {
resolveAgentDir,
resolveDefaultAgentId,
resolveSessionAgentId,
} from "../../agents/agent-scope.js";
import { resolveModelAuthLabel } from "../../agents/model-auth-label.js";
import { listSubagentRunsForRequester } from "../../agents/subagent-registry.js";
import {
resolveInternalSessionKey,
resolveMainSessionAlias,
} from "../../agents/tools/sessions-helpers.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { SessionEntry, SessionScope } from "../../config/sessions.js";
import { logVerbose } from "../../globals.js";
import {
formatUsageWindowSummary,
loadProviderUsageSummary,
resolveUsageProviderId,
} from "../../infra/provider-usage.js";
import type { MediaUnderstandingDecision } from "../../media-understanding/types.js";
import { normalizeGroupActivation } from "../group-activation.js";
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
import { buildStatusMessage } from "../status.js";
import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js";
import type { ReplyPayload } from "../types.js";
import type { CommandContext } from "./commands-types.js";
import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js";
import { resolveSubagentLabel } from "./subagents-utils.js";
export async function buildStatusReply(params: {
cfg: OpenClawConfig;
command: CommandContext;
sessionEntry?: SessionEntry;
sessionKey: string;
parentSessionKey?: string;
sessionScope?: SessionScope;
storePath?: string;
provider: string;
model: string;
contextTokens: number;
resolvedThinkLevel?: ThinkLevel;
resolvedVerboseLevel: VerboseLevel;
resolvedReasoningLevel: ReasoningLevel;
resolvedElevatedLevel?: ElevatedLevel;
resolveDefaultThinkingLevel: () => Promise<ThinkLevel | undefined>;
isGroup: boolean;
defaultGroupActivation: () => "always" | "mention";
mediaDecisions?: MediaUnderstandingDecision[];
}): Promise<ReplyPayload | undefined> {
const {
cfg,
command,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
storePath,
provider,
model,
contextTokens,
resolvedThinkLevel,
resolvedVerboseLevel,
resolvedReasoningLevel,
resolvedElevatedLevel,
resolveDefaultThinkingLevel,
isGroup,
defaultGroupActivation,
} = params;
if (!command.isAuthorizedSender) {
logVerbose(`Ignoring /status from unauthorized sender: ${command.senderId || "<unknown>"}`);
return undefined;
}
const statusAgentId = sessionKey
? resolveSessionAgentId({ sessionKey, config: cfg })
: resolveDefaultAgentId(cfg);
const statusAgentDir = resolveAgentDir(cfg, statusAgentId);
const currentUsageProvider = (() => {
try {
return resolveUsageProviderId(provider);
} catch {
return undefined;
}
})();
let usageLine: string | null = null;
if (currentUsageProvider) {
try {
const usageSummary = await loadProviderUsageSummary({
timeoutMs: 3500,
providers: [currentUsageProvider],
agentDir: statusAgentDir,
});
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: command.channel,
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;
if (sessionKey) {
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const requesterKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
const runs = listSubagentRunsForRequester(requesterKey);
const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off";
if (runs.length > 0) {
const active = runs.filter((entry) => !entry.endedAt);
const done = runs.length - active.length;
if (verboseEnabled) {
const labels = active
.map((entry) => resolveSubagentLabel(entry, ""))
.filter(Boolean)
.slice(0, 3);
const labelText = labels.length ? ` (${labels.join(", ")})` : "";
subagentsLine = `🤖 Subagents: ${active.length} active${labelText} · ${done} done`;
} else if (active.length > 0) {
subagentsLine = `🤖 Subagents: ${active.length} active`;
}
}
}
const groupActivation = isGroup
? (normalizeGroupActivation(sessionEntry?.groupActivation) ?? defaultGroupActivation())
: undefined;
const modelRefs = resolveSelectedAndActiveModel({
selectedProvider: provider,
selectedModel: model,
sessionEntry,
});
const selectedModelAuth = resolveModelAuthLabel({
provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
});
const activeModelAuth = modelRefs.activeDiffers
? resolveModelAuthLabel({
provider: modelRefs.active.provider,
cfg,
sessionEntry,
agentDir: statusAgentDir,
})
: selectedModelAuth;
const agentDefaults = cfg.agents?.defaults ?? {};
const statusText = buildStatusMessage({
config: cfg,
agent: {
...agentDefaults,
model: {
...agentDefaults.model,
primary: `${provider}/${model}`,
},
contextTokens,
thinkingDefault: agentDefaults.thinkingDefault,
verboseDefault: agentDefaults.verboseDefault,
elevatedDefault: agentDefaults.elevatedDefault,
},
agentId: statusAgentId,
sessionEntry,
sessionKey,
parentSessionKey,
sessionScope,
sessionStorePath: storePath,
groupActivation,
resolvedThink: resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()),
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,
mediaDecisions: params.mediaDecisions,
includeTranscriptUsage: false,
});
return { text: statusText };
}