docs: document doctor configuration checks

This commit is contained in:
Peter Steinberger
2026-06-04 11:57:47 -04:00
parent c0e9797644
commit d2ff1c31d6
6 changed files with 44 additions and 17 deletions
+6
View File
@@ -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);
+5
View File
@@ -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<string>;
};
/** 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,
+7
View File
@@ -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?: {
+4
View File
@@ -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;
+9 -17
View File
@@ -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<boolean> {
const shell = resolveShellFromEnv() as CompletionShell;
const cacheExists = await completionCacheExists(shell, binName);
+13
View File
@@ -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 | number>): string {
if (parts.length === 0) {
return "<root>";
@@ -36,6 +38,7 @@ export function formatConfigPath(parts: Array<string | number>): string {
return out || "<root>";
}
/** Resolves a config path against a loose config tree, returning null for invalid traversal. */
export function resolveConfigPathTarget(root: unknown, pathLocal: Array<string | number>): unknown {
let current: unknown = root;
for (const part of pathLocal) {
@@ -71,6 +74,12 @@ const STRIP_PROTECTED_KEYS: Record<string, Set<string>> = {
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 }>;