Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
87
scripts/e2e/lib/onboard/assert-config.mjs
Normal file
87
scripts/e2e/lib/onboard/assert-config.mjs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Config assertions for onboard E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import JSON5 from "json5";
|
||||
|
||||
const [scenario, configPath, expectedWorkspace] = process.argv.slice(2);
|
||||
if (!scenario || !configPath) {
|
||||
throw new Error("usage: assert-config.mjs <scenario> <config-path> [expected-workspace]");
|
||||
}
|
||||
|
||||
const cfg = JSON5.parse(fs.readFileSync(configPath, "utf8"));
|
||||
const errors = [];
|
||||
const got = (value) => value ?? "unset";
|
||||
const expectEqual = (label, actual, expected) => {
|
||||
if (actual !== expected) {
|
||||
errors.push(`${label} mismatch (got ${got(actual)})`);
|
||||
}
|
||||
};
|
||||
|
||||
const assertLocalWizard = () => {
|
||||
expectEqual("gateway.mode", cfg?.gateway?.mode, "local");
|
||||
expectEqual("wizard.lastRunMode", cfg?.wizard?.lastRunMode, "local");
|
||||
};
|
||||
|
||||
const assertSectionScopedConfigure = () => {
|
||||
expectEqual("wizard.lastRunCommand", cfg?.wizard?.lastRunCommand, "configure");
|
||||
expectEqual("wizard.lastRunMode", cfg?.wizard?.lastRunMode, "local");
|
||||
if (cfg?.gateway?.mode) {
|
||||
errors.push(`gateway.mode should stay unset (got ${cfg.gateway.mode})`);
|
||||
}
|
||||
};
|
||||
|
||||
switch (scenario) {
|
||||
case "local-basic": {
|
||||
expectEqual("agents.defaults.workspace", cfg?.agents?.defaults?.workspace, expectedWorkspace);
|
||||
assertLocalWizard();
|
||||
expectEqual("gateway.bind", cfg?.gateway?.bind, "loopback");
|
||||
expectEqual("gateway.tailscale.mode", cfg?.gateway?.tailscale?.mode ?? "off", "off");
|
||||
if (!cfg?.wizard?.lastRunAt) {
|
||||
errors.push("wizard.lastRunAt missing");
|
||||
}
|
||||
if (!cfg?.wizard?.lastRunVersion) {
|
||||
errors.push("wizard.lastRunVersion missing");
|
||||
}
|
||||
expectEqual("wizard.lastRunCommand", cfg?.wizard?.lastRunCommand, "onboard");
|
||||
break;
|
||||
}
|
||||
case "remote-non-interactive":
|
||||
expectEqual("gateway.mode", cfg?.gateway?.mode, "remote");
|
||||
expectEqual("gateway.remote.url", cfg?.gateway?.remote?.url, "ws://gateway.local:18789");
|
||||
expectEqual("gateway.remote.token", cfg?.gateway?.remote?.token, "remote-token");
|
||||
expectEqual("wizard.lastRunMode", cfg?.wizard?.lastRunMode, "remote");
|
||||
break;
|
||||
case "reset":
|
||||
assertLocalWizard();
|
||||
if (cfg?.gateway?.remote?.url) {
|
||||
errors.push(`gateway.remote.url should be cleared (got ${cfg.gateway.remote.url})`);
|
||||
}
|
||||
break;
|
||||
case "channels":
|
||||
if (cfg?.telegram?.botToken) {
|
||||
errors.push(`telegram.botToken should be unset (got ${cfg.telegram.botToken})`);
|
||||
}
|
||||
if (cfg?.discord?.token) {
|
||||
errors.push(`discord.token should be unset (got ${cfg.discord.token})`);
|
||||
}
|
||||
if (cfg?.slack?.botToken || cfg?.slack?.appToken) {
|
||||
errors.push(
|
||||
`slack tokens should be unset (got bot=${got(cfg?.slack?.botToken)}, app=${got(cfg?.slack?.appToken)})`,
|
||||
);
|
||||
}
|
||||
assertSectionScopedConfigure();
|
||||
break;
|
||||
case "skills":
|
||||
expectEqual("skills.install.nodeManager", cfg?.skills?.install?.nodeManager, "bun");
|
||||
if (!Array.isArray(cfg?.skills?.allowBundled) || cfg.skills.allowBundled[0] !== "__none__") {
|
||||
errors.push("skills.allowBundled missing");
|
||||
}
|
||||
assertSectionScopedConfigure();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`unknown onboard assertion scenario: ${scenario}`);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(errors.join("\n"));
|
||||
process.exit(1);
|
||||
}
|
||||
58
scripts/e2e/lib/onboard/log-contains.mjs
Normal file
58
scripts/e2e/lib/onboard/log-contains.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
// Log substring assertion helper for onboard E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const DEFAULT_MAX_LOG_BYTES = 120_000;
|
||||
|
||||
const normalizeScriptOutput = (value) => value.replace(/\r?\n/g, "").replace(/\r/g, "");
|
||||
const oscPattern = new RegExp(String.raw`\u001b\][^\u0007]*(?:\u0007|\u001b\\)`, "g");
|
||||
const csiPattern = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
|
||||
|
||||
const stripAnsi = (value) =>
|
||||
normalizeScriptOutput(value).replace(oscPattern, "").replace(csiPattern, "");
|
||||
|
||||
const compact = (value) =>
|
||||
stripAnsi(value)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z]+/g, "");
|
||||
|
||||
export function readLogTail(file, maxBytes = DEFAULT_MAX_LOG_BYTES) {
|
||||
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) {
|
||||
throw new Error("maxBytes must be a positive integer");
|
||||
}
|
||||
const stats = fs.statSync(file);
|
||||
if (!stats.isFile()) {
|
||||
throw new Error(`${file} is not a file`);
|
||||
}
|
||||
const length = Math.min(stats.size, maxBytes);
|
||||
const start = Math.max(0, stats.size - length);
|
||||
const fd = fs.openSync(file, "r");
|
||||
try {
|
||||
const buffer = Buffer.alloc(length);
|
||||
const bytesRead = fs.readSync(fd, buffer, 0, length, start);
|
||||
return buffer.subarray(0, bytesRead).toString("utf8");
|
||||
} finally {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
export function logTailContains(file, needle, maxBytes = DEFAULT_MAX_LOG_BYTES) {
|
||||
const compactNeedle = compact(needle);
|
||||
if (!compactNeedle) {
|
||||
return false;
|
||||
}
|
||||
return compact(readLogTail(file, maxBytes)).includes(compactNeedle);
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
const [file, needle] = process.argv.slice(2);
|
||||
if (!file || !needle) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
process.exit(logTailContains(file, needle) ? 0 : 1);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
299
scripts/e2e/lib/onboard/scenario.sh
Normal file
299
scripts/e2e/lib/onboard/scenario.sh
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
trap "" PIPE
|
||||
export TERM=xterm-256color
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY="${OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY:-0}"
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_FUNCTION_B64:?missing OPENCLAW_TEST_STATE_FUNCTION_B64}"
|
||||
fi
|
||||
ONBOARD_FLAGS="${ONBOARD_FLAGS:---flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui}"
|
||||
if [ -z "${OPENCLAW_ENTRY:-}" ] && [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)"
|
||||
fi
|
||||
export OPENCLAW_ENTRY
|
||||
ONBOARD_TMP_ROOT="${OPENCLAW_ONBOARD_E2E_TMPDIR:-${TMPDIR:-/tmp}}"
|
||||
ONBOARD_TMP_ROOT="${ONBOARD_TMP_ROOT%/}"
|
||||
[ -n "$ONBOARD_TMP_ROOT" ] || ONBOARD_TMP_ROOT="/tmp"
|
||||
mkdir -p "$ONBOARD_TMP_ROOT"
|
||||
ONBOARD_TMP_DIR="$(mktemp -d "$ONBOARD_TMP_ROOT/openclaw-onboard.XXXXXX")"
|
||||
OPENCLAW_E2E_LOG_DIR="$ONBOARD_TMP_DIR/logs"
|
||||
GATEWAY_LOG_PATH="$ONBOARD_TMP_DIR/gateway-e2e.log"
|
||||
export OPENCLAW_E2E_LOG_DIR
|
||||
export GATEWAY_LOG_PATH
|
||||
mkdir -p "$OPENCLAW_E2E_LOG_DIR"
|
||||
cleanup_onboard_artifacts() {
|
||||
openclaw_e2e_stop_process "${GATEWAY_PID:-}"
|
||||
rm -rf "$ONBOARD_TMP_DIR"
|
||||
}
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
trap cleanup_onboard_artifacts EXIT
|
||||
fi
|
||||
|
||||
# Provide a minimal trash shim to avoid noisy "missing trash" logs in containers.
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
openclaw_e2e_install_trash_shim
|
||||
fi
|
||||
|
||||
send() {
|
||||
local payload="$1"
|
||||
local delay="${2:-0.4}"
|
||||
# Let prompts render before sending keystrokes.
|
||||
sleep "$delay"
|
||||
printf "%b" "$payload" >&3 2>/dev/null || true
|
||||
}
|
||||
|
||||
wait_for_log() {
|
||||
local needle="$1"
|
||||
local timeout_s="${2:-45}"
|
||||
local quiet_on_timeout="${3:-false}"
|
||||
local start_s
|
||||
start_s="$(date +%s)"
|
||||
while true; do
|
||||
if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then
|
||||
if grep -a -F -q "$needle" "$WIZARD_LOG_PATH"; then
|
||||
return 0
|
||||
fi
|
||||
if node scripts/e2e/lib/onboard/log-contains.mjs "$WIZARD_LOG_PATH" "$needle"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [ $(($(date +%s) - start_s)) -ge "$timeout_s" ]; then
|
||||
if [ "$quiet_on_timeout" = "true" ]; then
|
||||
return 1
|
||||
fi
|
||||
echo "Timeout waiting for log: $needle"
|
||||
if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then
|
||||
tail -n 140 "$WIZARD_LOG_PATH" || true
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
}
|
||||
|
||||
start_gateway() {
|
||||
GATEWAY_PID="$(openclaw_e2e_start_gateway "$OPENCLAW_ENTRY" 18789 "$GATEWAY_LOG_PATH")"
|
||||
}
|
||||
|
||||
wait_for_gateway() {
|
||||
local wait_attempts
|
||||
wait_attempts="$(openclaw_e2e_read_positive_int_env OPENCLAW_ONBOARD_GATEWAY_WAIT_ATTEMPTS 20)" || return $?
|
||||
local wait_interval_s="${OPENCLAW_ONBOARD_GATEWAY_WAIT_INTERVAL_S:-1}"
|
||||
local saw_listening_log="false"
|
||||
for _ in $(seq 1 "$wait_attempts"); do
|
||||
if openclaw_e2e_probe_tcp 127.0.0.1 18789 500 >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$GATEWAY_LOG_PATH" ] && grep -E -q "listening on ws://[^ ]+:18789" "$GATEWAY_LOG_PATH"; then
|
||||
saw_listening_log="true"
|
||||
fi
|
||||
sleep "$wait_interval_s"
|
||||
done
|
||||
echo "Gateway failed to start"
|
||||
if [ "$saw_listening_log" = "true" ]; then
|
||||
echo "Gateway log reported listening, but TCP probe never succeeded"
|
||||
fi
|
||||
cat "$GATEWAY_LOG_PATH" || true
|
||||
return 1
|
||||
}
|
||||
|
||||
stop_gateway() {
|
||||
openclaw_e2e_stop_process "$1"
|
||||
}
|
||||
|
||||
cleanup_wizard_case() {
|
||||
exec 3>&- 2>/dev/null || true
|
||||
openclaw_e2e_stop_process "${wizard_pid:-}"
|
||||
stop_gateway "${gw_pid:-}"
|
||||
rm -rf "${input_fifo_dir:-}"
|
||||
}
|
||||
|
||||
run_wizard_cmd() {
|
||||
local case_name="$1"
|
||||
local state_ref="$2"
|
||||
local command="$3"
|
||||
local send_fn="$4"
|
||||
local with_gateway="${5:-false}"
|
||||
local validate_fn="${6:-}"
|
||||
local input_fifo_dir=""
|
||||
local input_fifo=""
|
||||
local wizard_pid=""
|
||||
local gw_pid=""
|
||||
local wizard_status=0
|
||||
|
||||
echo "== Wizard case: $case_name =="
|
||||
set_isolated_openclaw_env "$state_ref"
|
||||
|
||||
input_fifo_dir="$(mktemp -d "$ONBOARD_TMP_DIR/${case_name}.fifo.XXXXXX")"
|
||||
input_fifo="$input_fifo_dir/stdin.fifo"
|
||||
if ! mkfifo "$input_fifo"; then
|
||||
rm -rf "$input_fifo_dir"
|
||||
return 1
|
||||
fi
|
||||
local log_path="$OPENCLAW_E2E_LOG_DIR/${case_name}.log"
|
||||
WIZARD_LOG_PATH="$log_path"
|
||||
export WIZARD_LOG_PATH
|
||||
# Run under script to keep an interactive TTY for clack prompts.
|
||||
openclaw_e2e_run_script_with_pty "$command" "$log_path" <"$input_fifo" >/dev/null 2>&1 &
|
||||
wizard_pid=$!
|
||||
if ! exec 3>"$input_fifo"; then
|
||||
cleanup_wizard_case
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$with_gateway" = "true" ]; then
|
||||
start_gateway
|
||||
gw_pid="$GATEWAY_PID"
|
||||
if ! wait_for_gateway; then
|
||||
cleanup_wizard_case
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
"$send_fn" || wizard_status=$?
|
||||
if [ "$wizard_status" -ne 0 ]; then
|
||||
cleanup_wizard_case
|
||||
echo "Wizard input driver exited with status $wizard_status"
|
||||
if [ -f "$log_path" ]; then
|
||||
tail -n 160 "$log_path" || true
|
||||
fi
|
||||
exit "$wizard_status"
|
||||
fi
|
||||
|
||||
wait "$wizard_pid" || wizard_status=$?
|
||||
wizard_pid=""
|
||||
if [ "$wizard_status" -ne 0 ]; then
|
||||
cleanup_wizard_case
|
||||
echo "Wizard exited with status $wizard_status"
|
||||
if [ -f "$log_path" ]; then
|
||||
tail -n 160 "$log_path" || true
|
||||
fi
|
||||
exit "$wizard_status"
|
||||
fi
|
||||
cleanup_wizard_case
|
||||
if [ -n "$validate_fn" ]; then
|
||||
"$validate_fn" "$log_path"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_onboard_config() {
|
||||
local scenario="$1"
|
||||
shift
|
||||
openclaw_e2e_assert_file "$OPENCLAW_CONFIG_PATH"
|
||||
node scripts/e2e/lib/onboard/assert-config.mjs "$scenario" "$OPENCLAW_CONFIG_PATH" "$@"
|
||||
}
|
||||
|
||||
set_isolated_openclaw_env() {
|
||||
local state_ref="$1"
|
||||
openclaw_test_state_create "$state_ref" empty
|
||||
}
|
||||
|
||||
send_channels_flow() {
|
||||
# Configure channels via configure wizard. Use the remove-config branch for
|
||||
# a stable no-op smoke path when the config starts empty.
|
||||
# Section-scoped configure flows skip gateway run-mode selection.
|
||||
wait_for_log "Channel setup" 120
|
||||
send $'\e[B\r' 0.8
|
||||
# Keep stdin open until wizard exits.
|
||||
send "" 2.0
|
||||
}
|
||||
|
||||
send_skills_flow() {
|
||||
# configure --section skills still runs the configure wizard, without the
|
||||
# gateway run-mode prompt used by the full wizard.
|
||||
wait_for_log "Configure skills now?" 120
|
||||
send $'n\r' 0.8
|
||||
send "" 2.0
|
||||
}
|
||||
|
||||
run_case_local_basic() {
|
||||
set_isolated_openclaw_env local-basic
|
||||
openclaw_e2e_run_logged local-basic node "$OPENCLAW_ENTRY" onboard \
|
||||
--non-interactive \
|
||||
--accept-risk \
|
||||
--flow quickstart \
|
||||
--mode local \
|
||||
--skip-channels \
|
||||
--skip-skills \
|
||||
--skip-daemon \
|
||||
--skip-ui \
|
||||
--skip-health
|
||||
|
||||
validate_local_basic_log "$OPENCLAW_E2E_LAST_LOG_PATH"
|
||||
|
||||
# Assert config + workspace scaffolding.
|
||||
workspace_dir="$OPENCLAW_STATE_DIR/workspace"
|
||||
sessions_dir="$OPENCLAW_STATE_DIR/agents/main/sessions"
|
||||
|
||||
openclaw_e2e_assert_dir "$sessions_dir"
|
||||
for file in AGENTS.md BOOTSTRAP.md IDENTITY.md SOUL.md TOOLS.md USER.md; do
|
||||
openclaw_e2e_assert_file "$workspace_dir/$file"
|
||||
done
|
||||
|
||||
assert_onboard_config local-basic "$workspace_dir"
|
||||
|
||||
}
|
||||
|
||||
run_case_remote_non_interactive() {
|
||||
set_isolated_openclaw_env remote-non-interactive
|
||||
# Smoke test non-interactive remote config write.
|
||||
openclaw_e2e_run_logged remote-non-interactive node "$OPENCLAW_ENTRY" onboard --non-interactive --accept-risk \
|
||||
--mode remote \
|
||||
--remote-url ws://gateway.local:18789 \
|
||||
--remote-token remote-token \
|
||||
--skip-skills \
|
||||
--skip-health
|
||||
|
||||
assert_onboard_config remote-non-interactive
|
||||
}
|
||||
|
||||
run_case_reset() {
|
||||
set_isolated_openclaw_env reset-config
|
||||
node scripts/e2e/lib/onboard/write-config.mjs reset "$OPENCLAW_CONFIG_PATH"
|
||||
|
||||
openclaw_e2e_run_logged reset-config node "$OPENCLAW_ENTRY" onboard \
|
||||
--non-interactive \
|
||||
--accept-risk \
|
||||
--flow quickstart \
|
||||
--mode local \
|
||||
--reset \
|
||||
--skip-channels \
|
||||
--skip-skills \
|
||||
--skip-daemon \
|
||||
--skip-ui \
|
||||
--skip-health
|
||||
|
||||
assert_onboard_config reset
|
||||
}
|
||||
|
||||
run_case_channels() {
|
||||
# Channels-only configure flow.
|
||||
run_wizard_cmd channels channels "node \"$OPENCLAW_ENTRY\" configure --section channels" send_channels_flow
|
||||
|
||||
assert_onboard_config channels
|
||||
}
|
||||
|
||||
run_case_skills() {
|
||||
local home_dir
|
||||
set_isolated_openclaw_env skills
|
||||
home_dir="$HOME"
|
||||
node scripts/e2e/lib/onboard/write-config.mjs skills "$OPENCLAW_CONFIG_PATH"
|
||||
|
||||
run_wizard_cmd skills "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section skills" send_skills_flow
|
||||
|
||||
assert_onboard_config skills
|
||||
}
|
||||
|
||||
validate_local_basic_log() {
|
||||
local log_path="$1"
|
||||
openclaw_e2e_assert_log_not_contains "$log_path" "systemctl --user unavailable"
|
||||
}
|
||||
|
||||
if [ "$OPENCLAW_ONBOARD_SCENARIO_SOURCE_ONLY" != "1" ]; then
|
||||
run_case_local_basic
|
||||
run_case_remote_non_interactive
|
||||
run_case_reset
|
||||
run_case_channels
|
||||
run_case_skills
|
||||
fi
|
||||
21
scripts/e2e/lib/onboard/write-config.mjs
Normal file
21
scripts/e2e/lib/onboard/write-config.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Config writer helper for onboard E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
|
||||
const [scenario, configPath] = process.argv.slice(2);
|
||||
if (!scenario || !configPath) {
|
||||
throw new Error("usage: write-config.mjs <reset|skills> <config-path>");
|
||||
}
|
||||
|
||||
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];
|
||||
if (!config) {
|
||||
throw new Error(`unknown config scenario: ${scenario}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
Reference in New Issue
Block a user