UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

732 lines (731 loc) 28.9 kB
import { m as resolveSecretInputRef } from "./types.secrets-_0JOMGE5.js"; import "./agent-scope-MrLta7Pq.js"; import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js"; import { r as hasAmbiguousGatewayAuthModeConfig } from "./auth-mode-policy-Byq0myX3.js"; import { t as resolveGatewayAuthToken } from "./auth-token-resolution-AiDzF8d9.js"; import "./auth-DXSWhsnk.js"; import { n as resolveGatewayAuth } from "./auth-resolve-BSKl_nHz.js"; import { n as maybeArchiveLegacyClawdBrowserProfileResidue, r as noteChromeMcpBrowserReadiness, t as detectLegacyClawdBrowserProfileResidue } from "./doctor-browser-Ba7dFGtm.js"; import { n as hasConfiguredCommandOwners } from "./doctor-command-owner-BTofOD-K.js"; import { a as shellCompletionStatusToRepairEffects, i as shellCompletionStatusToHealthFindings, t as checkShellCompletionStatus } from "./doctor-completion-BndH-Q_9.js"; import { n as disableUnavailableSkillsInConfig } from "./doctor-skills-core-N-TJqicN.js"; import { i as uiProtocolFreshnessIssueToRepairEffects, r as uiProtocolFreshnessIssueToHealthFinding, t as detectUiProtocolFreshnessIssues } from "./doctor-ui-DY1NQkGD.js"; import { n as collectDisabledCodexPluginRouteIssues } from "./codex-route-warnings-CS_FhR54.js"; import { t as getSkippedExecRefStaticError } from "./exec-resolution-policy-LiH6fTkx.js"; import { a as registerHealthCheck } from "./health-check-registry-CGjpbWY5.js"; import path from "node:path"; //#region src/flows/doctor-core-checks.ts const BROWSER_CLAWD_PROFILE_RESIDUE_CHECK_ID = "core/doctor/browser-clawd-profile-residue"; const CODEX_SESSION_ROUTES_CHECK_ID = "core/doctor/codex-session-routes"; const FINAL_CONFIG_VALIDATION_CHECK_ID = "core/doctor/final-config-validation"; const loadDoctorCoreChecksRuntimeModule = async () => await import("./doctor-core-checks.runtime.js"); const loadDoctorWorkspaceModule = async () => await import("./doctor-workspace-Dok8_5Ha.js"); async function detectUnavailableSkillsWithRuntime(cfg) { return (await loadDoctorCoreChecksRuntimeModule()).detectUnavailableSkills(cfg); } async function collectSecurityWarningsWithRuntime(cfg) { const { collectSecurityWarnings } = await import("./doctor-security-BFxGNLoD.js"); return collectSecurityWarnings(cfg); } async function collectWorkspaceSuggestionNotesWithRuntime(workspaceDir) { const { collectWorkspaceBackupTip } = await import("./doctor-state-integrity-DUo5oD-Q.js"); const { MEMORY_SYSTEM_PROMPT, shouldSuggestMemorySystem } = await loadDoctorWorkspaceModule(); const notes = []; const backupTip = collectWorkspaceBackupTip(workspaceDir); if (backupTip) notes.push(backupTip); if (await shouldSuggestMemorySystem(workspaceDir)) notes.push(MEMORY_SYSTEM_PROMPT); return notes; } async function collectRuntimeToolSchemaFindingsWithRuntime(ctx) { return (await loadDoctorCoreChecksRuntimeModule()).collectRuntimeToolSchemaFindings(ctx.cfg); } async function collectProviderCatalogProjectionFindingsWithRuntime(ctx) { return (await loadDoctorCoreChecksRuntimeModule()).collectProviderCatalogProjectionFindings(ctx.cfg); } const defaultCoreHealthCheckDeps = { detectUnavailableSkills: detectUnavailableSkillsWithRuntime, collectSecurityWarnings: collectSecurityWarningsWithRuntime, collectWorkspaceSuggestionNotes: collectWorkspaceSuggestionNotesWithRuntime, collectRuntimeToolSchemaFindings: collectRuntimeToolSchemaFindingsWithRuntime, collectProviderCatalogProjectionFindings: collectProviderCatalogProjectionFindingsWithRuntime }; function configValidationIssuesToHealthFindings(issues) { return issues.map((issue) => ({ checkId: FINAL_CONFIG_VALIDATION_CHECK_ID, severity: "error", message: issue.message, path: issue.path || "<root>" })); } const gatewayConfigCheck = { id: "core/doctor/gateway-config", kind: "core", description: "openclaw.jsonc gateway block is set and unambiguous.", source: "doctor", async detect(ctx) { const findings = []; if (!ctx.cfg.gateway?.mode) findings.push({ checkId: "core/doctor/gateway-config", severity: "warning", message: "gateway.mode is unset; gateway start will be blocked.", path: "gateway.mode", fixHint: "Run `openclaw configure` and set Gateway mode (local/remote), or `openclaw config set gateway.mode local`." }); if (ctx.cfg.gateway?.mode !== "remote" && hasAmbiguousGatewayAuthModeConfig(ctx.cfg)) findings.push({ checkId: "core/doctor/gateway-config", severity: "warning", message: "gateway.auth.token and gateway.auth.password are both configured while gateway.auth.mode is unset; auth selection is ambiguous.", path: "gateway.auth.mode", fixHint: "Set an explicit mode: `openclaw config set gateway.auth.mode token` or `... password`." }); return findings; } }; const commandOwnerCheck = { id: "core/doctor/command-owner", kind: "core", description: "An owner account is configured for owner-only commands.", source: "doctor", async detect(ctx) { if (hasConfiguredCommandOwners(ctx.cfg)) return []; return [{ checkId: "core/doctor/command-owner", severity: "info", message: "No command owner is configured. Owner-only commands (/diagnostics, /export-trajectory, /config, exec approvals) have no allowed sender.", path: "commands.ownerAllowFrom", fixHint: "Set commands.ownerAllowFrom to your channel user id, e.g. `openclaw config set commands.ownerAllowFrom '[\"telegram:123456789\"]'`." }]; } }; function resolveDoctorMode(cfg) { return cfg.gateway?.mode === "remote" ? "remote" : "local"; } function buildGatewayTokenSecretRefUnavailableMessage(params) { if (params.unresolvedRefReason) return `Gateway token SecretRef could not be resolved: ${params.unresolvedRefReason}`; if (params.ref.source === "exec") { const staticError = getSkippedExecRefStaticError({ ref: params.ref, config: params.cfg }); if (staticError) return `Gateway token SecretRef could not be verified: ${staticError}`; return "Gateway token SecretRef uses an exec provider and did not resolve."; } return "Gateway token is managed via SecretRef and is currently unavailable."; } function buildGatewayTokenSecretRefFixHint(ref) { if (ref.source === "exec") return "Run `openclaw doctor --allow-exec` to verify exec SecretRefs during doctor, or `openclaw secrets audit --allow-exec` to audit all exec SecretRefs."; return "Resolve or rotate the external secret source, then rerun doctor."; } const gatewayAuthCheck = { id: "core/doctor/gateway-auth", kind: "core", description: "Local Gateway auth mode has a usable token or another explicit auth mode.", source: "doctor", async detect(ctx) { if (resolveDoctorMode(ctx.cfg) !== "local") 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") if (getSkippedExecRefStaticError({ ref: gatewayTokenRef, config: ctx.cfg })) unresolvedRefReason = void 0; else if (ctx.allowExecSecretRefs !== true) return []; else { const resolvedToken = await resolveGatewayAuthToken({ cfg: ctx.cfg, env: process.env, unresolvedReasonStyle: "detailed", envFallback: "never" }); if (resolvedToken.source === "secretRef") return []; unresolvedRefReason = resolvedToken.unresolvedRefReason; } else { const resolvedToken = await resolveGatewayAuthToken({ cfg: ctx.cfg, env: process.env, unresolvedReasonStyle: "detailed", envFallback: gatewayTokenRef ? "never" : "always" }); if (gatewayTokenRef ? resolvedToken.source === "secretRef" : resolvedToken.token) return []; unresolvedRefReason = resolvedToken.unresolvedRefReason; } if (gatewayTokenRef) return [{ checkId: "core/doctor/gateway-auth", severity: "warning", message: buildGatewayTokenSecretRefUnavailableMessage({ cfg: ctx.cfg, ref: gatewayTokenRef, unresolvedRefReason }), path: "gateway.auth.token", fixHint: buildGatewayTokenSecretRefFixHint(gatewayTokenRef) }]; return [{ checkId: "core/doctor/gateway-auth", severity: "warning", message: "Gateway auth is off or missing a token.", path: "gateway.auth", fixHint: "Run `openclaw doctor --fix --generate-gateway-token` to generate a token." }]; } }; const hooksModelCheck = { id: "core/doctor/hooks-model", kind: "core", description: "hooks.gmail.model resolves to an allowed catalog model.", source: "doctor", async detect(ctx) { if (!ctx.cfg.hooks?.gmail?.model?.trim()) return []; const { DEFAULT_MODEL, DEFAULT_PROVIDER } = await import("./defaults-RurGC76M.js"); const { loadModelCatalog } = await import("./model-catalog-BflZHqiC.js"); const { getModelRefStatus, resolveConfiguredModelRef, resolveHooksGmailModel } = await import("./model-selection-CZSV2CxT.js"); const hooksModelRef = resolveHooksGmailModel({ cfg: ctx.cfg, defaultProvider: DEFAULT_PROVIDER }); if (!hooksModelRef) return [{ checkId: "core/doctor/hooks-model", severity: "warning", message: `hooks.gmail.model "${ctx.cfg.hooks.gmail.model}" could not be resolved.`, path: "hooks.gmail.model" }]; 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 findings = []; if (!status.allowed) findings.push({ checkId: "core/doctor/hooks-model", severity: "warning", message: `hooks.gmail.model "${status.key}" is not in agents.defaults.models allowlist.`, path: "hooks.gmail.model", fixHint: "Add the model to agents.defaults.models or remove hooks.gmail.model." }); if (!status.inCatalog) findings.push({ checkId: "core/doctor/hooks-model", severity: "warning", message: `hooks.gmail.model "${status.key}" is not in the model catalog.`, path: "hooks.gmail.model", fixHint: "Choose a model from the configured provider catalog." }); return findings; } }; const legacyStateCheck = { id: "core/doctor/legacy-state", kind: "core", description: "Legacy sessions, agent state, and channel auth paths have been migrated.", source: "doctor", async detect(ctx) { const { detectLegacyStateMigrations } = await import("./doctor-state-migrations-DN_uDqjB.js"); const detected = await detectLegacyStateMigrations({ cfg: ctx.cfg }); return detected.preview.map((line) => ({ checkId: "core/doctor/legacy-state", severity: "warning", message: line.replace(/^- /, ""), path: detected.stateDir, fixHint: "Run `openclaw doctor --fix` to migrate legacy state." })); } }; const bootstrapSizeCheck = { id: "core/doctor/bootstrap-size", kind: "core", description: "Workspace bootstrap files fit within configured injection limits.", source: "doctor", async detect(ctx) { const { buildBootstrapInjectionStats, analyzeBootstrapBudget } = await import("./bootstrap-budget-BHXpMkTs.js"); const { resolveBootstrapContextForRun } = await import("./bootstrap-files-DL7aVB1p.js"); const { resolveBootstrapMaxChars, resolveBootstrapTotalMaxChars } = await import("./embedded-agent-helpers-CeQU0a64.js"); const workspaceDir = resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg)); const { bootstrapFiles, contextFiles } = await resolveBootstrapContextForRun({ workspaceDir, config: ctx.cfg }); const analysis = analyzeBootstrapBudget({ files: buildBootstrapInjectionStats({ bootstrapFiles, injectedFiles: contextFiles }), bootstrapMaxChars: resolveBootstrapMaxChars(ctx.cfg), bootstrapTotalMaxChars: resolveBootstrapTotalMaxChars(ctx.cfg) }); const findings = []; for (const file of analysis.truncatedFiles) findings.push({ checkId: "core/doctor/bootstrap-size", severity: "warning", message: `${file.name} exceeds bootstrap limits and will be truncated.`, path: file.path, fixHint: "Reduce the file size or tune agents.defaults.bootstrapMaxChars/TotalMaxChars." }); for (const file of analysis.nearLimitFiles) { if (file.truncated) continue; findings.push({ checkId: "core/doctor/bootstrap-size", severity: "info", message: `${file.name} is near the configured bootstrap file limit.`, path: file.path, fixHint: "Reduce the file size or tune agents.defaults.bootstrapMaxChars." }); } if (analysis.totalNearLimit) findings.push({ checkId: "core/doctor/bootstrap-size", severity: analysis.hasTruncation ? "warning" : "info", message: "Total bootstrap context is near the configured total limit.", path: workspaceDir, fixHint: "Reduce bootstrap file sizes or tune agents.defaults.bootstrapTotalMaxChars." }); return findings; } }; function createRuntimeToolSchemaCheck(deps) { return { id: "core/doctor/runtime-tool-schemas", kind: "core", description: "Active agent tool schemas project into model/runtime-compatible tool inputs.", source: "doctor", async detect(ctx) { return deps.collectRuntimeToolSchemaFindings(ctx); } }; } function createProviderCatalogProjectionCheck(deps) { return { id: "core/doctor/provider-catalog-projection", kind: "core", description: "Provider catalog hooks project into unified text model catalog rows.", source: "doctor", async detect(ctx) { return deps.collectProviderCatalogProjectionFindings(ctx); } }; } function normalizeDoctorNoteLine(line) { return line.replace(/^- /, "").trim(); } function noteTextToFinding(params) { const lines = params.text.split("\n"); const first = normalizeDoctorNoteLine(lines[0] ?? params.text); const rest = lines.slice(1).join("\n"); return { checkId: params.checkId, severity: params.severity, message: first, ...rest ? { fixHint: rest } : {} }; } function inferCapturedNoteSeverity(text) { if (text.includes("CRITICAL")) return "error"; if (text.includes("- Fix:") || text.includes("unavailable") || text.includes("not found") || text.includes("missing") || text.includes("not readable") || text.includes("not writable") || text.includes("readonly")) return "warning"; return "info"; } function createNoteCollector(checkId) { const findings = []; const noteFn = (message) => { const text = noteMessageToText(message); if (!text.trim()) return; const severity = inferCapturedNoteSeverity(text); if (severity === "info") return; findings.push(noteTextToFinding({ checkId, severity, text })); }; return { findings, noteFn }; } function noteMessageToText(message) { if (message instanceof Error) return message.message; if (message == null) return ""; if (typeof message === "string") return message; if (typeof message === "number" || typeof message === "boolean" || typeof message === "bigint") return String(message); try { return JSON.stringify(message) ?? ""; } catch { return ""; } } const claudeCliCheck = { id: "core/doctor/claude-cli", kind: "core", description: "Claude CLI readiness is captured as structured findings.", source: "doctor", async detect(ctx) { const { noteClaudeCliHealth } = await import("./doctor-claude-cli-6L-769MI.js"); const collector = createNoteCollector("core/doctor/claude-cli"); noteClaudeCliHealth(ctx.cfg, { noteFn: collector.noteFn, ...ctx.cwd ? { workspaceDir: ctx.cwd } : {} }); return collector.findings; } }; function createSecurityCheck(deps) { return { id: "core/doctor/security", kind: "core", description: "Security posture checks produce structured findings.", source: "doctor", async detect(ctx) { return (await deps.collectSecurityWarnings(ctx.cfg)).map((warning) => noteTextToFinding({ checkId: "core/doctor/security", severity: warning.includes("CRITICAL") ? "error" : "warning", text: warning })); } }; } const openAIOAuthTlsCheck = { id: "core/doctor/oauth-tls", kind: "core", description: "OpenAI OAuth TLS prerequisites are satisfied before browser auth.", source: "doctor", async detect(ctx) { const { formatOpenAIOAuthTlsPreflightFix, runOpenAIOAuthTlsPreflight, shouldRunOpenAIOAuthTlsPrerequisites } = await import("./oauth-tls-preflight-BoMlkoHh.js"); if (!shouldRunOpenAIOAuthTlsPrerequisites({ cfg: ctx.cfg, deep: ctx.mode === "doctor" })) return []; const result = await runOpenAIOAuthTlsPreflight({ timeoutMs: 4e3 }); if (result.ok || result.kind !== "tls-cert") return []; return [noteTextToFinding({ checkId: "core/doctor/oauth-tls", severity: "warning", text: formatOpenAIOAuthTlsPreflightFix(result) })]; } }; const legacyWhatsAppCrontabCheck = { id: "core/doctor/legacy-whatsapp-crontab", kind: "core", description: "Legacy WhatsApp crontab health entries are detected as structured findings.", source: "doctor", async detect() { const { collectLegacyWhatsAppCrontabHealthWarning } = await import("./doctor-cron-DOttbxU7.js"); const warning = await collectLegacyWhatsAppCrontabHealthWarning(); if (!warning) return []; return [noteTextToFinding({ checkId: "core/doctor/legacy-whatsapp-crontab", severity: "warning", text: warning })]; } }; const codexSessionRoutesCheck = { id: CODEX_SESSION_ROUTES_CHECK_ID, kind: "core", description: "Codex runtime routes have a registered Codex plugin harness before sessions run.", source: "doctor", async detect(ctx) { return collectDisabledCodexPluginRouteIssues(ctx.cfg).map((issue) => ({ checkId: CODEX_SESSION_ROUTES_CHECK_ID, severity: "warning", message: [`${issue.path} routes ${issue.modelRef} to ${issue.canonicalModel}`, "with Codex runtime, but the Codex plugin is disabled by config."].join(" "), path: issue.path, target: issue.canonicalModel, requirement: "Codex plugin enabled for routes that use the Codex runtime.", fixHint: issue.blockedOutsideEntry ? ["Enable plugin loading and remove codex from plugins.deny,", "or set the affected OpenAI models to an OpenClaw runtime policy."].join(" ") : ["Run `openclaw doctor --fix`: it enables plugins.entries.codex,", "or set the affected OpenAI models to an OpenClaw runtime policy."].join(" ") })); } }; const gatewayPlatformNotesCheck = { id: "core/doctor/gateway-services/platform-notes", kind: "core", description: "Gateway platform notes are captured as structured findings.", source: "doctor", async detect(ctx) { const { collectMacGatewayPlatformWarnings } = await import("./doctor-platform-notes-Ccvwt2-G.js"); return (await collectMacGatewayPlatformWarnings(ctx.cfg)).map((warning) => noteTextToFinding({ checkId: "core/doctor/gateway-services/platform-notes", severity: "warning", text: warning })); } }; const browserCheck = { id: "core/doctor/browser", kind: "core", description: "Browser readiness is captured as structured findings.", source: "doctor", async detect(ctx) { const collector = createNoteCollector("core/doctor/browser"); await noteChromeMcpBrowserReadiness(ctx.cfg, { noteFn: collector.noteFn }); return collector.findings; } }; const workspaceStatusCheck = { id: "core/doctor/workspace-status", kind: "core", description: "Workspace directory exists and has no legacy duplicates.", source: "doctor", async detect(ctx) { const { detectLegacyWorkspaceDirs } = await loadDoctorWorkspaceModule(); const workspaceDir = resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg)); const legacy = detectLegacyWorkspaceDirs({ workspaceDir }); if (legacy.legacyDirs.length === 0) return []; return [{ checkId: "core/doctor/workspace-status", severity: "info", message: `Detected ${legacy.legacyDirs.length} legacy workspace director${legacy.legacyDirs.length === 1 ? "y" : "ies"} alongside the active workspace.`, path: workspaceDir, fixHint: "Inspect the legacy directories and migrate or remove them; see `openclaw doctor` for the detailed migration prompt." }]; } }; function createSkillsReadinessCheck(deps) { return { id: "core/doctor/skills-readiness", kind: "core", description: "Allowed skills are usable in the current runtime environment.", source: "doctor", async detect(ctx, scope) { return filterUnavailableSkillsForScope(await deps.detectUnavailableSkills(ctx.cfg), scope?.paths).map(unavailableSkillToFinding); }, async repair(ctx, findings) { const unavailable = filterUnavailableSkillsForScope(await deps.detectUnavailableSkills(ctx.cfg), findings.map((finding) => finding.path)); if (unavailable.length === 0) return { changes: [] }; return { config: disableUnavailableSkillsInConfig(ctx.cfg, unavailable), changes: unavailable.map((skill) => `Disabled unavailable skill ${skill.name}.`), effects: unavailable.map((skill) => ({ kind: "config", action: ctx.dryRun === true ? "would-disable-skill" : "disable-skill", target: skillReadinessPath(skill), dryRunSafe: true })) }; } }; } function unavailableSkillToFinding(skill) { return { checkId: "core/doctor/skills-readiness", severity: "warning", message: `${skill.name} is allowed but unavailable: ${formatMissingSkillSummary(skill)}.`, path: skillReadinessPath(skill), fixHint: "Install/configure the missing requirement, or run `openclaw doctor --fix` to disable unused unavailable skills." }; } function filterUnavailableSkillsForScope(unavailable, paths) { const scopedPaths = new Set(paths?.filter((pathLocal) => pathLocal !== void 0) ?? []); if (scopedPaths.size === 0) return [...unavailable]; return unavailable.filter((skill) => scopedPaths.has(skillReadinessPath(skill))); } function skillReadinessPath(skill) { return `skills.entries.${skill.skillKey}.enabled`; } function browserResidueDeps(ctx) { return ctx.configPath ? { configDir: path.dirname(ctx.configPath) } : {}; } function browserResidueFinding(residue) { return { checkId: BROWSER_CLAWD_PROFILE_RESIDUE_CHECK_ID, severity: "warning", message: `Legacy managed browser profile residue was found at ${residue.legacyProfileDir}.`, path: residue.legacyProfileDir, ocPath: "oc://state/browser/clawd", fixHint: "Run `openclaw doctor --fix` to archive the stale clawd profile safely instead of deleting it in place." }; } function formatWouldArchiveBrowserResidue(residue) { return [ "Would archive legacy clawd managed browser profile residue.", `- legacy profile: ${residue.legacyProfileDir}`, `- canonical profile: ${residue.canonicalUserDataDir}` ].join("\n"); } const browserClawdProfileResidueCheck = { id: BROWSER_CLAWD_PROFILE_RESIDUE_CHECK_ID, kind: "core", description: "Legacy clawd managed browser profile residue has been archived after the OpenClaw rename.", source: "doctor", async detect(ctx, scope) { const residue = await detectLegacyClawdBrowserProfileResidue(ctx.cfg, browserResidueDeps(ctx)); if (!residue) return []; const scopedPaths = new Set(scope?.paths ?? []); if (scopedPaths.size > 0 && !scopedPaths.has(residue.legacyProfileDir)) return []; return [browserResidueFinding(residue)]; }, async repair(ctx) { const residue = await detectLegacyClawdBrowserProfileResidue(ctx.cfg, browserResidueDeps(ctx)); if (!residue) return { status: "skipped", reason: "legacy clawd browser profile residue no longer exists", changes: [] }; const effect = { kind: "state", action: ctx.dryRun === true ? "would-archive-legacy-browser-profile-residue" : "archive-legacy-browser-profile-residue", target: residue.legacyProfileDir, dryRunSafe: false }; if (ctx.dryRun === true) return { changes: [formatWouldArchiveBrowserResidue(residue)], effects: [effect] }; const result = await maybeArchiveLegacyClawdBrowserProfileResidue(ctx.cfg, browserResidueDeps(ctx)); if (result.changes.length === 0 && result.warnings.length > 0) return { status: "failed", reason: result.warnings.join("; "), changes: [], warnings: result.warnings, effects: [] }; return { changes: result.changes, warnings: result.warnings, effects: result.changes.length > 0 ? [effect] : [] }; } }; const finalConfigValidationCheck = { id: FINAL_CONFIG_VALIDATION_CHECK_ID, kind: "core", description: "Active openclaw.jsonc parses and conforms to the config schema.", source: "doctor", async detect() { const { readConfigFileSnapshot } = await import("./config/config.js"); const snap = await readConfigFileSnapshot({ observe: false }); if (!snap.exists || snap.valid) return []; return configValidationIssuesToHealthFindings(snap.issues); } }; const shellCompletionCheck = { id: "core/doctor/shell-completion", kind: "core", description: "Shell completion uses the cached completion path when configured.", source: "doctor", async detect() { return shellCompletionStatusToHealthFindings(await checkShellCompletionStatus()); }, async repair(ctx) { const effects = shellCompletionStatusToRepairEffects(await checkShellCompletionStatus()); if (ctx.dryRun === true) return { status: "repaired", changes: [], effects }; return { status: "skipped", reason: "legacy doctor shell-completion repair owns real mutations", changes: [], effects }; } }; const uiProtocolFreshnessCheck = { id: "core/doctor/ui-protocol-freshness", kind: "core", description: "Control UI assets are present and current with the Gateway protocol schema.", source: "doctor", async detect() { return (await detectUiProtocolFreshnessIssues()).map(uiProtocolFreshnessIssueToHealthFinding); }, async repair(ctx) { const effects = (await detectUiProtocolFreshnessIssues()).flatMap(uiProtocolFreshnessIssueToRepairEffects); if (ctx.dryRun === true) return { status: "repaired", changes: [], effects }; return { status: "skipped", reason: "legacy doctor UI freshness repair owns real mutations", changes: [], effects }; } }; function createWorkspaceSuggestionsCheck(deps) { return { id: "core/doctor/workspace-suggestions", kind: "core", description: "Workspace backup and memory-system suggestions are captured as structured findings.", source: "doctor", async detect(ctx) { const workspaceDir = resolveAgentWorkspaceDir(ctx.cfg, resolveDefaultAgentId(ctx.cfg)); return (await deps.collectWorkspaceSuggestionNotes(workspaceDir)).map((text) => noteTextToFinding({ checkId: "core/doctor/workspace-suggestions", severity: "info", text })); } }; } function createConvertedWorkflowChecks(deps) { return [ claudeCliCheck, gatewayAuthCheck, legacyStateCheck, legacyWhatsAppCrontabCheck, codexSessionRoutesCheck, shellCompletionCheck, uiProtocolFreshnessCheck, gatewayPlatformNotesCheck, createSecurityCheck(deps), browserCheck, openAIOAuthTlsCheck, hooksModelCheck, bootstrapSizeCheck, createProviderCatalogProjectionCheck(deps), createRuntimeToolSchemaCheck(deps), createWorkspaceSuggestionsCheck(deps) ]; } let registered = false; function registerCoreHealthChecks() { if (registered) return; for (const check of CORE_HEALTH_CHECKS) registerHealthCheck(check); registered = true; } function resetCoreHealthChecksForTest() { registered = false; } function createCoreHealthChecks(deps = defaultCoreHealthCheckDeps) { return [ gatewayConfigCheck, ...createConvertedWorkflowChecks(deps), commandOwnerCheck, workspaceStatusCheck, createSkillsReadinessCheck(deps), browserClawdProfileResidueCheck, finalConfigValidationCheck ]; } const CORE_HEALTH_CHECKS = createCoreHealthChecks(); function formatMissingSkillSummary(skill) { const missing = []; if (skill.missing.bins.length > 0) missing.push(`bins: ${skill.missing.bins.join(", ")}`); if (skill.missing.anyBins.length > 0) missing.push(`any bins: ${skill.missing.anyBins.join(", ")}`); if (skill.missing.env.length > 0) missing.push(`env: ${skill.missing.env.join(", ")}`); if (skill.missing.config.length > 0) missing.push(`config: ${skill.missing.config.join(", ")}`); if (skill.missing.os.length > 0) missing.push(`os: ${skill.missing.os.join(", ")}`); return missing.join("; ") || "unknown requirement"; } //#endregion export { createCoreHealthChecks as a, configValidationIssuesToHealthFindings as i, buildGatewayTokenSecretRefFixHint as n, registerCoreHealthChecks as o, buildGatewayTokenSecretRefUnavailableMessage as r, resetCoreHealthChecksForTest as s, CORE_HEALTH_CHECKS as t };