UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

617 lines (616 loc) 21.3 kB
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js"; import { a as readBestEffortConfig, s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js"; import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { y as resolveIsNixMode } from "./paths-D2sRr1a_.js"; import { h as resolveFutureConfigActionBlock } from "./config-env-vars-DUfQlcAk.js"; import { n as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-CsUZ07YX.js"; import "./config-Cs0XXL3x.js"; import { t as createGatewayCredentialPlan } from "./credential-planner-DAMjVc9j.js"; import { n as isGatewaySecretRefUnavailableError, t as GatewaySecretRefUnavailableError } from "./credentials-CXbS4PS0.js"; import { t as resolveGatewayAuthToken } from "./auth-token-resolution-B9de62Be.js"; import { t as createGatewayLifecycleMutationReporter } from "./service-mutation-BxHhMRDF.js"; import { t as appendGatewayLifecycleAuditLog } from "./restart-logs-uOTpYNS7.js"; import { m as isSystemdUserServiceAvailable } from "./systemd-exec-C9fneC4I.js"; import { r as checkTokenDrift } from "./service-audit-D98Rd9JS.js"; import { i as readGatewayServiceLoadState, r as inspectGatewayServiceStartRepair, s as startGatewayService, t as describeGatewayServiceRestart } from "./service-Cc6NX6Jm.js"; import "./systemd-C1W5NZTY.js"; import { a as renderSystemdUnavailableHints } from "./runtime-hints-DTsISVnA.js"; import { t as isWSL } from "./wsl-DjKrZ_YH.js"; import { f as buildDaemonServiceSnapshot, g as emitDaemonScheduledRestart, h as emitDaemonAlreadyRunning, l as resolveDaemonInstallBlockMessage, p as createDaemonActionContext, r as filterContainerGenericHints } from "./shared-11cJIS_p.js"; import { a as writeGatewayRestartIntentSync, t as clearGatewayRestartIntentSync } from "./restart-intent-HAvm7sr4.js"; import { r as isTerminalInteractive } from "./terminal-interactivity-DXUXAq5U.js"; import { n as formatPluginPackagingRuntimeOutputRecoveryHint, t as formatInvalidConfigRecoveryHint } from "./config-recovery-hints-BS5G0aQ-.js"; import { t as renderConfigValidationIssueLines } from "./issue-location-Dbpi1Rol.js"; //#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/gateway-token-drift.ts function authModeDisablesToken(mode) { return mode === "password" || mode === "none" || mode === "trusted-proxy"; } function isPasswordFallbackActive(params) { const plan = createGatewayCredentialPlan({ config: params.cfg, env: params.env }); if (plan.authMode !== void 0) return false; return plan.passwordCanWin && !plan.tokenCanWin; } /** 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 (authModeDisablesToken(mode)) return; if (isPasswordFallbackActive({ cfg: params.cfg, env })) 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 const ACTION_PROSE = { start: "start the gateway service", restart: "restart the gateway service", stop: "stop the gateway service", uninstall: "uninstall the gateway service" }; function formatPluginPackagingRuntimeOutputRecoveryHints() { return formatPluginPackagingRuntimeOutputRecoveryHint().split("\n"); } /** Best-effort validation before a service action mutates runtime state. */ async function getServiceActionPreflightFailure(action) { let snapshot; try { snapshot = await readConfigFileSnapshot({ observe: false }); if (snapshot.exists && !snapshot.valid) return { message: snapshot.issues.length > 0 ? renderConfigValidationIssueLines(snapshot, "").join("\n") : "Unknown validation issue.", ...isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot) ? { hints: formatPluginPackagingRuntimeOutputRecoveryHints() } : {} }; } catch { return null; } const futureBlock = resolveFutureConfigActionBlock({ action: ACTION_PROSE[action], snapshot }); if (futureBlock) return { message: futureBlock.message, hints: futureBlock.hints }; return null; } //#endregion //#region src/cli/daemon-cli/lifecycle-core.ts async function maybeAugmentSystemdHints(hints) { if (process.platform !== "linux") return hints; if (await isSystemdUserServiceAvailable().catch(() => false)) return hints; return [...hints, ...renderSystemdUnavailableHints({ wsl: await isWSL(), kind: "generic_unavailable" })]; } function mergeWarnings(captured, reported) { const combined = [...captured, ...reported ?? []]; return combined.length > 0 ? combined : void 0; } async function failServiceNotLoaded(params) { const hints = filterContainerGenericHints(await maybeAugmentSystemdHints(params.renderStartHints())); params.fail(`${params.serviceNoun} service ${params.service.notLoadedText}.`, hints); } async function resolveServiceLoadedOrFail(params) { 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") { params.fail(`${params.inspectionFailureMessage ?? `${params.serviceNoun} service check failed`}: ${loadState.detail}`); return null; } return loadState.status === "loaded" || Boolean(params.acceptInstalledDefinition) && await hasInstalledDefinition(); } async function runServiceUninstall(params) { const json = Boolean(params.opts?.json); const { stdout, emit, fail } = createDaemonActionContext({ action: "uninstall", json }); if (resolveIsNixMode(process.env)) { fail("Nix mode detected; service uninstall is disabled."); return; } { const preflight = await getServiceActionPreflightFailure("uninstall"); if (preflight) { fail(`${params.serviceNoun} uninstall blocked: ${preflight.message}`, preflight.hints); return; } } let loaded = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, 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({ serviceNoun: params.serviceNoun, service: params.service, 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; } 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, emit, fail } = createDaemonActionContext({ action: "start", json }); const warn = json ? (message) => warnings.push(message) : void 0; const emitStarted = async (result) => { await params.postStartCheck?.({ json, stdout, warnings, warn, fail }); emit({ ok: true, result: "started", message: result.message, warnings: mergeWarnings(warnings, result.reportedWarnings), service: buildDaemonServiceSnapshot(params.service, result.loaded) }); if (!json && result.message) defaultRuntime.log(result.message); }; const loaded = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, fail }); if (loaded === null) return; { const preflight = await getServiceActionPreflightFailure("start"); if (preflight) { fail(preflight.hints ? `${params.serviceNoun} start blocked: ${preflight.message}` : `${params.serviceNoun} aborted: config is invalid.\n${preflight.message}\n${formatInvalidConfigRecoveryHint()}`, 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) { 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({ serviceNoun: params.serviceNoun, service: params.service, renderStartHints: params.renderStartHints, 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: ${startResult.issues.map((issue) => issue.message).join("; ")}; run \`${serviceCommand} ${repairAction}\` to apply.`; warnings.push(warning); if (!json) defaultRuntime.log(warning); } emitDaemonAlreadyRunning({ serviceNoun: params.serviceNoun, service: params.service, pid: startResult.state.runtime?.pid, json, warnings, emit }); 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) { fail(`${params.serviceNoun} repair failed: ${String(err)}`, params.renderStartHints()); return; } fail(`${params.serviceNoun} service needs repair before it can start: ${startResult.issues.map((issue) => issue.message).join("; ")}`, [`${serviceCommand} install --force`]); return; } await emitStarted({ loaded: startResult.state.loadState.status === "loaded" }); } catch (err) { fail(`${params.serviceNoun} start failed: ${String(err)}`, params.renderStartHints()); } } async function runServiceStop(params) { const json = Boolean(params.opts?.json); const { stdout, emit, fail } = createDaemonActionContext({ action: "stop", json }); const gatewayStopAudit = createServiceLifecycleMutationAudit({ serviceNoun: params.serviceNoun, action: "stop" }); const loaded = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, fail }); if (loaded === null) return; { const preflight = await getServiceActionPreflightFailure("stop"); if (preflight) { fail(`${params.serviceNoun} stop blocked: ${preflight.message}`, preflight.hints); return; } } if (!loaded) { if (params.stopWhenNotLoaded) { 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; } emit({ ok: true, result: "stopped", service: buildDaemonServiceSnapshot(params.service, false) }); return; } try { const handled = await params.onNotLoaded?.({ json, stdout, fail }); if (handled) { emit({ ok: true, result: handled.result, message: handled.message, warnings: handled.warnings, service: buildDaemonServiceSnapshot(params.service, false) }); if (!json && handled.message) defaultRuntime.log(handled.message); return; } } catch (err) { fail(`${params.serviceNoun} stop failed: ${String(err)}`); return; } emit({ ok: true, result: "not-loaded", message: `${params.serviceNoun} service ${params.service.notLoadedText}.`, service: buildDaemonServiceSnapshot(params.service, loaded) }); if (!json) defaultRuntime.log(`${params.serviceNoun} service ${params.service.notLoadedText}.`); 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 = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, fail, inspectionFailureMessage: `${params.serviceNoun} stop verification failed because service status is unknown` }); if (finalLoaded === null) return; emit({ ok: true, result: "stopped", service: buildDaemonServiceSnapshot(params.service, finalLoaded) }); } async function runServiceRestart(params) { const json = Boolean(params.opts?.json); const { stdout, warnings, emit, fail } = createDaemonActionContext({ action: "restart", json }); const warn = json ? (message) => warnings.push(message) : void 0; const restartIntent = params.opts?.restartIntent; const gatewayRestartAudit = createServiceLifecycleMutationAudit({ serviceNoun: params.serviceNoun, action: "restart" }); let handledRecovery = null; let handledRepair = null; let recoveredLoadedState = null; let wroteRestartIntent = false; const prepareGatewayRestartIntent = async () => { if (params.serviceNoun !== "Gateway" || wroteRestartIntent) return; const runtime = await params.service.readRuntime(process.env).catch(() => null); wroteRestartIntent = writeGatewayRestartIntentSync({ targetPid: runtime?.pid, reason: "gateway.restart", ...restartIntent ? { intent: restartIntent } : {} }); }; const clearPreparedRestartIntent = () => { if (wroteRestartIntent) { clearGatewayRestartIntentSync(); wroteRestartIntent = false; } }; const emitScheduledRestart = (restartStatus, serviceLoaded) => { return emitDaemonScheduledRestart({ json, emit, result: restartStatus.daemonActionResult, message: restartStatus.message, service: params.service, loaded: serviceLoaded, warnings }); }; const loaded = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, fail, acceptInstalledDefinition: true }); if (loaded === null) return false; { const preflight = await getServiceActionPreflightFailure("restart"); if (preflight) { fail(preflight.hints ? `${params.serviceNoun} restart blocked: ${preflight.message}` : `${params.serviceNoun} aborted: config is invalid.\n${preflight.message}\n${formatInvalidConfigRecoveryHint()}`, preflight.hints); return false; } } if (loaded) params.beforeServiceMutation?.(); if (!loaded) { 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({ serviceNoun: params.serviceNoun, service: params.service, renderStartHints: params.renderStartHints, fail }); return false; } if (handledRecovery.warnings?.length) warnings.push(...handledRecovery.warnings); recoveredLoadedState = handledRecovery.loaded ?? null; } if (loaded && params.repairLoadedService) try { const { state, issues } = await inspectGatewayServiceStartRepair(params.service, { env: process.env }, 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: ${issues.map((issue) => issue.message).join("; ")}`, [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 = params.renderStartHints(); fail(`${params.serviceNoun} repair failed: ${String(err)}`, hints); return false; } if (loaded && 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`); } } try { let restartResult = { outcome: "completed" }; if (loaded && !handledRepair) { await prepareGatewayRestartIntent(); try { restartResult = await params.service.restart({ preserveDefinition: params.opts?.preserveDefinition, env: process.env, stdout, warn, onMutation: gatewayRestartAudit }); } catch (err) { clearPreparedRestartIntent(); throw err; } } let restartStatus = describeGatewayServiceRestart(params.serviceNoun, restartResult); if (restartStatus.scheduled) return emitScheduledRestart(restartStatus, loaded || recoveredLoadedState === true); if (params.postRestartCheck) { const postRestartResult = await params.postRestartCheck({ json, stdout, warnings, warn, fail }); if (postRestartResult) { restartStatus = describeGatewayServiceRestart(params.serviceNoun, postRestartResult); if (restartStatus.scheduled) return emitScheduledRestart(restartStatus, loaded || recoveredLoadedState === true); } } emit({ ok: true, result: "restarted", message: handledRecovery?.message ?? handledRepair?.message, service: buildDaemonServiceSnapshot(params.service, loaded || recoveredLoadedState === true), warnings: warnings.length ? warnings : void 0 }); const actionMessage = handledRecovery?.message ?? handledRepair?.message; if (!json && actionMessage) defaultRuntime.log(actionMessage); return true; } catch (err) { const hints = params.renderStartHints(); 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 };