From 5ffd2dfbc3d6de9f2c9f1f683dcc18069ce898bf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 4 Aug 2026 19:41:40 -0700 Subject: [PATCH] refactor(status): consolidate scan projections and lazy loaders (#119412) --- src/commands/status-overview-surface.ts | 21 +--- src/commands/status.command.ts | 59 +++++------ src/commands/status.scan-memory.ts | 6 +- src/commands/status.scan-overview.ts | 82 ++++----------- src/commands/status.scan-result.ts | 131 +++++++----------------- 5 files changed, 83 insertions(+), 216 deletions(-) diff --git a/src/commands/status-overview-surface.ts b/src/commands/status-overview-surface.ts index 0ae447e10b4b..0082e418b6f4 100644 --- a/src/commands/status-overview-surface.ts +++ b/src/commands/status-overview-surface.ts @@ -77,27 +77,12 @@ export function buildStatusOverviewSurfaceFromScan( export function buildStatusOverviewSurfaceFromOverview( params: { overview: StatusOverviewInput } & StatusOverviewServices, ): StatusOverviewSurface { - return { - cfg: params.overview.cfg, - update: params.overview.update, - tailscaleMode: params.overview.tailscaleMode, - tailscaleDns: params.overview.tailscaleDns, - tailscaleHttpsUrl: params.overview.tailscaleHttpsUrl, - ...(params.overview.advertisedControlUiLinks - ? { advertisedControlUiLinks: params.overview.advertisedControlUiLinks } - : {}), - gatewayMode: params.overview.gatewaySnapshot.gatewayMode, - remoteUrlMissing: params.overview.gatewaySnapshot.remoteUrlMissing, - gatewayConnection: params.overview.gatewaySnapshot.gatewayConnection, - gatewayReachable: params.overview.gatewaySnapshot.gatewayReachable, - gatewayProbe: params.overview.gatewaySnapshot.gatewayProbe, - gatewayProbeAuth: params.overview.gatewaySnapshot.gatewayProbeAuth, - gatewayProbeAuthWarning: params.overview.gatewaySnapshot.gatewayProbeAuthWarning, - gatewaySelf: params.overview.gatewaySnapshot.gatewaySelf, + return buildStatusOverviewSurfaceFromScan({ + scan: { ...params.overview, ...params.overview.gatewaySnapshot }, gatewayService: params.gatewayService, nodeService: params.nodeService, nodeOnlyGateway: params.nodeOnlyGateway, - }; + }); } /** Builds overview rows from an already-normalized surface. */ diff --git a/src/commands/status.command.ts b/src/commands/status.command.ts index 9e2a26bbcd99..ab2a6d121fff 100644 --- a/src/commands/status.command.ts +++ b/src/commands/status.command.ts @@ -37,26 +37,6 @@ const statusCommandTextRuntimeLoader = createLazyImportLoader( ); const statusNodeModeModuleLoader = createLazyImportLoader(() => import("./status.node-mode.js")); -function loadStatusScanModule() { - return statusScanModuleLoader.load(); -} - -function loadStatusScanFastJsonModule() { - return statusScanFastJsonModuleLoader.load(); -} - -function loadStatusAllModule() { - return statusAllModuleLoader.load(); -} - -function loadStatusCommandTextRuntime() { - return statusCommandTextRuntimeLoader.load(); -} - -function loadStatusNodeModeModule() { - return statusNodeModeModuleLoader.load(); -} - /** Extracts device-pairing recovery context from structured gateway errors or legacy message text. */ function resolvePairingRecoveryContext(params: { error?: string | null; @@ -131,9 +111,9 @@ export async function statusCommand( ) { if (opts.all && !opts.json) { // Human `--all` has a dedicated report path; JSON `--all` stays on the JSON schema. - await loadStatusAllModule().then(({ statusAllCommand }) => - statusAllCommand(runtime, { timeoutMs: opts.timeoutMs }), - ); + await statusAllModuleLoader + .load() + .then(({ statusAllCommand }) => statusAllCommand(runtime, { timeoutMs: opts.timeoutMs })); return; } @@ -145,16 +125,21 @@ export async function statusCommand( includePluginCompatibility: opts.all === true, suppressHealthErrors: true, scanStatusJsonFast: async (scanOpts, runtimeForScan) => - await loadStatusScanFastJsonModule().then(({ scanStatusJsonFast }) => - scanStatusJsonFast(scanOpts, runtimeForScan), - ), + await statusScanFastJsonModuleLoader + .load() + .then(({ scanStatusJsonFast }) => scanStatusJsonFast(scanOpts, runtimeForScan)), }); return; } - const scan = await loadStatusScanModule().then(({ scanStatus }) => - scanStatus({ json: false, timeoutMs: opts.timeoutMs, all: opts.all, deep: opts.deep }, runtime), - ); + const scan = await statusScanModuleLoader + .load() + .then(({ scanStatus }) => + scanStatus( + { json: false, timeoutMs: opts.timeoutMs, all: opts.all, deep: opts.deep }, + runtime, + ), + ); const { cfg, @@ -250,7 +235,7 @@ export async function statusCommand( resolveMemoryVectorState, shortenText, theme, - } = await loadStatusCommandTextRuntime(); + } = await statusCommandTextRuntimeLoader.load(); const muted = (value: string) => (rich ? theme.muted(value) : value); const ok = (value: string) => (rich ? theme.success(value) : value); const warn = (value: string) => (rich ? theme.warn(value) : value); @@ -289,12 +274,14 @@ export async function statusCommand( runtime.log(""); } - const nodeOnlyGateway = await loadStatusNodeModeModule().then(({ resolveNodeOnlyGatewayInfo }) => - resolveNodeOnlyGatewayInfo({ - daemon, - node: nodeDaemon, - }), - ); + const nodeOnlyGateway = await statusNodeModeModuleLoader + .load() + .then(({ resolveNodeOnlyGatewayInfo }) => + resolveNodeOnlyGatewayInfo({ + daemon, + node: nodeDaemon, + }), + ); const pairingRecovery = resolvePairingRecoveryContext({ error: gatewayProbe?.error ?? null, closeReason: gatewayProbe?.close?.reason ?? null, diff --git a/src/commands/status.scan-memory.ts b/src/commands/status.scan-memory.ts index feec8a3382a8..9426c4ef170b 100644 --- a/src/commands/status.scan-memory.ts +++ b/src/commands/status.scan-memory.ts @@ -16,10 +16,6 @@ const statusScanDepsRuntimeModuleLoader = createLazyImportLoader( () => import("./status.scan.deps.runtime.js"), ); -function loadStatusScanDepsRuntimeModule() { - return statusScanDepsRuntimeModuleLoader.load(); -} - /** Returns the owning agent database path for built-in memory. */ export function resolveDefaultMemoryDatabasePath(agentId: string): string { return resolveOpenClawAgentSqlitePath({ agentId }); @@ -32,7 +28,7 @@ export async function resolveStatusMemoryStatusSnapshot(params: { memoryPlugin: MemoryPluginStatus; requireDefaultDatabasePath?: (agentId: string) => string; }): Promise { - const { getMemorySearchManager } = await loadStatusScanDepsRuntimeModule(); + const { getMemorySearchManager } = await statusScanDepsRuntimeModuleLoader.load(); return await resolveSharedMemoryStatusSnapshot({ cfg: params.cfg, agentStatus: params.agentStatus, diff --git a/src/commands/status.scan-overview.ts b/src/commands/status.scan-overview.ts index 2154989e0262..539abeb7a02a 100644 --- a/src/commands/status.scan-overview.ts +++ b/src/commands/status.scan-overview.ts @@ -44,50 +44,6 @@ const commandSecretTargetsModuleLoader = createLazyImportLoader( () => import("../cli/command-secret-targets.js"), ); -function loadStatusScanDepsRuntimeModule() { - return statusScanDepsRuntimeModuleLoader.load(); -} - -function loadStatusAgentLocalModule() { - return statusAgentLocalModuleLoader.load(); -} - -function loadStatusUpdateModule() { - return statusUpdateModuleLoader.load(); -} - -function loadStatusScanRuntimeModule() { - return statusScanRuntimeModuleLoader.load(); -} - -function loadGatewayCallModule() { - return gatewayCallModuleLoader.load(); -} - -function loadStatusSummaryModule() { - return statusSummaryModuleLoader.load(); -} - -function loadChannelPluginIdsModule() { - return channelPluginIdsModuleLoader.load(); -} - -function loadConfigModule() { - return configModuleLoader.load(); -} - -function loadControlUiLinksModule() { - return controlUiLinksModuleLoader.load(); -} - -function loadCommandConfigResolutionModule() { - return commandConfigResolutionModuleLoader.load(); -} - -function loadCommandSecretTargetsModule() { - return commandSecretTargetsModuleLoader.load(); -} - async function resolveStatusChannelsStatus(params: { cfg: OpenClawConfig; gatewayReachable: boolean; @@ -99,7 +55,7 @@ async function resolveStatusChannelsStatus(params: { // Avoid a second gateway call after probe failure; channel tables can still summarize local config. return null; } - const { callGateway } = await loadGatewayCallModule(); + const { callGateway } = await gatewayCallModuleLoader.load(); return await callGateway({ config: params.cfg, method: "channels.status", @@ -193,17 +149,17 @@ export async function collectStatusScanOverview(params: { commandName: params.commandName, allowMissingConfigFastPath: params.allowMissingConfigFastPath, readConfigSnapshot: async () => - (await loadConfigModule()).readBestEffortConfigSnapshot({ + (await configModuleLoader.load()).readBestEffortConfigSnapshot({ observe: false, skipPluginValidation: params.skipConfigPluginValidation, }), resolveConfig: async (loadedConfig) => await ( - await loadCommandConfigResolutionModule() + await commandConfigResolutionModuleLoader.load() ).resolveCommandConfigWithSecrets({ config: loadedConfig, commandName: params.commandName, - targetIds: (await loadCommandSecretTargetsModule()).getStatusCommandSecretTargetIds( + targetIds: (await commandSecretTargetsModuleLoader.load()).getStatusCommandSecretTargetIds( loadedConfig, process.env, { includeChannelTargets: params.includeChannelSecretTargets }, @@ -215,7 +171,7 @@ export async function collectStatusScanOverview(params: { params.progress?.tick(); const hasConfiguredChannels = params.resolveHasConfiguredChannels ? await params.resolveHasConfiguredChannels(cfg, sourceConfig) - : await loadChannelPluginIdsModule().then(({ hasConfiguredChannelsForReadOnlyScope }) => + : await channelPluginIdsModuleLoader.load().then(({ hasConfiguredChannelsForReadOnlyScope }) => hasConfiguredChannelsForReadOnlyScope({ config: cfg, activationSourceConfig: sourceConfig, @@ -239,18 +195,18 @@ export async function collectStatusScanOverview(params: { includeLocalStatusRpcFallback: params.includeLocalStatusRpcFallback, gatewayProbeTimeoutMs, getTailnetHostname: async (runner) => { - return await loadStatusScanDepsRuntimeModule().then(({ getTailnetHostname }) => - getTailnetHostname(runner), - ); + return await statusScanDepsRuntimeModuleLoader + .load() + .then(({ getTailnetHostname }) => getTailnetHostname(runner)); }, getUpdateCheckResult: async (updateParams) => - await loadStatusUpdateModule().then(({ getUpdateCheckResult }) => - getUpdateCheckResult(updateParams), - ), + await statusUpdateModuleLoader + .load() + .then(({ getUpdateCheckResult }) => getUpdateCheckResult(updateParams)), getAgentLocalStatuses: async (bootstrapCfg) => - await loadStatusAgentLocalModule().then(({ getAgentLocalStatuses }) => - getAgentLocalStatuses(bootstrapCfg), - ), + await statusAgentLocalModuleLoader + .load() + .then(({ getAgentLocalStatuses }) => getAgentLocalStatuses(bootstrapCfg)), }); if (params.labels?.checkingTailscale) { @@ -280,9 +236,9 @@ export async function collectStatusScanOverview(params: { const tailscaleHttpsUrl = await bootstrap.resolveTailscaleHttpsUrl(); const advertisedControlUiLinks = params.includeAdvertisedControlUiLinks === true && cfg.gateway?.controlUi?.enabled !== false - ? await loadControlUiLinksModule().then(async ({ resolveAdvertisedControlUiLinks }) => + ? await controlUiLinksModuleLoader.load().then(async ({ resolveAdvertisedControlUiLinks }) => resolveAdvertisedControlUiLinks({ - port: (await loadConfigModule()).resolveGatewayPort(cfg), + port: (await configModuleLoader.load()).resolveGatewayPort(cfg), bind: cfg.gateway?.bind, customBindHost: cfg.gateway?.customBindHost, basePath: cfg.gateway?.controlUi?.basePath, @@ -309,7 +265,9 @@ export async function collectStatusScanOverview(params: { params.progress?.tick(); // Runtime channel helpers stay lazy because JSON fast paths can skip channel data entirely. const { collectChannelStatusIssues, buildChannelsTable } = - await loadStatusScanRuntimeModule().then(({ statusScanRuntime }) => statusScanRuntime); + await statusScanRuntimeModuleLoader + .load() + .then(({ statusScanRuntime }) => statusScanRuntime); const channelIssuesLocal = channelsStatusLocal ? collectChannelStatusIssues(channelsStatusLocal) : []; @@ -367,7 +325,7 @@ export async function resolveStatusSummaryFromOverview(params: { if (params.overview.skipColdStartNetworkChecks) { return buildColdStartStatusSummary(); } - return await loadStatusSummaryModule().then(({ getStatusSummary }) => + return await statusSummaryModuleLoader.load().then(({ getStatusSummary }) => getStatusSummary({ config: params.overview.cfg, sourceConfig: params.overview.sourceConfig, diff --git a/src/commands/status.scan-result.ts b/src/commands/status.scan-result.ts index d5b1dd63f9f5..b3549439aabf 100644 --- a/src/commands/status.scan-result.ts +++ b/src/commands/status.scan-result.ts @@ -1,107 +1,48 @@ // Normalized full status scan result shape. // Builders flatten the gateway snapshot so downstream text/JSON code reads one stable object. -import type { OpenClawConfig } from "../config/types.openclaw.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 type { PluginCompatibilityNotice } from "../plugins/status.js"; import type { getStatusSummary as getStatusSummaryFn } from "../status/summary.js"; -import type { pickGatewaySelfPresence } from "./gateway-presence.js"; -import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js"; -import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js"; -import type { - GatewayProbeSnapshot, - MemoryPluginStatus, - MemoryStatusSnapshot, -} from "./status.scan.shared.js"; +import type { StatusScanOverviewResult } from "./status.scan-overview.ts"; +import type { MemoryPluginStatus, MemoryStatusSnapshot } from "./status.scan.shared.js"; -export type StatusScanResult = { - cfg: OpenClawConfig; - sourceConfig: OpenClawConfig; - secretDiagnostics: string[]; - osSummary: ReturnType; - tailscaleMode: string; - tailscaleDns: string | null; - tailscaleHttpsUrl: string | null; - advertisedControlUiLinks?: { httpUrl: string; wsUrl: string }; - update: UpdateCheckResult; - gatewayConnection: GatewayProbeSnapshot["gatewayConnection"]; - remoteUrlMissing: boolean; - gatewayMode: "local" | "remote"; - gatewayProbeAuth: { - token?: string; - password?: string; +type StatusScanGatewayResult = Omit< + StatusScanOverviewResult["gatewaySnapshot"], + "gatewayCallOverrides" +>; + +export type StatusScanResult = Omit< + StatusScanOverviewResult, + | "coldStart" + | "hasConfiguredChannels" + | "skipColdStartNetworkChecks" + | "gatewaySnapshot" + | "channelsStatus" +> & + StatusScanGatewayResult & { + summary: Awaited>; + memory: MemoryStatusSnapshot | null; + memoryPlugin: MemoryPluginStatus; + pluginCompatibility: PluginCompatibilityNotice[]; }; - gatewayProbeAuthWarning?: string; - gatewayProbe: GatewayProbeSnapshot["gatewayProbe"]; - gatewayReachable: boolean; - gatewaySelf: ReturnType; - channelIssues: ReturnType; - agentStatus: Awaited>; - channels: Awaited>; - summary: Awaited>; - memory: MemoryStatusSnapshot | null; - memoryPlugin: MemoryPluginStatus; - pluginCompatibility: PluginCompatibilityNotice[]; -}; /** Flattens overview, gateway, channel, summary, memory, and compatibility inputs into a scan result. */ -export function buildStatusScanResult(params: { - cfg: OpenClawConfig; - sourceConfig: OpenClawConfig; - secretDiagnostics: string[]; - osSummary: ReturnType; - tailscaleMode: string; - tailscaleDns: string | null; - tailscaleHttpsUrl: string | null; - advertisedControlUiLinks?: { httpUrl: string; wsUrl: string }; - update: UpdateCheckResult; - gatewaySnapshot: Pick< - GatewayProbeSnapshot, - | "gatewayConnection" - | "remoteUrlMissing" - | "gatewayMode" - | "gatewayProbeAuth" - | "gatewayProbeAuthWarning" - | "gatewayProbe" - | "gatewayReachable" - | "gatewaySelf" - >; - channelIssues: ReturnType; - agentStatus: Awaited>; - channels: Awaited>; - summary: Awaited>; - memory: MemoryStatusSnapshot | null; - memoryPlugin: MemoryPluginStatus; - pluginCompatibility: PluginCompatibilityNotice[]; -}): StatusScanResult { +export function buildStatusScanResult( + params: Omit & { + gatewaySnapshot: StatusScanGatewayResult; + }, +): StatusScanResult { + const { gatewaySnapshot, advertisedControlUiLinks, ...result } = params; return { - cfg: params.cfg, - sourceConfig: params.sourceConfig, - secretDiagnostics: params.secretDiagnostics, - osSummary: params.osSummary, - tailscaleMode: params.tailscaleMode, - tailscaleDns: params.tailscaleDns, - tailscaleHttpsUrl: params.tailscaleHttpsUrl, - ...(params.advertisedControlUiLinks - ? { advertisedControlUiLinks: params.advertisedControlUiLinks } - : {}), - update: params.update, - gatewayConnection: params.gatewaySnapshot.gatewayConnection, - remoteUrlMissing: params.gatewaySnapshot.remoteUrlMissing, - gatewayMode: params.gatewaySnapshot.gatewayMode, - gatewayProbeAuth: params.gatewaySnapshot.gatewayProbeAuth, - gatewayProbeAuthWarning: params.gatewaySnapshot.gatewayProbeAuthWarning, - gatewayProbe: params.gatewaySnapshot.gatewayProbe, - gatewayReachable: params.gatewaySnapshot.gatewayReachable, - gatewaySelf: params.gatewaySnapshot.gatewaySelf, - channelIssues: params.channelIssues, - agentStatus: params.agentStatus, - channels: params.channels, - summary: params.summary, - memory: params.memory, - memoryPlugin: params.memoryPlugin, - pluginCompatibility: params.pluginCompatibility, + ...result, + ...(advertisedControlUiLinks ? { advertisedControlUiLinks } : {}), + gatewayConnection: gatewaySnapshot.gatewayConnection, + remoteUrlMissing: gatewaySnapshot.remoteUrlMissing, + gatewayMode: gatewaySnapshot.gatewayMode, + gatewayProbeAuth: gatewaySnapshot.gatewayProbeAuth, + gatewayProbeAuthWarning: gatewaySnapshot.gatewayProbeAuthWarning, + gatewayProbe: gatewaySnapshot.gatewayProbe, + gatewayReachable: gatewaySnapshot.gatewayReachable, + gatewaySelf: gatewaySnapshot.gatewaySelf, }; }