import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js"; import { getStatusCommandSecretTargetIds } from "../cli/command-secret-targets.js"; import { withProgress } from "../cli/progress.js"; import type { OpenClawConfig } from "../config/config.js"; import { readBestEffortConfig } from "../config/config.js"; import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js"; import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js"; import { probeGateway } from "../gateway/probe.js"; import { collectChannelStatusIssues } from "../infra/channels-status-issues.js"; import { resolveOsSummary } from "../infra/os-summary.js"; import { getTailnetHostname } from "../infra/tailscale.js"; import { getMemorySearchManager } from "../memory/index.js"; import type { MemoryProviderStatus } from "../memory/types.js"; import { runExec } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { buildChannelsTable } from "./status-all/channels.js"; import { getAgentLocalStatuses } from "./status.agent-local.js"; import { pickGatewaySelfPresence, resolveGatewayProbeAuthResolution, } from "./status.gateway-probe.js"; import { getStatusSummary } from "./status.summary.js"; import { getUpdateCheckResult } from "./status.update.js"; type MemoryStatusSnapshot = MemoryProviderStatus & { agentId: string; }; type MemoryPluginStatus = { enabled: boolean; slot: string | null; reason?: string; }; type DeferredResult = { ok: true; value: T } | { ok: false; error: unknown }; type GatewayProbeSnapshot = { gatewayConnection: ReturnType; remoteUrlMissing: boolean; gatewayMode: "local" | "remote"; gatewayProbeAuth: { token?: string; password?: string; }; gatewayProbeAuthWarning?: string; gatewayProbe: Awaited> | null; }; function deferResult(promise: Promise): Promise> { return promise.then( (value) => ({ ok: true, value }), (error: unknown) => ({ ok: false, error }), ); } function unwrapDeferredResult(result: DeferredResult): T { if (!result.ok) { throw result.error; } return result.value; } function resolveMemoryPluginStatus(cfg: OpenClawConfig): MemoryPluginStatus { const pluginsEnabled = cfg.plugins?.enabled !== false; if (!pluginsEnabled) { return { enabled: false, slot: null, reason: "plugins disabled" }; } const raw = typeof cfg.plugins?.slots?.memory === "string" ? cfg.plugins.slots.memory.trim() : ""; if (raw && raw.toLowerCase() === "none") { return { enabled: false, slot: null, reason: 'plugins.slots.memory="none"' }; } return { enabled: true, slot: raw || "memory-core" }; } async function resolveGatewayProbeSnapshot(params: { cfg: OpenClawConfig; opts: { timeoutMs?: number; all?: boolean }; }): Promise { const gatewayConnection = buildGatewayConnectionDetails({ config: params.cfg }); const isRemoteMode = params.cfg.gateway?.mode === "remote"; const remoteUrlRaw = typeof params.cfg.gateway?.remote?.url === "string" ? params.cfg.gateway.remote.url : ""; const remoteUrlMissing = isRemoteMode && !remoteUrlRaw.trim(); const gatewayMode = isRemoteMode ? "remote" : "local"; const gatewayProbeAuthResolution = resolveGatewayProbeAuthResolution(params.cfg); let gatewayProbeAuthWarning = gatewayProbeAuthResolution.warning; const gatewayProbe = remoteUrlMissing ? null : await probeGateway({ url: gatewayConnection.url, auth: gatewayProbeAuthResolution.auth, timeoutMs: Math.min(params.opts.all ? 5000 : 2500, params.opts.timeoutMs ?? 10_000), }).catch(() => null); if (gatewayProbeAuthWarning && gatewayProbe?.ok === false) { gatewayProbe.error = gatewayProbe.error ? `${gatewayProbe.error}; ${gatewayProbeAuthWarning}` : gatewayProbeAuthWarning; gatewayProbeAuthWarning = undefined; } return { gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbeAuth: gatewayProbeAuthResolution.auth, gatewayProbeAuthWarning, gatewayProbe, }; } async function resolveChannelsStatus(params: { cfg: OpenClawConfig; gatewayReachable: boolean; opts: { timeoutMs?: number; all?: boolean }; }) { if (!params.gatewayReachable) { return null; } return await callGateway({ config: params.cfg, method: "channels.status", params: { probe: false, timeoutMs: Math.min(8000, params.opts.timeoutMs ?? 10_000), }, timeoutMs: Math.min(params.opts.all ? 5000 : 2500, params.opts.timeoutMs ?? 10_000), }).catch(() => null); } export type StatusScanResult = { cfg: OpenClawConfig; sourceConfig: OpenClawConfig; secretDiagnostics: string[]; osSummary: ReturnType; tailscaleMode: string; tailscaleDns: string | null; tailscaleHttpsUrl: string | null; update: Awaited>; gatewayConnection: ReturnType; remoteUrlMissing: boolean; gatewayMode: "local" | "remote"; gatewayProbeAuth: { token?: string; password?: string; }; gatewayProbeAuthWarning?: string; gatewayProbe: Awaited> | null; gatewayReachable: boolean; gatewaySelf: ReturnType; channelIssues: ReturnType; agentStatus: Awaited>; channels: Awaited>; summary: Awaited>; memory: MemoryStatusSnapshot | null; memoryPlugin: MemoryPluginStatus; }; async function resolveMemoryStatusSnapshot(params: { cfg: OpenClawConfig; agentStatus: Awaited>; memoryPlugin: MemoryPluginStatus; }): Promise { const { cfg, agentStatus, memoryPlugin } = params; if (!memoryPlugin.enabled) { return null; } if (memoryPlugin.slot !== "memory-core") { return null; } const agentId = agentStatus.defaultId ?? "main"; const { manager } = await getMemorySearchManager({ cfg, agentId, purpose: "status" }); if (!manager) { return null; } try { await manager.probeVectorAvailability(); } catch {} const status = manager.status(); await manager.close?.().catch(() => {}); return { agentId, ...status }; } async function scanStatusJsonFast(opts: { timeoutMs?: number; all?: boolean; }): Promise { const loadedRaw = await readBestEffortConfig(); const { resolvedConfig: cfg, diagnostics: secretDiagnostics } = await resolveCommandSecretRefsViaGateway({ config: loadedRaw, commandName: "status --json", targetIds: getStatusCommandSecretTargetIds(), mode: "summary", }); const osSummary = resolveOsSummary(); const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const updateTimeoutMs = opts.all ? 6500 : 2500; const updatePromise = getUpdateCheckResult({ timeoutMs: updateTimeoutMs, fetchGit: true, includeRegistry: true, }); const agentStatusPromise = getAgentLocalStatuses(cfg); const summaryPromise = getStatusSummary({ config: cfg, sourceConfig: loadedRaw }); const tailscaleDnsPromise = tailscaleMode === "off" ? Promise.resolve(null) : getTailnetHostname((cmd, args) => runExec(cmd, args, { timeoutMs: 1200, maxBuffer: 200_000 }), ).catch(() => null); const gatewayProbePromise = resolveGatewayProbeSnapshot({ cfg, opts }); const [tailscaleDns, update, agentStatus, gatewaySnapshot, summary] = await Promise.all([ tailscaleDnsPromise, updatePromise, agentStatusPromise, gatewayProbePromise, summaryPromise, ]); const tailscaleHttpsUrl = tailscaleMode !== "off" && tailscaleDns ? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` : null; const { gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbeAuth, gatewayProbeAuthWarning, gatewayProbe, } = gatewaySnapshot; const gatewayReachable = gatewayProbe?.ok === true; const gatewaySelf = gatewayProbe?.presence ? pickGatewaySelfPresence(gatewayProbe.presence) : null; const channelsStatusPromise = resolveChannelsStatus({ cfg, gatewayReachable, opts }); const memoryPlugin = resolveMemoryPluginStatus(cfg); const memoryPromise = resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin }); const [channelsStatus, memory] = await Promise.all([channelsStatusPromise, memoryPromise]); const channelIssues = channelsStatus ? collectChannelStatusIssues(channelsStatus) : []; return { cfg, sourceConfig: loadedRaw, secretDiagnostics, osSummary, tailscaleMode, tailscaleDns, tailscaleHttpsUrl, update, gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbeAuth, gatewayProbeAuthWarning, gatewayProbe, gatewayReachable, gatewaySelf, channelIssues, agentStatus, channels: { rows: [], details: [] }, summary, memory, memoryPlugin, }; } export async function scanStatus( opts: { json?: boolean; timeoutMs?: number; all?: boolean; }, _runtime: RuntimeEnv, ): Promise { if (opts.json) { return await scanStatusJsonFast({ timeoutMs: opts.timeoutMs, all: opts.all }); } return await withProgress( { label: "Scanning status…", total: 10, enabled: true, }, async (progress) => { progress.setLabel("Loading config…"); const loadedRaw = await readBestEffortConfig(); const { resolvedConfig: cfg, diagnostics: secretDiagnostics } = await resolveCommandSecretRefsViaGateway({ config: loadedRaw, commandName: "status", targetIds: getStatusCommandSecretTargetIds(), mode: "summary", }); const osSummary = resolveOsSummary(); const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const tailscaleDnsPromise = tailscaleMode === "off" ? Promise.resolve(null) : getTailnetHostname((cmd, args) => runExec(cmd, args, { timeoutMs: 1200, maxBuffer: 200_000 }), ).catch(() => null); const updateTimeoutMs = opts.all ? 6500 : 2500; const updatePromise = deferResult( getUpdateCheckResult({ timeoutMs: updateTimeoutMs, fetchGit: true, includeRegistry: true, }), ); const agentStatusPromise = deferResult(getAgentLocalStatuses(cfg)); const summaryPromise = deferResult( getStatusSummary({ config: cfg, sourceConfig: loadedRaw }), ); progress.tick(); progress.setLabel("Checking Tailscale…"); const tailscaleDns = await tailscaleDnsPromise; const tailscaleHttpsUrl = tailscaleMode !== "off" && tailscaleDns ? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` : null; progress.tick(); progress.setLabel("Checking for updates…"); const update = unwrapDeferredResult(await updatePromise); progress.tick(); progress.setLabel("Resolving agents…"); const agentStatus = unwrapDeferredResult(await agentStatusPromise); progress.tick(); progress.setLabel("Probing gateway…"); const { gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbeAuth, gatewayProbeAuthWarning, gatewayProbe, } = await resolveGatewayProbeSnapshot({ cfg, opts }); const gatewayReachable = gatewayProbe?.ok === true; const gatewaySelf = gatewayProbe?.presence ? pickGatewaySelfPresence(gatewayProbe.presence) : null; progress.tick(); progress.setLabel("Querying channel status…"); const channelsStatus = await resolveChannelsStatus({ cfg, gatewayReachable, opts }); const channelIssues = channelsStatus ? collectChannelStatusIssues(channelsStatus) : []; progress.tick(); progress.setLabel("Summarizing channels…"); const channels = await buildChannelsTable(cfg, { // Show token previews in regular status; keep `status --all` redacted. // Set `CLAWDBOT_SHOW_SECRETS=0` to force redaction. showSecrets: process.env.CLAWDBOT_SHOW_SECRETS?.trim() !== "0", sourceConfig: loadedRaw, }); progress.tick(); progress.setLabel("Checking memory…"); const memoryPlugin = resolveMemoryPluginStatus(cfg); const memory = await resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin }); progress.tick(); progress.setLabel("Reading sessions…"); const summary = unwrapDeferredResult(await summaryPromise); progress.tick(); progress.setLabel("Rendering…"); progress.tick(); return { cfg, sourceConfig: loadedRaw, secretDiagnostics, osSummary, tailscaleMode, tailscaleDns, tailscaleHttpsUrl, update, gatewayConnection, remoteUrlMissing, gatewayMode, gatewayProbeAuth, gatewayProbeAuthWarning, gatewayProbe, gatewayReachable, gatewaySelf, channelIssues, agentStatus, channels, summary, memory, memoryPlugin, }; }, ); }