refactor(status): share scan helper state
This commit is contained in:
parent
03c6946125
commit
143530407d
@ -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,
|
||||
|
||||
157
src/commands/status.scan.shared.ts
Normal file
157
src/commands/status.scan.shared.ts
Normal 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 };
|
||||
@ -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({
|
||||
|
||||
@ -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…");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user