refactor(status): share scan helper state

This commit is contained in:
Peter Steinberger 2026-03-17 06:06:40 +00:00
parent 03c6946125
commit 143530407d
4 changed files with 223 additions and 359 deletions

View File

@ -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<ReturnType<typeof probeGateway>> | null;
};
let pluginRegistryModulePromise: Promise<typeof import("../cli/plugin-registry.js")> | undefined;
let configIoModulePromise: Promise<typeof import("../config/io.js")> | 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<GatewayProbeSnapshot> {
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<ReturnType<typeof getAgentLocalStatuses>>;
memoryPlugin: MemoryPluginStatus;
}): Promise<MemoryStatusSnapshot | null> {
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<OpenClawConfig> {
@ -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,

View File

@ -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<typeof buildGatewayConnectionDetails>;
remoteUrlMissing: boolean;
gatewayMode: "local" | "remote";
gatewayProbeAuth: {
token?: string;
password?: string;
};
gatewayProbeAuthWarning?: string;
gatewayProbe: Awaited<ReturnType<typeof probeGateway>> | 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<GatewayProbeSnapshot> {
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<void>;
status(): MemoryProviderStatus;
close?(): Promise<void>;
} | null;
}>;
requireDefaultStore?: (agentId: string) => string | null;
}): Promise<MemoryStatusSnapshot | null> {
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 };

View File

@ -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({

View File

@ -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<T> = { ok: true; value: T } | { ok: false; error: unknown };
type GatewayProbeSnapshot = {
gatewayConnection: ReturnType<typeof buildGatewayConnectionDetails>;
remoteUrlMissing: boolean;
gatewayMode: "local" | "remote";
gatewayProbeAuth: {
token?: string;
password?: string;
};
gatewayProbeAuthWarning?: string;
gatewayProbe: Awaited<ReturnType<typeof probeGateway>> | null;
};
let pluginRegistryModulePromise: Promise<typeof import("../cli/plugin-registry.js")> | undefined;
let statusScanRuntimeModulePromise: Promise<typeof import("./status.scan.runtime.js")> | undefined;
let statusScanDepsRuntimeModulePromise:
@ -97,54 +64,6 @@ function unwrapDeferredResult<T>(result: DeferredResult<T>): 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<GatewayProbeSnapshot> {
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<ReturnType<typeof getUpdateCheckResult>>;
gatewayConnection: ReturnType<typeof buildGatewayConnectionDetails>;
gatewayConnection: GatewayProbeSnapshot["gatewayConnection"];
remoteUrlMissing: boolean;
gatewayMode: "local" | "remote";
gatewayProbeAuth: {
@ -181,7 +100,7 @@ export type StatusScanResult = {
password?: string;
};
gatewayProbeAuthWarning?: string;
gatewayProbe: Awaited<ReturnType<typeof probeGateway>> | null;
gatewayProbe: GatewayProbeSnapshot["gatewayProbe"];
gatewayReachable: boolean;
gatewaySelf: ReturnType<typeof pickGatewaySelfPresence>;
channelIssues: ReturnType<typeof collectChannelStatusIssuesFn>;
@ -197,34 +116,14 @@ async function resolveMemoryStatusSnapshot(params: {
agentStatus: Awaited<ReturnType<typeof getAgentLocalStatuses>>;
memoryPlugin: MemoryPluginStatus;
}): Promise<MemoryStatusSnapshot | null> {
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…");