UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

629 lines (628 loc) • 22.7 kB
import { r as defaultRuntime } from "./runtime-BC29JSZp.mjs"; import { C as resolveIsNixMode } from "./paths-FklbMVOH.mjs"; import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs"; import { i as renderSystemdUnavailableHints } from "./runtime-format-C_HpOmfT.mjs"; import { t as appendGatewayLifecycleAuditLog } from "./restart-logs-tWQTBTd3.mjs"; import { i as assertGatewayServiceUpdateCurrent } from "./service-update-authority-CB0cX2Br.mjs"; import { t as isWSL } from "./wsl-BsdoMNVM.mjs"; import { _ as emitDaemonAlreadyRunning, h as createDaemonActionContext, m as buildDaemonServiceSnapshot, r as filterContainerGenericHints, u as resolveDaemonInstallBlockMessage, v as emitDaemonScheduledRestart } from "./shared-BmWy-70N.mjs"; import { n as resolveLaunchAgentLabel } from "./launchd-label-pEbQ4EyH.mjs"; import { b as resolveFutureConfigActionBlock } from "./config-env-vars-oFV7TqIg.mjs"; import { r as isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "./recovery-policy-D5E-mA-L.mjs"; import { c as readConfigFileSnapshot, o as readBestEffortConfig } from "./io.runtime-FunTy0y1.mjs"; import "./config-B-SAEPyK.mjs"; import { t as mergeGatewayServiceEnv } from "./service-env-merge-BGtT4Ore.mjs"; import { U as isSystemdUserServiceAvailable, s as resolveSystemdServiceName } from "./systemd-service-files-B4hMJjkF.mjs"; import { a as readGatewayServiceLoadState, i as readGatewayServiceState, o as collectGatewayServiceStartRepairIssues, r as startGatewayService, t as describeGatewayServiceRestart } from "./service-BF2g9b8b.mjs"; import { t as createGatewayLifecycleMutationReporter } from "./service-mutation-BITjAaaK.mjs"; import { a as prepareGatewayRestartIntentLegacyProcess, c as GatewayRestartPreparationError, o as writeGatewayRestartIntentSync, s as writeGatewayServiceRestartIntentSync, t as clearGatewayRestartIntentSync } from "./restart-intent-BGfMIhce.mjs"; import "./systemd-DNRC_sMB.mjs"; import { t as createGatewayCredentialPlan } from "./credential-planner-DyCAIsET.mjs"; import { n as isGatewaySecretRefUnavailableError, t as GatewaySecretRefUnavailableError } from "./credentials-CNiOgt14.mjs"; import { r as isTerminalInteractive } from "./terminal-interactivity-DXUXAq5U.mjs"; import { n as formatPluginPackagingRuntimeOutputRecoveryHint, t as formatInvalidConfigRecoveryHint } from "./config-recovery-hints-BpoTGCnY.mjs"; import { t as renderConfigValidationIssueLines } from "./issue-location-CQRu3JlV.mjs"; import { r as checkTokenDrift } from "./service-audit-CoK78RVU.mjs"; import { t as resolveGatewayAuthToken } from "./auth-token-resolution-Cy9aKOVb.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 /** Best-effort validation before a service action mutates runtime state. */ async function getServiceActionPreflightFailure(action) { let snapshot; try { snapshot = await readConfigFileSnapshot({ observe: false, pluginValidation: action === "stop" ? "core-only" : void 0 }); if (snapshot.exists && !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: futureBlock.message, 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 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 blockInvalidServiceAction(serviceNoun, action, fail) { const preflight = await getServiceActionPreflightFailure(action); if (!preflight) return false; fail(!preflight.hints && (action === "start" || action === "restart") ? `${serviceNoun} aborted: config is invalid.\n${preflight.message}\n${formatInvalidConfigRecoveryHint()}` : `${serviceNoun} ${action} blocked: ${preflight.message}`, preflight.hints); return true; } 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; } if (await blockInvalidServiceAction(params.serviceNoun, "uninstall", fail)) 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, emitMessage, 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 }); emitMessage({ ok: true, result: "started", message: result.message, warnings: mergeWarnings(warnings, result.reportedWarnings), service: buildDaemonServiceSnapshot(params.service, result.loaded) }); }; const loaded = await resolveServiceLoadedOrFail({ serviceNoun: params.serviceNoun, service: params.service, fail }); if (loaded === null) return; if (await blockInvalidServiceAction(params.serviceNoun, "start", fail)) 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, warnings, emitMessage }); 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, emitMessage, 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; if (await blockInvalidServiceAction(params.serviceNoun, "stop", fail)) return; if (!loaded && !params.stopWhenNotLoaded) { try { const handled = await params.onNotLoaded?.({ json, stdout, fail }); if (handled) { emitMessage({ ok: true, result: handled.result, message: handled.message, warnings: handled.warnings, service: buildDaemonServiceSnapshot(params.service, false) }); return; } } catch (err) { fail(`${params.serviceNoun} stop failed: ${String(err)}`); return; } 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({ serviceNoun: params.serviceNoun, service: params.service, fail, inspectionFailureMessage: `${params.serviceNoun} stop verification failed because service status is unknown` }) : false; 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, emitMessage, 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; const { prepare: prepareGatewayRestartIntent, clear: clearPreparedRestartIntent } = createServiceRestartIntent({ serviceNoun: params.serviceNoun, service: params.service, intent: restartIntent }); const emitScheduledRestart = (restartStatus, serviceLoaded) => { return emitDaemonScheduledRestart({ emitMessage, 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; if (await blockInvalidServiceAction(params.serviceNoun, "restart", fail)) return false; 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({ 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 && !handledRecovery && 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: ${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 && !handledRecovery && 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: params.opts?.preserveDefinition, env: process.env, stdout, warn, onMutation: gatewayRestartAudit }); } catch (err) { clearPreparedRestartIntent(); throw err; } } 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), 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 = 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 };