fix: harden parallels smoke verification

This commit is contained in:
Peter Steinberger 2026-03-28 01:48:56 +00:00
parent a724246547
commit 923b316ddc
6 changed files with 109 additions and 5 deletions

View File

@ -45,6 +45,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- The macOS smoke should include a dashboard load phase after gateway health: resolve the tokenized URL with `openclaw dashboard --no-open`, verify the served HTML contains the Control UI title/root shell, then open Safari and require an established localhost TCP connection from Safari to the gateway port.
- `prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters.
- Multi-word `openclaw agent --message ...` checks should go through a guest shell wrapper (`guest_current_user_sh` / `guest_current_user_cli` or `/bin/sh -lc ...`), not raw `prlctl exec ... node openclaw.mjs ...`, or the message can be split into extra argv tokens and Commander reports `too many arguments for 'agent'`.
- When ref-mode onboarding stores `OPENAI_API_KEY` as an env secret ref, the post-onboard agent verification should also export `OPENAI_API_KEY` for the guest command. The gateway can still reject with pairing-required and fall back to embedded execution, and that fallback needs the env-backed credential available in the shell.
- On the fresh Tahoe snapshot, `brew` exists but `node` may be missing from PATH in noninteractive exec. Use `/opt/homebrew/bin/node` when needed.
- Fresh host-served tgz installs should install as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- Root-installed tgz smoke can log plugin blocks for world-writable `extensions/*`; do not treat that as an onboarding or gateway failure unless plugin loading is the task.
@ -60,6 +61,8 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Windows installer/tgz phases now retry once after guest-ready recheck; keep new Windows smoke steps idempotent so a transport-flake retry is safe.
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
- Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes.
- Fresh Windows ref-mode agent verification should set `OPENAI_API_KEY` in the PowerShell environment before invoking `openclaw.cmd agent`, for the same pairing-required fallback reason as macOS.
- The Windows upgrade smoke lane should restart the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`, or the old process can keep the previous gateway token and fail `gateway-health` with `unauthorized: gateway token mismatch`.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.

View File

@ -740,6 +740,7 @@ show_gateway_status_compat() {
verify_turn() {
guest_current_user_cli \
/usr/bin/env "OPENAI_API_KEY=$OPENAI_API_KEY_VALUE" \
"$GUEST_OPENCLAW_BIN" agent \
--agent main \
--message "Reply with exact ASCII text OK only." \

View File

@ -925,6 +925,100 @@ verify_gateway() {
guest_run_openclaw "" "" gateway status --deep --require-rpc
}
restart_gateway() {
local runner_name log_name done_name done_status launcher_state
local poll_rc state_rc log_rc start_seconds poll_deadline startup_checked
runner_name="openclaw-gateway-restart-$RANDOM-$RANDOM.ps1"
log_name="openclaw-gateway-restart-$RANDOM-$RANDOM.log"
done_name="openclaw-gateway-restart-$RANDOM-$RANDOM.done"
start_seconds="$SECONDS"
poll_deadline=$((SECONDS + TIMEOUT_GATEWAY_S + 60))
startup_checked=0
guest_powershell "$(cat <<EOF
\$runner = Join-Path \$env:TEMP '$runner_name'
\$log = Join-Path \$env:TEMP '$log_name'
\$done = Join-Path \$env:TEMP '$done_name'
Remove-Item \$runner, \$log, \$done -Force -ErrorAction SilentlyContinue
@'
\$ErrorActionPreference = 'Stop'
\$PSNativeCommandUseErrorActionPreference = \$false
\$log = Join-Path \$env:TEMP '$log_name'
\$done = Join-Path \$env:TEMP '$done_name'
try {
\$openclaw = Join-Path \$env:APPDATA 'npm\openclaw.cmd'
& \$openclaw gateway restart *>&1 | Tee-Object -FilePath \$log -Append | Out-Null
Set-Content -Path \$done -Value ([string]\$LASTEXITCODE)
} catch {
if (Test-Path \$log) {
Add-Content -Path \$log -Value (\$_ | Out-String)
} else {
(\$_ | Out-String) | Set-Content -Path \$log
}
Set-Content -Path \$done -Value '1'
}
'@ | Set-Content -Path \$runner
Start-Process powershell.exe -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', \$runner) -WindowStyle Hidden | Out-Null
EOF
)"
while :; do
set +e
done_status="$(
guest_powershell_poll 20 "\$done = Join-Path \$env:TEMP '$done_name'; if (Test-Path \$done) { (Get-Content \$done -Raw).Trim() }"
)"
poll_rc=$?
set -e
done_status="${done_status//$'\r'/}"
if [[ $poll_rc -ne 0 ]]; then
warn "windows gateway restart helper poll failed; retrying"
if (( SECONDS >= poll_deadline )); then
warn "windows gateway restart helper timed out while polling done file"
return 1
fi
sleep 2
continue
fi
if [[ -n "$done_status" ]]; then
set +e
guest_powershell_poll 20 "\$log = Join-Path \$env:TEMP '$log_name'; if (Test-Path \$log) { Get-Content \$log }"
log_rc=$?
set -e
if [[ $log_rc -ne 0 ]]; then
warn "windows gateway restart helper log drain failed after completion"
fi
[[ "$done_status" == "0" ]]
return $?
fi
if [[ "$startup_checked" -eq 0 && $((SECONDS - start_seconds)) -ge 20 ]]; then
set +e
launcher_state="$(
guest_powershell_poll 20 "\$runner = Join-Path \$env:TEMP '$runner_name'; \$log = Join-Path \$env:TEMP '$log_name'; \$done = Join-Path \$env:TEMP '$done_name'; 'runner=' + (Test-Path \$runner) + ' log=' + (Test-Path \$log) + ' done=' + (Test-Path \$done)"
)"
state_rc=$?
set -e
launcher_state="${launcher_state//$'\r'/}"
startup_checked=1
if [[ $state_rc -eq 0 && "$launcher_state" == *"runner=False"* && "$launcher_state" == *"log=False"* && "$launcher_state" == *"done=False"* ]]; then
warn "windows gateway restart helper failed to materialize guest files"
return 1
fi
fi
if (( SECONDS >= poll_deadline )); then
set +e
guest_powershell_poll 20 "\$log = Join-Path \$env:TEMP '$log_name'; if (Test-Path \$log) { Get-Content \$log }"
log_rc=$?
set -e
if [[ $log_rc -ne 0 ]]; then
warn "windows gateway restart helper log drain failed after timeout"
fi
warn "windows gateway restart helper timed out waiting for done file"
return 1
fi
sleep 2
done
}
show_gateway_status_compat() {
if guest_run_openclaw "" "" gateway status --help | grep -Fq -- "--require-rpc"; then
guest_run_openclaw "" "" gateway status --deep --require-rpc
@ -934,7 +1028,8 @@ show_gateway_status_compat() {
}
verify_turn() {
guest_run_openclaw "" "" agent --agent main --message "Reply with exact ASCII text OK only." --json
guest_run_openclaw "OPENAI_API_KEY" "$OPENAI_API_KEY_VALUE" \
agent --agent main --message "Reply with exact ASCII text OK only." --json
}
capture_latest_ref_failure() {
@ -990,6 +1085,7 @@ run_upgrade_lane() {
phase_run "upgrade.install-main" "$TIMEOUT_INSTALL_S" install_main_tgz "$host_ip" "openclaw-main-upgrade.tgz" || return $?
UPGRADE_MAIN_VERSION="$(extract_last_version "$(phase_log_path upgrade.install-main)")"
phase_run "upgrade.verify-main-version" "$TIMEOUT_VERIFY_S" verify_target_version || return $?
phase_run "upgrade.gateway-restart" "$TIMEOUT_GATEWAY_S" restart_gateway || return $?
phase_run "upgrade.onboard-ref" "$TIMEOUT_ONBOARD_S" run_ref_onboard || return $?
phase_run "upgrade.gateway-status" "$TIMEOUT_GATEWAY_S" verify_gateway || return $?
UPGRADE_GATEWAY_STATUS="pass"

View File

@ -55,11 +55,12 @@ const TAR_GZ_TRAVERSAL_BUFFER = Buffer.from(
function buildEntry(name: string): SkillEntry {
const skillDir = path.join(workspaceDir, "skills", name);
const filePath = path.join(skillDir, "SKILL.md");
return {
skill: createFixtureSkill({
name,
description: `${name} test skill`,
filePath: path.join(skillDir, "SKILL.md"),
filePath,
baseDir: skillDir,
source: "openclaw-workspace",
}),

View File

@ -30,12 +30,14 @@ function makeEntry(params: {
label?: string;
}>;
}): SkillEntry {
const filePath = `/tmp/${params.name}/SKILL.md`;
const baseDir = `/tmp/${params.name}`;
return {
skill: createFixtureSkill({
name: params.name,
description: `desc:${params.name}`,
filePath: `/tmp/${params.name}/SKILL.md`,
baseDir: `/tmp/${params.name}`,
filePath,
baseDir,
source: params.source ?? "openclaw-workspace",
}),
frontmatter: {},

View File

@ -32,12 +32,13 @@ describe("skills-cli (e2e)", () => {
function createEntries(): SkillEntry[] {
const baseDir = path.join(tempWorkspaceDir, "peekaboo");
const filePath = path.join(baseDir, "SKILL.md");
return [
{
skill: createFixtureSkill({
name: "peekaboo",
description: "Capture UI screenshots",
filePath: path.join(baseDir, "SKILL.md"),
filePath,
baseDir,
source: "openclaw-bundled",
}),