openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
625 lines (624 loc) • 23.6 kB
JavaScript
import { r as defaultRuntime } from "./runtime-BRg2NubB.mjs";
import { C as resolveIsNixMode } from "./paths-BP0DYpE8.mjs";
import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs";
import { y as resolveFutureConfigActionBlock } from "./config-env-vars-BzUE_dOh.mjs";
import { i as renderSystemdUnavailableHints } from "./runtime-format-BNZPOq4x.mjs";
import { t as appendGatewayLifecycleAuditLog } from "./restart-logs-Bbl7UFnQ.mjs";
import { i as assertGatewayServiceUpdateCurrent } from "./service-update-authority-CkaptUU4.mjs";
import { t as isWSL } from "./wsl-DAucJ7y0.mjs";
import { h as createDaemonActionContext, m as buildDaemonServiceSnapshot, r as filterContainerGenericHints, u as resolveDaemonInstallBlockMessage } from "./shared-BlizK8_P.mjs";
import { n as resolveLaunchAgentLabel } from "./launchd-label-vbzwT2H_.mjs";
import { r as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-CgbblNbu.mjs";
import { c as readConfigFileSnapshot, o as readBestEffortConfig } from "./io.runtime-B134MOMz.mjs";
import "./config-VnHbY_QV.mjs";
import { t as mergeGatewayServiceEnv } from "./service-env-merge-D7nWnWSC.mjs";
import { G as isSystemdUserServiceAvailable, c as resolveSystemdServiceName } from "./systemd-service-files-Crio-kus.mjs";
import { a as readGatewayServiceLoadState, i as readGatewayServiceState, o as collectGatewayServiceStartRepairIssues, r as startGatewayService, s as formatGatewayServiceStartRepairIssues, t as describeGatewayServiceRestart } from "./service-BFsg6EmN.mjs";
import { t as createGatewayLifecycleMutationReporter } from "./service-mutation-BITjAaaK.mjs";
import { a as writeGatewayServiceRestartIntentSync, i as writeGatewayRestartIntentSync, r as prepareGatewayRestartIntentLegacyProcess, s as GatewayRestartPreparationError, t as clearGatewayRestartIntentSync } from "./restart-intent-DXoh0HU4.mjs";
import "./systemd-DygLJFXs.mjs";
import { t as createGatewayCredentialPlan } from "./credential-planner-CZkSE9xV.mjs";
import { n as isGatewaySecretRefUnavailableError, t as GatewaySecretRefUnavailableError } from "./credentials-CXbFcSOg.mjs";
import { r as isTerminalInteractive } from "./terminal-interactivity-D92bdO16.mjs";
import { n as formatPluginPackagingRuntimeOutputRecoveryHint, t as formatInvalidConfigRecoveryHint } from "./config-recovery-hints-CsQKexIa.mjs";
import { t as renderConfigValidationIssueLines } from "./issue-location-eG_-bRvY.mjs";
import { r as checkTokenDrift } from "./service-audit-BuoA3viB.mjs";
import { t as resolveGatewayAuthToken } from "./auth-token-resolution-da44MR3g.mjs";
//#region src/cli/daemon-cli/gateway-token-drift.ts
/** Resolve the expected Gateway token for service drift checks, or undefined when token auth is inactive. */
async function resolveGatewayTokenForDriftCheck(params) {
const env = params.env ?? process.env;
const mode = params.cfg.gateway?.auth?.mode;
if (mode === "password" || mode === "none" || mode === "trusted-proxy") return;
const plan = createGatewayCredentialPlan({
config: params.cfg,
env
});
if (plan.authMode === void 0 && plan.passwordCanWin && !plan.tokenCanWin) return;
const resolved = await resolveGatewayAuthToken({
cfg: params.cfg,
env,
envFallback: "never",
unresolvedReasonStyle: "detailed"
});
if (resolved.token) return resolved.token;
if (!resolved.secretRefConfigured) return;
throw new GatewaySecretRefUnavailableError("gateway.auth.token");
}
//#endregion
//#region src/cli/daemon-cli/lifecycle-action-preflight.ts
/** Startup admission, or a diagnostic to report after recovery actions. */
async function getServiceActionPreflightFailure(action) {
let snapshot;
try {
snapshot = await readConfigFileSnapshot({ observe: false });
if (!snapshot.valid) return {
message: snapshot.issues.length > 0 ? renderConfigValidationIssueLines(snapshot, "").join("\n") : "Unknown validation issue.",
...isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? { hints: formatPluginPackagingRuntimeOutputRecoveryHint().split("\n") } : {}
};
} catch {
return null;
}
const futureBlock = resolveFutureConfigActionBlock({
action: `${action} the gateway service`,
snapshot
});
if (futureBlock) return {
message: action === "start" ? futureBlock.message : `Config was last written by OpenClaw ${futureBlock.touchedVersion}; this binary is ${futureBlock.currentVersion}.`,
hints: futureBlock.hints
};
return null;
}
//#endregion
//#region src/cli/daemon-cli/lifecycle-audit.ts
function appendGatewayLifecycleAudit(params) {
appendGatewayLifecycleAuditLog(params.env ?? process.env, {
action: params.action,
source: params.source,
mode: params.mode,
...params.pid === void 0 ? {} : { pid: params.pid },
interactive: isTerminalInteractive()
});
}
function createGatewayLifecycleMutationAudit(params) {
const reportMutation = createGatewayLifecycleMutationReporter((mutation) => {
appendGatewayLifecycleAudit({
action: params.action,
source: params.source ?? "cli",
mode: mutation.mode,
...params.env === void 0 ? {} : { env: params.env }
});
});
return (mutation) => reportMutation(mutation.mode);
}
function createServiceLifecycleMutationAudit(params) {
return params.serviceNoun === "Gateway" ? createGatewayLifecycleMutationAudit({ action: params.action }) : void 0;
}
function appendServiceLifecycleRepairAudit(params) {
if (params.serviceNoun !== "Gateway") return;
appendGatewayLifecycleAudit({
action: params.action,
source: "cli",
mode: "service-repair",
...params.pid === void 0 ? {} : { pid: params.pid }
});
}
//#endregion
//#region src/cli/daemon-cli/lifecycle-restart-intent.ts
function createServiceRestartIntent(params) {
let recorded = false;
let env = process.env;
return {
prepare: async () => {
if (params.serviceNoun !== "Gateway" || recorded) return;
const runtime = await params.service.readRuntime(process.env).catch(() => null);
assertGatewayServiceUpdateCurrent();
const nativeService = process.platform === "linux" || process.platform === "darwin";
let service;
let legacyProcess;
if (nativeService) try {
const command = await params.service.readCommand(process.env, { requireEffective: true });
assertGatewayServiceUpdateCurrent();
if (!command) throw new GatewayRestartPreparationError("service-command");
env = mergeGatewayServiceEnv(process.env, command);
service = process.platform === "linux" ? {
kind: "systemd",
name: runtime?.systemd?.unit ?? resolveSystemdServiceName(process.env)
} : {
kind: "launchd",
name: resolveLaunchAgentLabel(process.env)
};
legacyProcess = await prepareGatewayRestartIntentLegacyProcess({
env,
command,
runtimePid: runtime?.pid,
readRuntime: () => params.service.readRuntime(process.env),
assertCurrent: assertGatewayServiceUpdateCurrent
});
} catch {
assertGatewayServiceUpdateCurrent();
throw new GatewayRestartPreparationError("service-command");
}
assertGatewayServiceUpdateCurrent();
const options = {
env,
reason: "gateway.restart",
...params.intent ? { intent: params.intent } : {}
};
recorded = service ? writeGatewayServiceRestartIntentSync({
...options,
service,
legacyProcess,
nativeStopped: runtime?.status === "stopped" && runtime.pid === void 0,
assertCurrent: assertGatewayServiceUpdateCurrent
}) : writeGatewayRestartIntentSync({
...options,
targetPid: runtime?.pid
});
},
clear: () => {
if (recorded) {
assertGatewayServiceUpdateCurrent();
clearGatewayRestartIntentSync(env);
recorded = false;
}
}
};
}
//#endregion
//#region src/cli/daemon-cli/lifecycle-core.ts
function mergeWarnings(captured, reported) {
const combined = [...captured, ...reported ?? []];
return combined.length > 0 ? combined : void 0;
}
async function failServiceNotLoaded(params, fail) {
let hints = params.renderStartHints();
if (process.platform === "linux" && !await isSystemdUserServiceAvailable().catch(() => false)) hints = [...hints, ...renderSystemdUnavailableHints({
wsl: await isWSL(),
kind: "generic_unavailable"
})];
fail(`${params.serviceNoun} service ${params.service.notLoadedText}.`, filterContainerGenericHints(hints));
}
async function resolveServiceLoadedOrFail(params, fail, opts = {}) {
const hasInstalledDefinition = async () => params.service.hasInstalledDefinition ? await params.service.hasInstalledDefinition({ env: process.env }).catch(() => false) : Boolean(await params.service.readCommand(process.env).catch(() => null));
const loadState = await readGatewayServiceLoadState(params.service, { env: process.env });
if (loadState.status === "unknown") {
fail(`${opts.inspectionFailureMessage ?? `${params.serviceNoun} service check failed`}: ${loadState.detail}`);
return null;
}
return loadState.status === "loaded" || Boolean(opts.acceptInstalledDefinition) && await hasInstalledDefinition();
}
function warnServiceConfig(issue, context) {
if (!issue) return;
const warning = [
`Config needs repair: ${issue.message}`,
...issue.hints ?? [],
formatCliCommand("openclaw doctor --fix")
].join("\n");
context.warnings.push(warning);
if (!context.json) defaultRuntime.error(`WARNING: ${warning}`);
}
async function runServiceUninstall(params) {
const json = Boolean(params.opts?.json);
const { stdout, warnings, emit, fail } = createDaemonActionContext({
action: "uninstall",
json
});
if (resolveIsNixMode(process.env)) {
fail("Nix mode detected; service uninstall is disabled.");
return;
}
let loaded = await resolveServiceLoadedOrFail(params, fail, { inspectionFailureMessage: `${params.serviceNoun} uninstall aborted because service status is unknown; resolve the inspection error before retrying` });
if (loaded === null) return;
if (loaded && params.stopBeforeUninstall) try {
await params.service.stop({
env: process.env,
stdout
});
} catch {}
try {
await params.service.uninstall({
env: process.env,
stdout
});
} catch (err) {
fail(`${params.serviceNoun} uninstall failed: ${String(err)}`);
return;
}
loaded = await resolveServiceLoadedOrFail(params, fail, { inspectionFailureMessage: `${params.serviceNoun} uninstall verification failed because service status is unknown` });
if (loaded === null) return;
if (loaded && params.assertNotLoadedAfterUninstall) {
fail(`${params.serviceNoun} service still loaded after uninstall.`);
return;
}
warnServiceConfig(await getServiceActionPreflightFailure("uninstall"), {
json,
warnings
});
emit({
ok: true,
result: "uninstalled",
service: buildDaemonServiceSnapshot(params.service, loaded)
});
}
async function runServiceStart(params) {
const json = Boolean(params.opts?.json);
const serviceCommand = formatCliCommand(`openclaw ${params.serviceNoun.toLowerCase()}`);
const { stdout, warnings, emitMessage, fail } = createDaemonActionContext({
action: "start",
json
});
const warn = json ? (message) => warnings.push(message) : void 0;
let postCheckFailed = false;
const checkReadiness = async () => {
await params.postStartCheck?.({
json,
stdout,
warnings,
warn,
fail: (message, hints, result) => {
postCheckFailed = true;
fail(message, hints, result);
}
});
return !postCheckFailed;
};
const emitStarted = async (result) => {
if (!await checkReadiness()) return;
emitMessage({
ok: true,
result: "started",
message: result.message,
warnings: mergeWarnings(warnings, result.reportedWarnings),
service: buildDaemonServiceSnapshot(params.service, result.loaded)
});
};
const loaded = await resolveServiceLoadedOrFail(params, fail);
if (loaded === null) return;
const preflight = await getServiceActionPreflightFailure("start");
if (preflight) {
fail(!preflight.hints ? `${params.serviceNoun} aborted: config is invalid.\n${preflight.message}\n${formatInvalidConfigRecoveryHint()}` : `${params.serviceNoun} start blocked: ${preflight.message}`, preflight.hints);
return;
}
if (!loaded) try {
const handled = await params.onNotLoaded?.({
json,
stdout,
warn,
fail
});
if (handled) {
await emitStarted({
loaded: handled.loaded ?? false,
message: handled.message,
reportedWarnings: handled.warnings
});
return;
}
} catch (err) {
if (postCheckFailed) throw err;
fail(`${params.serviceNoun} start failed: ${String(err)}`, params.renderStartHints());
return;
}
try {
const startResult = await startGatewayService(params.service, {
env: process.env,
stdout,
warn,
onMutation: createServiceLifecycleMutationAudit({
serviceNoun: params.serviceNoun,
action: "start"
})
}, params.expectedPort);
if (startResult.outcome === "missing-install") {
await failServiceNotLoaded(params, fail);
return;
}
if (startResult.outcome === "already-running") {
if (startResult.issues.length > 0) {
const repairAction = params.repairLoadedService ? "restart" : "install --force";
const warning = `${params.serviceNoun} service already running, but its installed service definition needs repair: ${formatGatewayServiceStartRepairIssues(startResult.issues)}; run \`${serviceCommand} ${repairAction}\` to apply.`;
warnings.push(warning);
if (!json) defaultRuntime.log(warning);
}
if (!await checkReadiness()) return;
const pid = startResult.state.runtime?.pid;
emitMessage({
ok: true,
result: "already-running",
message: `${params.serviceNoun} service already running${pid === void 0 ? "" : ` (pid ${pid})`}.`,
service: buildDaemonServiceSnapshot(params.service, true),
warnings: warnings.length ? warnings : void 0
});
return;
}
if (startResult.outcome === "repair-required") {
try {
const handled = await params.repairLoadedService?.({
json,
stdout,
warn,
fail,
state: startResult.state,
issues: startResult.issues
});
if (handled) {
appendServiceLifecycleRepairAudit({
serviceNoun: params.serviceNoun,
action: "start"
});
await emitStarted({
loaded: handled.loaded ?? true,
message: handled.message,
reportedWarnings: handled.warnings
});
return;
}
} catch (err) {
if (postCheckFailed) throw err;
fail(`${params.serviceNoun} repair failed: ${String(err)}`, params.renderStartHints());
return;
}
fail(`${params.serviceNoun} service needs repair before it can start: ${formatGatewayServiceStartRepairIssues(startResult.issues)}`, [`${serviceCommand} install --force`]);
return;
}
await emitStarted({ loaded: startResult.state.loadState.status === "loaded" });
} catch (err) {
if (postCheckFailed) throw err;
fail(`${params.serviceNoun} start failed: ${String(err)}`, params.renderStartHints());
}
}
async function runServiceStop(params) {
const json = Boolean(params.opts?.json);
const { stdout, warnings, emit, emitMessage, fail } = createDaemonActionContext({
action: "stop",
json
});
const gatewayStopAudit = createServiceLifecycleMutationAudit({
serviceNoun: params.serviceNoun,
action: "stop"
});
const loaded = await resolveServiceLoadedOrFail(params, fail);
if (loaded === null) return;
if (!loaded && !params.stopWhenNotLoaded) {
try {
const handled = await params.onNotLoaded?.({
json,
stdout,
fail
});
if (handled) {
warnServiceConfig(await getServiceActionPreflightFailure("stop"), {
json,
warnings
});
emitMessage({
ok: true,
result: handled.result,
message: handled.message,
warnings: mergeWarnings(warnings, handled.warnings),
service: buildDaemonServiceSnapshot(params.service, false)
});
return;
}
} catch (err) {
fail(`${params.serviceNoun} stop failed: ${String(err)}`);
return;
}
warnServiceConfig(await getServiceActionPreflightFailure("stop"), {
json,
warnings
});
emitMessage({
ok: true,
result: "not-loaded",
message: `${params.serviceNoun} service ${params.service.notLoadedText}.`,
service: buildDaemonServiceSnapshot(params.service, loaded)
});
return;
}
try {
await params.service.stop({
env: process.env,
stdout,
disable: params.opts?.disable,
onMutation: gatewayStopAudit
});
} catch (err) {
fail(`${params.serviceNoun} stop failed: ${String(err)}`);
return;
}
const finalLoaded = loaded ? await resolveServiceLoadedOrFail(params, fail, { inspectionFailureMessage: `${params.serviceNoun} stop verification failed because service status is unknown` }) : false;
if (finalLoaded === null) return;
warnServiceConfig(await getServiceActionPreflightFailure("stop"), {
json,
warnings
});
emit({
ok: true,
result: "stopped",
service: buildDaemonServiceSnapshot(params.service, finalLoaded)
});
}
async function runServiceRestart(params) {
const json = Boolean(params.opts?.json);
const { stdout, warnings, emitMessage, fail } = createDaemonActionContext({
action: "restart",
json
});
const warn = json ? (message) => warnings.push(message) : void 0;
const renderRestartFailureHints = (error) => error instanceof GatewayRestartPreparationError && error.reason === "serving-owner" ? [formatCliCommand("openclaw gateway status --deep"), `Fix the reported startup failure, then run \`${formatCliCommand("openclaw gateway start")}\` to wait for readiness without restarting the process.`] : params.renderStartHints();
const restartIntent = params.opts?.restartIntent;
const gatewayRestartAudit = createServiceLifecycleMutationAudit({
serviceNoun: params.serviceNoun,
action: "restart"
});
let handledRecovery = null;
let handledRepair = null;
let recoveredLoadedState = null;
const { prepare: prepareGatewayRestartIntent, clear: clearPreparedRestartIntent } = createServiceRestartIntent({
serviceNoun: params.serviceNoun,
service: params.service,
intent: restartIntent
});
const emitScheduledRestart = (restartStatus, serviceLoaded) => {
emitMessage({
ok: true,
result: restartStatus.daemonActionResult,
message: restartStatus.message,
service: buildDaemonServiceSnapshot(params.service, serviceLoaded),
warnings: warnings.length ? warnings : void 0
});
return true;
};
const loaded = await resolveServiceLoadedOrFail(params, fail, { acceptInstalledDefinition: true });
if (loaded === null) return false;
const configIssue = await getServiceActionPreflightFailure("restart");
if (params.restartOwnedProcess) try {
handledRecovery = await params.restartOwnedProcess({
json,
stdout,
warn,
fail
});
} catch (err) {
fail(`${params.serviceNoun} restart failed: ${String(err)}`);
return false;
}
if (loaded && !handledRecovery) params.beforeServiceMutation?.();
if (!loaded && !handledRecovery) {
try {
handledRecovery = await params.onNotLoaded?.({
json,
stdout,
warn,
fail
}) ?? null;
} catch (err) {
fail(`${params.serviceNoun} restart failed: ${String(err)}`);
return false;
}
if (!handledRecovery) {
await failServiceNotLoaded(params, fail);
return false;
}
if (handledRecovery.warnings?.length) warnings.push(...handledRecovery.warnings);
recoveredLoadedState = handledRecovery.loaded ?? null;
}
if (loaded && !handledRecovery && !configIssue && params.repairLoadedService) try {
const state = await readGatewayServiceState(params.service, { env: process.env });
const issues = collectGatewayServiceStartRepairIssues(state, params.expectedPort);
if (issues.length > 0) {
await prepareGatewayRestartIntent();
handledRepair = await params.repairLoadedService({
json,
stdout,
warn,
fail,
state,
issues
});
if (!handledRepair) {
clearPreparedRestartIntent();
fail(`${params.serviceNoun} service needs repair before restart: ${formatGatewayServiceStartRepairIssues(issues)}`, [formatCliCommand("openclaw gateway install --force")]);
return false;
}
appendServiceLifecycleRepairAudit({
serviceNoun: params.serviceNoun,
action: "restart",
pid: state.runtime?.pid
});
if (handledRepair.warnings?.length) warnings.push(...handledRepair.warnings);
}
} catch (err) {
clearPreparedRestartIntent();
const hints = renderRestartFailureHints(err);
fail(`${params.serviceNoun} repair failed: ${String(err)}`, hints);
return false;
}
if (loaded && !handledRecovery && !configIssue && params.checkTokenDrift) try {
const command = await params.service.readCommand(process.env);
const serviceToken = command?.environment?.OPENCLAW_GATEWAY_TOKEN;
const configToken = await resolveGatewayTokenForDriftCheck({
cfg: await readBestEffortConfig(),
env: {
...process.env,
...command?.environment
}
});
const driftIssue = checkTokenDrift({
serviceToken,
configToken
});
if (driftIssue) {
const recovery = resolveDaemonInstallBlockMessage("gateway") ?? `Run \`${formatCliCommand("openclaw gateway install --force")}\` to refresh the service token source.`;
const warning = `${driftIssue.message} ${recovery}`;
warnings.push(warning);
if (!json) defaultRuntime.log(`\n⚠️ ${warning}\n`);
}
} catch (err) {
if (isGatewaySecretRefUnavailableError(err, "gateway.auth.token")) {
const warning = "Unable to verify gateway token drift: gateway.auth.token SecretRef is configured but unavailable in this command path.";
warnings.push(warning);
if (!json) defaultRuntime.log(`\n⚠️ ${warning}\n`);
}
}
let postCheckFailed = false;
try {
let restartResult;
if (loaded && !handledRepair && !handledRecovery) {
await prepareGatewayRestartIntent();
try {
restartResult = await params.service.restart({
preserveDefinition: configIssue ? true : params.opts?.preserveDefinition,
env: process.env,
stdout,
warn,
onMutation: gatewayRestartAudit
});
} catch (err) {
clearPreparedRestartIntent();
throw err;
}
}
warnServiceConfig(configIssue, {
json,
warnings
});
let restartStatus = describeGatewayServiceRestart(params.serviceNoun, restartResult ?? { outcome: "completed" });
if (restartStatus.scheduled) return emitScheduledRestart(restartStatus, loaded || recoveredLoadedState === true);
if (params.postRestartCheck) {
const postRestartResult = await params.postRestartCheck({
json,
stdout,
warnings,
warn,
activationAccepted: restartResult?.outcome === "completed" || Boolean(handledRecovery),
preserveDefinition: configIssue ? true : params.opts?.preserveDefinition,
fail: (message, hints, result) => {
postCheckFailed = true;
fail(message, hints, result);
}
});
if (postRestartResult) {
restartStatus = describeGatewayServiceRestart(params.serviceNoun, postRestartResult);
if (restartStatus.scheduled) return emitScheduledRestart(restartStatus, loaded || recoveredLoadedState === true);
}
}
emitMessage({
ok: true,
result: "restarted",
message: handledRecovery?.message ?? handledRepair?.message,
service: buildDaemonServiceSnapshot(params.service, loaded || recoveredLoadedState === true),
warnings: warnings.length ? warnings : void 0
});
return true;
} catch (err) {
if (postCheckFailed) throw err;
const hints = renderRestartFailureHints(err);
fail(`${params.serviceNoun} restart failed: ${String(err)}`, hints);
return false;
}
}
//#endregion
export { appendGatewayLifecycleAudit as a, runServiceUninstall as i, runServiceStart as n, createGatewayLifecycleMutationAudit as o, runServiceStop as r, runServiceRestart as t };