openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
508 lines (507 loc) • 27.1 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { E as tryResolveDefaultAgentId, m as resolveAgentWorkspaceDir, o as listAgentIds, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js";
import { n as findNormalizedProviderValue, r as normalizeProviderId } from "./provider-id-DMd-TDFp.js";
import { d as isSecretRef } from "./types.secrets-kC0nOetj.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { n as defaultSlotIdForKey } from "./slots-CQdAEuat.js";
import { l as normalizePluginsConfig } from "./config-state-BkU1frVq.js";
import { a as resolveManifestOwnerBasePolicyBlock } from "./manifest-owner-policy-D98oU3cV.js";
import { n as loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry-contributions-BwuDvm89.js";
import "./plugin-registry-DD_0nL_t.js";
import "./legacy-config-migrations.runtime.models-Ak4QjGte.js";
import { c as resolveRememberAcrossConversations } from "./legacy-D15RYgFr.js";
import "./agent-scope-DbtJyKUL.js";
import { t as getProviderEnvVars } from "./provider-env-vars-B8XgJCSL.js";
import { n as loadTrustedExternalProviderPolicyArtifacts, t as listTrustedExternalProviderPolicyOwners } from "./provider-public-artifacts-Bone7gkJ.js";
import { C as resolveMemoryDreamingPluginConfig, S as resolveMemoryDreamingConfig } from "./dreaming-DmJZL5-O.js";
import { n as hasAuthProfileStoreSourceForProvider, t as hasAnyAuthProfileStoreSource } from "./source-check-BuLDxD9s.js";
import { t as isConfiguredAwsSdkAuthProfileForProvider } from "./order-CC2RBzI5.js";
import { t as resolveEnvApiKey } from "./model-auth-env-Dq9W4xg9.js";
import { S as resolveUsableCustomProviderApiKey } from "./model-auth-provider-config-C_kr_q2g.js";
import "./auth-profiles-BdUEhE7u.js";
import { t as resolveApiKeyForProviderCore } from "./model-auth-provider-Dd_3dXeY.js";
import "./model-auth-C48_DZ-I.js";
import { t as resolveMemorySearchConfig } from "./memory-search-DTACVvxE.js";
import { a as getActiveMemorySearchManagerCore, o as resolveActiveMemoryBackendConfig } from "./memory-runtime-DgrGeT4a.js";
import { t as note } from "./note-DqHk3fA1.js";
import { t as hasConfiguredMemorySecretInput } from "./secret-input-B_UskQwz.js";
import "./secret-CJgC9Be0.js";
import { i as getMissingLocalMemoryEmbeddingProviderMessage, n as auditShortTermPromotionArtifacts, o as repairDreamingArtifacts, s as repairShortTermPromotionArtifacts, t as auditDreamingArtifacts } from "./memory-core-bundled-runtime-CacBOGXk.js";
import { i as maybeRepairWorkspaceMemoryHealth, o as noteWorkspaceMemoryHealth } from "./doctor-workspace-Uw56WRcQ.js";
import fs from "node:fs";
//#region src/commands/doctor-memory-search.ts
function resolveMemoryDoctorAgentScopes(cfg) {
return listAgentIds(cfg).map((agentId) => ({
agentId,
agentDir: resolveAgentDir(cfg, agentId),
workspaceDir: resolveAgentWorkspaceDir(cfg, agentId)
}));
}
function formatAgentMessage(agentId, labelAgent, message) {
return `${labelAgent ? `Agent "${agentId}": ` : ""}${message}`;
}
function formatLocalRuntimeDoctorNote(facts) {
const backend = facts.backend ?? "unknown";
const build = facts.buildInfo ? `, ${facts.buildInfo}` : "";
const model = facts.model?.id ? `\nModel: ${facts.model.id}${facts.model.path ? ` (${facts.model.path})` : ""}` : "";
const capabilities = facts.capabilities ? `\nCapabilities: ${[facts.capabilities.vision ? "vision" : null, facts.capabilities.draft ? "draft" : null].filter(Boolean).join(", ") || "text only"}` : "";
const endpoints = facts.endpoints ? `\nEndpoints: ${Object.entries(facts.endpoints).map(([name, status]) => `${name}=${status}`).join(" ")}` : "";
const loadError = facts.loadError ? `\nLoad error: ${facts.loadError}` : "";
return `llama.cpp server: ${backend}${build}${facts.state === "ready" ? "" : ` (${facts.state})`}${model}${capabilities}${endpoints}${loadError}`;
}
function resolveLocalProviderPolicyBlockGuidance(reason, pluginId) {
switch (reason) {
case "plugins-disabled": return {
message: "Plugin loading is disabled for this config.",
fix: `Fix: ${formatCliCommand("openclaw config set plugins.enabled true --strict-json")}, or select another memory provider.`
};
case "blocked-by-denylist": return {
message: `Installed plugin "${pluginId}" is blocked by plugins.deny.`,
fix: `Fix: Remove "${pluginId}" from plugins.deny, or select another memory provider.`
};
case "plugin-disabled": return {
message: `Installed plugin "${pluginId}" is disabled for this config.`,
fix: `Fix: Enable it: ${formatCliCommand(`openclaw plugins enable ${pluginId} --accept-capabilities`)}, or select another memory provider.`
};
case "not-in-allowlist": return {
message: `Installed plugin "${pluginId}" is omitted from plugins.allow.`,
fix: `Fix: Include "${pluginId}" in plugins.allow, or select another memory provider.`
};
}
return reason;
}
const MEMORY_EMBEDDING_PROVIDER_AUTH_IDS = /* @__PURE__ */ new Map([
["github-copilot", "github-copilot"],
["openai", "openai"],
["gemini", "google"],
["voyage", "voyage"],
["mistral", "mistral"],
["bedrock", "amazon-bedrock"]
]);
const OPENAI_COMPATIBLE_MEMORY_EMBEDDING_PROVIDER = "openai-compatible";
const OPENAI_COMPATIBLE_MODEL_APIS = /* @__PURE__ */ new Set(["openai-completions", "openai-responses"]);
function hasConfiguredAwsSdkAuthForProvider(provider, cfg) {
if (findNormalizedProviderValue(cfg.models?.providers, provider)?.auth === "aws-sdk") return true;
return (findNormalizedProviderValue(cfg.auth?.order, provider) ?? (cfg.auth?.profiles ? Object.keys(cfg.auth.profiles) : [])).some((profileId) => isConfiguredAwsSdkAuthProfileForProvider({
cfg,
provider,
profileId
}));
}
function isOpenAICompatibleMemoryProvider(providerId, cfg) {
const normalizedProviderId = normalizeProviderId(providerId);
if (normalizedProviderId === OPENAI_COMPATIBLE_MEMORY_EMBEDDING_PROVIDER) return true;
if (MEMORY_EMBEDDING_PROVIDER_AUTH_IDS.has(normalizedProviderId)) return false;
const providerConfig = findNormalizedProviderValue(cfg.models?.providers, providerId);
if (!providerConfig) return false;
const api = normalizeProviderId(providerConfig.api ?? "");
if (api === OPENAI_COMPATIBLE_MEMORY_EMBEDDING_PROVIDER || OPENAI_COMPATIBLE_MODEL_APIS.has(api)) return true;
return !api && Boolean(normalizeOptionalString(providerConfig.baseUrl));
}
function resolveOpenAICompatibleMemoryBaseUrl(providerId, cfg, remoteBaseUrl) {
return normalizeOptionalString(remoteBaseUrl) ?? normalizeOptionalString(findNormalizedProviderValue(cfg.models?.providers, providerId)?.baseUrl);
}
function isKeyOptionalMemoryProvider(providerId, cfg) {
return providerId === "local" || providerId === "ollama" || providerId === "lmstudio" || isOpenAICompatibleMemoryProvider(providerId, cfg);
}
async function resolveRuntimeMemoryAuditContext(cfg, agentId) {
const manager = (await getActiveMemorySearchManagerCore({
cfg,
agentId,
purpose: "status"
})).manager;
if (!manager) return null;
try {
return { workspaceDir: manager.status().workspaceDir?.trim() };
} finally {
await manager.close?.().catch(() => void 0);
}
}
function buildMemoryRecallIssueNote(audit) {
if (audit.issues.length === 0) return null;
const issueLines = audit.issues.map((issue) => `- ${issue.message}`);
const guidance = audit.issues.some((issue) => issue.fixable) ? `Fix: ${formatCliCommand("openclaw doctor --fix")} or ${formatCliCommand("openclaw memory status --fix")}` : `Verify: ${formatCliCommand("openclaw memory status --deep")}`;
return [
"Memory recall artifacts need attention:",
...issueLines,
`Recall store: ${audit.storePath}`,
guidance
].join("\n");
}
function buildDreamingArtifactIssueNote(audit) {
if (audit.issues.length === 0) return null;
const issueLines = audit.issues.map((issue) => `- ${issue.message}`);
const hasFixableIssue = audit.issues.some((issue) => issue.fixable);
return [
"Dreaming artifacts need attention:",
...issueLines,
`Dream corpus: ${audit.sessionCorpusDir}`,
hasFixableIssue ? `Fix: ${formatCliCommand("openclaw doctor --fix")} or ${formatCliCommand("openclaw memory status --fix")}` : `Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n");
}
async function noteMemoryRecallHealth(cfg) {
const scopes = resolveMemoryDoctorAgentScopes(cfg);
const labelAgents = scopes.length > 1;
const dreaming = resolveMemoryDreamingConfig({
cfg,
pluginConfig: resolveMemoryDreamingPluginConfig(cfg)
});
for (const scope of scopes) try {
const workspaceDir = (await resolveRuntimeMemoryAuditContext(cfg, scope.agentId))?.workspaceDir?.trim();
if (!workspaceDir) continue;
const message = buildMemoryRecallIssueNote(await auditShortTermPromotionArtifacts({ workspaceDir }));
if (message) note(formatAgentMessage(scope.agentId, labelAgents, message), "Memory search");
const dreamingMessage = buildDreamingArtifactIssueNote(await auditDreamingArtifacts({ workspaceDir }));
if (dreamingMessage) note(formatAgentMessage(scope.agentId, labelAgents, dreamingMessage), "Memory search");
} catch (err) {
note(formatAgentMessage(scope.agentId, labelAgents, `Memory recall audit could not be completed: ${formatErrorMessage(err)}`), "Memory search");
} finally {
note(formatAgentMessage(scope.agentId, labelAgents, `Dreaming: ${dreaming.enabled ? "enabled" : "disabled"} (cadence ${dreaming.frequency}).`), "Memory search");
}
}
async function maybeRepairMemoryRecallHealth(params) {
const scopes = resolveMemoryDoctorAgentScopes(params.cfg);
const labelAgents = scopes.length > 1;
for (const scope of scopes) {
await maybeRepairWorkspaceMemoryHealth({
...params,
scope: {
agentId: scope.agentId,
workspaceDir: scope.workspaceDir,
labelAgent: labelAgents
}
});
try {
const workspaceDir = (await resolveRuntimeMemoryAuditContext(params.cfg, scope.agentId))?.workspaceDir?.trim();
if (!workspaceDir) continue;
if ((await auditShortTermPromotionArtifacts({ workspaceDir })).issues.some((issue) => issue.fixable)) {
if (await params.prompter.confirmRuntimeRepair({
message: formatAgentMessage(scope.agentId, labelAgents, "Remove dangling memory recalls, normalize recall artifacts, and remove stale promotion locks?"),
initialValue: true
})) {
const repair = await repairShortTermPromotionArtifacts({ workspaceDir });
if (repair.changed) {
const removedOverflowEntries = repair.removedOverflowEntries ?? 0;
const details = [
repair.removedInvalidEntries > 0 ? `-${repair.removedInvalidEntries} invalid entries` : null,
(repair.removedDanglingEntries ?? 0) > 0 ? `-${repair.removedDanglingEntries} dangling entries` : null,
removedOverflowEntries > 0 ? `-${removedOverflowEntries} overflow entries` : null
].filter(Boolean).join(", ");
const lines = [
"Memory recall artifacts repaired:",
repair.rewroteStore ? `- rewrote recall store${details ? ` (${details})` : ""}` : null,
repair.removedStaleLock ? "- removed stale promotion lock" : null,
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].filter(Boolean);
note(formatAgentMessage(scope.agentId, labelAgents, lines.join("\n")), "Doctor changes");
}
}
}
if (!(await auditDreamingArtifacts({ workspaceDir })).issues.some((issue) => issue.fixable)) continue;
if (!await params.prompter.confirmRuntimeRepair({
message: formatAgentMessage(scope.agentId, labelAgents, "Archive contaminated dreaming artifacts and reset derived dream corpus state?"),
initialValue: true
})) continue;
const dreamingRepair = await repairDreamingArtifacts({ workspaceDir });
if (!dreamingRepair.changed) continue;
const lines = [
"Dreaming artifacts repaired:",
dreamingRepair.archivedSessionCorpus ? "- archived session corpus" : null,
dreamingRepair.archivedSessionIngestion ? "- archived session-ingestion state" : null,
dreamingRepair.archivedDreamsDiary ? "- archived dream diary" : null,
dreamingRepair.archiveDir ? `- archive dir: ${dreamingRepair.archiveDir}` : null,
...dreamingRepair.warnings.map((warning) => `- warning: ${warning}`),
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].filter(Boolean);
note(formatAgentMessage(scope.agentId, labelAgents, lines.join("\n")), "Doctor changes");
} catch (err) {
note(formatAgentMessage(scope.agentId, labelAgents, `Memory artifact repair could not be completed: ${formatErrorMessage(err)}`), "Memory search");
}
}
}
function hasActiveAlternateMemoryPluginSlot(cfg) {
const plugins = normalizePluginsConfig(cfg.plugins);
if (!plugins.enabled) return false;
const memorySlot = plugins.slots.memory;
if (typeof memorySlot !== "string" || memorySlot.length === 0) return false;
if (memorySlot === defaultSlotIdForKey("memory")) return false;
if (plugins.deny.includes(memorySlot)) return false;
if (!Object.hasOwn(plugins.entries, memorySlot)) return false;
const entry = plugins.entries[memorySlot];
if (!entry || entry.enabled === false) return false;
return entry.enabled === true || entry.config !== void 0;
}
function isActiveMemoryPluginAvailable(cfg) {
const plugins = normalizePluginsConfig(cfg.plugins);
if (!plugins.enabled || plugins.deny.includes("active-memory")) return false;
if (plugins.allow.length > 0 && !plugins.allow.includes("active-memory")) return false;
const entry = plugins.entries["active-memory"];
if (entry?.enabled === false) return false;
return (isRecord(entry?.config) ? entry.config : void 0)?.enabled !== false;
}
function resolveActiveMemoryConversationRecallSupport(cfg) {
const providerSupported = normalizePluginsConfig(cfg.plugins).slots.memory === defaultSlotIdForKey("memory");
const entry = cfg.plugins?.entries?.["active-memory"];
const config = isRecord(entry?.config) ? entry.config : void 0;
if (!Array.isArray(config?.toolsAllow)) return {
providerSupported,
memorySearchAllowed: true
};
return {
providerSupported,
memorySearchAllowed: config.toolsAllow.some((toolName) => typeof toolName === "string" && toolName.trim().toLowerCase() === "memory_search")
};
}
function noteRememberAcrossConversationsHealth(params) {
if (!resolveRememberAcrossConversations(params.cfg, params.agentId)) return { enabled: false };
const activeMemoryAvailable = isActiveMemoryPluginAvailable(params.cfg);
const conversationRecallSupport = resolveActiveMemoryConversationRecallSupport(params.cfg);
if (!activeMemoryAvailable) params.noteFn(`Remember across conversations is effectively enabled for agent "${params.agentId}", but the Active Memory plugin is disabled. Enable the plugin or set memory.search.rememberAcrossConversations to false.`, "Memory search");
if (activeMemoryAvailable && !conversationRecallSupport.providerSupported) params.noteFn(`Remember across conversations is effectively enabled for agent "${params.agentId}", but the current memory provider does not support protected private transcript recall. Set memory.search.rememberAcrossConversations to false or use that provider's own recall path; advanced Active Memory can still use its recall tools.`, "Memory search");
else if (activeMemoryAvailable && !conversationRecallSupport.memorySearchAllowed) params.noteFn(`Remember across conversations is effectively enabled for agent "${params.agentId}", but Active Memory does not allow memory_search. Add memory_search to the plugin toolsAllow list or set memory.search.rememberAcrossConversations to false.`, "Memory search");
return { enabled: true };
}
async function noteMemorySearchHealth(cfg, opts) {
const scopes = resolveMemoryDoctorAgentScopes(cfg);
const defaultAgentId = tryResolveDefaultAgentId(cfg);
const labelAgents = scopes.length > 1;
for (const scope of scopes) {
if (opts?.includeWorkspaceMemoryHealth !== false) await noteWorkspaceMemoryHealth(cfg, {
agentId: scope.agentId,
workspaceDir: scope.workspaceDir,
labelAgent: labelAgents
});
const outputNote = opts?.noteFn ?? note;
const noteFn = (message, title) => outputNote(formatAgentMessage(scope.agentId, labelAgents, String(message)), title);
await noteMemorySearchHealthForAgent(cfg, scope, {
...opts,
noteFn,
includeWorkspaceMemoryHealth: false,
gatewayMemoryProbe: scope.agentId === defaultAgentId || opts?.gatewayMemoryProbe?.skipped ? opts?.gatewayMemoryProbe : void 0
});
}
}
async function noteMemorySearchHealthForAgent(cfg, scope, opts) {
const { agentId, agentDir } = scope;
const noteFn = opts.noteFn ?? note;
const resolved = resolveMemorySearchConfig(cfg, agentId);
if (!resolved) {
noteFn(noteRememberAcrossConversationsHealth({
cfg,
agentId,
noteFn
}).enabled ? `Remember across conversations is effectively enabled for agent "${agentId}", but memory search is disabled. Enable memory search or set memory.search.rememberAcrossConversations to false.` : "Memory search is explicitly disabled (enabled: false).", "Memory search");
return;
}
const provider = resolved.provider;
const normalizedPlugins = normalizePluginsConfig(cfg.plugins);
if (provider === "local" && !normalizedPlugins.enabled) {
const policyBlock = resolveLocalProviderPolicyBlockGuidance("plugins-disabled", provider);
noteFn([
policyBlock.message,
"",
policyBlock.fix,
"",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n"), "Memory search");
return;
}
noteRememberAcrossConversationsHealth({
cfg,
agentId,
noteFn
});
const hasRemoteApiKey = hasConfiguredMemorySecretInput(resolved.remote?.apiKey);
if (!resolveActiveMemoryBackendConfig({
cfg,
agentId
})) {
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) return;
if (hasActiveAlternateMemoryPluginSlot(cfg)) return;
noteFn("No active memory plugin is registered for the current config.", "Memory search");
return;
}
if (provider === "none") return;
if (provider === "local") {
const runtimeFacts = opts?.gatewayMemoryProbe?.runtimeFacts;
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) {
if (runtimeFacts) noteFn(formatLocalRuntimeDoctorNote(runtimeFacts), "Memory search");
return;
}
const hasExplicitLocalModel = hasLocalEmbeddings(resolved.local);
const hasUnavailableConfiguredLocalModel = Boolean(normalizeOptionalString(resolved.local.modelPath)) && !hasExplicitLocalModel;
if (opts?.gatewayMemoryProbe?.skipped && !hasUnavailableConfiguredLocalModel) return;
const detail = opts?.gatewayMemoryProbe?.error?.trim();
const gatewayDetail = detail && detail !== runtimeFacts?.loadError ? detail : null;
const env = opts.env ?? process.env;
const manifestRegistry = loadPluginManifestRegistryForPluginRegistry({
config: cfg,
env,
includeDisabled: true
});
const installedOwners = listTrustedExternalProviderPolicyOwners(provider, manifestRegistry);
if (installedOwners.length === 0) {
noteFn(getMissingLocalMemoryEmbeddingProviderMessage(), "Memory search");
return;
}
const ownerPolicies = installedOwners.map((owner) => ({
owner,
policyBlock: resolveManifestOwnerBasePolicyBlock({
plugin: owner,
normalizedConfig: normalizedPlugins
})
}));
const eligibleOwners = ownerPolicies.filter(({ policyBlock }) => !policyBlock).map(({ owner }) => owner);
const policyArtifacts = eligibleOwners.length > 0 ? loadTrustedExternalProviderPolicyArtifacts(eligibleOwners) : null;
let installedOwner;
let ownerPolicyBlock;
if (policyArtifacts) {
installedOwner = policyArtifacts.owner;
ownerPolicyBlock = null;
} else {
const blockedOwner = ownerPolicies.find(({ policyBlock }) => policyBlock);
if (!blockedOwner) throw new Error(`Unable to resolve the installed provider owner for "${provider}".`);
installedOwner = blockedOwner.owner;
ownerPolicyBlock = blockedOwner.policyBlock;
}
const providerPolicy = policyArtifacts?.surface;
const inspectSetup = ownerPolicyBlock ? void 0 : providerPolicy?.inspectEmbeddingProviderSetup;
const setup = inspectSetup ? await inspectSetup({
config: cfg,
env,
agentId,
provider
}) : null;
const setupReason = setup?.reason.trim();
const setupFix = setup?.fixHint?.trim();
const updateFix = !ownerPolicyBlock && !inspectSetup ? `Fix: Update the installed plugin: ${formatCliCommand(`openclaw plugins update ${installedOwner.id}`)}` : null;
const policyBlock = ownerPolicyBlock ? resolveLocalProviderPolicyBlockGuidance(ownerPolicyBlock, installedOwner.id) : null;
const hasRuntimeFailureDetail = Boolean(gatewayDetail || runtimeFacts?.loadError);
noteFn([
runtimeFacts ? formatLocalRuntimeDoctorNote(runtimeFacts) : null,
runtimeFacts ? "" : null,
hasExplicitLocalModel ? "Memory search provider is set to \"local\" and a local model path is configured, but local embeddings are not confirmed ready." : "Memory search provider is set to \"local\", but local embeddings are not confirmed ready.",
setupReason ? `Setup: ${setupReason}` : null,
policyBlock?.message,
updateFix ? `Installed plugin "${installedOwner.id}" does not provide current local-memory setup diagnostics.` : null,
gatewayDetail && gatewayDetail !== setupReason ? `Gateway probe: ${gatewayDetail}` : null,
"",
policyBlock?.fix ?? updateFix ?? (setupFix ? `Fix: ${setupFix}` : hasUnavailableConfiguredLocalModel ? "Fix: Set memory.search.local.modelPath to an existing GGUF file, or remove it to use the managed default." : hasRuntimeFailureDetail ? "Fix: Repair the llama.cpp server problem reported by the Gateway." : null),
"",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].filter(Boolean).join("\n"), "Memory search");
return;
}
if (isOpenAICompatibleMemoryProvider(provider, cfg) && !resolveOpenAICompatibleMemoryBaseUrl(provider, cfg, resolved.remote?.baseUrl)) {
noteFn([
`Memory search provider is set to "${provider}" but no OpenAI-compatible embeddings endpoint was configured.`,
"Set memory.search.remote.baseUrl to the /v1 endpoint for your embeddings server.",
"",
"Fix:",
`- ${formatCliCommand("openclaw config set memory.search.remote.baseUrl http://127.0.0.1:1234/v1")}`,
"",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n"), "Memory search");
return;
}
if (isOpenAICompatibleMemoryProvider(provider, cfg) && !normalizeOptionalString(resolved.model)) {
noteFn([
`Memory search provider is set to "${provider}" but no OpenAI-compatible embedding model was configured.`,
"Set memory.search.model to the embedding model id your server expects.",
"",
"Fix:",
`- ${formatCliCommand("openclaw config set memory.search.model text-embedding-bge-m3")}`,
"",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n"), "Memory search");
return;
}
if (isKeyOptionalMemoryProvider(provider, cfg)) {
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) return;
if (opts?.gatewayMemoryProbe?.skipped) return;
const gatewayProbeWarning = buildGatewayProbeWarning(opts?.gatewayMemoryProbe);
noteFn([
gatewayProbeWarning ? `Memory search provider "${provider}" is configured, but the gateway reports embeddings are not ready.` : `Memory search provider "${provider}" is configured, but the gateway could not confirm embeddings are ready.`,
gatewayProbeWarning,
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].filter(Boolean).join("\n"), "Memory search");
return;
}
if (hasRemoteApiKey || await hasApiKeyForProvider(provider, cfg, agentDir, { skipProfileResolution: opts?.skipAuthProfileResolution === true })) return;
if (opts?.gatewayMemoryProbe?.checked && opts.gatewayMemoryProbe.ready) {
noteFn([
`Memory search provider is set to "${provider}" but the API key was not found in the CLI environment.`,
"The running gateway reports memory embeddings are ready for the default agent.",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n"), "Memory search");
return;
}
const gatewayProbeWarning = buildGatewayProbeWarning(opts?.gatewayMemoryProbe);
const envVar = resolvePrimaryMemoryProviderEnvVar(provider);
noteFn([
`Memory search provider is set to "${provider}" but no API key was found.`,
`Semantic recall will not work without a valid API key.`,
gatewayProbeWarning ? gatewayProbeWarning : null,
"",
"Fix (pick one):",
`- Set ${envVar} in your environment`,
`- Configure credentials: ${formatCliCommand("openclaw configure --section model")}`,
`- To disable: ${formatCliCommand("openclaw config set memory.search.enabled false")}`,
"",
`Verify: ${formatCliCommand("openclaw memory status --deep")}`
].join("\n"), "Memory search");
}
/**
* Check whether local embeddings are available.
*
*/
function hasLocalEmbeddings(local) {
const modelPath = normalizeOptionalString(local.modelPath);
if (!modelPath) return false;
if (/^(hf:|https?:)/i.test(modelPath)) return true;
const resolved = resolveUserPath(modelPath);
try {
return fs.statSync(resolved).isFile();
} catch {
return false;
}
}
async function hasApiKeyForProvider(provider, cfg, agentDir, opts) {
const authProviderId = MEMORY_EMBEDDING_PROVIDER_AUTH_IDS.get(provider) ?? provider;
if (isSecretRef(findNormalizedProviderValue(cfg.models?.providers, authProviderId)?.apiKey) || resolveEnvApiKey(authProviderId) || resolveUsableCustomProviderApiKey({
cfg,
provider: authProviderId
})) return true;
if (opts?.skipProfileResolution === true) {
if (authProviderId === "amazon-bedrock") return hasConfiguredAwsSdkAuthForProvider(authProviderId, cfg);
const orderedProfileIds = findNormalizedProviderValue(cfg.auth?.order, authProviderId);
return orderedProfileIds === void 0 ? hasAuthProfileStoreSourceForProvider(authProviderId, agentDir) : hasAuthProfileStoreSourceForProvider(authProviderId, agentDir, { profileIds: orderedProfileIds });
}
if (authProviderId !== "amazon-bedrock" && !hasAnyAuthProfileStoreSource(agentDir)) return false;
try {
await resolveApiKeyForProviderCore({
provider: authProviderId,
cfg,
agentDir
});
return true;
} catch {
return false;
}
}
function resolvePrimaryMemoryProviderEnvVar(provider) {
if (provider === "openai") return "OPENAI_API_KEY";
const authProviderId = MEMORY_EMBEDDING_PROVIDER_AUTH_IDS.get(provider);
return (authProviderId ? getProviderEnvVars(authProviderId)[0] : void 0) ?? `${provider.toUpperCase()}_API_KEY`;
}
function buildGatewayProbeWarning(probe) {
if (!probe?.checked || probe.ready) return null;
const detail = probe.error?.trim();
return detail ? `Gateway memory probe for default agent is not ready: ${detail}` : "Gateway memory probe for default agent is not ready.";
}
//#endregion
export { maybeRepairMemoryRecallHealth, noteMemoryRecallHealth, noteMemorySearchHealth };