openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
440 lines (439 loc) • 20.7 kB
JavaScript
import { n as resolveOpenClawPackageRoot } from "./openclaw-root-CBoxhfke.mjs";
import { _ as resolveNodeLaunchAgentLabel, m as resolveGatewaySystemdServiceName, u as resolveGatewayLaunchAgentLabel } from "./constants-BLghGsId.mjs";
import { t as ExitError } from "./runtime-DlO3PTAd.mjs";
import { h as sleep } from "./utils-DgZn2dLu.mjs";
import { b as resolveGatewayPort, o as isDefaultInstallIdentity } from "./paths-DGIwWbpu.mjs";
import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs";
import { r as ensureSqliteLibrarySelected } from "./bun-sqlite-library-CBSPJxTD.mjs";
import { a as getResolvedLoggerSettings } from "./logger-Mnn26Jj7.mjs";
import { a as buildGatewayRuntimeRecoveryHints, i as renderSystemdUnavailableHints, r as isSystemdUnavailableDetail, t as formatRuntimeStatus } from "./runtime-format-BFughB9s.mjs";
import { t as quoteCliArg } from "./quote-cli-arg-BEt71TUh.mjs";
import { o as classifySystemdUnavailableDetail } from "./systemd-user-transport-DHkASHh1.mjs";
import { n as NON_DEFAULT_INSTALL_SERVICE_SKIP_REASON } from "./gateway-supervision-CUdBruRD.mjs";
import { r as isWSLEnv, t as isWSL } from "./wsl-DAucJ7y0.mjs";
import { a as isSystemdStartLimitHit, i as isSystemdCgroupHygieneRisk, n as getSystemdCgroupHygieneSummary } from "./service-runtime-Cbp6mAbj.mjs";
import { n as readInstallOwner, t as formatInstallOwnerMessage } from "./install-owner-ImshV9Dw.mjs";
import "./config-DLycWFvB.mjs";
import "./logging-CIwBT2dJ.mjs";
import { c as summarizeGatewayServiceLayout } from "./service-layout-BNLNn9Ng.mjs";
import { g as resolveGatewayBindHost, v as resolveGatewayRequiredListenHosts } from "./net-C6WEu3pd.mjs";
import { i as isLaunchAgentLoaded, s as launchAgentPlistExists } from "./launchd-runtime-CkSyqekH.mjs";
import { i as readGatewayServiceState, n as resolveGatewayService, t as describeGatewayServiceRestart } from "./service-C5e3lih7.mjs";
import { a as repairLaunchAgentBootstrap } from "./launchd-C7LlThPn.mjs";
import { a as isExpectedGatewayListeners, r as formatPortDiagnostics } from "./ports-format-B_qarAEO.mjs";
import { n as inspectPortUsage, t as inspectPortConnections } from "./ports-inspect-CMru5KsE.mjs";
import { n as findSystemGatewayServices } from "./inspect-BsMlqh1l.mjs";
import { t as note } from "./note-Byxgt4MB.mjs";
import { n as formatGatewayRestartHandoffDiagnostic, r as readGatewayRestartHandoffSync } from "./restart-handoff-HHx3Pddl.mjs";
import { n as GATEWAY_DAEMON_RUNTIME_OPTIONS } from "./daemon-runtime-DLyccFUz.mjs";
import { n as gatewayInstallErrorHint } from "./daemon-install-helpers-Dr3Z2yOJ.mjs";
import { t as resolveGatewayInstallToken } from "./gateway-install-token-BN102WV5.mjs";
import { n as readLastGatewayErrorLine } from "./diagnostics-BEnEelkI.mjs";
import { n as prepareGatewayServiceInstall, t as resolveGatewaySetupRuntime } from "./gateway-setup-runtime-B34d6q8G.mjs";
import { i as formatHealthCheckFailure, n as formatGatewayClosedDiagnostic } from "./health-format-DWa2HpAp.mjs";
import { a as healthCommandNonExiting } from "./health-BHyYkS9T.mjs";
import { a as isServiceRepairDeferred, i as formatServiceRepairDeferredNote, l as shouldManageGatewayService, r as confirmDoctorServiceRepair, s as resolveServiceRepairPolicy, t as SERVICE_REPAIR_POLICY_ENV } from "./doctor-service-repair-policy-dEj5KU5E.mjs";
//#region src/commands/doctor-format.ts
function buildGatewayRuntimeHints(runtime, options = {}) {
const hints = [];
if (!runtime) return hints;
const platform = options.platform ?? process.platform;
const env = options.env ?? process.env;
const fileLog = (() => {
try {
return getResolvedLoggerSettings().file;
} catch {
return null;
}
})();
const systemdDetail = runtime.inspectionFailure?.detail ?? runtime.detail;
if (platform === "linux" && isSystemdUnavailableDetail(systemdDetail)) {
hints.push(...renderSystemdUnavailableHints({
wsl: isWSLEnv(env),
kind: classifySystemdUnavailableDetail(systemdDetail),
env
}));
if (fileLog) hints.push(`File logs: ${fileLog}`);
return hints;
}
if (runtime.cachedLabel && platform === "darwin") {
const label = resolveGatewayLaunchAgentLabel(env.OPENCLAW_PROFILE);
hints.push(`LaunchAgent label cached but plist missing. Clear with: launchctl bootout gui/$UID/${label}`);
hints.push(`Then reinstall: ${formatCliCommand("openclaw gateway install", env)}`);
}
if (runtime.missingUnit) {
hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`);
if (fileLog) hints.push(`File logs: ${fileLog}`);
return hints;
}
const missingGuiSession = runtime.missingGuiSession && platform === "darwin";
const disabledTask = platform === "win32" && runtime.state === "Disabled";
if (missingGuiSession || disabledTask || runtime.status === "stopped") {
if (!missingGuiSession && platform === "linux" && isSystemdStartLimitHit(runtime)) hints.push("systemd stopped restarting the gateway after repeated crashes.", `Recover with: ${formatCliCommand("openclaw gateway restart", env)}, then inspect logs if it keeps crashing.`);
else if (!missingGuiSession && !disabledTask) hints.push("Service is loaded but not running (likely exited immediately).");
hints.push(...buildGatewayRuntimeRecoveryHints({
kind: missingGuiSession ? "gui-session" : disabledTask ? "disabled-task" : "stopped",
restartCommand: formatCliCommand("openclaw gateway restart", env),
logFile: fileLog,
platform,
env,
systemd: runtime.systemd
}));
if (missingGuiSession) return hints;
}
if (platform === "linux" && isSystemdCgroupHygieneRisk(runtime.systemd)) {
const unit = quoteCliArg(runtime.systemd?.unit ?? `${resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE)}.service`);
const system = runtime.systemd?.scope === "system";
const summary = getSystemdCgroupHygieneSummary(runtime.systemd);
if (summary) hints.push(`Systemd cgroup hygiene looks elevated: ${summary}.`, "This usually means old helper or browser processes may still be attached to the gateway service.", `Run: systemctl ${system ? "--system" : "--user"} show ${unit} -p KillMode -p TasksCurrent -p MemoryCurrent -p MainPID`, `Run: systemd-cgls ${system ? "--unit" : "--user-unit"} ${unit}`, `After reviewing service settings, run: ${formatCliCommand("openclaw gateway restart", env)}`);
}
return hints;
}
//#endregion
//#region src/commands/doctor-gateway-daemon-flow.ts
function noteGatewayRuntime(serviceRuntime, env) {
const summary = formatRuntimeStatus(serviceRuntime);
const hints = buildGatewayRuntimeHints(serviceRuntime, {
platform: process.platform,
env
});
const lines = summary ? [`Runtime: ${summary}`, ...hints] : hints;
const sqliteLibrary = ensureSqliteLibrarySelected();
if (sqliteLibrary.source !== "runtime") lines.push(`SQLite (doctor process): ${sqliteLibrary.path} (${sqliteLibrary.version}, extension loading enabled)`);
else if (sqliteLibrary.ignoredOverride) lines.push(`SQLite (doctor process): ${sqliteLibrary.ignoredOverride}; override ignored`);
if (lines.length > 0) note(lines.join("\n"), "Gateway");
}
async function maybeRepairLaunchAgentBootstrap(params) {
if (process.platform !== "darwin" || !await launchAgentPlistExists(params.env) || await isLaunchAgentLoaded({ env: params.env })) return { status: "skipped" };
note("LaunchAgent is installed but not loaded in launchd.", `${params.title} LaunchAgent`);
if (params.serviceRepairDeferred) {
note(formatServiceRepairDeferredNote(), `${params.title} LaunchAgent`);
return { status: "not-loaded" };
}
if (!await confirmDoctorServiceRepair(params.prompter, {
message: `Repair ${params.title} LaunchAgent bootstrap now?`,
initialValue: true
})) return { status: "not-loaded" };
params.runtime.log(`Bootstrapping ${params.title} LaunchAgent...`);
const repair = await repairLaunchAgentBootstrap({ env: params.env });
if (!repair.ok) {
if (repair.status === "system-launchdaemon-conflict" || repair.status === "system-launchdaemon-unverifiable") return {
status: "system-launchdaemon-blocked",
detail: repair.detail
};
if (repair.status === "gui-session-unavailable") return {
status: "gui-session-unavailable",
detail: repair.detail
};
params.runtime.error(`${params.title} LaunchAgent bootstrap failed: ${repair.detail ?? "unknown error"}`);
return { status: "not-loaded" };
}
if (!await isLaunchAgentLoaded({ env: params.env })) {
params.runtime.error(`${params.title} LaunchAgent still not loaded after repair.`);
return { status: "not-loaded" };
}
note(`${params.title} LaunchAgent repaired.`, `${params.title} LaunchAgent`);
return { status: "repaired" };
}
function renderBlockingSystemGatewayServices(services) {
return [
"System-level OpenClaw gateway service detected while the user gateway service is not installed.",
...services.map((svc) => `- ${svc.label} (${svc.detail})`),
"OpenClaw will not install a second user-level gateway service automatically.",
"Run `openclaw gateway status --deep` or `openclaw doctor --deep` to inspect duplicate services.",
`Set ${SERVICE_REPAIR_POLICY_ENV}=external if a system supervisor owns the gateway lifecycle.`
].join("\n");
}
function renderEstablishedGatewayConnections(connections) {
return [
"Established Gateway TCP clients detected:",
...connections.slice(0, 8).map((connection) => {
return `- ${connection.pid ? `pid=${connection.pid}` : "pid=?"} ${connection.direction}${connection.command ? ` ${connection.command}` : ""}${connection.address ? ` ${connection.address}` : ""}${connection.commandLine ? ` cmd=${connection.commandLine}` : ""}`;
}),
...connections.length > 8 ? [`- ... ${connections.length - 8} more connection(s)`] : [],
"If logs show protocol mismatch after rollback, stop stale OpenClaw client processes listed here and rerun doctor."
].join("\n");
}
async function maybeReportEstablishedGatewayClients(cfg, deep, port) {
if (!deep) return;
const targetPort = port ?? resolveGatewayPort(cfg, process.env);
const clients = (await inspectPortConnections(targetPort).catch(() => null))?.connections.filter(({ direction }) => direction !== "server");
if (clients?.length) note(renderEstablishedGatewayConnections(clients), "Gateway clients");
}
async function noteGatewayPortDiagnostics(cfg, deep) {
const port = resolveGatewayPort(cfg, process.env);
const bindHost = await resolveGatewayBindHost(cfg.gateway?.bind ?? "loopback", cfg.gateway?.customBindHost);
const diagnostics = await inspectPortUsage(port, { probeHosts: resolveGatewayRequiredListenHosts(bindHost) });
await maybeReportEstablishedGatewayClients(cfg, deep, port);
const conflict = diagnostics.status === "busy" && !isExpectedGatewayListeners(diagnostics.listeners, diagnostics.port);
if (conflict) note(formatPortDiagnostics(diagnostics).join("\n"), "Gateway port");
return conflict;
}
async function noteGatewayServiceInspectionFailure(loadState) {
const lines = [`Gateway service status could not be determined: ${loadState.detail}`];
const kind = process.platform === "linux" && classifySystemdUnavailableDetail(loadState.detail);
if (kind) lines.push(...renderSystemdUnavailableHints({
wsl: await isWSL(),
kind
}));
lines.push(`Run ${formatCliCommand("openclaw gateway status --deep")} and retry doctor.`);
note(lines.join("\n"), "Gateway");
}
/**
* Repairs or diagnoses the local gateway service after the health check fails.
*
* Remote gateway mode is only diagnosed; local mode may bootstrap launchd, install missing
* services, report port conflicts, or restart unhealthy supervision when policy allows.
*/
async function maybeRepairGatewayDaemon(params) {
if (!isDefaultInstallIdentity(process.env)) {
note(NON_DEFAULT_INSTALL_SERVICE_SKIP_REASON, "Gateway");
return;
}
if (params.cfg.gateway?.mode === "remote") return;
if (params.healthOk) {
await maybeReportEstablishedGatewayClients(params.cfg, params.options.deep ?? false);
return;
}
const root = await resolveOpenClawPackageRoot({
moduleUrl: import.meta.url,
argv1: process.argv[1]
});
const installOwner = await readInstallOwner(root);
if (installOwner) {
await noteGatewayPortDiagnostics(params.cfg, params.options.deep ?? false);
note(formatInstallOwnerMessage(installOwner), "Gateway");
return;
}
if (!await shouldManageGatewayService()) {
await noteGatewayPortDiagnostics(params.cfg, params.options.deep ?? false);
note(formatServiceRepairDeferredNote(), "Gateway");
return;
}
const serviceRepairPolicy = resolveServiceRepairPolicy();
const serviceRepairDeferred = isServiceRepairDeferred(serviceRepairPolicy);
const service = resolveGatewayService();
const restartGatewayService = async () => {
try {
return await service.restart({
env: process.env,
stdout: process.stdout
});
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
note(`Gateway service restart failed: ${detail}`, "Gateway");
return null;
}
};
const isLocalDarwinGateway = process.platform === "darwin";
const serviceState = await readGatewayServiceState(service, { env: process.env });
const serviceLayout = await summarizeGatewayServiceLayout(serviceState.command);
const serviceOwner = await readInstallOwner(serviceLayout?.packageRootReal ?? serviceLayout?.packageRoot ?? null);
if (serviceOwner) {
await noteGatewayPortDiagnostics(params.cfg, params.options.deep ?? false);
note(formatInstallOwnerMessage(serviceOwner), "Gateway");
return;
}
if (serviceState.loadState.status === "unknown") {
if (service.unsupportedReason) note(service.unsupportedReason, "Gateway");
else await noteGatewayServiceInspectionFailure(serviceState.loadState);
return;
}
let loaded = serviceState.loadState.status === "loaded";
let serviceRuntime = serviceState.runtime;
const serviceEnv = serviceState.env;
if (params.options.deep) {
const handoff = readGatewayRestartHandoffSync(serviceEnv);
if (handoff) note(formatGatewayRestartHandoffDiagnostic(handoff), "Gateway");
}
if (isLocalDarwinGateway) {
const gatewayRepair = serviceRuntime?.missingGuiSession ? {
status: "gui-session-unavailable",
detail: serviceRuntime.detail ?? ""
} : await maybeRepairLaunchAgentBootstrap({
env: process.env,
title: "Gateway",
runtime: params.runtime,
prompter: params.prompter,
serviceRepairDeferred
});
await maybeRepairLaunchAgentBootstrap({
env: {
...process.env,
OPENCLAW_LAUNCHD_LABEL: resolveNodeLaunchAgentLabel()
},
title: "Node",
runtime: params.runtime,
prompter: params.prompter,
serviceRepairDeferred
});
if (gatewayRepair.status === "not-loaded") return;
if (gatewayRepair.status === "system-launchdaemon-blocked") {
note(gatewayRepair.detail, "Gateway");
return;
}
if (gatewayRepair.status === "gui-session-unavailable") serviceRuntime = {
status: "unknown",
detail: gatewayRepair.detail || serviceRuntime?.detail,
missingGuiSession: true
};
if (gatewayRepair.status === "repaired") {
const repairedState = await readGatewayServiceState(service, { env: process.env });
if (repairedState.loadState.status === "unknown") {
await noteGatewayServiceInspectionFailure(repairedState.loadState);
return;
}
loaded = repairedState.loadState.status === "loaded";
serviceRuntime = repairedState.runtime;
}
}
if (isLocalDarwinGateway && serviceRuntime?.systemLaunchDaemon) {
noteGatewayRuntime(serviceRuntime, process.env);
return;
}
if (!await noteGatewayPortDiagnostics(params.cfg, params.options.deep ?? false) && loaded && serviceRuntime?.status === "running") {
const lastError = await readLastGatewayErrorLine(process.env);
if (lastError) note(`Last gateway error: ${lastError}`, "Gateway");
}
if (!loaded) {
if (isLocalDarwinGateway && (serviceRuntime?.missingGuiSession || serviceRuntime?.cachedLabel || serviceRuntime?.systemLaunchDaemon)) {
noteGatewayRuntime(serviceRuntime, process.env);
return;
}
note("Gateway service not installed.", "Gateway");
if (process.platform === "linux") {
const systemGatewayServices = await findSystemGatewayServices();
if (systemGatewayServices.length > 0) {
note(renderBlockingSystemGatewayServices(systemGatewayServices), "Gateway");
return;
}
}
if (serviceRepairDeferred) {
note(formatServiceRepairDeferredNote(), "Gateway");
return;
}
const install = await confirmDoctorServiceRepair(params.prompter, {
message: "Install gateway service now?",
initialValue: true,
requiresInteractiveConfirmation: true
}, serviceRepairPolicy);
if (!install) note(`Run ${formatCliCommand("openclaw gateway install")} when you want to install the gateway service.`, "Gateway");
if (install) {
const selection = await resolveGatewaySetupRuntime({
env: process.env,
existingCommand: serviceState.command,
selectRuntime: (suggested) => params.prompter.select({
message: "Gateway service runtime",
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
initialValue: suggested
}, suggested)
});
const tokenResolution = await resolveGatewayInstallToken({
config: params.cfg,
env: process.env
});
for (const warning of tokenResolution.warnings) note(warning, "Gateway");
if (tokenResolution.unavailableReason) {
note([
"Gateway service install aborted.",
tokenResolution.unavailableReason,
"Fix gateway auth config/token input and rerun doctor."
].join("\n"), "Gateway");
return;
}
const port = resolveGatewayPort(params.cfg, process.env);
const installation = await prepareGatewayServiceInstall({
service,
selection,
port,
existingCommand: serviceState.command,
warn: (message, title) => note(message, title),
config: params.cfg
});
try {
await installation.install();
} catch (err) {
note(`Gateway service install failed: ${String(err)}`, "Gateway");
note(gatewayInstallErrorHint(), "Gateway");
}
}
return;
}
noteGatewayRuntime(serviceRuntime, serviceEnv);
if (serviceRuntime?.status !== "running") {
if (params.healthSkipped && serviceRuntime?.status !== "stopped") return;
if (serviceRepairDeferred) {
note(formatServiceRepairDeferredNote(), "Gateway");
return;
}
if (await confirmDoctorServiceRepair(params.prompter, {
message: "Start gateway service now?",
initialValue: true
}, serviceRepairPolicy)) {
if (process.platform === "win32" && serviceRuntime?.state === "Disabled") {
try {
await service.start({
env: serviceEnv,
stdout: process.stdout
});
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
note(`Gateway service start failed: ${detail}`, "Gateway");
}
return;
}
const restartResult = await restartGatewayService();
if (!restartResult) return;
const restartStatus = describeGatewayServiceRestart("Gateway", restartResult);
if (!restartStatus.scheduled) await sleep(1500);
else note(restartStatus.message, "Gateway");
}
}
if (process.platform === "darwin") {
const label = resolveGatewayLaunchAgentLabel(process.env.OPENCLAW_PROFILE);
note(`LaunchAgent loaded; stopping requires "${formatCliCommand("openclaw gateway stop")}" or launchctl bootout gui/$UID/${label}.`, "Gateway");
}
if (serviceRuntime?.status === "running") {
if (params.healthSkipped) return;
if (serviceRepairDeferred) {
note(formatServiceRepairDeferredNote(), "Gateway");
return;
}
if (readGatewayRestartHandoffSync(serviceEnv)) try {
await healthCommandNonExiting({
json: false,
config: params.cfg
}, params.runtime);
note("Preserving the recent Gateway restart; skipping restart prompt.", "Gateway");
return;
} catch {}
if (params.options.nonInteractive === true) return;
if (await confirmDoctorServiceRepair(params.prompter, {
message: "Restart gateway service now?",
initialValue: false
}, serviceRepairPolicy)) {
const restartResult = await restartGatewayService();
if (!restartResult) return;
const restartStatus = describeGatewayServiceRestart("Gateway", restartResult);
if (restartStatus.scheduled) {
note(restartStatus.message, "Gateway");
return;
}
try {
await healthCommandNonExiting({
json: false,
config: params.cfg
}, params.runtime);
} catch (err) {
if (err instanceof ExitError) return;
const closedDiagnostic = formatGatewayClosedDiagnostic(err);
if (closedDiagnostic) {
note(closedDiagnostic, "Gateway");
note(params.gatewayDetailsMessage, "Gateway connection");
} else params.runtime.error(formatHealthCheckFailure(err));
}
}
}
}
//#endregion
export { maybeRepairGatewayDaemon };