import { constants } from "node:fs"; import { access } from "node:fs/promises"; import path from "node:path"; export interface HermesBackendConfig { enabled: boolean; command: string; args: string[]; cwd?: string; timeoutMs: number; defaultModel?: string; toolsets: string[]; skills: string[]; sourceTag: string; } export type HermesBackendAvailabilityStatus = "disabled" | "misconfigured" | "ready"; export interface HermesBackendAvailability { status: HermesBackendAvailabilityStatus; selectable: boolean; configured: boolean; reason: | "disabled" | "command_not_set" | "command_not_found" | "workdir_not_found" | "script_not_found" | "ready"; reasonLabel: string; command?: string; cwd?: string; } function parseBoolean(value: string | undefined) { return value?.trim().toLowerCase() === "true"; } function parseArgs(value: string | undefined) { return String(value || "") .trim() .split(/\s+/) .filter(Boolean); } function parseCsv(value: string | undefined) { return String(value || "") .split(",") .map((item) => item.trim()) .filter(Boolean); } function parseTimeoutMs(value: string | undefined) { const parsed = Number.parseInt(value || "", 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : 120000; } export function getHermesBackendConfig(): HermesBackendConfig { return { enabled: parseBoolean(process.env.BOSS_HERMES_ENABLED), command: process.env.BOSS_HERMES_COMMAND?.trim() || "hermes", args: parseArgs(process.env.BOSS_HERMES_ARGS), cwd: process.env.BOSS_HERMES_WORKDIR?.trim() || undefined, timeoutMs: parseTimeoutMs(process.env.BOSS_HERMES_TIMEOUT_MS), defaultModel: process.env.BOSS_HERMES_DEFAULT_MODEL?.trim() || undefined, toolsets: parseCsv(process.env.BOSS_HERMES_TOOLSETS), skills: parseCsv(process.env.BOSS_HERMES_SKILLS), sourceTag: process.env.BOSS_HERMES_SOURCE_TAG?.trim() || "tool", }; } export function isHermesBackendConfigured(config: HermesBackendConfig) { return config.enabled && Boolean(config.command); } function commandLooksLikePath(command: string) { return command.includes("/") || command.includes("\\"); } async function fileExists(filePath: string, mode = constants.F_OK) { try { await access(filePath, mode); return true; } catch { return false; } } function resolveScriptCandidate(config: HermesBackendConfig) { if (!config.command || config.args.length === 0) { return null; } const commandName = path.basename(config.command).toLowerCase(); const scriptRuntimes = new Set([ "node", "node.exe", "tsx", "tsx.cmd", "bun", "bun.exe", "deno", "deno.exe", "python", "python.exe", "python3", "python3.exe", ]); if (!scriptRuntimes.has(commandName)) { return null; } const candidate = config.args[0]; if (!candidate || candidate.startsWith("-")) { return null; } return path.isAbsolute(candidate) ? candidate : path.resolve(config.cwd?.trim() || process.cwd(), candidate); } async function isCommandReachable(command: string) { if (commandLooksLikePath(command)) { return fileExists(path.resolve(command), constants.X_OK); } const searchPaths = (process.env.PATH || "") .split(path.delimiter) .map((entry) => entry.trim()) .filter(Boolean); for (const entry of searchPaths) { const candidate = path.join(entry, command); if (await fileExists(candidate, constants.X_OK)) { return true; } } return false; } export async function getHermesBackendAvailability( config: HermesBackendConfig = getHermesBackendConfig(), ): Promise { const base = { command: config.command, cwd: config.cwd, configured: isHermesBackendConfigured(config), }; if (!config.enabled) { return { ...base, status: "disabled", selectable: false, reason: "disabled", reasonLabel: "Hermes Runtime 当前未启用。", }; } if (!config.command) { return { ...base, status: "misconfigured", selectable: false, reason: "command_not_set", reasonLabel: "Hermes Runtime 缺少启动命令。", }; } if (!(await isCommandReachable(config.command))) { return { ...base, status: "misconfigured", selectable: false, reason: "command_not_found", reasonLabel: "未检测到可执行的 Hermes 启动命令。", }; } if (config.cwd && !(await fileExists(config.cwd, constants.F_OK))) { return { ...base, status: "misconfigured", selectable: false, reason: "workdir_not_found", reasonLabel: "Hermes Runtime 工作目录不存在。", }; } const scriptCandidate = resolveScriptCandidate(config); if (scriptCandidate && !(await fileExists(scriptCandidate, constants.F_OK))) { return { ...base, status: "misconfigured", selectable: false, reason: "script_not_found", reasonLabel: "未检测到有效的 Hermes 启动脚本,将自动回退到默认后端。", }; } return { ...base, status: "ready", selectable: true, reason: "ready", reasonLabel: "Hermes Runtime 可用。", }; } export const getHermesBackendConfigForTesting = getHermesBackendConfig; export const isHermesBackendConfiguredForTesting = isHermesBackendConfigured; export const getHermesBackendAvailabilityForTesting = getHermesBackendAvailability;