UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

748 lines (747 loc) 33.7 kB
import { t as restoreTerminalState } from "./restore-DuVRJEfl.js"; import { t as ExitError } from "./runtime-CF2WjnNZ.js"; import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js"; import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js"; import "./utils-P__uGsPB.js"; import { n as resolveCliName } from "./cli-name-CVj-3DWf.js"; import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { v as resolveDefaultAgentDir } from "./agent-scope-config-DcbEhP0R.js"; import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js"; import { i as formatExternalSupervisorActionRequired, o as isGatewayExternallySupervised } from "./gateway-supervision-D7p37rG2.js"; import { n as gatewayInstallErrorHint, t as buildGatewayInstallPlan } from "./daemon-install-helpers-w30vR2Jt.js"; import { n as GATEWAY_DAEMON_RUNTIME_OPTIONS, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-CdTwKGNX.js"; import { t as resolveGatewayInstallToken } from "./gateway-install-token-09efDYo7.js"; import { t as isContainerEnvironment } from "./container-environment-CNsJSTpY.js"; import { m as isSystemdUserServiceAvailable } from "./systemd-exec-C9fneC4I.js"; import { n as formatGatewayServiceStartRepairIssues, o as resolveGatewayService, s as startGatewayService, t as describeGatewayServiceRestart } from "./service-Cc6NX6Jm.js"; import "./systemd-C1W5NZTY.js"; import { S as resolveTuiShutdownHardExitMs, T as scheduleProcessExitAfterTuiReturn, n as cancelProcessExitAfterTuiReturn, w as runTui } from "./tui-C72O-TQi.js"; import { t as resolveGatewayStartupTiming } from "./gateway-startup-timing-D9NqKiRl.js"; import { r as resolveLocalControlUiProbeLinks, t as resolveAdvertisedControlUiLinks } from "./control-ui-links-BY4F5DYV.js"; import { t as formatWindowsGatewayFirewallGuidance } from "./windows-gateway-firewall-diagnostics-CJp45O42.js"; import { t as PreparedModelCatalogConfigReplacedError } from "./prepared-model-catalog.errors-DftAtHN9.js"; import { n as DEFAULT_BOOTSTRAP_FILENAME } from "./workspace-ConDEamr.js"; import { t as describeCodexNativeWebSearch } from "./codex-native-web-search.shared-CvCOr9VP.js"; import { i as hasAuthProfileForProvider } from "./model-config.helpers-zn_oC6Fp.js"; import { r as listConfiguredWebSearchProviders } from "./runtime-CPS9j3kB.js"; import { n as t } from "./i18n-hynzGFbD.js"; import { p as waitForGatewayReachable, u as probeGatewayReachable } from "./onboard-helpers-BDY-6Z-t.js"; import { a as formatCompletionReloadCommand, d as resolveCompletionProfilePath, i as findCompletionProfileWriteError, o as installCompletion, u as resolveCompletionProfileHint } from "./completion-runtime-CjrvYENP.js"; import { r as ensureCompletionCacheExists, t as checkShellCompletionStatus } from "./doctor-completion-CtrQMyqI.js"; import { i as formatHealthCheckFailure } from "./health-format-BptmWUeD.js"; import { a as healthCommandNonExiting } from "./health-CmrSgBb8.js"; import { i as waitForControlUiDocument, r as resolveControlUiHandoffTarget } from "./control-ui-handoff-CipgXwfB.js"; import { t as resolveSetupSecretInputString } from "./setup.secret-input-Dtx44qLw.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/wizard/setup.completion.ts async function setupWizardShellCompletion(params) { const deps = { resolveCliName, checkShellCompletionStatus, ensureCompletionCacheExists, installCompletion, ...params.deps }; const cliName = deps.resolveCliName(); const completionStatus = await deps.checkShellCompletionStatus(cliName); const installCompletionForSetup = async () => { try { await deps.installCompletion(completionStatus.shell, true, cliName); return true; } catch (error) { const writeError = findCompletionProfileWriteError(error); if (!writeError) throw error; await params.prompter.note(t("wizard.completion.profileNotWritable", { profile: writeError.path ?? resolveCompletionProfilePath(completionStatus.shell), shell: completionStatus.shell, command: formatCompletionReloadCommand(completionStatus.shell, completionStatus.cachePath) }), t("wizard.completion.title")); return false; } }; const generationOptions = { generationMode: "full" }; const ensureCompletionCache = async () => { const cacheGenerated = await deps.ensureCompletionCacheExists(cliName, generationOptions); if (!cacheGenerated) await params.prompter.note(t("wizard.completion.cacheFailed", { command: `${cliName} completion --write-state --install` }), t("wizard.completion.title")); return cacheGenerated; }; if (completionStatus.usesSlowPattern) { if (await ensureCompletionCache()) await installCompletionForSetup(); return; } if (completionStatus.profileInstalled && !completionStatus.cacheExists) { await ensureCompletionCache(); return; } if (!completionStatus.profileInstalled) { if (!(params.flow === "quickstart" ? true : await params.prompter.confirm({ message: t("wizard.completion.enable", { shell: completionStatus.shell, cli: cliName }), initialValue: true }))) return; if (!await ensureCompletionCache()) return; if (!await installCompletionForSetup()) return; const shell = completionStatus.shell; const command = formatCompletionReloadCommand(shell, resolveCompletionProfileHint(shell)); const reloadHint = shell === "powershell" ? t("wizard.completion.reloadPowerShell", { command }) : t("wizard.completion.reloadShell", { profile: command.slice(7) }); await params.prompter.note(t("wizard.completion.installed", { reloadHint }), t("wizard.completion.title")); } } //#endregion //#region src/wizard/setup.finalize.ts const HATCH_TUI_TIMEOUT_MS = 3e5; function buildSessionGatewayAuthOverride(params) { if (params.settings.authMode === "token" && params.settings.gatewayToken) return { ...params.nextConfig.gateway?.auth, mode: "token", token: params.settings.gatewayToken }; if (params.settings.authMode === "password" && params.resolvedGatewayPassword) return { ...params.nextConfig.gateway?.auth, mode: "password", password: params.resolvedGatewayPassword }; return params.nextConfig.gateway?.auth; } async function startSessionGatewayForOnboarding(params) { const progress = params.prompter.progress(t("wizard.finalize.sessionGatewayStarting")); try { const { startGatewayServer } = await import("./server-Dh8qT1MR.js"); const server = await startGatewayServer(params.settings.port, { bind: params.settings.bind, ...params.settings.bind === "custom" && params.settings.customBindHost ? { host: params.settings.customBindHost } : {}, auth: buildSessionGatewayAuthOverride({ nextConfig: params.nextConfig, settings: params.settings, resolvedGatewayPassword: params.resolvedGatewayPassword }), tailscale: params.nextConfig.gateway?.tailscale }); progress.stop(t("wizard.finalize.sessionGatewayStarted")); return server; } catch (error) { progress.stop(t("wizard.finalize.sessionGatewayStartFailed")); await params.prompter.note([ t("wizard.finalize.sessionGatewayStartFailed"), formatErrorMessage(error), t("wizard.finalize.startGatewayNow", { command: formatCliCommand("openclaw gateway run") }) ].join("\n"), "Gateway"); return; } } async function closeSessionGatewayForOnboarding(params) { await params.sessionGateway.close({ reason: params.reason }).catch((error) => { params.runtime.error(formatErrorMessage(error)); }); } function getLocalizedGatewayDaemonRuntimeOptions() { return GATEWAY_DAEMON_RUNTIME_OPTIONS.map((option) => ({ hint: t(option.value === "node" ? "wizard.finalize.daemonRuntimeNodeHint" : "wizard.finalize.daemonRuntimeBunHint"), label: t(option.value === "node" ? "wizard.finalize.daemonRuntimeNode" : "wizard.finalize.daemonRuntimeBun"), value: option.value })); } const loadSearchSetupModule = createLazyRuntimeModule(() => import("./search-setup-3CsvMo3b.js")); function buildGatewayRecoveryProjection(params) { const { gateway } = params; const notDetected = t("wizard.finalize.gatewayNotDetected"); if (params.reachable && gateway.status !== "failed") return { detail: t("wizard.finalize.gatewayReachable"), summary: t("wizard.guided.complete") }; if (gateway.status === "ready") { const service = params.serviceLabel ?? t("wizard.finalize.gatewayService"); const detail = t("wizard.finalize.managedGatewayUnreachable", { service, statusCommand: formatCliCommand("openclaw gateway status --deep"), recoveryCommand: formatCliCommand("openclaw gateway restart") }); return { detail, summary: `${notDetected} ${detail.replaceAll("\n", " ")}` }; } if (gateway.status === "failed") { const service = params.serviceLabel ?? t("wizard.finalize.gatewayService"); const detail = t("wizard.finalize.managedGatewaySetupFailed", { service, error: gateway.error, statusCommand: formatCliCommand("openclaw gateway status --deep"), recoveryCommand: formatCliCommand("openclaw gateway install --force") }); return { detail, summary: `${params.reachable ? "" : `${notDetected} `}${detail.replaceAll("\n", " ")}` }; } const startGuidance = gateway.reason === "external" ? formatExternalSupervisorActionRequired("start the gateway") : t("wizard.finalize.startGatewayNow", { command: formatCliCommand("openclaw gateway run") }); const summary = [notDetected, startGuidance].join(" "); if (gateway.reason === "external") return { detail: [notDetected, startGuidance].join("\n"), summary }; return { detail: [ notDetected, t("wizard.finalize.noBackgroundGatewayExpected"), startGuidance, t("wizard.finalize.rerunInstallDaemon", { command: formatCliCommand("openclaw onboard --install-daemon") }), t("wizard.finalize.skipHealthNextTime", { command: formatCliCommand("openclaw onboard --skip-health") }) ].join("\n"), summary }; } /** * Ensure the gateway service matches the onboarding decision: prompt/decide * whether to install the daemon, then install/restart/reinstall it. Shared by * the classic wizard finalize and the bootstrap onboarding flow. */ async function ensureGatewayServiceForOnboarding(params) { const { flow, opts, nextConfig, settings, prompter, runtime } = params; const withWizardProgress = async (label, optionsLocal, work) => { const progress = prompter.progress(label); try { return await work(progress); } finally { progress.stop(typeof optionsLocal.doneMessage === "function" ? optionsLocal.doneMessage() : optionsLocal.doneMessage); } }; if (isGatewayExternallySupervised()) { await prompter.note(formatExternalSupervisorActionRequired("manage the gateway service"), "Gateway"); return { gateway: { status: "skipped", reason: "external" }, containerWithoutUserSystemd: false }; } const systemdAvailable = process.platform === "linux" ? await isSystemdUserServiceAvailable() : true; const linuxWithoutUserSystemd = process.platform === "linux" && !systemdAvailable; const containerWithoutUserSystemd = linuxWithoutUserSystemd && isContainerEnvironment(); if (linuxWithoutUserSystemd) await prompter.note(t(containerWithoutUserSystemd ? "wizard.finalize.containerSystemdUnavailable" : "wizard.finalize.systemdUnavailable"), containerWithoutUserSystemd ? t("wizard.finalize.containerRuntimeTitle") : "Systemd"); if (process.platform === "linux" && systemdAvailable && opts.installDaemon !== false) { const { ensureSystemdUserLingerInteractive } = await import("./systemd-linger-M4fD_OeM.js"); await ensureSystemdUserLingerInteractive({ runtime, prompter: { confirm: prompter.confirm, note: prompter.note }, reason: t("wizard.finalize.systemdLingerReason"), requireConfirm: false }); } const explicitInstallDaemon = typeof opts.installDaemon === "boolean" ? opts.installDaemon : void 0; let installDaemon; if (explicitInstallDaemon !== void 0) installDaemon = explicitInstallDaemon; else if (linuxWithoutUserSystemd) installDaemon = false; else if (flow === "quickstart") installDaemon = true; else installDaemon = await prompter.confirm({ message: t("wizard.finalize.installGateway"), initialValue: true }); if (linuxWithoutUserSystemd && installDaemon) { await prompter.note(t("wizard.finalize.systemdInstallSkipped"), t("wizard.finalize.gatewayService")); installDaemon = false; } if (!installDaemon) return { gateway: { status: "skipped", reason: linuxWithoutUserSystemd ? "systemd-unavailable" : "explicit" }, containerWithoutUserSystemd }; let gateway = { status: "ready", action: "reused" }; if (installDaemon) { const daemonRuntime = flow === "quickstart" ? DEFAULT_GATEWAY_DAEMON_RUNTIME : await prompter.select({ message: t("wizard.finalize.daemonRuntime"), options: getLocalizedGatewayDaemonRuntimeOptions(), initialValue: opts.daemonRuntime ?? "node" }); if (flow === "quickstart") await prompter.note(t("wizard.finalize.quickstartNodeRuntime"), t("wizard.finalize.daemonRuntime")); const service = resolveGatewayService(); if (params.loadedAction === "resume") try { const started = await startGatewayService(service, { env: process.env, stdout: process.stdout }, settings.port); if (started.outcome === "already-running" || started.outcome === "started") return { gateway: { status: "ready", action: started.outcome === "already-running" ? "reused" : "started" }, containerWithoutUserSystemd }; if (started.outcome === "repair-required") return { gateway: { status: "failed", error: formatGatewayServiceStartRepairIssues(started.issues) }, containerWithoutUserSystemd }; } catch (error) { return { gateway: { status: "failed", error: formatErrorMessage(error) }, containerWithoutUserSystemd }; } const loaded = await service.isLoaded({ env: process.env }); let shouldInstall = !loaded; if (loaded) { const action = (params.loadedAction === "restart" ? params.loadedAction : void 0) ?? await prompter.select({ message: t("wizard.finalize.alreadyInstalled"), options: [ { value: "restart", label: t("wizard.finalize.restart") }, { value: "reinstall", label: t("wizard.finalize.reinstall") }, { value: "skip", label: t("common.skip") } ] }); if (action === "restart") { let restartDoneMessage = t("wizard.finalize.gatewayServiceRestarted"); await withWizardProgress(t("wizard.finalize.gatewayService"), { doneMessage: () => restartDoneMessage }, async (progress) => { progress.update(t("wizard.finalize.gatewayServiceRestarting")); const restartResult = await service.restart({ env: process.env, stdout: process.stdout }); const restartStatus = describeGatewayServiceRestart("Gateway", restartResult); restartDoneMessage = restartStatus.scheduled ? t("wizard.finalize.gatewayServiceRestartScheduled") : t("wizard.finalize.gatewayServiceRestarted"); gateway = { status: "ready", action: restartStatus.scheduled ? "restart-scheduled" : "restarted" }; }); } else if (action === "reinstall") shouldInstall = true; } if (shouldInstall) { const progress = prompter.progress(t("wizard.finalize.gatewayService")); let installError = null; const installWarnings = []; const flushInstallWarnings = async () => { let warning; while ((warning = installWarnings.shift()) !== void 0) await prompter.note(warning.message, warning.title); }; try { progress.update(t("wizard.finalize.gatewayServicePreparing")); const tokenResolution = await resolveGatewayInstallToken({ config: nextConfig, env: process.env }); for (const warning of tokenResolution.warnings) await prompter.note(warning, "Gateway service"); if (tokenResolution.unavailableReason) installError = [ t("wizard.finalize.gatewayInstallBlocked"), tokenResolution.unavailableReason, t("wizard.finalize.gatewayInstallFixAuth") ].join(" "); else { const existingCommand = await service.readCommand(process.env).catch(() => null); const { programArguments, workingDirectory, environment, environmentValueSources } = await buildGatewayInstallPlan({ env: process.env, port: settings.port, runtime: daemonRuntime, existingCommand, warn: (message, title) => { installWarnings.push({ message, title }); }, config: nextConfig }); await flushInstallWarnings(); progress.update(t("wizard.finalize.gatewayServiceInstalling")); await service.install({ env: process.env, stdout: process.stdout, programArguments, workingDirectory, environment, environmentValueSources }); gateway = { status: "ready", action: "installed" }; } } catch (err) { await flushInstallWarnings(); installError = formatErrorMessage(err); } finally { progress.stop(installError ? t("wizard.finalize.gatewayServiceInstallFailed") : t("wizard.finalize.gatewayServiceInstalled")); } if (installError) { await prompter.note(t("wizard.finalize.gatewayServiceInstallFailedWithError", { error: installError }), "Gateway"); await prompter.note(gatewayInstallErrorHint(), "Gateway"); gateway = { status: "failed", error: installError }; } } } return { gateway, containerWithoutUserSystemd }; } async function finalizeSetupWizard(options) { const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options; let gatewayProbe = { ok: true }; let gatewayHealthCheckFailed = false; let resolvedGatewayPassword = ""; let sessionGateway; const { gateway, containerWithoutUserSystemd } = await ensureGatewayServiceForOnboarding({ flow, opts, nextConfig, settings, prompter, runtime }); if (settings.authMode === "password") try { resolvedGatewayPassword = await resolveSetupSecretInputString({ config: nextConfig, value: nextConfig.gateway?.auth?.password, path: "gateway.auth.password", env: process.env }) ?? ""; } catch (error) { await prompter.note([t("wizard.finalize.secretRefAuthFailed", { field: "gateway.auth.password" }), formatErrorMessage(error)].join("\n"), t("wizard.gateway.auth")); } if (containerWithoutUserSystemd && !opts.skipUi) sessionGateway = await startSessionGatewayForOnboarding({ nextConfig, settings, resolvedGatewayPassword, prompter }); try { if (!opts.skipHealth) { const probeLinks = resolveLocalControlUiProbeLinks({ bind: nextConfig.gateway?.bind ?? "loopback", port: settings.port, customBindHost: nextConfig.gateway?.customBindHost, basePath: void 0, tlsEnabled: nextConfig.gateway?.tls?.enabled === true }); const probeOptions = { url: probeLinks.wsUrl, token: settings.authMode === "token" ? settings.gatewayToken : void 0, password: settings.authMode === "password" ? resolvedGatewayPassword : void 0 }; gatewayProbe = gateway.status === "failed" ? await probeGatewayReachable(probeOptions) : await waitForGatewayReachable({ ...probeOptions, ...gateway.status === "ready" && gateway.action !== "reused" ? resolveGatewayStartupTiming() : { deadlineMs: 15e3 } }); if (gatewayProbe.ok) try { const healthConfig = settings.authMode === "token" && settings.gatewayToken ? { ...nextConfig, gateway: { ...nextConfig.gateway, auth: { ...nextConfig.gateway?.auth, mode: "token", token: settings.gatewayToken } } } : nextConfig; await healthCommandNonExiting({ json: false, timeoutMs: 1e4, config: healthConfig, token: settings.authMode === "token" ? settings.gatewayToken : void 0, password: settings.authMode === "password" ? resolvedGatewayPassword : void 0 }, runtime); } catch (err) { gatewayHealthCheckFailed = true; if (!(err instanceof ExitError)) runtime.error(formatHealthCheckFailure(err)); await prompter.note([ t("common.docs"), "https://docs.openclaw.ai/gateway/health", "https://docs.openclaw.ai/gateway/troubleshooting" ].join("\n"), t("wizard.finalize.healthCheckHelp")); } else if (gateway.status !== "skipped") { runtime.error(formatHealthCheckFailure(new Error(gatewayProbe.detail ?? `gateway did not become reachable at ${probeLinks.wsUrl}`))); await prompter.note([ t("common.docs"), "https://docs.openclaw.ai/gateway/health", "https://docs.openclaw.ai/gateway/troubleshooting" ].join("\n"), t("wizard.finalize.healthCheckHelp")); await prompter.note(buildGatewayRecoveryProjection({ gateway, reachable: false, serviceLabel: resolveGatewayService().label }).detail, "Gateway"); } else await prompter.note(buildGatewayRecoveryProjection({ gateway, reachable: false }).detail, "Gateway"); } await prompter.note([ t("wizard.finalize.addNodes"), `- ${t("wizard.finalize.nodeMac")}`, `- ${t("wizard.finalize.nodeIos")}`, `- ${t("wizard.finalize.nodeAndroid")}` ].join("\n"), t("wizard.finalize.optionalApps")); const controlUiBasePath = nextConfig.gateway?.controlUi?.basePath ?? baseConfig.gateway?.controlUi?.basePath; const displayLinks = await resolveAdvertisedControlUiLinks({ bind: settings.bind, port: settings.port, customBindHost: settings.customBindHost, basePath: controlUiBasePath, tlsEnabled: nextConfig.gateway?.tls?.enabled === true }); const probeLinks = resolveLocalControlUiProbeLinks({ bind: settings.bind, port: settings.port, customBindHost: settings.customBindHost, basePath: controlUiBasePath, tlsEnabled: nextConfig.gateway?.tls?.enabled === true }); if (opts.skipHealth || !gatewayProbe.ok && gateway.status !== "failed") gatewayProbe = await probeGatewayReachable({ url: probeLinks.wsUrl, token: settings.authMode === "token" ? settings.gatewayToken : void 0, password: settings.authMode === "password" ? resolvedGatewayPassword : "" }); const controlUiEnabled = nextConfig.gateway?.controlUi?.enabled ?? baseConfig.gateway?.controlUi?.enabled ?? true; let dashboardReady = controlUiEnabled && opts.skipUi === true; if (!opts.skipUi && controlUiEnabled && gatewayProbe.ok) { let progress; try { const target = await resolveControlUiHandoffTarget({ config: { ...nextConfig, gateway: { ...nextConfig.gateway, port: settings.port, bind: settings.bind, ...settings.customBindHost ? { customBindHost: settings.customBindHost } : {}, ...controlUiBasePath ? { controlUi: { ...nextConfig.gateway?.controlUi, basePath: controlUiBasePath } } : {} } }, env: { ...process.env, OPENCLAW_GATEWAY_PORT: String(settings.port) } }); const document = await waitForControlUiDocument({ url: target.documentUrl, tlsConfig: target.tlsConfig, onPending: () => { progress = prompter.progress(t("wizard.guided.controlUiPreparing")); } }); dashboardReady = document.ready; if (!document.ready) runtime.error(document.reason); } catch (error) { dashboardReady = false; runtime.error(formatErrorMessage(error)); } finally { progress?.stop(); } } const gatewayStatusLine = gatewayProbe.ok ? t("wizard.finalize.gatewayReachable") : t("wizard.finalize.gatewayNotDetectedStatus", { detail: gatewayProbe.detail ? ` (${gatewayProbe.detail})` : "" }); const windowsFirewallLines = formatWindowsGatewayFirewallGuidance({ bind: settings.bind }); const bootstrapPath = path.join(resolveUserPath(options.workspaceDir), DEFAULT_BOOTSTRAP_FILENAME); const hasBootstrap = await fs.access(bootstrapPath).then(() => true).catch(() => false); const agentDir = resolveDefaultAgentDir(nextConfig); const [{ resolveDefaultModelAuthStatus, resolveDefaultModelCatalogFacts }, { loadPreparedModelCatalogSnapshot }] = await Promise.all([import("./auth-choice-CiJJPKKB.js"), import("./prepared-model-catalog-BH7NC_p8.js")]); const modelCatalog = await loadPreparedModelCatalogSnapshot({ config: nextConfig, readOnly: true }).catch((error) => { if (!(error instanceof PreparedModelCatalogConfigReplacedError)) throw error; }); const modelCatalogFacts = modelCatalog ? resolveDefaultModelCatalogFacts(nextConfig, modelCatalog.entries, { routeVariants: modelCatalog.routeVariants }) : void 0; const modelAuthStatus = modelCatalogFacts ? resolveDefaultModelAuthStatus(nextConfig, { agentDir, ...modelCatalogFacts.observedRoutes ? { observedRoutes: modelCatalogFacts.observedRoutes } : {} }) : void 0; const shouldSeedBootstrapHatch = hasBootstrap && options.hadExistingConfig !== true && modelAuthStatus?.status === "ready"; await prompter.note([ dashboardReady ? t("wizard.finalize.webUiUrl", { url: displayLinks.httpUrl }) : void 0, t("wizard.finalize.gatewayWsUrl", { url: displayLinks.wsUrl }), gatewayStatusLine, ...windowsFirewallLines, t("wizard.finalize.controlUiDocs") ].filter(Boolean).join("\n"), "Control UI"); let launchedTui = false; const shouldLaunchTui = !opts.skipUi; if (shouldLaunchTui) { if (hasBootstrap) await prompter.note([ t("wizard.finalize.workspaceReady"), ...shouldSeedBootstrapHatch ? [t("wizard.finalize.firstTerminalChat")] : [], t("wizard.finalize.editBootstrap") ].join("\n"), t("wizard.finalize.hatchYourAgent")); if (modelAuthStatus?.status === "missing") await prompter.note([t("wizard.finalize.noModelAuth", { provider: modelAuthStatus.provider }), t("wizard.finalize.noModelAuthNext", { command: formatCliCommand("openclaw configure --section model") })].join("\n"), t("wizard.finalize.noModelAuthTitle")); if (gatewayProbe.ok) { const tokenNotes = [ t("wizard.finalize.gatewayTokenShared"), t("wizard.finalize.gatewayTokenStored"), t("wizard.finalize.gatewayTokenView", { command: formatCliCommand("openclaw gateway auth-token --show") }), t("wizard.finalize.gatewayTokenGenerate", { command: formatCliCommand("openclaw doctor --generate-gateway-token") }), t("wizard.finalize.dashboardOpenAnytime", { command: formatCliCommand("openclaw dashboard --no-open") }) ].filter(Boolean); await prompter.note(tokenNotes.join("\n"), "Token"); } } else if (opts.skipUi) await prompter.note(t("wizard.finalize.skipControlUi"), t("wizard.finalize.controlUiTitle")); await prompter.note([t("wizard.finalize.backupWorkspace"), t("wizard.finalize.workspaceDocs")].join("\n"), t("wizard.finalize.workspaceBackupTitle")); await prompter.note(t("wizard.finalize.securityReminder"), t("wizard.security.title")); await setupWizardShellCompletion({ flow, prompter }); const codexNativeSummary = describeCodexNativeWebSearch(nextConfig); const webSearchProvider = nextConfig.tools?.web?.search?.provider; const webSearchEnabled = nextConfig.tools?.web?.search?.enabled; const configuredSearchProviders = listConfiguredWebSearchProviders({ config: nextConfig }); if (webSearchProvider) { const { resolveExistingKey, hasExistingKey, hasKeyInEnv } = await loadSearchSetupModule(); const entry = configuredSearchProviders.find((e) => e.id === webSearchProvider); const label = entry?.label ?? webSearchProvider; const storedKey = entry ? resolveExistingKey(nextConfig, webSearchProvider) : void 0; const keyConfigured = entry ? hasExistingKey(nextConfig, webSearchProvider) : false; const envAvailable = entry ? hasKeyInEnv(entry) : false; const hasKey = keyConfigured || envAvailable; const authProviderId = entry?.authProviderId?.trim(); const authProviderLabel = authProviderId === "xai" ? "xAI" : authProviderId; const providerAuthProfileAvailable = authProviderId ? hasAuthProfileForProvider({ provider: authProviderId, agentDir }) : false; const oauthAuthProfileAvailable = authProviderId && providerAuthProfileAvailable ? hasAuthProfileForProvider({ provider: authProviderId, agentDir, type: "oauth" }) : false; const hasCredential = hasKey || providerAuthProfileAvailable; const keySource = storedKey ? t("wizard.finalize.webSearchKeyStored") : keyConfigured ? t("wizard.finalize.webSearchKeyRef") : envAvailable ? t("wizard.finalize.webSearchKeyEnv", { env: entry?.envVars.join(" / ") ?? "" }) : oauthAuthProfileAvailable && authProviderLabel ? t("wizard.finalize.webSearchOAuthProfile", { provider: authProviderLabel }) : providerAuthProfileAvailable && authProviderLabel ? t("wizard.finalize.webSearchAuthProfile", { provider: authProviderLabel }) : void 0; if (!entry) await prompter.note([ t("wizard.finalize.webSearchProviderUnavailable", { provider: label }), t("wizard.finalize.webSearchUnavailableAction"), ` ${formatCliCommand("openclaw configure --section web")}`, "", t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); else if (webSearchEnabled !== false && entry.requiresCredential === false) await prompter.note([ t("wizard.finalize.webSearchKeyFree"), "", t("wizard.finalize.webSearchProvider", { provider: label }), t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); else if (webSearchEnabled !== false && hasCredential) await prompter.note([ t("wizard.finalize.webSearchEnabled"), "", t("wizard.finalize.webSearchProvider", { provider: label }), ...keySource ? [keySource] : [], t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); else if (entry.requiresCredential !== false && !hasCredential) await prompter.note([ t("wizard.finalize.webSearchNoKey", { provider: label }), t("wizard.finalize.webSearchNeedsKey"), ` ${formatCliCommand("openclaw configure --section web")}`, "", t("wizard.finalize.webSearchGetKey", { url: entry?.signupUrl ?? "https://docs.openclaw.ai/tools/web" }), t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); else await prompter.note([ t("wizard.finalize.webSearchDisabled", { provider: label }), t("wizard.finalize.webSearchReenable", { command: formatCliCommand("openclaw configure --section web") }), "", t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); } else { const { hasExistingKey, hasKeyInEnv } = await loadSearchSetupModule(); const legacyDetected = configuredSearchProviders.find((e) => hasExistingKey(nextConfig, e.id) || hasKeyInEnv(e)); if (legacyDetected) await prompter.note([t("wizard.finalize.webSearchAutoDetected", { provider: legacyDetected.label }), t("wizard.finalize.webDocs")].join("\n"), t("wizard.finalize.webSearchTitle")); else if (codexNativeSummary) await prompter.note([ t("wizard.finalize.managedWebSearchSkipped"), codexNativeSummary, t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); else await prompter.note([ t("wizard.finalize.webSearchSkipped"), ` ${formatCliCommand("openclaw configure --section web")}`, "", t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.webSearchTitle")); } if (codexNativeSummary) await prompter.note([ codexNativeSummary, t("wizard.finalize.codexNativeSearchOnly"), t("wizard.finalize.webDocs") ].join("\n"), t("wizard.finalize.codexNativeSearchTitle")); await prompter.note(t("wizard.finalize.whatNow"), t("wizard.finalize.whatNowTitle")); await prompter.outro(!gatewayProbe.ok || gateway.status === "failed" ? buildGatewayRecoveryProjection({ gateway, reachable: gatewayProbe.ok, serviceLabel: gateway.status === "skipped" ? void 0 : resolveGatewayService().label }).summary : gatewayHealthCheckFailed ? t("wizard.finalize.outroHealthCheckFailed", { command: formatCliCommand("openclaw health") }) : dashboardReady ? t("wizard.finalize.outroDashboardLink") : controlUiEnabled ? [t("wizard.guided.complete"), t("wizard.finalize.dashboardWhenReady", { command: formatCliCommand("openclaw dashboard") })].join(" ") : t("wizard.guided.complete")); if (shouldLaunchTui) { restoreTerminalState("pre-setup tui", { resumeStdinIfPaused: false }); try { await runTui({ ...gatewayProbe.ok ? { config: nextConfig, boundGateway: { url: displayLinks.wsUrl, ...settings.authMode === "token" && settings.gatewayToken ? { token: settings.gatewayToken } : {}, ...settings.authMode === "password" && resolvedGatewayPassword ? { password: resolvedGatewayPassword } : {} } } : { local: true }, deliver: false, message: shouldSeedBootstrapHatch ? t("wizard.finalize.bootstrapHatchMessage") : void 0, initialMessageTimeoutMs: HATCH_TUI_TIMEOUT_MS }); } finally { restoreTerminalState("post-setup tui", { resumeStdinIfPaused: false }); if (sessionGateway) { const cleanupExitTimer = scheduleProcessExitAfterTuiReturn({ delayMs: resolveTuiShutdownHardExitMs({ localMode: true }) }); try { await closeSessionGatewayForOnboarding({ sessionGateway, runtime, reason: "onboarding tui exited" }); sessionGateway = void 0; } finally { cancelProcessExitAfterTuiReturn(cleanupExitTimer); } } } scheduleProcessExitAfterTuiReturn(); launchedTui = true; } return { launchedTui }; } finally { if (sessionGateway) await closeSessionGatewayForOnboarding({ sessionGateway, runtime, reason: "onboarding finalize exited" }); } } //#endregion export { ensureGatewayServiceForOnboarding, finalizeSetupWizard };