openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
146 lines (145 loc) • 5.78 kB
JavaScript
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 };