Onboarding: keep TUI flow exclusive

main
Shakker 2026-02-03 06:11:11 +00:00
parent 157d6d2db7
commit 58d5b39c9a
4 changed files with 21 additions and 14 deletions

View File

@ -16,6 +16,8 @@ Docs: https://docs.openclaw.ai
### Fixes ### Fixes
- Onboarding: keep TUI flow exclusive (skip completion prompt + background Web UI seed).
- TUI: block onboarding output while TUI is active and restore terminal state on exit.
- Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode. - Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.
- fix(agents): validate AbortSignal instances before calling AbortSignal.any() (#7277) (thanks @Elarwei001) - fix(agents): validate AbortSignal instances before calling AbortSignal.any() (#7277) (thanks @Elarwei001)
- fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz) - fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)

View File

@ -662,4 +662,10 @@ export async function runTui(opts: TuiOptions) {
updateFooter(); updateFooter();
tui.start(); tui.start();
client.start(); client.start();
await new Promise<void>((resolve) => {
const finish = () => resolve();
process.once("exit", finish);
process.once("SIGINT", finish);
process.once("SIGTERM", finish);
});
} }

View File

@ -21,7 +21,6 @@ import {
detectBrowserOpenSupport, detectBrowserOpenSupport,
formatControlUiSshHint, formatControlUiSshHint,
openUrl, openUrl,
openUrlInBackground,
probeGatewayReachable, probeGatewayReachable,
waitForGatewayReachable, waitForGatewayReachable,
resolveControlUiLinks, resolveControlUiLinks,
@ -29,6 +28,7 @@ import {
import { resolveGatewayService } from "../daemon/service.js"; import { resolveGatewayService } from "../daemon/service.js";
import { isSystemdUserServiceAvailable } from "../daemon/systemd.js"; import { isSystemdUserServiceAvailable } from "../daemon/systemd.js";
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js"; import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
import { restoreTerminalState } from "../terminal/restore.js";
import { runTui } from "../tui/tui.js"; import { runTui } from "../tui/tui.js";
import { resolveUserPath } from "../utils.js"; import { resolveUserPath } from "../utils.js";
@ -43,7 +43,9 @@ type FinalizeOnboardingOptions = {
runtime: RuntimeEnv; runtime: RuntimeEnv;
}; };
export async function finalizeOnboardingWizard(options: FinalizeOnboardingOptions) { export async function finalizeOnboardingWizard(
options: FinalizeOnboardingOptions,
): Promise<{ launchedTui: boolean }> {
const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options; const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options;
const withWizardProgress = async <T>( const withWizardProgress = async <T>(
@ -286,6 +288,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
let controlUiOpenHint: string | undefined; let controlUiOpenHint: string | undefined;
let seededInBackground = false; let seededInBackground = false;
let hatchChoice: "tui" | "web" | "later" | null = null; let hatchChoice: "tui" | "web" | "later" | null = null;
let launchedTui = false;
if (!opts.skipUi && gatewayProbe.ok) { if (!opts.skipUi && gatewayProbe.ok) {
if (hasBootstrap) { if (hasBootstrap) {
@ -321,6 +324,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
}); });
if (hatchChoice === "tui") { if (hatchChoice === "tui") {
restoreTerminalState("pre-onboarding tui");
await runTui({ await runTui({
url: links.wsUrl, url: links.wsUrl,
token: settings.authMode === "token" ? settings.gatewayToken : undefined, token: settings.authMode === "token" ? settings.gatewayToken : undefined,
@ -329,17 +333,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
deliver: false, deliver: false,
message: hasBootstrap ? "Wake up, my friend!" : undefined, message: hasBootstrap ? "Wake up, my friend!" : undefined,
}); });
if (settings.authMode === "token" && settings.gatewayToken) { launchedTui = true;
seededInBackground = await openUrlInBackground(authedUrl);
}
if (seededInBackground) {
await prompter.note(
`Web UI seeded in the background. Open later with: ${formatCliCommand(
"openclaw dashboard --no-open",
)}`,
"Web UI",
);
}
} else if (hatchChoice === "web") { } else if (hatchChoice === "web") {
const browserSupport = await detectBrowserOpenSupport(); const browserSupport = await detectBrowserOpenSupport();
if (browserSupport.ok) { if (browserSupport.ok) {
@ -471,4 +465,6 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
? "Onboarding complete. Web UI seeded in the background; open it anytime with the tokenized link above." ? "Onboarding complete. Web UI seeded in the background; open it anytime with the tokenized link above."
: "Onboarding complete. Use the tokenized dashboard link above to control OpenClaw.", : "Onboarding complete. Use the tokenized dashboard link above to control OpenClaw.",
); );
return { launchedTui };
} }

View File

@ -455,7 +455,7 @@ export async function runOnboardingWizard(
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode }); nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig); await writeConfigFile(nextConfig);
await finalizeOnboardingWizard({ const { launchedTui } = await finalizeOnboardingWizard({
flow, flow,
opts, opts,
baseConfig, baseConfig,
@ -465,6 +465,9 @@ export async function runOnboardingWizard(
prompter, prompter,
runtime, runtime,
}); });
if (launchedTui) {
return;
}
const installShell = await prompter.confirm({ const installShell = await prompter.confirm({
message: "Install shell completion script?", message: "Install shell completion script?",