65 lines
3.6 KiB
TypeScript
65 lines
3.6 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { probeAcpBackend } from "../acp/probe.js";
|
|
import { defaultStateFile, type AppConfig } from "../config.js";
|
|
import { RoleRegistry } from "../roles/role-registry.js";
|
|
import { printUrlHints } from "./net.js";
|
|
|
|
export async function runDoctor(config: AppConfig, configPath: string): Promise<number> {
|
|
let problems = 0;
|
|
console.log(`Config: ${configPath}`);
|
|
console.log(`Server: ${config.server.host}:${config.server.port}`);
|
|
try {
|
|
new RoleRegistry(config);
|
|
console.log(`Default role: ${config.defaultRole}`);
|
|
} catch (error) {
|
|
problems += reportError(error instanceof Error ? error.message : String(error));
|
|
}
|
|
|
|
for (const role of config.roles) {
|
|
if (!fs.existsSync(role.workspace)) problems += reportError(`role '${role.id}' workspace does not exist: ${role.workspace}`);
|
|
if (role.policy.permissionMode === "auto") console.log(`WARN: role '${role.id}' auto-approves every permission request.`);
|
|
if (role.policy.permissionMode === "allowlist" && role.policy.allowedTools.includes("bash") && role.policy.allowedCommandPatterns.length === 0) {
|
|
console.log(`WARN: role '${role.id}' allows bash by name but has no command patterns; bash requests will be denied.`);
|
|
}
|
|
}
|
|
|
|
for (const backend of config.backends) {
|
|
try {
|
|
const initialized = await probeAcpBackend(backend, config.acp.initializeTimeoutMs);
|
|
const capabilities = initialized.agentCapabilities;
|
|
console.log(`ACP '${backend.id}': ready (${initialized.agentInfo?.name || "unknown"} ${initialized.agentInfo?.version || ""})`);
|
|
console.log(` load=${Boolean(capabilities?.loadSession)} resume=${Boolean(capabilities?.sessionCapabilities?.resume)} list=${Boolean(capabilities?.sessionCapabilities?.list)} close=${Boolean(capabilities?.sessionCapabilities?.close)}`);
|
|
if (!capabilities?.loadSession && !capabilities?.sessionCapabilities?.resume) problems += reportError(`backend '${backend.id}' cannot restore sessions`);
|
|
console.log(" model config: not advertised by initialize; using the Kimi default model");
|
|
} catch (error) {
|
|
problems += reportError(`ACP '${backend.id}' initialize failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|
|
|
|
const stateFile = defaultStateFile(config);
|
|
try {
|
|
const stateDirectory = path.dirname(stateFile);
|
|
fs.mkdirSync(stateDirectory, { recursive: true });
|
|
fs.accessSync(stateDirectory, fs.constants.R_OK | fs.constants.W_OK);
|
|
console.log(`State file: ${stateFile}`);
|
|
} catch (error) {
|
|
problems += reportError(`state directory is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
|
|
const enabledPlatforms = Object.entries(config.platforms).filter(([, value]) => value.enabled).map(([name]) => name);
|
|
console.log(`Enabled platforms: ${enabledPlatforms.join(", ") || "none"}`);
|
|
if (config.platforms.qq.enabled) {
|
|
console.log(`QQ connection mode: ${config.platforms.qq.connectionMode}`);
|
|
if (!config.platforms.qq.appId) problems += reportError("QQ appId is empty.");
|
|
if (!config.platforms.qq.clientSecret || config.platforms.qq.clientSecret === "replace-me") console.log("WARN: QQ clientSecret is missing or placeholder.");
|
|
if (config.platforms.qq.connectionMode === "webhook" && config.platforms.qq.verifySignature && !(config.platforms.qq.botSecret || config.platforms.qq.clientSecret)) {
|
|
problems += reportError("QQ botSecret or clientSecret is required for webhook signature verification.");
|
|
}
|
|
}
|
|
await printUrlHints(config);
|
|
return problems > 0 ? 1 : 0;
|
|
}
|
|
|
|
function reportError(message: string): 1 { console.log(`ERROR: ${message}`); return 1; }
|