diff --git a/src/commands/status.scan.fast-json.ts b/src/commands/status.scan.fast-json.ts index 505084ef992..73b0b1feae6 100644 --- a/src/commands/status.scan.fast-json.ts +++ b/src/commands/status.scan.fast-json.ts @@ -2,53 +2,25 @@ import { existsSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { hasPotentialConfiguredChannels } from "../channels/config-presence.js"; -import { resolveConfigPath, resolveGatewayPort, resolveStateDir } from "../config/paths.js"; +import { resolveConfigPath, resolveStateDir } from "../config/paths.js"; import type { OpenClawConfig } from "../config/types.js"; -import { isSecureWebSocketUrl } from "../gateway/net.js"; -import { probeGateway } from "../gateway/probe.js"; import { resolveOsSummary } from "../infra/os-summary.js"; -import type { MemoryProviderStatus } from "../memory/types.js"; import { runExec } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { getAgentLocalStatuses } from "./status.agent-local.js"; -import { - pickGatewaySelfPresence, - resolveGatewayProbeAuthResolution, -} from "./status.gateway-probe.js"; import type { StatusScanResult } from "./status.scan.js"; +import { + buildTailscaleHttpsUrl, + pickGatewaySelfPresence, + resolveGatewayProbeSnapshot, + resolveMemoryPluginStatus, + resolveSharedMemoryStatusSnapshot, + type MemoryPluginStatus, + type MemoryStatusSnapshot, +} from "./status.scan.shared.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 GatewayConnectionDetails = { - url: string; - urlSource: string; - bindDetail?: string; - remoteFallbackNote?: string; - message: string; -}; - -type GatewayProbeSnapshot = { - gatewayConnection: GatewayConnectionDetails; - remoteUrlMissing: boolean; - gatewayMode: "local" | "remote"; - gatewayProbeAuth: { - token?: string; - password?: string; - }; - gatewayProbeAuthWarning?: string; - gatewayProbe: Awaited> | null; -}; - let pluginRegistryModulePromise: Promise | undefined; let configIoModulePromise: Promise | undefined; let commandSecretTargetsModulePromise: @@ -100,204 +72,25 @@ function shouldSkipMissingConfigFastPath(): boolean { ); } -function hasExplicitMemorySearchConfig(cfg: OpenClawConfig, agentId: string): boolean { - if ( - cfg.agents?.defaults && - Object.prototype.hasOwnProperty.call(cfg.agents.defaults, "memorySearch") - ) { - return true; - } - const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : []; - return agents.some( - (agent) => agent?.id === agentId && Object.prototype.hasOwnProperty.call(agent, "memorySearch"), - ); -} - -function normalizeControlUiBasePath(basePath?: string): string { - if (!basePath) { - return ""; - } - let normalized = basePath.trim(); - if (!normalized) { - return ""; - } - if (!normalized.startsWith("/")) { - normalized = `/${normalized}`; - } - if (normalized === "/") { - return ""; - } - if (normalized.endsWith("/")) { - normalized = normalized.slice(0, -1); - } - return normalized; -} - -function trimToUndefined(value: string | undefined): string | undefined { - const trimmed = value?.trim(); - return trimmed ? trimmed : undefined; -} - -function buildGatewayConnectionDetails(options: { - config: OpenClawConfig; - url?: string; - configPath?: string; - urlSource?: "cli" | "env"; -}): GatewayConnectionDetails { - const config = options.config; - const configPath = - options.configPath ?? resolveConfigPath(process.env, resolveStateDir(process.env)); - const isRemoteMode = config.gateway?.mode === "remote"; - const remote = isRemoteMode ? config.gateway?.remote : undefined; - const tlsEnabled = config.gateway?.tls?.enabled === true; - const localPort = resolveGatewayPort(config); - const bindMode = config.gateway?.bind ?? "loopback"; - const scheme = tlsEnabled ? "wss" : "ws"; - const localUrl = `${scheme}://127.0.0.1:${localPort}`; - const cliUrlOverride = - typeof options.url === "string" && options.url.trim().length > 0 - ? options.url.trim() - : undefined; - const envUrlOverride = cliUrlOverride - ? undefined - : (trimToUndefined(process.env.OPENCLAW_GATEWAY_URL) ?? - trimToUndefined(process.env.CLAWDBOT_GATEWAY_URL)); - const urlOverride = cliUrlOverride ?? envUrlOverride; - const remoteUrl = - typeof remote?.url === "string" && remote.url.trim().length > 0 ? remote.url.trim() : undefined; - const remoteMisconfigured = isRemoteMode && !urlOverride && !remoteUrl; - const urlSourceHint = - options.urlSource ?? (cliUrlOverride ? "cli" : envUrlOverride ? "env" : undefined); - const url = urlOverride || remoteUrl || localUrl; - const urlSource = urlOverride - ? urlSourceHint === "env" - ? "env OPENCLAW_GATEWAY_URL" - : "cli --url" - : remoteUrl - ? "config gateway.remote.url" - : remoteMisconfigured - ? "missing gateway.remote.url (fallback local)" - : "local loopback"; - const bindDetail = !urlOverride && !remoteUrl ? `Bind: ${bindMode}` : undefined; - const remoteFallbackNote = remoteMisconfigured - ? "Warn: gateway.mode=remote but gateway.remote.url is missing; set gateway.remote.url or switch gateway.mode=local." - : undefined; - const allowPrivateWs = process.env.OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1"; - if (!isSecureWebSocketUrl(url, { allowPrivateWs })) { - throw new Error( - [ - `SECURITY ERROR: Gateway URL "${url}" uses plaintext ws:// to a non-loopback address.`, - "Both credentials and chat data would be exposed to network interception.", - `Source: ${urlSource}`, - `Config: ${configPath}`, - ].join("\n"), - ); - } - return { - url, - urlSource, - bindDetail, - remoteFallbackNote, - message: [ - `Gateway target: ${url}`, - `Source: ${urlSource}`, - `Config: ${configPath}`, - bindDetail, - remoteFallbackNote, - ] - .filter(Boolean) - .join("\n"), - }; -} - function resolveDefaultMemoryStorePath(agentId: string): string { return path.join(resolveStateDir(process.env, os.homedir), "memory", `${agentId}.sqlite`); } -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), - detailLevel: "presence", - }).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 resolveMemoryStatusSnapshot(params: { cfg: OpenClawConfig; agentStatus: Awaited>; memoryPlugin: MemoryPluginStatus; }): Promise { - const { cfg, agentStatus, memoryPlugin } = params; - if (!memoryPlugin.enabled || memoryPlugin.slot !== "memory-core") { - return null; - } - const agentId = agentStatus.defaultId ?? "main"; - const explicitMemoryConfig = hasExplicitMemorySearchConfig(cfg, agentId); - const defaultStorePath = resolveDefaultMemoryStorePath(agentId); - if (!explicitMemoryConfig && !existsSync(defaultStorePath)) { - return null; - } const { resolveMemorySearchConfig } = await loadMemorySearchModule(); - const resolvedMemory = resolveMemorySearchConfig(cfg, agentId); - if (!resolvedMemory) { - return null; - } - const shouldInspectStore = - hasExplicitMemorySearchConfig(cfg, agentId) || existsSync(resolvedMemory.store.path); - if (!shouldInspectStore) { - return null; - } const { getMemorySearchManager } = await loadStatusScanDepsRuntimeModule(); - 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 }; + return await resolveSharedMemoryStatusSnapshot({ + cfg: params.cfg, + agentStatus: params.agentStatus, + memoryPlugin: params.memoryPlugin, + resolveMemoryConfig: resolveMemorySearchConfig, + getMemorySearchManager, + requireDefaultStore: resolveDefaultMemoryStorePath, + }); } async function readStatusSourceConfig(): Promise { @@ -372,10 +165,11 @@ export async function scanStatusJsonFast( gatewayProbePromise, summaryPromise, ]); - const tailscaleHttpsUrl = - tailscaleMode !== "off" && tailscaleDns - ? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` - : null; + const tailscaleHttpsUrl = buildTailscaleHttpsUrl({ + tailscaleMode, + tailscaleDns, + controlUiBasePath: cfg.gateway?.controlUi?.basePath, + }); const { gatewayConnection, diff --git a/src/commands/status.scan.shared.ts b/src/commands/status.scan.shared.ts new file mode 100644 index 00000000000..b855c85320a --- /dev/null +++ b/src/commands/status.scan.shared.ts @@ -0,0 +1,157 @@ +import { existsSync } from "node:fs"; +import type { OpenClawConfig } from "../config/types.js"; +import { buildGatewayConnectionDetails } from "../gateway/call.js"; +import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js"; +import { probeGateway } from "../gateway/probe.js"; +import type { MemoryProviderStatus } from "../memory/types.js"; +import { + pickGatewaySelfPresence, + resolveGatewayProbeAuthResolution, +} from "./status.gateway-probe.js"; + +export type MemoryStatusSnapshot = MemoryProviderStatus & { + agentId: string; +}; + +export type MemoryPluginStatus = { + enabled: boolean; + slot: string | null; + reason?: string; +}; + +export type GatewayProbeSnapshot = { + gatewayConnection: ReturnType; + remoteUrlMissing: boolean; + gatewayMode: "local" | "remote"; + gatewayProbeAuth: { + token?: string; + password?: string; + }; + gatewayProbeAuthWarning?: string; + gatewayProbe: Awaited> | null; +}; + +export function hasExplicitMemorySearchConfig(cfg: OpenClawConfig, agentId: string): boolean { + if ( + cfg.agents?.defaults && + Object.prototype.hasOwnProperty.call(cfg.agents.defaults, "memorySearch") + ) { + return true; + } + const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : []; + return agents.some( + (agent) => agent?.id === agentId && Object.prototype.hasOwnProperty.call(agent, "memorySearch"), + ); +} + +export 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" }; +} + +export 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), + detailLevel: "presence", + }).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, + }; +} + +export function buildTailscaleHttpsUrl(params: { + tailscaleMode: string; + tailscaleDns: string | null; + controlUiBasePath?: string; +}): string | null { + return params.tailscaleMode !== "off" && params.tailscaleDns + ? `https://${params.tailscaleDns}${normalizeControlUiBasePath(params.controlUiBasePath)}` + : null; +} + +export async function resolveSharedMemoryStatusSnapshot(params: { + cfg: OpenClawConfig; + agentStatus: { defaultId?: string | null }; + memoryPlugin: MemoryPluginStatus; + resolveMemoryConfig: (cfg: OpenClawConfig, agentId: string) => { store: { path: string } } | null; + getMemorySearchManager: (params: { + cfg: OpenClawConfig; + agentId: string; + purpose: "status"; + }) => Promise<{ + manager: { + probeVectorAvailability(): Promise; + status(): MemoryProviderStatus; + close?(): Promise; + } | null; + }>; + requireDefaultStore?: (agentId: string) => string | null; +}): Promise { + const { cfg, agentStatus, memoryPlugin } = params; + if (!memoryPlugin.enabled || memoryPlugin.slot !== "memory-core") { + return null; + } + const agentId = agentStatus.defaultId ?? "main"; + const defaultStorePath = params.requireDefaultStore?.(agentId); + if ( + defaultStorePath && + !hasExplicitMemorySearchConfig(cfg, agentId) && + !existsSync(defaultStorePath) + ) { + return null; + } + const resolvedMemory = params.resolveMemoryConfig(cfg, agentId); + if (!resolvedMemory) { + return null; + } + const shouldInspectStore = + hasExplicitMemorySearchConfig(cfg, agentId) || existsSync(resolvedMemory.store.path); + if (!shouldInspectStore) { + return null; + } + const { manager } = await params.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 }; +} + +export { pickGatewaySelfPresence }; diff --git a/src/commands/status.scan.test.ts b/src/commands/status.scan.test.ts index edb77ae4fcf..168c2f55017 100644 --- a/src/commands/status.scan.test.ts +++ b/src/commands/status.scan.test.ts @@ -1,6 +1,7 @@ -import { describe, expect, it, vi } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ + hasPotentialConfiguredChannels: vi.fn(), readBestEffortConfig: vi.fn(), resolveCommandSecretRefsViaGateway: vi.fn(), buildChannelsTable: vi.fn(), @@ -15,6 +16,15 @@ const mocks = vi.hoisted(() => ({ ensurePluginRegistryLoaded: vi.fn(), })); +beforeEach(() => { + vi.clearAllMocks(); + mocks.hasPotentialConfiguredChannels.mockReturnValue(false); +}); + +vi.mock("../channels/config-presence.js", () => ({ + hasPotentialConfiguredChannels: mocks.hasPotentialConfiguredChannels, +})); + vi.mock("../cli/progress.js", () => ({ withProgress: vi.fn(async (_opts, run) => await run({ setLabel: vi.fn(), tick: vi.fn() })), })); @@ -333,6 +343,7 @@ describe("scanStatus", () => { }); it("preloads configured channel plugins for status --json when channel config exists", async () => { + mocks.hasPotentialConfiguredChannels.mockReturnValue(true); mocks.readBestEffortConfig.mockResolvedValue({ session: {}, plugins: { enabled: false }, @@ -395,6 +406,7 @@ describe("scanStatus", () => { }); it("preloads configured channel plugins for status --json when channel auth is env-only", async () => { + mocks.hasPotentialConfiguredChannels.mockReturnValue(true); const prevMatrixToken = process.env.MATRIX_ACCESS_TOKEN; process.env.MATRIX_ACCESS_TOKEN = "token"; mocks.readBestEffortConfig.mockResolvedValue({ diff --git a/src/commands/status.scan.ts b/src/commands/status.scan.ts index 6c2bd67f3dd..3eb6fc8ed3d 100644 --- a/src/commands/status.scan.ts +++ b/src/commands/status.scan.ts @@ -1,4 +1,3 @@ -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"; @@ -6,62 +5,30 @@ import { getStatusCommandSecretTargetIds } from "../cli/command-secret-targets.j 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 { callGateway } from "../gateway/call.js"; import { resolveOsSummary } from "../infra/os-summary.js"; -import type { MemoryProviderStatus } from "../memory/types.js"; import { runExec } from "../process/exec.js"; import type { RuntimeEnv } from "../runtime.js"; import { getAgentLocalStatuses } from "./status.agent-local.js"; -import { - pickGatewaySelfPresence, - resolveGatewayProbeAuthResolution, -} from "./status.gateway-probe.js"; import type { buildChannelsTable as buildChannelsTableFn, collectChannelStatusIssues as collectChannelStatusIssuesFn, } from "./status.scan.runtime.js"; +import { + buildTailscaleHttpsUrl, + pickGatewaySelfPresence, + resolveGatewayProbeSnapshot, + resolveMemoryPluginStatus, + resolveSharedMemoryStatusSnapshot, + type GatewayProbeSnapshot, + type MemoryPluginStatus, + type MemoryStatusSnapshot, +} from "./status.scan.shared.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; -}; - -function hasExplicitMemorySearchConfig(cfg: OpenClawConfig, agentId: string): boolean { - if ( - cfg.agents?.defaults && - Object.prototype.hasOwnProperty.call(cfg.agents.defaults, "memorySearch") - ) { - return true; - } - const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : []; - return agents.some( - (agent) => agent?.id === agentId && Object.prototype.hasOwnProperty.call(agent, "memorySearch"), - ); -} - 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; -}; - let pluginRegistryModulePromise: Promise | undefined; let statusScanRuntimeModulePromise: Promise | undefined; let statusScanDepsRuntimeModulePromise: @@ -97,54 +64,6 @@ function unwrapDeferredResult(result: DeferredResult): T { 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), - detailLevel: "presence", - }).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; @@ -173,7 +92,7 @@ export type StatusScanResult = { tailscaleDns: string | null; tailscaleHttpsUrl: string | null; update: Awaited>; - gatewayConnection: ReturnType; + gatewayConnection: GatewayProbeSnapshot["gatewayConnection"]; remoteUrlMissing: boolean; gatewayMode: "local" | "remote"; gatewayProbeAuth: { @@ -181,7 +100,7 @@ export type StatusScanResult = { password?: string; }; gatewayProbeAuthWarning?: string; - gatewayProbe: Awaited> | null; + gatewayProbe: GatewayProbeSnapshot["gatewayProbe"]; gatewayReachable: boolean; gatewaySelf: ReturnType; channelIssues: ReturnType; @@ -197,34 +116,14 @@ async function resolveMemoryStatusSnapshot(params: { 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 resolvedMemory = resolveMemorySearchConfig(cfg, agentId); - if (!resolvedMemory) { - return null; - } - const shouldInspectStore = - hasExplicitMemorySearchConfig(cfg, agentId) || existsSync(resolvedMemory.store.path); - if (!shouldInspectStore) { - return null; - } const { getMemorySearchManager } = await loadStatusScanDepsRuntimeModule(); - 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 }; + return await resolveSharedMemoryStatusSnapshot({ + cfg: params.cfg, + agentStatus: params.agentStatus, + memoryPlugin: params.memoryPlugin, + resolveMemoryConfig: resolveMemorySearchConfig, + getMemorySearchManager, + }); } async function scanStatusJsonFast(opts: { @@ -274,10 +173,11 @@ async function scanStatusJsonFast(opts: { gatewayProbePromise, summaryPromise, ]); - const tailscaleHttpsUrl = - tailscaleMode !== "off" && tailscaleDns - ? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` - : null; + const tailscaleHttpsUrl = buildTailscaleHttpsUrl({ + tailscaleMode, + tailscaleDns, + controlUiBasePath: cfg.gateway?.controlUi?.basePath, + }); const { gatewayConnection, @@ -376,10 +276,11 @@ export async function scanStatus( progress.setLabel("Checking Tailscale…"); const tailscaleDns = await tailscaleDnsPromise; - const tailscaleHttpsUrl = - tailscaleMode !== "off" && tailscaleDns - ? `https://${tailscaleDns}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` - : null; + const tailscaleHttpsUrl = buildTailscaleHttpsUrl({ + tailscaleMode, + tailscaleDns, + controlUiBasePath: cfg.gateway?.controlUi?.basePath, + }); progress.tick(); progress.setLabel("Checking for updates…");