Files
gori-agent/src/cli/doctor.ts
T
2026-08-16 10:08:03 +08:00

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; }