fix: harden doctor install checks
This commit is contained in:
@@ -21,7 +21,11 @@
|
||||
- Gateway: allow Tailscale Serve identity headers to satisfy token auth; rebuild Control UI assets when protocol schema is newer.
|
||||
- Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal.
|
||||
|
||||
### Installer
|
||||
- Install: run `clawdbot doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected.
|
||||
|
||||
### Fixes
|
||||
- Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds.
|
||||
- Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles.
|
||||
- Gemini: normalize Gemini 3 ids to preview variants; strip Gemini CLI tool call/response ids; downgrade missing `thought_signature`; strip Claude `msg_*` thought_signature fields to avoid base64 decode errors.
|
||||
- MiniMax: strip malformed tool invocation XML; include `MiniMax-VL-01` in implicit provider for image pairing.
|
||||
|
||||
@@ -76,6 +76,7 @@ cat ~/.clawdbot/clawdbot.json
|
||||
- Security warnings for open DM policies.
|
||||
- Gateway auth warnings when no `gateway.auth.token` is set (local mode; offers token generation).
|
||||
- systemd linger check on Linux.
|
||||
- Source install checks (pnpm workspace mismatch, missing UI assets, missing tsx binary).
|
||||
- Writes updated config + wizard metadata.
|
||||
|
||||
## Detailed behavior and rationale
|
||||
|
||||
@@ -30,6 +30,7 @@ What it does (high level):
|
||||
- `git`: clone/build a source checkout and install a wrapper script
|
||||
- On Linux: avoid global npm permission errors by switching npm’s prefix to `~/.npm-global` when needed.
|
||||
- If upgrading an existing install: runs `clawdbot doctor --non-interactive` (best effort).
|
||||
- For git installs: runs `clawdbot doctor --non-interactive` after install/update (best effort).
|
||||
- Mitigates `sharp` native install gotchas by defaulting `SHARP_IGNORE_GLOBAL_LIBVIPS=1` (avoids building against system libvips).
|
||||
|
||||
If you *want* `sharp` to link against a globally-installed libvips (or you’re debugging), set:
|
||||
|
||||
39
src/commands/doctor-install.ts
Normal file
39
src/commands/doctor-install.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import { note } from "../terminal/note.js";
|
||||
|
||||
export function noteSourceInstallIssues(root: string | null) {
|
||||
if (!root) return;
|
||||
|
||||
const workspaceMarker = path.join(root, "pnpm-workspace.yaml");
|
||||
if (!fs.existsSync(workspaceMarker)) return;
|
||||
|
||||
const warnings: string[] = [];
|
||||
const nodeModules = path.join(root, "node_modules");
|
||||
const pnpmStore = path.join(nodeModules, ".pnpm");
|
||||
const tsxBin = path.join(nodeModules, ".bin", "tsx");
|
||||
const srcEntry = path.join(root, "src", "entry.ts");
|
||||
|
||||
if (fs.existsSync(nodeModules) && !fs.existsSync(pnpmStore)) {
|
||||
warnings.push(
|
||||
"- node_modules was not installed by pnpm (missing node_modules/.pnpm). Run: pnpm install",
|
||||
);
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(root, "package-lock.json"))) {
|
||||
warnings.push(
|
||||
"- package-lock.json present in a pnpm workspace. If you ran npm install, remove it and reinstall with pnpm.",
|
||||
);
|
||||
}
|
||||
|
||||
if (fs.existsSync(srcEntry) && !fs.existsSync(tsxBin)) {
|
||||
warnings.push(
|
||||
"- tsx binary is missing for source runs. Run: pnpm install",
|
||||
);
|
||||
}
|
||||
|
||||
if (warnings.length > 0) {
|
||||
note(warnings.join("\n"), "Install");
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,52 @@ export async function maybeRepairUiProtocolFreshness(
|
||||
|
||||
try {
|
||||
const [schemaStats, uiStats] = await Promise.all([
|
||||
fs.stat(schemaPath),
|
||||
fs.stat(uiIndexPath),
|
||||
fs.stat(schemaPath).catch(() => null),
|
||||
fs.stat(uiIndexPath).catch(() => null),
|
||||
]);
|
||||
|
||||
if (schemaStats && !uiStats) {
|
||||
note(
|
||||
[
|
||||
"- Control UI assets are missing.",
|
||||
"- Run: pnpm ui:build",
|
||||
].join("\n"),
|
||||
"UI",
|
||||
);
|
||||
|
||||
const shouldRepair = await prompter.confirmRepair({
|
||||
message: "Build Control UI assets now?",
|
||||
initialValue: true,
|
||||
});
|
||||
|
||||
if (shouldRepair) {
|
||||
note("Building Control UI assets... (this may take a moment)", "UI");
|
||||
const uiScriptPath = path.join(root, "scripts/ui.js");
|
||||
const buildResult = await runCommandWithTimeout(
|
||||
[process.execPath, uiScriptPath, "build"],
|
||||
{
|
||||
cwd: root,
|
||||
timeoutMs: 120_000,
|
||||
env: { ...process.env, FORCE_COLOR: "1" },
|
||||
},
|
||||
);
|
||||
if (buildResult.code === 0) {
|
||||
note("UI build complete.", "UI");
|
||||
} else {
|
||||
const details = [
|
||||
`UI build failed (exit ${buildResult.code ?? "unknown"}).`,
|
||||
buildResult.stderr.trim() ? buildResult.stderr.trim() : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
note(details, "UI");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!schemaStats || !uiStats) return;
|
||||
|
||||
if (schemaStats.mtime > uiStats.mtime) {
|
||||
const uiMtimeIso = uiStats.mtime.toISOString();
|
||||
// Find changes since the UI build
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
maybeRepairGatewayServiceConfig,
|
||||
maybeScanExtraGatewayServices,
|
||||
} from "./doctor-gateway-services.js";
|
||||
import { noteSourceInstallIssues } from "./doctor-install.js";
|
||||
import {
|
||||
maybeMigrateLegacyConfigFile,
|
||||
normalizeLegacyConfigValues,
|
||||
@@ -187,6 +188,12 @@ export async function doctorCommand(
|
||||
printWizardHeader(runtime);
|
||||
intro("Clawdbot doctor");
|
||||
|
||||
const root = await resolveClawdbotPackageRoot({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
const updateInProgress = process.env.CLAWDBOT_UPDATE_IN_PROGRESS === "1";
|
||||
const canOfferUpdate =
|
||||
!updateInProgress &&
|
||||
@@ -195,11 +202,6 @@ export async function doctorCommand(
|
||||
options.repair !== true &&
|
||||
Boolean(process.stdin.isTTY);
|
||||
if (canOfferUpdate) {
|
||||
const root = await resolveClawdbotPackageRoot({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
if (root) {
|
||||
const git = await detectClawdbotGitCheckout(root);
|
||||
if (git === "git") {
|
||||
@@ -250,6 +252,7 @@ export async function doctorCommand(
|
||||
}
|
||||
|
||||
await maybeRepairUiProtocolFreshness(runtime, prompter);
|
||||
noteSourceInstallIssues(root);
|
||||
|
||||
await maybeMigrateLegacyConfigFile(runtime);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user