mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 18:42:25 +00:00
fix: keep skip-ui onboarding on guided flow
This commit is contained in:
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Guided onboarding skip-UI routing:** keep `openclaw onboard --skip-ui` and `openclaw setup --skip-ui` on guided onboarding while skipping both browser and terminal handoffs, instead of silently switching to the classic wizard. Thanks @shakkernerd.
|
||||
- **Telegram durable ingress:** preserve pre-identity control-lane ownership during replay and attempt each drain snapshot row only once per pass, preventing targeted commands from spinning the spool and blocking polling shutdown.
|
||||
- **Control UI operator session permissions:** honor Gateway-advertised operator scopes for new-thread creation, thread management, checkpoints, and sharing controls while preserving read-only navigation and legacy Gateway compatibility. Fixes #117786. Thanks @shakkernerd.
|
||||
- **Control UI delayed session commands:** bind slash-command mutations and confirmed resets to their originating Gateway, recheck current operator scopes after asynchronous work, and retain reset authorization through queued delivery so reconnects cannot target a replacement connection. Thanks @shakkernerd.
|
||||
|
||||
@@ -24,6 +24,7 @@ export GATEWAY_LOG_PATH
|
||||
mkdir -p "$OPENCLAW_E2E_LOG_DIR"
|
||||
cleanup_onboard_artifacts() {
|
||||
openclaw_e2e_stop_process "${GATEWAY_PID:-}"
|
||||
openclaw_e2e_stop_process "${mock_openai_pid:-}"
|
||||
rm -rf "$ONBOARD_TMP_DIR"
|
||||
}
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
@@ -241,6 +242,72 @@ send_skills_flow() {
|
||||
send "" 2.0
|
||||
}
|
||||
|
||||
send_guided_skip_ui_flow() {
|
||||
wait_for_log "How should I set things up?" 120 || return $?
|
||||
send $'\r' 0.8
|
||||
wait_for_log "Use Current model?" 120 || return $?
|
||||
send $'\r' 0.8
|
||||
}
|
||||
|
||||
validate_guided_skip_ui_log() {
|
||||
local log_path="$1"
|
||||
local mock_request_log="$2"
|
||||
log_contains "Hi — I'm OpenClaw. I keep this system running. Let's get you set up." || {
|
||||
echo "Guided onboarding introduction was not rendered"
|
||||
return 1
|
||||
}
|
||||
log_contains "OpenClaw is ready." || {
|
||||
echo "Guided onboarding did not reach its skip-UI completion"
|
||||
return 1
|
||||
}
|
||||
if log_contains "Opening the Control UI dashboard"; then
|
||||
echo "Guided skip-UI onboarding attempted a browser handoff"
|
||||
return 1
|
||||
fi
|
||||
if log_contains "Your browser is ready"; then
|
||||
echo "Guided skip-UI onboarding completed a browser handoff"
|
||||
return 1
|
||||
fi
|
||||
if log_contains "Hatching your agent now"; then
|
||||
echo "Guided skip-UI onboarding attempted a terminal handoff"
|
||||
return 1
|
||||
fi
|
||||
grep -q '"/v1/responses"' "$mock_request_log" || {
|
||||
echo "Guided onboarding did not verify the configured model through /v1/responses"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
run_case_guided_skip_ui() {
|
||||
local mock_port="19091"
|
||||
local mock_log="$ONBOARD_TMP_DIR/guided-skip-ui-mock-openai.log"
|
||||
local mock_request_log="$ONBOARD_TMP_DIR/guided-skip-ui-mock-requests.jsonl"
|
||||
set_isolated_openclaw_env guided-skip-ui
|
||||
export OPENAI_API_KEY="sk-openclaw-guided-skip-ui-e2e"
|
||||
node scripts/e2e/lib/onboard/write-config.mjs \
|
||||
guided-skip-ui \
|
||||
"$OPENCLAW_CONFIG_PATH" \
|
||||
"$OPENCLAW_TEST_WORKSPACE_DIR" \
|
||||
"$mock_port"
|
||||
mock_openai_pid="$(
|
||||
openclaw_e2e_start_tracked_process \
|
||||
"$mock_log" \
|
||||
env MOCK_PORT="$mock_port" MOCK_REQUEST_LOG="$mock_request_log" \
|
||||
node scripts/e2e/mock-openai-server.mjs
|
||||
)"
|
||||
openclaw_e2e_wait_mock_openai "$mock_port"
|
||||
|
||||
run_wizard_cmd \
|
||||
guided-skip-ui \
|
||||
"$HOME" \
|
||||
"node \"$OPENCLAW_ENTRY\" onboard --skip-ui" \
|
||||
send_guided_skip_ui_flow
|
||||
|
||||
validate_guided_skip_ui_log "$WIZARD_LOG_PATH" "$mock_request_log"
|
||||
openclaw_e2e_stop_process "$mock_openai_pid"
|
||||
mock_openai_pid=""
|
||||
}
|
||||
|
||||
run_case_local_basic() {
|
||||
set_isolated_openclaw_env local-basic
|
||||
openclaw_e2e_run_logged local-basic node "$OPENCLAW_ENTRY" onboard \
|
||||
@@ -325,6 +392,7 @@ validate_local_basic_log() {
|
||||
}
|
||||
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
run_case_guided_skip_ui
|
||||
run_case_local_basic
|
||||
run_case_remote_non_interactive
|
||||
run_case_reset
|
||||
|
||||
@@ -1,19 +1,38 @@
|
||||
// Config writer helper for onboard E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import { applyMockOpenAiModelConfig } from "../fixtures/mock-openai-config.mjs";
|
||||
|
||||
const [scenario, configPath] = process.argv.slice(2);
|
||||
const [scenario, configPath, ...scenarioArgs] = process.argv.slice(2);
|
||||
if (!scenario || !configPath) {
|
||||
throw new Error("usage: write-config.mjs <reset|skills> <config-path>");
|
||||
throw new Error("usage: write-config.mjs <reset|skills|guided-skip-ui> <config-path> [...args]");
|
||||
}
|
||||
|
||||
const config = {
|
||||
reset: {
|
||||
meta: {},
|
||||
agents: { defaults: { workspace: "/root/old" } },
|
||||
gateway: { mode: "remote", remote: { url: "ws://old.example:18789", token: "old-token" } },
|
||||
},
|
||||
skills: { meta: {}, skills: { allowBundled: ["__none__"], install: { nodeManager: "bun" } } },
|
||||
}[scenario];
|
||||
let config;
|
||||
if (scenario === "guided-skip-ui") {
|
||||
const [workspace, mockPort] = scenarioArgs;
|
||||
if (!workspace || !mockPort) {
|
||||
throw new Error("guided-skip-ui requires workspace and mock OpenAI port arguments");
|
||||
}
|
||||
config = {
|
||||
gateway: { mode: "local", bind: "loopback", controlUi: { enabled: false } },
|
||||
agents: { defaults: { workspace } },
|
||||
wizard: {
|
||||
securityAcknowledgedAt: "2026-01-01T00:00:00.000Z",
|
||||
accessMode: "full",
|
||||
appRecommendations: false,
|
||||
},
|
||||
};
|
||||
applyMockOpenAiModelConfig(config, { mockPort });
|
||||
} else {
|
||||
config = {
|
||||
reset: {
|
||||
meta: {},
|
||||
agents: { defaults: { workspace: "/root/old" } },
|
||||
gateway: { mode: "remote", remote: { url: "ws://old.example:18789", token: "old-token" } },
|
||||
},
|
||||
skills: { meta: {}, skills: { allowBundled: ["__none__"], install: { nodeManager: "bun" } } },
|
||||
}[scenario];
|
||||
}
|
||||
if (!config) {
|
||||
throw new Error(`unknown config scenario: ${scenario}`);
|
||||
}
|
||||
|
||||
@@ -210,6 +210,12 @@ describe("registerOnboardCommand", () => {
|
||||
expect(setupWizardOptions().tui).toBe(true);
|
||||
});
|
||||
|
||||
it("forwards --skip-ui to guided onboarding", async () => {
|
||||
await runCli(["onboard", "--skip-ui"]);
|
||||
|
||||
expect(setupWizardOptions().skipUi).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects conflicting custom model input capabilities", async () => {
|
||||
await runCli(["onboard", "--custom-image-input", "--custom-text-input"]);
|
||||
|
||||
|
||||
@@ -378,6 +378,13 @@ describe("registerSetupCommand", () => {
|
||||
expect(setupCommandMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("forwards --skip-ui through the canonical onboarding path", async () => {
|
||||
await runCli(["setup", "--skip-ui"]);
|
||||
|
||||
expect(lastWizardOptions()?.skipUi).toBe(true);
|
||||
expect(setupCommandMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects conflicting custom model input capabilities", async () => {
|
||||
await runCli(["setup", "--custom-image-input", "--custom-text-input"]);
|
||||
|
||||
|
||||
@@ -921,12 +921,15 @@ describe("setupWizardCommand", () => {
|
||||
expect(mocks.runNonInteractiveSetup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps --tui on guided onboarding", async () => {
|
||||
it.each([
|
||||
["--tui", { tui: true }],
|
||||
["--skip-ui", { skipUi: true }],
|
||||
])("keeps %s on guided onboarding", async (_label, opts) => {
|
||||
const runtime = makeRuntime();
|
||||
|
||||
await setupWizardCommand({ tui: true }, runtime);
|
||||
await setupWizardCommand(opts, runtime);
|
||||
|
||||
expect(mocks.runGuidedOnboarding).toHaveBeenCalledWith({ tui: true }, runtime);
|
||||
expect(mocks.runGuidedOnboarding).toHaveBeenCalledWith(opts, runtime);
|
||||
expect(mocks.runInteractiveSetup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -424,6 +424,7 @@ const GUIDED_SAFE_ONBOARD_KEYS = new Set([
|
||||
"nonInteractive",
|
||||
"classic",
|
||||
"tui",
|
||||
"skipUi",
|
||||
]);
|
||||
|
||||
function wantsClassicInteractiveSetup(opts: OnboardOptions): boolean {
|
||||
|
||||
@@ -87,6 +87,41 @@ describe("onboard config fixture helpers", () => {
|
||||
expect(assertResult.stderr).toBe("");
|
||||
});
|
||||
|
||||
it("writes configured guided skip-UI fixtures with a local mock model", () => {
|
||||
const root = makeTempDir(tempDirs, "openclaw-onboard-config-guided-");
|
||||
const configPath = path.join(root, "openclaw.json");
|
||||
const workspace = path.join(root, "workspace");
|
||||
|
||||
const writeResult = runScript(WRITE_CONFIG_SCRIPT, [
|
||||
"guided-skip-ui",
|
||||
configPath,
|
||||
workspace,
|
||||
"19091",
|
||||
]);
|
||||
const config = readJson(configPath);
|
||||
|
||||
expect(writeResult.status).toBe(0);
|
||||
expect(config.gateway).toEqual({
|
||||
mode: "local",
|
||||
bind: "loopback",
|
||||
controlUi: { enabled: false },
|
||||
});
|
||||
expect(config.agents.defaults.workspace).toBe(workspace);
|
||||
expect(config.agents.defaults.model.primary).toBe("openai/gpt-5.6-luna");
|
||||
expect(config.models.providers.openai.baseUrl).toBe("http://127.0.0.1:19091/v1");
|
||||
expect(config.models.providers.openai.apiKey).toEqual({
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "OPENAI_API_KEY",
|
||||
});
|
||||
expect(config.wizard).toMatchObject({
|
||||
securityAcknowledgedAt: "2026-01-01T00:00:00.000Z",
|
||||
accessMode: "full",
|
||||
appRecommendations: false,
|
||||
});
|
||||
expect(readFileSync(configPath, "utf8")).toMatch(/\n$/u);
|
||||
});
|
||||
|
||||
it("accepts local and remote onboard assertion fixtures", () => {
|
||||
const root = makeTempDir(tempDirs, "openclaw-onboard-config-success-");
|
||||
const workspace = path.join(root, "workspace");
|
||||
|
||||
Reference in New Issue
Block a user