UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

146 lines (145 loc) 5.78 kB
import { t as sleep } from "./sleep-D7nua6TP.js"; import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { t as note } from "./note-DqHk3fA1.js"; import path from "node:path"; import { spawnSync } from "node:child_process"; //#region src/commands/doctor-whatsapp-responsiveness.ts /** Doctor hints for WhatsApp responsiveness when local TUI clients block gateway work. */ const LOCAL_TUI_SUBCOMMANDS = /* @__PURE__ */ new Set([ "chat", "terminal", "tui" ]); const WHATSAPP_RESPONSIVENESS_CHECK_ID = "core/doctor/whatsapp-responsiveness"; const LOCAL_TUI_PROCESS_PROBE_TIMEOUT_MS = 1e3; function tokenizeCommandLine(command) { return command.trim().split(/\s+/u).filter(Boolean); } function normalizeExecutableName(value) { return path.basename(value ?? "").replace(/\.exe$/iu, ""); } function isLocalTuiCommand(command) { const argv = tokenizeCommandLine(command); const executable = normalizeExecutableName(argv[0]); if (executable === "openclaw-tui") return true; return executable === "openclaw" && LOCAL_TUI_SUBCOMMANDS.has(argv[1] ?? ""); } function parsePsPidLine(line) { const match = line.match(/^\s*(\d+)\s+(.+)$/); if (!match) return null; const pid = Number(match[1]); if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null; const command = match[2]?.trim() ?? ""; if (!isLocalTuiCommand(command)) return null; return { pid, command }; } /** Lists local OpenClaw TUI processes that can contend with gateway responsiveness. */ function listLocalTuiProcesses() { if (process.platform === "win32") return []; const ps = spawnSync("ps", ["-axo", "pid=,command="], { encoding: "utf8", killSignal: "SIGKILL", timeout: LOCAL_TUI_PROCESS_PROBE_TIMEOUT_MS }); if (ps.error || ps.status !== 0 || typeof ps.stdout !== "string") return []; const seen = /* @__PURE__ */ new Set(); const processes = []; for (const line of ps.stdout.split(/\r?\n/)) { const proc = parsePsPidLine(line); if (!proc || seen.has(proc.pid)) continue; seen.add(proc.pid); processes.push(proc); } return processes; } function hasWhatsappEnabled(cfg) { const whatsapp = cfg.channels?.whatsapp; if (!whatsapp || whatsapp.enabled === false) return false; const accounts = whatsapp.accounts; if (accounts && Object.keys(accounts).length > 0) return Object.values(accounts).some((account) => account?.enabled !== false); return true; } function formatPidList(processes) { return processes.map((proc) => String(proc.pid)).join(", "); } /** Collects read-only structured findings for WhatsApp responsiveness pressure. */ function collectWhatsappResponsivenessHealthFindings(params) { if (!hasWhatsappEnabled(params.cfg)) return []; if ((params.status?.eventLoop)?.degraded !== true) return []; const tuiProcesses = (params.listLocalTuiProcesses ?? listLocalTuiProcesses)(); if (tuiProcesses.length === 0) return []; const pids = formatPidList(tuiProcesses); return [{ checkId: WHATSAPP_RESPONSIVENESS_CHECK_ID, severity: "warning", message: "Gateway event loop is degraded while local TUI clients are running; WhatsApp replies can queue behind TUI startup/session refresh work.", path: "channels.whatsapp", target: pids, requirement: "local-tui-event-loop-pressure", fixHint: `Close local TUI sessions (${pids}), or run ${formatCliCommand("openclaw doctor --fix")}.` }]; } function isProcessAlive(controller, pid) { try { controller.kill(pid, 0); return true; } catch { return false; } } /** Terminates local TUI processes with SIGTERM, then SIGKILL for remaining pids. */ async function terminateLocalTuiProcesses(params) { const controller = params.controller ?? process; const graceMs = Math.max(0, params.graceMs ?? 500); const stopped = []; const failed = []; for (const proc of params.processes) try { controller.kill(proc.pid, "SIGTERM"); } catch {} if (graceMs > 0) await sleep(graceMs); for (const proc of params.processes) { if (!isProcessAlive(controller, proc.pid)) { stopped.push(proc.pid); continue; } try { controller.kill(proc.pid, "SIGKILL"); } catch {} if (isProcessAlive(controller, proc.pid)) failed.push(proc.pid); else stopped.push(proc.pid); } return { stopped, failed }; } if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.doctorWhatsappResponsivenessTestApi")] = { listLocalTuiProcesses, terminateLocalTuiProcesses }; /** Emits WhatsApp responsiveness warnings and optionally stops contending local TUI clients. */ async function noteWhatsappResponsivenessHealth(params) { if (!hasWhatsappEnabled(params.cfg)) return; const warnings = []; const tuiProcesses = (params.listLocalTuiProcesses ?? listLocalTuiProcesses)(); if ((params.status?.eventLoop)?.degraded === true && tuiProcesses.length > 0) { warnings.push([ "Gateway event loop is degraded while local TUI clients are running.", "WhatsApp replies can queue behind TUI startup/session refresh work.", `Local TUI pids: ${formatPidList(tuiProcesses)}` ].join("\n")); if (params.shouldRepair) { const repair = await (params.terminateLocalTuiProcesses ?? terminateLocalTuiProcesses)({ processes: tuiProcesses }); const repairLines = []; if (repair.stopped.length > 0) repairLines.push(`Stopped local TUI clients: ${repair.stopped.join(", ")}`); if (repair.failed.length > 0) repairLines.push(`Could not stop local TUI clients: ${repair.failed.join(", ")}`); if (repairLines.length > 0) warnings.push(repairLines.join("\n")); } else warnings.push(`Fix: close those TUI sessions, or run ${formatCliCommand("openclaw doctor --fix")}.`); } if (warnings.length > 0) note(warnings.join("\n\n"), "WhatsApp responsiveness"); } //#endregion export { collectWhatsappResponsivenessHealthFindings, noteWhatsappResponsivenessHealth };