diff --git a/src/commands/doctor-bootstrap-size.ts b/src/commands/doctor-bootstrap-size.ts index 63f62d55c91c..9dc74be767e6 100644 --- a/src/commands/doctor-bootstrap-size.ts +++ b/src/commands/doctor-bootstrap-size.ts @@ -1,3 +1,4 @@ +/** Doctor note for workspace bootstrap file size and truncation risk. */ import { note } from "../../packages/terminal-core/src/note.js"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { @@ -30,6 +31,11 @@ function formatCauses(causes: Array<"per-file-limit" | "total-limit">): string { return causes.map((cause) => (cause === "per-file-limit" ? "max/file" : "max/total")).join(", "); } +/** + * Analyzes configured bootstrap files and emits warnings when injection will truncate content. + * + * Returns the raw budget analysis for tests and callers that need structured evidence. + */ export async function noteBootstrapFileSize(cfg: OpenClawConfig) { const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)); const bootstrapMaxChars = resolveBootstrapMaxChars(cfg); diff --git a/src/commands/doctor-browser.ts b/src/commands/doctor-browser.ts index 4fd085b7f36b..810b44931632 100644 --- a/src/commands/doctor-browser.ts +++ b/src/commands/doctor-browser.ts @@ -1,3 +1,4 @@ +/** Facade-backed doctor checks and cleanup for bundled browser plugin state. */ import fs from "node:fs"; import path from "node:path"; import { note } from "../../packages/terminal-core/src/note.js"; @@ -27,6 +28,7 @@ export type BrowserDoctorRepairDeps = { movePathToTrash?: (targetPath: string) => Promise; }; +/** Legacy browser profile paths detected before cleanup moves them aside. */ export type LegacyClawdBrowserProfileResidue = { legacyProfileDir: string; legacyUserDataDir: string; @@ -64,6 +66,7 @@ function mayHaveLegacyClawdBrowserProfileResidue(deps?: BrowserDoctorRepairDeps) } } +/** Emits browser readiness notes through the bundled browser plugin doctor surface. */ export async function noteChromeMcpBrowserReadiness(cfg: OpenClawConfig, deps?: BrowserDoctorDeps) { try { await loadBrowserDoctorSurface().noteChromeMcpBrowserReadiness(cfg, deps); @@ -74,6 +77,7 @@ export async function noteChromeMcpBrowserReadiness(cfg: OpenClawConfig, deps?: } } +/** Detects old clawd browser profile residue without loading plugin cleanup when paths are absent. */ export async function detectLegacyClawdBrowserProfileResidue( cfg: OpenClawConfig, deps?: BrowserDoctorRepairDeps, @@ -88,6 +92,7 @@ export async function detectLegacyClawdBrowserProfileResidue( return detect(cfg, deps); } +/** Archives legacy clawd browser profile residue through the browser plugin repair hook. */ export async function maybeArchiveLegacyClawdBrowserProfileResidue( cfg: OpenClawConfig, deps?: BrowserDoctorRepairDeps, diff --git a/src/commands/doctor-claude-cli.ts b/src/commands/doctor-claude-cli.ts index b2d5e40be3d5..8db0c2c6f07d 100644 --- a/src/commands/doctor-claude-cli.ts +++ b/src/commands/doctor-claude-cli.ts @@ -1,3 +1,4 @@ +/** Doctor health note for Claude CLI binary, auth, and workspace/project directories. */ import fs from "node:fs"; import { normalizeOptionalLowercaseString, @@ -191,6 +192,12 @@ function resolveClaudeCliWorkspaceTargets(params: { }); } +/** + * Emits Claude CLI health diagnostics for every agent currently routed through the CLI backend. + * + * The optional deps let tests inject auth stores, PATH resolution, and workspace roots without + * touching the user's real Claude credentials or filesystem. + */ export function noteClaudeCliHealth( cfg: OpenClawConfig, deps?: { diff --git a/src/commands/doctor-command-owner.ts b/src/commands/doctor-command-owner.ts index 9151a2747212..fe33b76dcdbf 100644 --- a/src/commands/doctor-command-owner.ts +++ b/src/commands/doctor-command-owner.ts @@ -1,3 +1,4 @@ +/** Doctor warning for missing command owners on privileged channel commands. */ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; import { note } from "../../packages/terminal-core/src/note.js"; @@ -13,10 +14,12 @@ function resolveConfiguredCommandOwners(cfg: OpenClawConfig): string[] { return normalizeStringEntries(owners.map((entry) => String(entry ?? ""))); } +/** Returns true when at least one owner sender id is configured. */ export function hasConfiguredCommandOwners(cfg: OpenClawConfig): boolean { return resolveConfiguredCommandOwners(cfg).length > 0; } +/** Formats a channel sender id into the commands.ownerAllowFrom entry shape. */ export function formatCommandOwnerFromChannelSender(params: { channel: PairingChannel; id: string; @@ -35,6 +38,7 @@ export function formatCommandOwnerFromChannelSender(params: { return `${params.channel}:${id}`; } +/** Emits setup guidance when privileged command ownership is not configured. */ export function noteCommandOwnerHealth(cfg: OpenClawConfig): void { if (hasConfiguredCommandOwners(cfg)) { return; diff --git a/src/commands/doctor-completion.ts b/src/commands/doctor-completion.ts index 7709d6ef0e48..f08d8a7963ac 100644 --- a/src/commands/doctor-completion.ts +++ b/src/commands/doctor-completion.ts @@ -1,3 +1,4 @@ +/** Doctor checks and repair effects for cached shell completion setup. */ import { spawnSync } from "node:child_process"; import path from "node:path"; import { note } from "../../packages/terminal-core/src/note.js"; @@ -86,6 +87,7 @@ export async function checkShellCompletionStatus( }; } +/** Converts shell completion status into health findings shown by check flows. */ export function shellCompletionStatusToHealthFindings( status: ShellCompletionStatus, ): readonly HealthFinding[] { @@ -116,6 +118,7 @@ export function shellCompletionStatusToHealthFindings( return []; } +/** Converts shell completion status into dry-run repair effects for health check reporting. */ export function shellCompletionStatusToRepairEffects( status: ShellCompletionStatus, ): readonly HealthRepairEffect[] { @@ -151,10 +154,10 @@ export type DoctorCompletionOptions = { }; /** - * Doctor check for shell completion. - * - If profile uses slow dynamic pattern: upgrade to cached version - * - If profile has completion but no cache: auto-generate cache and upgrade profile - * - If no completion at all: prompt to install (with user confirmation) + * Repairs shell completion setup when doctor runs interactively. + * + * Slow dynamic profiles are upgraded to cached completion; configured profiles with a missing + * cache regenerate it; missing completion prompts unless non-interactive mode is active. */ export async function doctorShellCompletion( _runtime: RuntimeEnv, @@ -164,14 +167,13 @@ export async function doctorShellCompletion( const cliName = resolveCliName(); const status = await checkShellCompletionStatus(cliName); - // Profile uses slow dynamic pattern - upgrade to cached version + // Slow dynamic completion runs the CLI during shell startup; cache it to keep login shells fast. if (status.usesSlowPattern) { note( `Your ${status.shell} profile uses slow dynamic completion (source <(...)).\nUpgrading to cached completion for faster shell startup...`, "Shell completion", ); - // Ensure cache exists first if (!status.cacheExists) { const generated = await generateCompletionCache(); if (!generated) { @@ -183,13 +185,11 @@ export async function doctorShellCompletion( } } - // Upgrade profile to use cached file await installCompletion(status.shell, true, cliName); note(formatCompletionReloadNote(status.shell, "upgraded"), "Shell completion"); return; } - // Profile has completion but no cache - auto-fix if (status.profileInstalled && !status.cacheExists) { note( `Shell completion is configured in your ${status.shell} profile but the cache is missing.\nRegenerating cache...`, @@ -207,10 +207,8 @@ export async function doctorShellCompletion( return; } - // No completion at all - prompt to install if (!status.profileInstalled) { if (options.nonInteractive) { - // In non-interactive mode, just note that completion is not installed return; } @@ -220,7 +218,6 @@ export async function doctorShellCompletion( }); if (shouldInstall) { - // First generate the cache const generated = await generateCompletionCache(); if (!generated) { note( @@ -230,18 +227,13 @@ export async function doctorShellCompletion( return; } - // Then install to profile await installCompletion(status.shell, true, cliName); note(formatCompletionReloadNote(status.shell, "installed"), "Shell completion"); } } } -/** - * Ensure completion cache exists. Used during setup/update to fix - * cases where profile has completion but no cache. - * This is a silent fix - no prompts. - */ +/** Ensures the shell completion cache exists without prompting during setup/update flows. */ export async function ensureCompletionCacheExists(binName = "openclaw"): Promise { const shell = resolveShellFromEnv() as CompletionShell; const cacheExists = await completionCacheExists(shell, binName); diff --git a/src/commands/doctor-config-analysis.ts b/src/commands/doctor-config-analysis.ts index f909e28a37c7..3bdcd4cdbd3a 100644 --- a/src/commands/doctor-config-analysis.ts +++ b/src/commands/doctor-config-analysis.ts @@ -1,3 +1,4 @@ +/** Doctor analysis helpers for config schema cleanup and ambiguous model fallback shapes. */ import path from "node:path"; import { resolvePrimaryStringValue } from "@openclaw/normalization-core/string-coerce"; import type { ZodIssue } from "zod"; @@ -21,6 +22,7 @@ function isUnrecognizedKeysIssue(issue: ZodIssue): issue is UnrecognizedKeysIssu return issue.code === "unrecognized_keys"; } +/** Formats a parsed config issue path into a user-facing dotted path. */ export function formatConfigPath(parts: Array): string { if (parts.length === 0) { return ""; @@ -36,6 +38,7 @@ export function formatConfigPath(parts: Array): string { return out || ""; } +/** Resolves a config path against a loose config tree, returning null for invalid traversal. */ export function resolveConfigPathTarget(root: unknown, pathLocal: Array): unknown { let current: unknown = root; for (const part of pathLocal) { @@ -71,6 +74,12 @@ const STRIP_PROTECTED_KEYS: Record> = { plugins: new Set(["installs"]), }; +/** + * Removes unknown config keys reported by schema validation, except protected migration keys. + * + * Doctor skips this while an update is in progress so partially written upgrade state is not + * stripped before its migration can finish. + */ export function stripUnknownConfigKeys(config: OpenClawConfig): { config: OpenClawConfig; removed: string[]; @@ -119,6 +128,7 @@ export function stripUnknownConfigKeys(config: OpenClawConfig): { return { config: next, removed }; } +/** Warns when legacy OpenCode provider overrides shadow the built-in catalog. */ export function noteOpencodeProviderOverrides(cfg: OpenClawConfig): void { const providers = cfg.models?.providers; if (!providers) { @@ -174,6 +184,7 @@ function isImplicitFallbackClobber(model: unknown): boolean { return false; } +/** Collects warnings for agent model shapes that unintentionally drop default fallbacks. */ export function collectImplicitFallbackClobberWarnings(cfg: OpenClawConfig): string[] { const defaultFallbacks = resolveAgentModelFallbackValues(cfg.agents?.defaults?.model); if (defaultFallbacks.length === 0) { @@ -204,6 +215,7 @@ export function collectImplicitFallbackClobberWarnings(cfg: OpenClawConfig): str return warnings; } +/** Emits doctor notes for model fallback clobber warnings. */ export function noteImplicitFallbackClobberWarnings(cfg: OpenClawConfig): void { const warnings = collectImplicitFallbackClobberWarnings(cfg); if (warnings.length === 0) { @@ -212,6 +224,7 @@ export function noteImplicitFallbackClobberWarnings(cfg: OpenClawConfig): void { note(warnings.join("\n"), "Doctor warnings"); } +/** Emits a config include warning when an include path escapes the config directory. */ export function noteIncludeConfinementWarning(snapshot: { path?: string | null; issues?: Array<{ message: string }>;