UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

659 lines (658 loc) • 29.2 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs"; import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import { a as writeRuntimeJson, r as defaultRuntime, t as ExitError } from "./runtime-DlO3PTAd.mjs"; import { p as redactSecrets } from "./redact-DJ8j6ADa.mjs"; import { a as createGatewayEnvSecretRef } from "./ref-contract-D0kWy5jy.mjs"; import "./utils-DgZn2dLu.mjs"; import { r as resolveStateDir } from "./state-dir-BHJORNly.mjs"; import { b as resolveGatewayPort, s as isDefaultStateDir } from "./paths-DGIwWbpu.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./agent-scope-config-CDYpzA0r.mjs"; import "./session-key-C09ow4h0.mjs"; import { n as listAgentEntries } from "./agent-roster-BXD1uMJD.mjs"; import { t as ConfigMutationConflictError } from "./mutation-conflict-Be0wSyDG.mjs"; import { c as readConfigFileSnapshot } from "./io.runtime-BsUXSsD4.mjs"; import "./io-BYWYXJXl.mjs"; import { r as replaceConfigFile } from "./mutate-DJq1JylX.mjs"; import "./config-DLycWFvB.mjs"; import { i as withPluginLifecycleLease } from "./plugin-lifecycle-lease-DHQMWweY.mjs"; import { t as withOpenClawStateLease } from "./openclaw-state-lease-CdKflcqg.mjs"; import { n as resolveCanonicalConfiguredSecretInputWithFallback } from "./resolve-configured-secret-input-string-0l_u8CA_.mjs"; import { r as resolveLocalControlUiProbeLinks } from "./control-ui-links-BAEKzPie.mjs"; import { n as applyWizardMetadata, p as waitForGatewayReachable, t as DEFAULT_WORKSPACE, u as probeGatewayReachable } from "./onboard-helpers-CMSgvi-w.mjs"; import { a as ensureOnboardingAgentWorkspace, c as resolveOnboardingAgentTarget, l as resolveOnboardingSetupTarget } from "./onboard-agent-target-C1NZC0Wr.mjs"; import { t as enableDefaultOnboardingInternalHooks } from "./onboard-hooks-CWFxUH6y.mjs"; import { t as resolveGatewayStartupTiming } from "./gateway-startup-timing-D9NqKiRl.mjs"; import { r as logConfigUpdated } from "./logging-BBc-wU1i.mjs"; import "./daemon-runtime-DLyccFUz.mjs"; import { t as resolveGatewayAuthToken } from "./auth-token-resolution-B9VLhqWq.mjs"; import { t as createNonInteractiveLoggingPrompter } from "./non-interactive-prompter-C7yy4gSh.mjs"; import { n as applySkipBootstrapConfig, r as resolveOnboardingWorkspaceConflict, t as applyLocalSetupWorkspaceConfig } from "./onboard-config-BwDPn8wc.mjs"; import { u as withSetupMigrationTargetLock } from "./setup.migration-snapshot-Bw2NZurp.mjs"; import { t as rejectOnboardingOption } from "./onboard-options-CNC8ppU5.mjs"; import { t as applyNonInteractiveGatewayConfig } from "./gateway-config--8xln9bh.mjs"; import path from "node:path"; import { isDeepStrictEqual } from "node:util"; //#region src/commands/onboard-non-interactive/config-write.ts /** Commits a non-interactive onboard config update with pending plugin records handled first. */ async function commitNonInteractiveOnboardConfig(params) { const { writeWizardConfigFile } = await import("./setup.shared-FFZuR7xZ.mjs"); return (await writeWizardConfigFile(params.nextConfig, { mergeBase: params.baseConfig, allowConfigSizeDrop: params.reset === true, ...params.baseHash !== void 0 ? { baseHash: params.baseHash } : {} })).nextConfig; } //#endregion //#region src/commands/onboard-non-interactive/local/output.ts function logNonInteractiveOnboardingJson(params) { if (!params.opts.json) return; writeRuntimeJson(params.runtime, { ok: true, mode: params.mode, workspace: params.workspaceDir, authChoice: params.authChoice, gateway: params.gateway, installDaemon: Boolean(params.installDaemon), daemonInstall: params.daemonInstall, daemonRuntime: params.daemonRuntime, skipSkills: Boolean(params.skipSkills), skipHealth: Boolean(params.skipHealth) }); } function formatGatewayRuntimeSummary(diagnostics) { const service = diagnostics?.service; if (!service?.runtimeStatus) return; const parts = [service.runtimeStatus]; if (typeof service.pid === "number") parts.push(`pid ${service.pid}`); if (service.state) parts.push(`state ${service.state}`); if (typeof service.lastExitStatus === "number") parts.push(`last exit ${service.lastExitStatus}`); if (service.lastExitReason) parts.push(`reason ${service.lastExitReason}`); return parts.join(", "); } function classifyGatewayHealthFailure(params) { const detail = params.detail ?? ""; const connectionRefused = /\b(?:econnrefused|connection refused|connect refused)\b/i.test(detail); const lastGatewayError = params.diagnostics?.lastGatewayError ?? ""; const combined = `${detail}\n${lastGatewayError}`; if (/\b(?:unauthorized|forbidden|invalid token|invalid password|auth mismatch)\b/i.test(combined)) return "auth-mismatch"; if (/\b(?:runtime[- ]deps?|runtime dependencies|cannot find (?:module|package)|(?:err_)?module_not_found|sqlite-vec|loadextension)\b/i.test(combined)) return "module-missing"; if (params.diagnostics?.service?.loadState.status === "not-loaded" && connectionRefused) return "service-missing"; const runtimeStatus = params.diagnostics?.service?.runtimeStatus; if (runtimeStatus && runtimeStatus !== "running" && runtimeStatus !== "active" && connectionRefused) return "service-stopped"; if (lastGatewayError.trim()) return "startup-blocked"; if (connectionRefused) return "not-listening"; } function recoveryHintForGatewayHealthFailure(classification) { switch (classification) { case "auth-mismatch": case "module-missing": return `Fix: run \`${formatCliCommand("openclaw doctor --fix")}\`.`; case "service-missing": return `Fix: run \`${formatCliCommand("openclaw gateway install --force")}\`.`; case "service-stopped": return `Fix: run \`${formatCliCommand("openclaw gateway restart")}\`.`; case "startup-blocked": return `Fix: run \`${formatCliCommand("openclaw gateway status --deep")}\`.`; case "not-listening": return `Fix: start \`${formatCliCommand("openclaw gateway run")}\`, or run \`${formatCliCommand("openclaw gateway restart")}\` for a managed gateway.`; default: return; } } function logNonInteractiveOnboardingFailure(params) { const classification = classifyGatewayHealthFailure({ detail: params.detail, diagnostics: params.diagnostics }); const callerHints = params.hints?.filter(Boolean) ?? []; const recoveryHint = callerHints.some((hint) => hint.startsWith("Fix:")) ? void 0 : recoveryHintForGatewayHealthFailure(classification); const hints = [...recoveryHint ? [recoveryHint] : [], ...callerHints]; const output = redactSecrets({ message: params.message, detail: params.detail, hints, gateway: params.gateway, daemonInstall: params.daemonInstall, daemonRuntime: params.daemonRuntime, diagnostics: params.diagnostics }); const gatewayRuntime = formatGatewayRuntimeSummary(output.diagnostics); const service = output.diagnostics?.service; const serviceLoadText = service ? service.loadState.status === "loaded" ? service.loadedText : service.loadState.status.replace("-", " ") : void 0; if (params.opts.json) { writeRuntimeJson(params.runtime, { ok: false, mode: params.mode, phase: params.phase, message: output.message, classification, detail: output.detail, gateway: output.gateway, installDaemon: Boolean(params.installDaemon), daemonInstall: output.daemonInstall, daemonRuntime: output.daemonRuntime, diagnostics: output.diagnostics, hints: output.hints.length > 0 ? output.hints : void 0 }); return; } const lines = [ output.message, classification ? `Classification: ${classification}` : void 0, output.detail ? `Last probe: ${output.detail}` : void 0, service ? `Service: ${service.label} (${serviceLoadText})` : void 0, gatewayRuntime ? `Runtime: ${gatewayRuntime}` : void 0, output.diagnostics?.lastGatewayError ? `Last gateway error: ${output.diagnostics.lastGatewayError}` : void 0, output.diagnostics?.inspectError ? `Diagnostics warning: ${output.diagnostics.inspectError}` : void 0, output.hints.length > 0 ? output.hints.join("\n") : void 0 ].filter(Boolean).join("\n"); if (params.informational) params.runtime.log(lines); else params.runtime.error(lines); } //#endregion //#region src/commands/onboard-non-interactive/local/skills-config.ts function applyNonInteractiveSkillsConfig(params) { const { nextConfig, opts } = params; if (opts.skipSkills) return nextConfig; return { ...nextConfig, skills: { ...nextConfig.skills, install: { ...nextConfig.skills?.install, nodeManager: opts.nodeManager ?? nextConfig.skills?.install?.nodeManager ?? "npm" } } }; } //#endregion //#region src/commands/onboard-non-interactive/local/workspace.ts function resolveNonInteractiveWorkspaceDir(params) { const env = params.env ?? process.env; const implicitWorkspaceDir = isDefaultStateDir(env) ? params.defaultWorkspaceDir : path.join(resolveStateDir(env), "workspace"); const raw = (params.opts.workspace?.trim() || params.baseConfig.agents?.defaults?.workspace?.trim() || env.OPENCLAW_WORKSPACE_DIR?.trim() || implicitWorkspaceDir).trim(); return resolveUserPath(raw, env); } //#endregion //#region src/commands/onboard-non-interactive/local.ts async function collectGatewayHealthFailureDiagnostics() { const diagnostics = {}; try { const { readGatewayServiceState, resolveGatewayService } = await import("./service-C5exmZL9.mjs"); const service = resolveGatewayService(); const state = await readGatewayServiceState(service, { env: process.env }); const runtime = state.runtime; const loaded = state.loadState.status === "unknown" ? null : state.loadState.status === "loaded"; diagnostics.service = { label: service.label, loaded, loadState: state.loadState, loadedText: service.loadedText, runtimeStatus: runtime?.status, state: runtime?.state, pid: runtime?.pid, lastExitStatus: runtime?.lastExitStatus, lastExitReason: runtime?.lastExitReason }; } catch (err) { diagnostics.inspectError = `service diagnostics failed: ${String(err)}`; } try { const { readLastGatewayErrorLine } = await import("./diagnostics-BRkyS2cA.mjs"); diagnostics.lastGatewayError = await readLastGatewayErrorLine(process.env) ?? void 0; } catch (err) { diagnostics.inspectError = diagnostics.inspectError ? `${diagnostics.inspectError}; log diagnostics failed: ${String(err)}` : `log diagnostics failed: ${String(err)}`; } return diagnostics.service || diagnostics.lastGatewayError || diagnostics.inspectError ? diagnostics : void 0; } /** Resolves the auth material used by the post-setup gateway health probe. */ async function resolveGatewayHealthProbeAuth(nextConfig) { if (nextConfig.gateway?.auth?.mode === "password" || nextConfig.gateway?.auth?.mode === "trusted-proxy") { const resolved = await resolveCanonicalConfiguredSecretInputWithFallback({ config: nextConfig, env: process.env, value: nextConfig.gateway.auth.password, path: "gateway.auth.password", unresolvedReasonStyle: "detailed", readFallback: () => process.env.OPENCLAW_GATEWAY_PASSWORD }); return { password: resolved.value, unresolvedRefReason: resolved.unresolvedRefReason }; } const resolved = await resolveGatewayAuthToken({ cfg: nextConfig, env: process.env, unresolvedReasonStyle: "detailed" }); const probeAuth = {}; if (resolved.token) probeAuth.token = resolved.token; if (resolved.unresolvedRefReason) probeAuth.unresolvedRefReason = resolved.unresolvedRefReason; return probeAuth; } /** Runs local non-interactive setup from config mutation through health verification. */ async function runNonInteractiveLocalSetup(params) { const { opts, runtime, baseConfig, sourceConfigBeforeMigrations, baseHash } = params; const mode = "local"; const requestedWorkspaceDir = resolveNonInteractiveWorkspaceDir({ opts, baseConfig, defaultWorkspaceDir: DEFAULT_WORKSPACE }); const hasAuthoredRoster = listAgentEntries(sourceConfigBeforeMigrations).length > 0; if (opts.team && hasAuthoredRoster) { rejectOnboardingOption(opts, runtime, "An agent roster already exists. Use `openclaw agents team create` to add a team."); return; } const firstAgentName = opts.agentName ?? (opts.team ? "coordinator" : "main"); const workspaceConflict = resolveOnboardingWorkspaceConflict(sourceConfigBeforeMigrations, requestedWorkspaceDir); const workspaceDir = workspaceConflict?.currentWorkspaceDir ?? requestedWorkspaceDir; if (workspaceConflict) runtime.error([ "Warning: existing agents keep their current workspace during non-interactive onboarding.", `Current workspace: ${workspaceConflict.currentWorkspaceDir}`, `Requested workspace: ${workspaceConflict.requestedWorkspaceDir}`, `Run \`${formatCliCommand("openclaw onboard --classic")}\` to confirm moving the existing agent fleet.` ].join("\n")); let nextConfig = applyLocalSetupWorkspaceConfig(baseConfig, requestedWorkspaceDir, { allowWorkspaceChange: !hasAuthoredRoster && !workspaceConflict }); if (opts.skipBootstrap) nextConfig = applySkipBootstrapConfig(nextConfig); const authTarget = resolveOnboardingSetupTarget(nextConfig, !hasAuthoredRoster && (opts.agentName || opts.team) ? { name: firstAgentName, workspaceDir: opts.team ? path.join(workspaceDir, normalizeAgentId(firstAgentName)) : workspaceDir } : void 0); const inferredAuthChoice = opts.authChoice ? void 0 : (await import("./auth-choice-inference-FbnIHY-W.mjs")).inferAuthChoiceFromFlags(opts, { config: nextConfig, workspaceDir: authTarget.workspaceDir, env: process.env }); if (!opts.authChoice && inferredAuthChoice && inferredAuthChoice.matches.length > 1) { const message = [ "Multiple API key flags were provided for non-interactive setup.", "Use a single provider flag or pass --auth-choice explicitly.", `Flags: ${inferredAuthChoice.matches.map((match) => match.label).join(", ")}` ].join("\n"); rejectOnboardingOption(opts, runtime, message); return; } const authChoice = opts.authChoice ?? inferredAuthChoice?.choice ?? "skip"; const gatewayResult = await applyNonInteractiveGatewayConfig({ nextConfig, opts, runtime, defaultPort: resolveGatewayPort(baseConfig) }); if (!gatewayResult) return; nextConfig = gatewayResult.nextConfig; nextConfig = applyNonInteractiveSkillsConfig({ nextConfig, opts }); if (authChoice !== "skip") { const { applyNonInteractiveAuthChoice } = await import("./auth-choice-CN9c7i_B.mjs"); const nextConfigAfterAuth = await applyNonInteractiveAuthChoice({ nextConfig, authChoice, opts, runtime, baseConfig, target: authTarget }); if (!nextConfigAfterAuth) return; nextConfig = nextConfigAfterAuth; } if (!opts.skipHooks) nextConfig = enableDefaultOnboardingInternalHooks(nextConfig); const { ensureOnboardingAgent } = await import("./onboard-agent-EB6075nc.mjs"); const created = await ensureOnboardingAgent({ config: nextConfig, workspace: workspaceDir, baseConfig, firstAgent: { name: firstAgentName, ...opts.team ? { team: true } : {} }, expectedConfigHash: baseHash ?? null }); for (const warning of created.sessionMigrationWarnings ?? []) runtime.log(`Warning: ${warning}`); nextConfig = applyLocalSetupWorkspaceConfig(created.config, requestedWorkspaceDir); const effectiveBaseHash = created.configHash ?? baseHash; if (opts.skipBootstrap) nextConfig = applySkipBootstrapConfig(nextConfig); const finalTarget = resolveOnboardingAgentTarget(nextConfig, created.agentId); await ensureOnboardingAgentWorkspace(finalTarget, runtime, { skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap), skipOptionalBootstrapFiles: nextConfig.agents?.defaults?.skipOptionalBootstrapFiles }); nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode }); nextConfig = await commitNonInteractiveOnboardConfig({ nextConfig, baseConfig: created.configBase, baseHash: effectiveBaseHash, reset: opts.reset }); logConfigUpdated(runtime); const daemonRuntimeRaw = opts.daemonRuntime ?? "node"; let daemonInstallStatus; let gatewayNotRunning = false; if (opts.installDaemon) { const { installGatewayDaemonNonInteractive } = await import("./daemon-install-t6T6Fyc2.mjs"); const daemonInstall = await installGatewayDaemonNonInteractive({ nextConfig, opts, runtime, port: gatewayResult.port }); daemonInstallStatus = { requested: true, installed: daemonInstall.installed, ...!daemonInstall.installed ? { skippedReason: daemonInstall.skippedReason } : {} }; if (!daemonInstall.installed) { logNonInteractiveOnboardingFailure({ opts, runtime, mode, phase: "daemon-install", message: daemonInstall.skippedReason === "systemd-user-unavailable" ? "Gateway service install is unavailable because systemd user services are not reachable in this Linux session." : "Gateway service install did not complete successfully.", installDaemon: true, daemonInstall: daemonInstallStatus, daemonRuntime: daemonRuntimeRaw, hints: daemonInstall.skippedReason === "systemd-user-unavailable" ? ["Fix: rerun without `--install-daemon` for one-shot setup, or enable a working user-systemd session and retry.", "If your auth profile uses env-backed refs, keep those env vars set in the shell that runs `openclaw gateway run` or `openclaw agent --local`."] : [`Run \`${formatCliCommand("openclaw gateway status --deep")}\` for more detail.`] }); runtime.exit(1); return; } } if (!opts.skipHealth) { const { healthCommandNonExiting } = await import("./health-CRr_R5ic.mjs"); const links = resolveLocalControlUiProbeLinks({ bind: gatewayResult.bind, port: gatewayResult.port, customBindHost: nextConfig.gateway?.customBindHost, basePath: void 0, tlsEnabled: nextConfig.gateway?.tls?.enabled === true }); const healthFailureContext = { opts, runtime, mode, phase: "gateway-health", gateway: { wsUrl: links.wsUrl, httpUrl: links.httpUrl }, installDaemon: Boolean(opts.installDaemon), daemonInstall: daemonInstallStatus, daemonRuntime: opts.installDaemon ? daemonRuntimeRaw : void 0 }; const startupTiming = opts.installDaemon ? resolveGatewayStartupTiming() : { deadlineMs: 15e3 }; const probeAuth = await resolveGatewayHealthProbeAuth(nextConfig); const probeParams = { url: links.wsUrl, token: probeAuth.token, password: probeAuth.password }; const probe = opts.installDaemon === false ? await probeGatewayReachable(probeParams) : await waitForGatewayReachable({ ...probeParams, ...startupTiming }); if (!probe.ok) { const detail = [probe.detail, probeAuth.unresolvedRefReason].filter(Boolean).join("\n") || void 0; const diagnostics = opts.installDaemon ? await collectGatewayHealthFailureDiagnostics() : void 0; const explicitlySkippedAbsentGateway = opts.installDaemon === false && classifyGatewayHealthFailure({ detail, diagnostics }) === "not-listening"; if (explicitlySkippedAbsentGateway && !opts.json) runtime.log("Setup complete; gateway was not installed or started because daemon installation was explicitly skipped."); if (!explicitlySkippedAbsentGateway || !opts.json) logNonInteractiveOnboardingFailure({ ...healthFailureContext, message: `Gateway did not become reachable at ${links.wsUrl}.`, detail, diagnostics, hints: !opts.installDaemon ? [ "Non-interactive local setup only waits for an already-running gateway unless you pass `--install-daemon` to `openclaw onboard`.", `Fix: start \`${formatCliCommand("openclaw gateway run")}\`, re-run \`${formatCliCommand("openclaw onboard --install-daemon")}\`, or use \`${formatCliCommand("openclaw onboard --skip-health")}\`.`, process.platform === "win32" ? "Native Windows managed gateway install tries Scheduled Tasks first and falls back to a per-user Startup-folder login item when task creation is denied." : void 0 ].filter((value) => Boolean(value)) : [`Run \`${formatCliCommand("openclaw gateway status --deep")}\` for more detail.`], informational: explicitlySkippedAbsentGateway }); if (!explicitlySkippedAbsentGateway) { runtime.exit(1); return; } gatewayNotRunning = true; } else { const capturedHealthLines = []; const healthRuntime = opts.json ? { ...runtime, log: (...args) => { capturedHealthLines.push(args.map(String).join(" ")); } } : runtime; try { await healthCommandNonExiting({ json: false, timeoutMs: opts.installDaemon && process.platform === "win32" ? 9e4 : 1e4, config: nextConfig, localPortOverride: gatewayResult.port, token: probeAuth.token, password: probeAuth.password }, healthRuntime); } catch (err) { const detail = err instanceof ExitError ? capturedHealthLines.join("\n") || void 0 : formatErrorMessage(err); logNonInteractiveOnboardingFailure({ ...healthFailureContext, message: `Gateway is reachable at ${links.wsUrl}, but the health check failed.`, detail, hints: [`Run \`${formatCliCommand("openclaw health")}\` for full diagnostics.`] }); runtime.exit(1); return; } } } logNonInteractiveOnboardingJson({ opts, runtime, mode, workspaceDir: finalTarget.workspaceDir, authChoice, gateway: { port: gatewayResult.port, bind: gatewayResult.bind, authMode: gatewayResult.authMode, tailscaleMode: gatewayResult.tailscaleMode, ...gatewayNotRunning ? { reachable: false } : {} }, installDaemon: Boolean(opts.installDaemon), daemonInstall: daemonInstallStatus, daemonRuntime: opts.installDaemon ? daemonRuntimeRaw : void 0, skipSkills: Boolean(opts.skipSkills), skipHealth: Boolean(opts.skipHealth) }); if (!opts.json) runtime.log(`Tip: run \`${formatCliCommand("openclaw configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.openclaw.ai/tools/web`); } //#endregion //#region src/commands/onboard-non-interactive/remote.ts /** * Remote non-interactive onboarding orchestration. * * It writes gateway.remote config without local gateway setup, preserving the * same config commit path as local onboarding. */ /** Runs non-interactive setup for clients that connect to an existing remote gateway. */ async function runNonInteractiveRemoteSetup(params) { const { opts, runtime, baseConfig, baseHash } = params; const mode = "remote"; const remoteUrl = normalizeOptionalString(opts.remoteUrl); if (!remoteUrl) { runtime.error(`Missing --remote-url for remote mode. Example: ${formatCliCommand("openclaw onboard --non-interactive --mode remote --remote-url ws://127.0.0.1:3000")}.`); runtime.exit(1); return; } const remoteToken = normalizeOptionalString(opts.remoteToken); const remotePassword = normalizeOptionalString(opts.remotePassword); for (const [flag, input, normalized] of [[ "--remote-token", opts.remoteToken, remoteToken ], [ "--remote-password", opts.remotePassword, remotePassword ]]) if (input !== void 0 && !normalized) { runtime.error(`Invalid ${flag}: value cannot be empty.`); runtime.exit(1); return; } if (remoteToken && remotePassword) { runtime.error("Use either --remote-token or --remote-password, not both."); runtime.exit(1); return; } const existingRemote = baseConfig.gateway?.remote; const preservedRemote = normalizeOptionalString(existingRemote?.url) !== remoteUrl ? {} : { ...existingRemote }; if (remoteToken) delete preservedRemote.password; if (remotePassword) delete preservedRemote.token; let nextConfig = { ...baseConfig, gateway: { ...baseConfig.gateway, mode: "remote", remote: { ...preservedRemote, url: remoteUrl, ...remoteToken ? { token: opts.secretInputMode === "ref" ? createGatewayEnvSecretRef(baseConfig, "OPENCLAW_GATEWAY_TOKEN") : remoteToken } : {}, ...remotePassword ? { password: opts.secretInputMode === "ref" ? createGatewayEnvSecretRef(baseConfig, "OPENCLAW_GATEWAY_PASSWORD") : remotePassword } : {} } } }; if (opts.skipBootstrap) nextConfig = applySkipBootstrapConfig(nextConfig); nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode }); await commitNonInteractiveOnboardConfig({ nextConfig, baseConfig, baseHash, reset: opts.reset }); logConfigUpdated(runtime); const payload = { mode, remoteUrl, auth: nextConfig.gateway?.remote?.token ? "token" : nextConfig.gateway?.remote?.password ? ["pass", "word"].join("") : "none" }; if (opts.json) writeRuntimeJson(runtime, payload); else { runtime.log(`Remote gateway: ${remoteUrl}`); runtime.log(`Auth: ${payload.auth}`); runtime.log(`Tip: run \`${formatCliCommand("openclaw configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.openclaw.ai/tools/web`); } } //#endregion //#region src/commands/onboard-non-interactive.ts function isMigrationImport(opts) { return Boolean(opts.importFrom || opts.importSource || opts.importSecrets || opts.flow === "import"); } /** Runs a setup migration import with non-interactive prompt failures. */ async function runNonInteractiveMigrationImport(params) { const providerId = params.opts.importFrom?.trim(); if (!providerId) { rejectOnboardingOption(params.opts, params.runtime, `--import-from is required for non-interactive migration import. Run ${formatCliCommand("openclaw migrate list")} to choose a provider.`); return; } const { detectSetupMigrationSources, runSetupMigrationImport } = await import("./setup.migration-import-DRKheNDp.mjs"); const discovery = await detectSetupMigrationSources({ config: params.baseConfig, runtime: params.runtime }); const outcome = await runSetupMigrationImport({ opts: { ...params.opts, importFrom: providerId, nonInteractive: true }, baseConfig: params.baseConfig, ...discovery, prompter: createNonInteractiveLoggingPrompter(params.runtime, (message) => `Non-interactive migration import needs explicit flags before prompting: ${message}`), runtime: params.runtime, async readConfigFile() { const snapshot = await readConfigFileSnapshot(); if (!snapshot.valid) throw new Error("Migration target config became invalid. Run `openclaw doctor --fix` to apply supported repairs."); return snapshot.exists ? snapshot.sourceConfig ?? snapshot.config : {}; }, async commitConfigFile(config, expectedConfig) { const latest = await readConfigFileSnapshot(); if (!latest.valid) throw new Error("Migration target config became invalid. Run `openclaw doctor --fix` to apply supported repairs."); const latestConfig = latest.exists ? latest.sourceConfig ?? latest.config : {}; if (!isDeepStrictEqual(latestConfig, expectedConfig)) throw new ConfigMutationConflictError("config changed during migration promotion"); const committed = await replaceConfigFile({ sourceConfig: config, snapshot: latest, ...latest.hash !== void 0 ? { baseHash: latest.hash } : {}, writeOptions: { allowConfigSizeDrop: true } }); logConfigUpdated(params.runtime); return committed.nextConfig; } }); if (outcome.kind === "back") throw new Error("Non-interactive migration import cannot navigate back."); await outcome.acknowledgePromotion?.(); } async function runNonInteractiveSetupExclusive(opts, runtime) { const snapshot = await readConfigFileSnapshot(); if (snapshot.exists && !snapshot.valid) { rejectOnboardingOption(opts, runtime, `Config invalid. Run \`${formatCliCommand("openclaw doctor --fix")}\` to apply supported repairs, then re-run setup.`); return; } const baseConfig = snapshot.valid && snapshot.exists ? snapshot.sourceConfig ?? snapshot.config : {}; const mode = opts.mode ?? "local"; if (mode !== "local" && mode !== "remote") { rejectOnboardingOption(opts, runtime, `Invalid --mode "${String(mode)}". Use "local" or "remote", or run ${formatCliCommand("openclaw onboard")} for interactive setup.`); return; } if (isMigrationImport(opts)) { await runNonInteractiveMigrationImport({ opts, runtime, baseConfig }); return; } if (mode === "remote") { await runNonInteractiveRemoteSetup({ opts, runtime, baseConfig, baseHash: snapshot.hash }); return; } await runNonInteractiveLocalSetup({ opts, runtime, baseConfig, sourceConfigBeforeMigrations: snapshot.sourceConfigBeforeMigrations ?? {}, baseHash: snapshot.hash }); } /** Runs non-interactive onboarding in local, remote, or migration-import mode. */ async function runNonInteractiveSetup(opts, runtime = defaultRuntime) { await withSetupMigrationTargetLock(resolveStateDir(), async () => { if (isMigrationImport(opts)) { await runNonInteractiveSetupExclusive(opts, runtime); return; } await withOpenClawStateLease({ scope: "core:onboarding", key: "global", database: { scope: "shared" }, leaseMs: 3e5, waitMs: 6e5, leaseLabel: "non-interactive onboarding lease", operationLabel: "onboarding.non-interactive.lease" }, () => withPluginLifecycleLease({}, () => runNonInteractiveSetupExclusive(opts, runtime))); }); } //#endregion export { runNonInteractiveSetup };