openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
958 lines (957 loc) • 38.7 kB
JavaScript
import { a as isLegacyParentWritableUpdateDoctorPass } from "./update-phase-Br0EIrpk.js";
import fs from "node:fs";
//#region src/flows/doctor-health-contributions.ts
const PRE_HEALTH_POSITIONAL_HEALTH_CHECK_IDS = new Set(["core/doctor/ui-protocol-freshness"]);
const loadAgentDefaultsModule = async () => await import("./defaults-RurGC76M.js");
const loadAgentScopeModule = async () => await import("./agent-scope-Do4dPjL6.js");
const loadCommandFormatModule = async () => await import("./command-format-XdapRFrK.js");
const loadConfigModule = async () => await import("./config/config.js");
const loadDoctorCoreChecksModule = async () => await import("./doctor-core-checks-Cvy74Jn6.js");
const loadDoctorStateIntegrityModule = async () => await import("./doctor-state-integrity-DUo5oD-Q.js");
const loadHealthCheckRegistryModule = async () => await import("./health-check-registry-D4TYIWd8.js");
const loadModelCatalogModule = async () => await import("./model-catalog-BflZHqiC.js");
const loadModelSelectionModule = async () => await import("./model-selection-CZSV2CxT.js");
const loadNoteModule = async () => await import("./terminal-core/note.js");
const loadOnboardHelpersModule = async () => await import("./onboard-helpers-CfAsmV3U.js");
const loadSecretTypesModule = async () => await import("./types.secrets-CU-vkNdB.js");
function isUpdateDoctorRun(env) {
const value = env.OPENCLAW_UPDATE_IN_PROGRESS;
return value === "1" || value === "true";
}
function resolveDoctorMode(cfg) {
return cfg.gateway?.mode === "remote" ? "remote" : "local";
}
function isTruthyEnvValue(value) {
if (!value) return false;
const normalized = value.trim().toLowerCase();
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
}
function shouldSkipLegacyUpdateDoctorConfigWrite(params) {
if (!isTruthyEnvValue(params.env.OPENCLAW_UPDATE_IN_PROGRESS)) return false;
if (isTruthyEnvValue(params.env["OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE"])) return false;
return true;
}
function createDoctorHealthContribution(params) {
return {
id: params.id,
kind: "core",
surface: "health",
option: {
value: params.id,
label: params.label,
...params.hint ? { hint: params.hint } : {}
},
source: "doctor",
healthCheckIds: params.healthCheckIds ?? [],
run: params.run
};
}
function resolvePositionalHealthCheckIds() {
const ids = new Set(PRE_HEALTH_POSITIONAL_HEALTH_CHECK_IDS);
for (const contribution of resolveDoctorHealthContributions()) {
if (contribution.id === "doctor:structured-health-repairs") continue;
for (const checkId of contribution.healthCheckIds) ids.add(checkId);
}
return ids;
}
async function runGatewayConfigHealth(ctx) {
const { formatCliCommand } = await loadCommandFormatModule();
const { hasAmbiguousGatewayAuthModeConfig } = await import("./auth-mode-policy-__i7UhnC.js");
const { note } = await loadNoteModule();
if (!ctx.cfg.gateway?.mode) {
const lines = [
"gateway.mode is unset; gateway start will be blocked.",
`Fix: run ${formatCliCommand("openclaw configure")} and set Gateway mode (local/remote).`,
`Or set directly: ${formatCliCommand("openclaw config set gateway.mode local")}`
];
if (!fs.existsSync(ctx.configPath)) lines.push(`Missing config: run ${formatCliCommand("openclaw setup")} first.`);
note(lines.join("\n"), "Gateway");
}
if (resolveDoctorMode(ctx.cfg) === "local" && hasAmbiguousGatewayAuthModeConfig(ctx.cfg)) note([
"gateway.auth.token and gateway.auth.password are both configured while gateway.auth.mode is unset.",
"Set an explicit mode to avoid ambiguous auth selection and startup/runtime failures.",
`Set token mode: ${formatCliCommand("openclaw config set gateway.auth.mode token")}`,
`Set password mode: ${formatCliCommand("openclaw config set gateway.auth.mode password")}`
].join("\n"), "Gateway auth");
}
async function runAuthProfileHealth(ctx) {
const { maybeRepairLegacyFlatAuthProfileStores, maybeRepairCanonicalApiKeyFieldAlias } = await import("./doctor-auth-flat-profiles-B1K8wSef.js");
const { maybeRepairLegacyOAuthProfileIds } = await import("./doctor-auth-legacy-oauth-49e8uXX-.js");
const { maybeRepairLegacyOAuthSidecarProfiles } = await import("./doctor-auth-oauth-sidecar-DrwjJ1od.js");
const { noteAuthProfileHealth, noteLegacyCodexProviderOverride } = await import("./doctor-auth-CUDMkvhU.js");
const { buildGatewayConnectionDetails } = await import("./call-BEl78mhZ.js");
const { note } = await loadNoteModule();
await maybeRepairLegacyFlatAuthProfileStores({
cfg: ctx.cfg,
prompter: ctx.prompter
});
await maybeRepairCanonicalApiKeyFieldAlias({
cfg: ctx.cfg,
prompter: ctx.prompter
});
await maybeRepairLegacyOAuthSidecarProfiles({
cfg: ctx.cfg,
prompter: ctx.prompter
});
ctx.cfg = await maybeRepairLegacyOAuthProfileIds(ctx.cfg, ctx.prompter);
await noteAuthProfileHealth({
cfg: ctx.cfg,
prompter: ctx.prompter,
allowKeychainPrompt: ctx.options.nonInteractive !== true && process.stdin.isTTY
});
noteLegacyCodexProviderOverride(ctx.cfg);
ctx.gatewayDetails = buildGatewayConnectionDetails({ config: ctx.cfg });
if (ctx.gatewayDetails.remoteFallbackNote) note(ctx.gatewayDetails.remoteFallbackNote, "Gateway");
}
async function runGatewayAuthHealth(ctx) {
const { resolveSecretInputRef } = await loadSecretTypesModule();
const { buildGatewayTokenSecretRefFixHint, buildGatewayTokenSecretRefUnavailableMessage } = await loadDoctorCoreChecksModule();
const { resolveGatewayAuth } = await import("./auth-ZhQVtXub.js");
const { resolveGatewayAuthToken } = await import("./auth-token-resolution-BZnPAg2c.js");
const { note } = await loadNoteModule();
const { randomToken } = await loadOnboardHelpersModule();
if (resolveDoctorMode(ctx.cfg) !== "local" || !ctx.sourceConfigValid) return;
const gatewayTokenRef = resolveSecretInputRef({
value: ctx.cfg.gateway?.auth?.token,
defaults: ctx.cfg.secrets?.defaults
}).ref;
const auth = resolveGatewayAuth({
authConfig: ctx.cfg.gateway?.auth,
tailscaleMode: ctx.cfg.gateway?.tailscale?.mode ?? "off"
});
const hasInlineToken = typeof auth.token === "string" && auth.token.trim() !== "";
if (!(auth.mode !== "password" && auth.mode !== "none" && auth.mode !== "trusted-proxy" && (auth.mode !== "token" || !hasInlineToken || Boolean(gatewayTokenRef)))) return;
let unresolvedRefReason;
if (gatewayTokenRef && gatewayTokenRef.source === "exec") {
const { getSkippedExecRefStaticError } = await import("./exec-resolution-policy-BFCoYvbC.js");
if (getSkippedExecRefStaticError({
ref: gatewayTokenRef,
config: ctx.cfg
})) unresolvedRefReason = void 0;
else if (ctx.options.allowExec !== true) return;
else {
const resolvedToken = await resolveGatewayAuthToken({
cfg: ctx.cfg,
env: ctx.env ?? process.env,
unresolvedReasonStyle: "detailed",
envFallback: "never"
});
if (resolvedToken.source === "secretRef") return;
unresolvedRefReason = resolvedToken.unresolvedRefReason;
}
} else {
const resolvedToken = await resolveGatewayAuthToken({
cfg: ctx.cfg,
env: ctx.env ?? process.env,
unresolvedReasonStyle: "detailed",
envFallback: gatewayTokenRef ? "never" : "always"
});
if (gatewayTokenRef ? resolvedToken.source === "secretRef" : resolvedToken.token) return;
unresolvedRefReason = resolvedToken.unresolvedRefReason;
}
if (gatewayTokenRef) {
note([
buildGatewayTokenSecretRefUnavailableMessage({
cfg: ctx.cfg,
ref: gatewayTokenRef,
unresolvedRefReason
}),
"Doctor will not overwrite gateway.auth.token with a plaintext value.",
buildGatewayTokenSecretRefFixHint(gatewayTokenRef)
].join("\n"), "Gateway auth");
return;
}
note("Gateway auth is off or missing a token. Token auth is now the recommended default (including loopback).", "Gateway auth");
if (!(ctx.options.generateGatewayToken === true ? true : ctx.options.nonInteractive === true ? false : await ctx.prompter.confirmAutoFix({
message: "Generate and configure a gateway token now?",
initialValue: true
}))) return;
const nextToken = randomToken();
ctx.cfg = {
...ctx.cfg,
gateway: {
...ctx.cfg.gateway,
auth: {
...ctx.cfg.gateway?.auth,
mode: "token",
token: nextToken
}
}
};
note("Gateway token configured.", "Gateway auth");
}
async function runCommandOwnerHealth(ctx) {
const { noteCommandOwnerHealth } = await import("./doctor-command-owner-DIx5N0HZ.js");
noteCommandOwnerHealth(ctx.cfg);
}
async function runStructuredHealthRepairs(ctx) {
if (!ctx.prompter.shouldRepair) return;
const { registerCoreHealthChecks } = await loadDoctorCoreChecksModule();
const { registerBundledHealthChecks } = await import("./bundled-health-checks-D6eURhvs.js");
const { listHealthChecks } = await loadHealthCheckRegistryModule();
const { runDoctorHealthRepairs } = await import("./doctor-repair-flow-C5TvgV-o.js");
const { resolveAgentWorkspaceDir, resolveDefaultAgentId } = await loadAgentScopeModule();
const { note } = await loadNoteModule();
registerCoreHealthChecks();
const workspaceDir = resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg));
registerBundledHealthChecks({
cfg: ctx.cfg,
cwd: workspaceDir
});
const positionalHealthCheckIds = resolvePositionalHealthCheckIds();
const checks = listHealthChecks().filter((check) => !positionalHealthCheckIds.has(check.id));
const result = await runDoctorHealthRepairs({
mode: "fix",
runtime: ctx.runtime,
cfg: ctx.cfg,
cwd: workspaceDir,
configPath: ctx.configPath
}, { checks });
ctx.cfg = result.config;
if (result.changes.length > 0) note(result.changes.join("\n"), "Doctor changes");
if (result.warnings.length > 0) note(result.warnings.join("\n"), "Doctor warnings");
}
async function runClaudeCliHealth(ctx) {
const { noteClaudeCliHealth } = await import("./doctor-claude-cli-6L-769MI.js");
noteClaudeCliHealth(ctx.cfg);
}
async function runLegacyStateHealth(ctx) {
const { detectLegacyStateMigrations, runLegacyStateMigrations } = await import("./doctor-state-migrations-DN_uDqjB.js");
const { note } = await loadNoteModule();
const legacyState = await detectLegacyStateMigrations({ cfg: ctx.cfg });
if (legacyState.preview.length === 0) return;
note(legacyState.preview.join("\n"), "Legacy state detected");
if (!(ctx.options.nonInteractive === true ? true : await ctx.prompter.confirm({
message: "Migrate legacy state (sessions/agent/WhatsApp auth) now?",
initialValue: true
}))) return;
const migrated = await runLegacyStateMigrations({
detected: legacyState,
recoverCorruptTargetStore: ctx.options.repair === true || ctx.options.yes === true
});
if (migrated.changes.length > 0) note(migrated.changes.join("\n"), "Doctor changes");
if (migrated.warnings.length > 0) note(migrated.warnings.join("\n"), "Doctor warnings");
}
async function runLegacyPluginManifestHealth(ctx) {
const { maybeRepairLegacyPluginManifestContracts } = await import("./doctor-plugin-manifests-Bhje1tY7.js");
await maybeRepairLegacyPluginManifestContracts({
config: ctx.cfg,
env: process.env,
runtime: ctx.runtime,
prompter: ctx.prompter
});
}
async function runPluginRegistryHealth(ctx) {
const { maybeRepairPluginRegistryState } = await import("./doctor-plugin-registry-B-yf6xUA.js");
ctx.cfg = await maybeRepairPluginRegistryState({
config: ctx.cfg,
env: process.env,
prompter: ctx.prompter
});
}
async function runReleaseConfiguredPluginInstallsHealth(ctx) {
if (!ctx.sourceConfigValid) return;
if (!ctx.prompter.shouldRepair) return;
const { maybeRunConfiguredPluginInstallReleaseStep } = await import("./release-configured-plugin-installs-vbkrnVos.js");
const { note } = await loadNoteModule();
const { VERSION } = await import("./version-Dpd_K_tD.js");
const result = await maybeRunConfiguredPluginInstallReleaseStep({
cfg: ctx.cfg,
env: ctx.env ?? process.env,
touchedVersion: ctx.configResult.sourceLastTouchedVersion ?? ctx.cfg.meta?.lastTouchedVersion
});
if (result.changes.length > 0) note(result.changes.join("\n"), "Doctor changes");
if (result.warnings.length > 0) note(result.warnings.join("\n"), "Doctor warnings");
if (!result.touchedConfig) return;
const lastTouchedVersion = isLegacyParentWritableUpdateDoctorPass(ctx.env ?? process.env) ? ctx.configResult.sourceLastTouchedVersion?.trim() || ctx.cfg.meta?.lastTouchedVersion || VERSION : VERSION;
ctx.cfg = {
...ctx.cfg,
meta: {
...ctx.cfg.meta,
lastTouchedVersion,
lastTouchedAt: (/* @__PURE__ */ new Date()).toISOString()
}
};
}
async function runDiskSpaceHealth(ctx) {
const { noteDiskSpace } = await import("./doctor-disk-space-BTLjHxKM.js");
noteDiskSpace(ctx.cfg);
}
async function runStateIntegrityHealth(ctx) {
const { noteStateIntegrity } = await loadDoctorStateIntegrityModule();
await noteStateIntegrity(ctx.cfg, ctx.prompter, ctx.configPath);
}
async function runCodexSessionRouteHealth(ctx) {
const { maybeRepairCodexSessionRoutes } = await import("./codex-route-warnings-BsO-Lg_W.js");
const { note } = await loadNoteModule();
const result = await maybeRepairCodexSessionRoutes({
cfg: ctx.cfg,
env: ctx.env ?? process.env,
shouldRepair: ctx.prompter.shouldRepair
});
if (result.changes.length > 0) note(result.changes.join("\n"), "Doctor changes");
if (result.warnings.length > 0) note(result.warnings.join("\n"), "Doctor warnings");
}
async function runSessionLocksHealth(ctx) {
const { noteSessionLockHealth } = await import("./doctor-session-locks-BFuRU53-.js");
await noteSessionLockHealth({
shouldRepair: ctx.prompter.shouldRepair,
config: ctx.cfg,
env: ctx.env
});
}
async function runSessionTranscriptsHealth(ctx) {
const { noteSessionTranscriptHealth } = await import("./doctor-session-transcripts-B43WEdus.js");
await noteSessionTranscriptHealth({ shouldRepair: ctx.prompter.shouldRepair });
}
async function runSessionSnapshotsHealth(ctx) {
const { noteSessionSnapshotHealth } = await import("./doctor-session-snapshots-Dc48A56R.js");
await noteSessionSnapshotHealth({
cfg: ctx.cfg,
env: ctx.env ?? process.env,
shouldRepair: ctx.prompter.shouldRepair
});
}
async function runConfigAuditScrubHealth(ctx) {
const { maybeScrubConfigAuditLog } = await import("./doctor-config-audit-scrub-Db-H2Kc-.js");
await maybeScrubConfigAuditLog({ shouldRepair: ctx.prompter.shouldRepair });
}
async function runLegacyCronHealth(ctx) {
const { maybeRepairLegacyCronStore, noteLegacyWhatsAppCrontabHealthCheck } = await import("./doctor-cron-DOttbxU7.js");
await noteLegacyWhatsAppCrontabHealthCheck();
await maybeRepairLegacyCronStore({
cfg: ctx.cfg,
options: ctx.options,
prompter: ctx.prompter
});
}
async function runSandboxHealth(ctx) {
const { maybeRepairSandboxImages, maybeRepairSandboxRegistryFiles, noteSandboxScopeWarnings } = await import("./doctor-sandbox-CUGXNNxr.js");
await maybeRepairSandboxRegistryFiles(ctx.prompter);
ctx.cfg = await maybeRepairSandboxImages(ctx.cfg, ctx.runtime, ctx.prompter);
noteSandboxScopeWarnings(ctx.cfg);
}
async function runGatewayServicesHealth(ctx) {
const { maybeRepairGatewayServiceConfig, maybeScanExtraGatewayServices } = await import("./doctor-gateway-services-BXqfBT_r.js");
const { noteMacLaunchAgentOverrides, noteMacLaunchctlGatewayEnvOverrides, noteMacStaleOpenClawUpdateLaunchdJobs } = await import("./doctor-platform-notes-Ccvwt2-G.js");
await maybeScanExtraGatewayServices(ctx.options, ctx.runtime, ctx.prompter);
await maybeRepairGatewayServiceConfig(ctx.cfg, resolveDoctorMode(ctx.cfg), ctx.runtime, ctx.prompter, { allowExecSecretRefs: ctx.options.allowExec === true });
await noteMacLaunchAgentOverrides();
await noteMacStaleOpenClawUpdateLaunchdJobs();
await noteMacLaunchctlGatewayEnvOverrides(ctx.cfg);
}
async function runStartupChannelMaintenanceHealth(ctx) {
const { maybeRunDoctorStartupChannelMaintenance } = await import("./doctor-startup-channel-maintenance-DO26fT4n.js");
await maybeRunDoctorStartupChannelMaintenance({
cfg: ctx.cfg,
env: process.env,
runtime: ctx.runtime,
shouldRepair: ctx.prompter.shouldRepair
});
}
async function runSecurityHealth(ctx) {
const { noteInstallPolicyHealth } = await import("./doctor-install-policy-Djh0LmrT.js");
const { noteSecurityWarnings } = await import("./doctor-security-BFxGNLoD.js");
await noteSecurityWarnings(ctx.cfg);
await noteInstallPolicyHealth(ctx.cfg, {
deep: ctx.options.deep === true,
env: ctx.env
});
}
async function runBrowserHealth(ctx) {
const { noteChromeMcpBrowserReadiness } = await import("./doctor-browser-DK2w1UtF.js");
await noteChromeMcpBrowserReadiness(ctx.cfg);
}
async function runOpenAIOAuthTlsHealth(ctx) {
const { noteOpenAIOAuthTlsPrerequisites } = await import("./oauth-tls-preflight-BoMlkoHh.js");
await noteOpenAIOAuthTlsPrerequisites({
cfg: ctx.cfg,
deep: ctx.options.deep === true
});
}
async function runHooksModelHealth(ctx) {
if (!ctx.cfg.hooks?.gmail?.model?.trim()) return;
const { DEFAULT_MODEL, DEFAULT_PROVIDER } = await loadAgentDefaultsModule();
const { loadModelCatalog } = await loadModelCatalogModule();
const { getModelRefStatus, resolveConfiguredModelRef, resolveHooksGmailModel } = await loadModelSelectionModule();
const { note } = await loadNoteModule();
const hooksModelRef = resolveHooksGmailModel({
cfg: ctx.cfg,
defaultProvider: DEFAULT_PROVIDER
});
if (!hooksModelRef) {
note(`- hooks.gmail.model "${ctx.cfg.hooks.gmail.model}" could not be resolved`, "Hooks");
return;
}
const { provider: defaultProvider, model: defaultModel } = resolveConfiguredModelRef({
cfg: ctx.cfg,
defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL
});
const catalog = await loadModelCatalog({
config: ctx.cfg,
readOnly: true
});
const status = getModelRefStatus({
cfg: ctx.cfg,
catalog,
ref: hooksModelRef,
defaultProvider,
defaultModel
});
const warnings = [];
if (!status.allowed) warnings.push(`- hooks.gmail.model "${status.key}" not in agents.defaults.models allowlist (will use primary instead)`);
if (!status.inCatalog) warnings.push(`- hooks.gmail.model "${status.key}" not in the model catalog (may fail at runtime)`);
if (warnings.length > 0) note(warnings.join("\n"), "Hooks");
}
async function runToolResultCapHealth(ctx) {
const { resolveAgentContextLimits } = await loadAgentScopeModule();
const { normalizeAgentId } = await import("./session-key-Bivs29uf.js");
const targets = [];
const defaultsConfiguredCap = ctx.cfg.agents?.defaults?.contextLimits?.toolResultMaxChars;
if (ctx.options.deep === true || defaultsConfiguredCap !== void 0) targets.push({
configuredCap: defaultsConfiguredCap,
scopeLabel: "defaults"
});
for (const entry of ctx.cfg.agents?.list ?? []) {
const normalizedAgentId = normalizeAgentId(entry.id);
if (!normalizedAgentId || ctx.options.deep !== true && defaultsConfiguredCap === void 0 && entry.contextLimits?.toolResultMaxChars === void 0) continue;
targets.push({
agentId: normalizedAgentId,
configuredCap: resolveAgentContextLimits(ctx.cfg, normalizedAgentId)?.toolResultMaxChars,
scopeLabel: `agent "${normalizedAgentId}"`
});
}
if (targets.length === 0) return;
const { DEFAULT_CONTEXT_TOKENS } = await loadAgentDefaultsModule();
const { loadModelCatalog, findModelCatalogEntry } = await loadModelCatalogModule();
const { resolveContextWindowInfo } = await import("./context-window-guard-CHd1bxq2.js");
const { resolveDefaultModelForAgent, modelKey } = await loadModelSelectionModule();
const { buildToolResultCapDoctorAdvice } = await import("./doctor-tool-result-cap-advice-j6xSW1V5.js");
const { note } = await loadNoteModule();
const catalog = await loadModelCatalog({ config: ctx.cfg });
const lines = targets.flatMap((target) => {
const modelRef = resolveDefaultModelForAgent({
cfg: ctx.cfg,
agentId: target.agentId
});
const entry = findModelCatalogEntry(catalog, {
provider: modelRef.provider,
modelId: modelRef.model
});
return buildToolResultCapDoctorAdvice({
contextWindowTokens: resolveContextWindowInfo({
cfg: ctx.cfg,
provider: modelRef.provider,
modelId: modelRef.model,
modelContextTokens: entry?.contextTokens,
modelContextWindow: entry?.contextWindow,
defaultTokens: DEFAULT_CONTEXT_TOKENS
}).tokens,
modelKey: modelKey(modelRef.provider, modelRef.model),
configuredCap: target.configuredCap,
deep: ctx.options.deep === true,
scopeLabel: target.scopeLabel
});
});
if (lines.length > 0) note(lines.join("\n"), "Tool result cap");
}
async function runSystemdLingerHealth(ctx) {
if (ctx.options.nonInteractive === true || process.platform !== "linux" || resolveDoctorMode(ctx.cfg) !== "local") return;
const { resolveGatewayService } = await import("./service-T_yX9_lb.js");
const { ensureSystemdUserLingerInteractive } = await import("./systemd-linger-CJppyf46.js");
const { note } = await loadNoteModule();
const service = resolveGatewayService();
let loaded;
try {
loaded = await service.isLoaded({ env: process.env });
} catch {
loaded = false;
}
if (!loaded) return;
await ensureSystemdUserLingerInteractive({
runtime: ctx.runtime,
prompter: {
confirm: async (p) => ctx.prompter.confirm(p),
note
},
reason: "Gateway runs as a systemd user service. Without lingering, systemd stops the user session on logout/idle and kills the Gateway.",
requireConfirm: true
});
}
async function hasActiveGatewayExecCredential(ctx, mode = resolveDoctorMode(ctx.cfg)) {
const { resolveSecretInputRef } = await loadSecretTypesModule();
const { gatewaySecretInputPathCanWin } = await import("./credentials-secret-inputs-CnmSrWHD.js");
const { ALL_GATEWAY_SECRET_INPUT_PATHS, readGatewaySecretInputValue } = await import("./secret-input-paths-B4b1wPE8.js");
return ALL_GATEWAY_SECRET_INPUT_PATHS.some((path) => {
if (!gatewaySecretInputPathCanWin({
config: ctx.cfg,
env: process.env,
modeOverride: mode,
path
})) return false;
return resolveSecretInputRef({
value: readGatewaySecretInputValue(ctx.cfg, path),
defaults: ctx.cfg.secrets?.defaults
}).ref?.source === "exec";
});
}
async function runWorkspaceStatusHealth(ctx) {
let pluginVersionDrift;
if (ctx.cfg.gateway?.mode !== "remote") try {
const { gatherDaemonStatus } = await import("./status.gather-DIr8hvwr.js");
const allowExecSecretRefs = ctx.options.allowExec === true;
const status = await gatherDaemonStatus({
rpc: {
timeout: ctx.options.nonInteractive === true ? "3000" : "10000",
json: true
},
probe: true,
requireRpc: false,
deep: ctx.options.deep === true,
allowExecSecretRefs
});
const hasProbedGatewayVersion = typeof status.gateway?.version === "string" && status.gateway.version.trim() !== "";
if (status.pluginVersionDrift && hasProbedGatewayVersion && !status.rpc?.authWarning) pluginVersionDrift = status.pluginVersionDrift;
} catch {}
const { noteWorkspaceStatus } = await import("./doctor-workspace-status-BUTa3hNp.js");
noteWorkspaceStatus(ctx.cfg, { pluginVersionDrift });
}
async function runSkillsHealth(ctx) {
const { maybeRepairSkillReadiness } = await import("./doctor-skills-4CQXsLsl.js");
ctx.cfg = await maybeRepairSkillReadiness({
cfg: ctx.cfg,
prompter: ctx.prompter
});
}
async function runBootstrapSizeHealth(ctx) {
const { noteBootstrapFileSize } = await import("./doctor-bootstrap-size-f62lPJW-.js");
await noteBootstrapFileSize(ctx.cfg);
}
async function runHeartbeatTemplateRepairHealth(ctx) {
const { maybeRepairHeartbeatTemplate } = await import("./doctor-heartbeat-template-repair-DBvlour-.js");
await maybeRepairHeartbeatTemplate({
cfg: ctx.cfg,
shouldRepair: ctx.prompter.shouldRepair
});
}
async function runShellCompletionHealth(ctx) {
const { doctorShellCompletion } = await import("./doctor-completion-iWLIQ8B-.js");
await doctorShellCompletion(ctx.runtime, ctx.prompter, { nonInteractive: ctx.options.nonInteractive });
}
async function runGatewayHealthChecks(ctx) {
const { note } = await loadNoteModule();
if (await hasActiveGatewayExecCredential(ctx) && ctx.options.allowExec !== true) {
note("Gateway health probes skipped because gateway credentials use an exec SecretRef. Run `openclaw doctor --allow-exec` to verify Gateway health with exec SecretRefs.", "Gateway");
ctx.gatewayHealthSkipped = true;
ctx.gatewayMemoryProbe = {
checked: false,
ready: false,
skipped: true
};
return;
}
const { checkGatewayHealth, probeGatewayMemoryStatus } = await import("./doctor-gateway-health-jT0uhGYk.js");
const { healthOk, authenticated, status } = await checkGatewayHealth({
runtime: ctx.runtime,
cfg: ctx.cfg,
timeoutMs: ctx.options.nonInteractive === true ? 3e3 : 1e4
});
ctx.gatewayHealthSkipped = false;
ctx.healthOk = healthOk;
ctx.gatewayHealthAuthenticated = authenticated;
ctx.gatewayStatus = status;
ctx.gatewayMemoryProbe = authenticated ? await probeGatewayMemoryStatus({
cfg: ctx.cfg,
timeoutMs: ctx.options.nonInteractive === true ? 3e3 : 1e4
}) : {
checked: false,
ready: false,
skipped: healthOk
};
}
async function runWhatsappResponsivenessHealth(ctx) {
const { noteWhatsappResponsivenessHealth } = await import("./doctor-whatsapp-responsiveness-DCQU3rzm.js");
await noteWhatsappResponsivenessHealth({
cfg: ctx.cfg,
status: ctx.gatewayStatus,
shouldRepair: ctx.prompter.shouldRepair
});
}
async function runMemorySearchHealthContribution(ctx) {
const { maybeRepairMemoryRecallHealth, noteMemoryRecallHealth, noteMemorySearchHealth } = await import("./doctor-memory-search-C3qfCGTd.js");
if (ctx.prompter.shouldRepair) await maybeRepairMemoryRecallHealth({
cfg: ctx.cfg,
prompter: ctx.prompter
});
await noteMemorySearchHealth(ctx.cfg, { gatewayMemoryProbe: ctx.gatewayMemoryProbe ?? {
checked: false,
ready: false,
skipped: false
} });
if (ctx.options.deep === true) await noteMemoryRecallHealth(ctx.cfg);
}
async function runDevicePairingHealth(ctx) {
const { noteDevicePairingHealth } = await import("./doctor-device-pairing-D3YcJ_Dr.js");
await noteDevicePairingHealth({
cfg: ctx.cfg,
healthOk: ctx.healthOk ?? false
});
}
async function runGatewayDaemonHealth(ctx) {
const { maybeRepairGatewayDaemon } = await import("./doctor-gateway-daemon-flow-CcObAucR.js");
await maybeRepairGatewayDaemon({
cfg: ctx.cfg,
runtime: ctx.runtime,
prompter: ctx.prompter,
options: ctx.options,
gatewayDetailsMessage: ctx.gatewayDetails?.message ?? "",
healthOk: ctx.healthOk ?? false,
healthSkipped: ctx.gatewayHealthSkipped === true
});
}
async function runWriteConfigHealth(ctx) {
const { formatCliCommand } = await loadCommandFormatModule();
const { applyWizardMetadata } = await loadOnboardHelpersModule();
const { replaceConfigFile } = await loadConfigModule();
const { logConfigUpdated } = await import("./logging-vb-o7PUC.js");
const { shortenHomePath } = await import("./utils-Cn91lUWV.js");
if (ctx.configResult.shouldWriteConfig || JSON.stringify(ctx.cfg) !== JSON.stringify(ctx.cfgForPersistence)) {
const updateDoctorRun = isUpdateDoctorRun(ctx.env ?? process.env);
ctx.cfg = applyWizardMetadata(ctx.cfg, {
command: "doctor",
mode: resolveDoctorMode(ctx.cfg)
});
if (shouldSkipLegacyUpdateDoctorConfigWrite({ env: ctx.env ?? process.env })) {
ctx.runtime.log("Skipping doctor config write during legacy update handoff.");
return;
}
const legacyParentVersionOverride = isLegacyParentWritableUpdateDoctorPass(ctx.env ?? process.env) ? ctx.configResult.sourceLastTouchedVersion?.trim() || ctx.cfg.meta?.lastTouchedVersion : void 0;
await replaceConfigFile({
nextConfig: ctx.cfg,
afterWrite: { mode: "auto" },
writeOptions: {
allowConfigSizeDrop: ctx.configResult.shouldWriteConfig === true || updateDoctorRun,
skipPluginValidation: ctx.configResult.skipPluginValidationOnWrite === true || updateDoctorRun,
preservedLegacyRootKeys: ctx.configResult.preservedLegacyRootKeys,
...legacyParentVersionOverride ? { lastTouchedVersionOverride: legacyParentVersionOverride } : {}
}
});
logConfigUpdated(ctx.runtime);
const preUpdateSnapshotPath = `${ctx.configPath}.pre-update`;
if (updateDoctorRun && fs.existsSync(preUpdateSnapshotPath)) ctx.runtime.log(`Update changed config; pre-update backup: ${shortenHomePath(preUpdateSnapshotPath)}`);
const backupPath = `${ctx.configPath}.bak`;
if (fs.existsSync(backupPath)) ctx.runtime.log(`Backup: ${shortenHomePath(backupPath)}`);
return;
}
if (!ctx.prompter.shouldRepair) ctx.runtime.log(`Run "${formatCliCommand("openclaw doctor --fix")}" to apply changes.`);
}
async function runWorkspaceSuggestionsHealth(ctx) {
if (ctx.options.workspaceSuggestions === false) return;
const { resolveAgentWorkspaceDir, resolveDefaultAgentId } = await loadAgentScopeModule();
const { noteWorkspaceBackupTip } = await loadDoctorStateIntegrityModule();
const { MEMORY_SYSTEM_PROMPT, shouldSuggestMemorySystem } = await import("./doctor-workspace-Dok8_5Ha.js");
const { note } = await loadNoteModule();
const workspaceDir = resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg));
noteWorkspaceBackupTip(workspaceDir);
if (await shouldSuggestMemorySystem(workspaceDir)) note(MEMORY_SYSTEM_PROMPT, "Workspace");
}
async function runFinalConfigValidationHealth(ctx) {
const { readConfigFileSnapshot } = await loadConfigModule();
const finalSnapshot = await readConfigFileSnapshot({
skipPluginValidation: isUpdateDoctorRun(ctx.env ?? process.env),
preservedLegacyRootKeys: ctx.configResult.preservedLegacyRootKeys
});
if (finalSnapshot.exists && !finalSnapshot.valid) {
ctx.runtime.error("Invalid config:");
for (const issue of finalSnapshot.issues) {
const path = issue.path || "<root>";
ctx.runtime.error(`- ${path}: ${issue.message}`);
}
}
}
function formatHealthFindings(findings) {
return findings.map((finding) => {
const lines = [`- ${finding.message}`];
if (finding.path) lines.push(` path: ${finding.path}`);
if (finding.requirement) lines.push(` issue: ${finding.requirement}`);
if (finding.fixHint) lines.push(` fix: ${finding.fixHint}`);
return lines.join("\n");
}).join("\n");
}
async function runProviderCatalogProjectionHealth(ctx) {
const { registerCoreHealthChecks } = await loadDoctorCoreChecksModule();
const { getHealthCheck } = await loadHealthCheckRegistryModule();
const { resolveAgentWorkspaceDir, resolveDefaultAgentId } = await loadAgentScopeModule();
const { note } = await loadNoteModule();
registerCoreHealthChecks();
const check = getHealthCheck("core/doctor/provider-catalog-projection");
if (!check) return;
const findings = await check.detect({
mode: "doctor",
runtime: ctx.runtime,
cfg: ctx.cfg,
cwd: resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg)),
configPath: ctx.configPath
});
if (findings.length === 0) return;
ctx.healthOk = false;
note(formatHealthFindings(findings), "Doctor warnings");
}
async function runRuntimeToolSchemasHealth(ctx) {
const { registerCoreHealthChecks } = await loadDoctorCoreChecksModule();
const { getHealthCheck } = await loadHealthCheckRegistryModule();
const { resolveAgentWorkspaceDir, resolveDefaultAgentId } = await loadAgentScopeModule();
const { note } = await loadNoteModule();
registerCoreHealthChecks();
const check = getHealthCheck("core/doctor/runtime-tool-schemas");
if (!check) return;
const findings = await check.detect({
mode: "doctor",
runtime: ctx.runtime,
cfg: ctx.cfg,
cwd: resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg)),
configPath: ctx.configPath
});
if (findings.length === 0) return;
ctx.healthOk = false;
note(formatHealthFindings(findings), "Doctor warnings");
}
function resolveDoctorHealthContributions() {
return [
createDoctorHealthContribution({
id: "doctor:gateway-config",
label: "Gateway config",
healthCheckIds: ["core/doctor/gateway-config"],
run: runGatewayConfigHealth
}),
createDoctorHealthContribution({
id: "doctor:auth-profiles",
label: "Auth profiles",
run: runAuthProfileHealth
}),
createDoctorHealthContribution({
id: "doctor:claude-cli",
label: "Claude CLI",
healthCheckIds: ["core/doctor/claude-cli"],
run: runClaudeCliHealth
}),
createDoctorHealthContribution({
id: "doctor:gateway-auth",
label: "Gateway auth",
healthCheckIds: ["core/doctor/gateway-auth"],
run: runGatewayAuthHealth
}),
createDoctorHealthContribution({
id: "doctor:command-owner",
label: "Command owner",
healthCheckIds: ["core/doctor/command-owner"],
run: runCommandOwnerHealth
}),
createDoctorHealthContribution({
id: "doctor:structured-health-repairs",
label: "Structured health repairs",
run: runStructuredHealthRepairs
}),
createDoctorHealthContribution({
id: "doctor:legacy-state",
label: "Legacy state",
healthCheckIds: ["core/doctor/legacy-state"],
run: runLegacyStateHealth
}),
createDoctorHealthContribution({
id: "doctor:legacy-plugin-manifests",
label: "Legacy plugin manifests",
run: runLegacyPluginManifestHealth
}),
createDoctorHealthContribution({
id: "doctor:release-configured-plugin-installs",
label: "Configured plugin repair",
run: runReleaseConfiguredPluginInstallsHealth
}),
createDoctorHealthContribution({
id: "doctor:plugin-registry",
label: "Plugin registry",
run: runPluginRegistryHealth
}),
createDoctorHealthContribution({
id: "doctor:disk-space",
label: "Disk space",
run: runDiskSpaceHealth
}),
createDoctorHealthContribution({
id: "doctor:state-integrity",
label: "State integrity",
run: runStateIntegrityHealth
}),
createDoctorHealthContribution({
id: "doctor:codex-session-routes",
label: "Codex session routes",
run: runCodexSessionRouteHealth
}),
createDoctorHealthContribution({
id: "doctor:session-locks",
label: "Session locks",
run: runSessionLocksHealth
}),
createDoctorHealthContribution({
id: "doctor:session-transcripts",
label: "Session transcripts",
run: runSessionTranscriptsHealth
}),
createDoctorHealthContribution({
id: "doctor:session-snapshots",
label: "Session snapshots",
run: runSessionSnapshotsHealth
}),
createDoctorHealthContribution({
id: "doctor:config-audit-scrub",
label: "Config audit",
run: runConfigAuditScrubHealth
}),
createDoctorHealthContribution({
id: "doctor:legacy-cron",
label: "Legacy cron",
healthCheckIds: ["core/doctor/legacy-whatsapp-crontab"],
run: runLegacyCronHealth
}),
createDoctorHealthContribution({
id: "doctor:sandbox",
label: "Sandbox",
run: runSandboxHealth
}),
createDoctorHealthContribution({
id: "doctor:gateway-services",
label: "Gateway services",
healthCheckIds: ["core/doctor/gateway-services/platform-notes"],
run: runGatewayServicesHealth
}),
createDoctorHealthContribution({
id: "doctor:startup-channel-maintenance",
label: "Startup channel maintenance",
run: runStartupChannelMaintenanceHealth
}),
createDoctorHealthContribution({
id: "doctor:security",
label: "Security",
healthCheckIds: ["core/doctor/security"],
run: runSecurityHealth
}),
createDoctorHealthContribution({
id: "doctor:browser",
label: "Browser",
healthCheckIds: ["core/doctor/browser"],
run: runBrowserHealth
}),
createDoctorHealthContribution({
id: "doctor:oauth-tls",
label: "OAuth TLS",
healthCheckIds: ["core/doctor/oauth-tls"],
run: runOpenAIOAuthTlsHealth
}),
createDoctorHealthContribution({
id: "doctor:hooks-model",
label: "Hooks model",
healthCheckIds: ["core/doctor/hooks-model"],
run: runHooksModelHealth
}),
createDoctorHealthContribution({
id: "doctor:tool-result-cap",
label: "Tool result cap",
run: runToolResultCapHealth
}),
createDoctorHealthContribution({
id: "doctor:provider-catalog-projection",
label: "Provider catalog projection",
healthCheckIds: ["core/doctor/provider-catalog-projection"],
run: runProviderCatalogProjectionHealth
}),
createDoctorHealthContribution({
id: "doctor:runtime-tool-schemas",
label: "Runtime tool schemas",
healthCheckIds: ["core/doctor/runtime-tool-schemas"],
run: runRuntimeToolSchemasHealth
}),
createDoctorHealthContribution({
id: "doctor:systemd-linger",
label: "systemd linger",
run: runSystemdLingerHealth
}),
createDoctorHealthContribution({
id: "doctor:workspace-status",
label: "Workspace status",
healthCheckIds: ["core/doctor/workspace-status"],
run: runWorkspaceStatusHealth
}),
createDoctorHealthContribution({
id: "doctor:skills",
label: "Skills",
healthCheckIds: ["core/doctor/skills-readiness"],
run: runSkillsHealth
}),
createDoctorHealthContribution({
id: "doctor:bootstrap-size",
label: "Bootstrap size",
healthCheckIds: ["core/doctor/bootstrap-size"],
run: runBootstrapSizeHealth
}),
createDoctorHealthContribution({
id: "doctor:heartbeat-template-repair",
label: "Heartbeat template repair",
run: runHeartbeatTemplateRepairHealth
}),
createDoctorHealthContribution({
id: "doctor:shell-completion",
label: "Shell completion",
healthCheckIds: ["core/doctor/shell-completion"],
run: runShellCompletionHealth
}),
createDoctorHealthContribution({
id: "doctor:gateway-health",
label: "Gateway health",
run: runGatewayHealthChecks
}),
createDoctorHealthContribution({
id: "doctor:whatsapp-responsiveness",
label: "WhatsApp responsiveness",
run: runWhatsappResponsivenessHealth
}),
createDoctorHealthContribution({
id: "doctor:memory-search",
label: "Memory search",
run: runMemorySearchHealthContribution
}),
createDoctorHealthContribution({
id: "doctor:device-pairing",
label: "Device pairing",
run: runDevicePairingHealth
}),
createDoctorHealthContribution({
id: "doctor:gateway-daemon",
label: "Gateway daemon",
run: runGatewayDaemonHealth
}),
createDoctorHealthContribution({
id: "doctor:write-config",
label: "Write config",
run: runWriteConfigHealth
}),
createDoctorHealthContribution({
id: "doctor:workspace-suggestions",
label: "Workspace suggestions",
healthCheckIds: ["core/doctor/workspace-suggestions"],
run: runWorkspaceSuggestionsHealth
}),
createDoctorHealthContribution({
id: "doctor:final-config-validation",
label: "Final config validation",
healthCheckIds: ["core/doctor/final-config-validation"],
run: runFinalConfigValidationHealth
})
];
}
async function runDoctorHealthContributions(ctx) {
for (const contribution of resolveDoctorHealthContributions()) await contribution.run(ctx);
}
//#endregion
export { runDoctorHealthContributions };