import { existsSync } from "node:fs"; import { resolveMemorySearchConfig } from "../agents/memory-search.js"; import { hasPotentialConfiguredChannels } from "../channels/config-presence.js"; 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 { resolveConfigPath } from "../config/paths.js"; import type { collectChannelStatusIssues as collectChannelStatusIssuesFn } from "../infra/channels-status-issues.js"; import { resolveOsSummary } from "../infra/os-summary.js"; import type { UpdateCheckResult } from "../infra/update-check.js"; import { buildPluginCompatibilityNotices, type PluginCompatibilityNotice, } from "../plugins/status.js"; import { runExec } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js"; import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js"; import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js"; import { buildColdStartUpdateResult, scanStatusJsonCore } from "./status.scan.json-core.js"; import { buildTailscaleHttpsUrl, pickGatewaySelfPresence, resolveGatewayProbeSnapshot, resolveMemoryPluginStatus, resolveSharedMemoryStatusSnapshot, type GatewayProbeSnapshot, type MemoryPluginStatus, type MemoryStatusSnapshot, } from "./status.scan.shared.js"; import type { getStatusSummary as getStatusSummaryFn } from "./status.summary.js"; type DeferredResult = { ok: true; value: T } | { ok: false; error: unknown }; let statusScanDepsRuntimeModulePromise: | Promise | undefined; let statusAgentLocalModulePromise: Promise | undefined; let statusSummaryModulePromise: Promise | undefined; let statusUpdateModulePromise: Promise | undefined; let gatewayCallModulePromise: Promise | undefined; const loadStatusScanRuntimeModule = createLazyRuntimeSurface( () => import("./status.scan.runtime.js"), ({ statusScanRuntime }) => statusScanRuntime, ); function loadStatusScanDepsRuntimeModule() { statusScanDepsRuntimeModulePromise ??= import("./status.scan.deps.runtime.js"); return statusScanDepsRuntimeModulePromise; } function loadStatusAgentLocalModule() { statusAgentLocalModulePromise ??= import("./status.agent-local.js"); return statusAgentLocalModulePromise; } function loadStatusSummaryModule() { statusSummaryModulePromise ??= import("./status.summary.js"); return statusSummaryModulePromise; } function loadStatusUpdateModule() { statusUpdateModulePromise ??= import("./status.update.js"); return statusUpdateModulePromise; } function loadGatewayCallModule() { gatewayCallModulePromise ??= import("../gateway/call.js"); return gatewayCallModulePromise; } 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 isMissingConfigColdStart(): boolean { return !existsSync(resolveConfigPath(process.env)); } async function resolveChannelsStatus(params: { cfg: OpenClawConfig; gatewayReachable: boolean; opts: { timeoutMs?: number; all?: boolean }; }) { if (!params.gatewayReachable) { return null; } const { callGateway } = await loadGatewayCallModule(); 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: UpdateCheckResult; gatewayConnection: GatewayProbeSnapshot["gatewayConnection"]; remoteUrlMissing: boolean; gatewayMode: "local" | "remote"; gatewayProbeAuth: { token?: string; password?: string; }; gatewayProbeAuthWarning?: string; gatewayProbe: GatewayProbeSnapshot["gatewayProbe"]; gatewayReachable: boolean; gatewaySelf: ReturnType; channelIssues: ReturnType; agentStatus: Awaited>; channels: Awaited>; summary: Awaited>; memory: MemoryStatusSnapshot | null; memoryPlugin: MemoryPluginStatus; pluginCompatibility: PluginCompatibilityNotice[]; }; async function resolveMemoryStatusSnapshot(params: { cfg: OpenClawConfig; agentStatus: Awaited>; memoryPlugin: MemoryPluginStatus; }): Promise { const { getMemorySearchManager } = await loadStatusScanDepsRuntimeModule(); return await resolveSharedMemoryStatusSnapshot({ cfg: params.cfg, agentStatus: params.agentStatus, memoryPlugin: params.memoryPlugin, resolveMemoryConfig: resolveMemorySearchConfig, getMemorySearchManager, }); } function buildColdStartAgentLocalStatuses(): Awaited> { return { defaultId: "main", agents: [], totalSessions: 0, bootstrapPendingCount: 0, }; } function buildColdStartStatusSummary(): Awaited> { return { runtimeVersion: null, heartbeat: { defaultAgentId: "main", agents: [], }, channelSummary: [], queuedSystemEvents: [], sessions: { paths: [], count: 0, defaults: { model: null, contextTokens: null }, recent: [], byAgent: [], }, }; } async function scanStatusJsonFast(opts: { timeoutMs?: number; all?: boolean; runtime: RuntimeEnv; }): Promise { const coldStart = isMissingConfigColdStart(); const loadedRaw = await readBestEffortConfig(); const { resolvedConfig: cfg, diagnostics: secretDiagnostics } = await resolveCommandSecretRefsViaGateway({ config: loadedRaw, commandName: "status --json", targetIds: getStatusCommandSecretTargetIds(), mode: "read_only_status", }); return await scanStatusJsonCore({ coldStart, cfg, sourceConfig: loadedRaw, secretDiagnostics, hasConfiguredChannels: hasPotentialConfiguredChannels(cfg), opts, resolveOsSummary, resolveMemory: async ({ cfg, agentStatus, memoryPlugin }) => await resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin }), runtime: opts.runtime, }); } 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, runtime: _runtime, }); } return await withProgress( { label: "Scanning status…", total: 11, enabled: true, }, async (progress) => { const coldStart = isMissingConfigColdStart(); progress.setLabel("Loading config…"); const loadedRaw = await readBestEffortConfig(); const { resolvedConfig: cfg, diagnostics: secretDiagnostics } = await resolveCommandSecretRefsViaGateway({ config: loadedRaw, commandName: "status", targetIds: getStatusCommandSecretTargetIds(), mode: "read_only_status", }); const hasConfiguredChannels = hasPotentialConfiguredChannels(cfg); const skipColdStartNetworkChecks = coldStart && !hasConfiguredChannels && opts.all !== true; const osSummary = resolveOsSummary(); const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const tailscaleDnsPromise = tailscaleMode === "off" ? Promise.resolve(null) : loadStatusScanDepsRuntimeModule() .then(({ getTailnetHostname }) => getTailnetHostname((cmd, args) => runExec(cmd, args, { timeoutMs: 1200, maxBuffer: 200_000 }), ), ) .catch(() => null); const updateTimeoutMs = opts.all ? 6500 : 2500; const updatePromise = deferResult( skipColdStartNetworkChecks ? Promise.resolve(buildColdStartUpdateResult()) : loadStatusUpdateModule().then(({ getUpdateCheckResult }) => getUpdateCheckResult({ timeoutMs: updateTimeoutMs, fetchGit: true, includeRegistry: true, }), ), ); const agentStatusPromise = deferResult( skipColdStartNetworkChecks ? Promise.resolve(buildColdStartAgentLocalStatuses()) : loadStatusAgentLocalModule().then(({ getAgentLocalStatuses }) => getAgentLocalStatuses(cfg), ), ); const summaryPromise = deferResult( skipColdStartNetworkChecks ? Promise.resolve(buildColdStartStatusSummary()) : loadStatusSummaryModule().then(({ getStatusSummary }) => getStatusSummary({ config: cfg, sourceConfig: loadedRaw }), ), ); progress.tick(); progress.setLabel("Checking Tailscale…"); const tailscaleDns = await tailscaleDnsPromise; const tailscaleHttpsUrl = buildTailscaleHttpsUrl({ tailscaleMode, tailscaleDns, controlUiBasePath: cfg.gateway?.controlUi?.basePath, }); 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: { ...opts, ...(skipColdStartNetworkChecks ? { skipProbe: true } : {}), }, }); 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 { collectChannelStatusIssues, buildChannelsTable } = await loadStatusScanRuntimeModule(); 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 `OPENCLAW_SHOW_SECRETS=0` to force redaction. showSecrets: process.env.OPENCLAW_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("Checking plugins…"); const pluginCompatibility = buildPluginCompatibilityNotices({ config: cfg }); 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, pluginCompatibility, }; }, ); }