diff --git a/.agents/skills/openclaw-parallels-smoke/SKILL.md b/.agents/skills/openclaw-parallels-smoke/SKILL.md index 1ae78e93c9b..3a2df5aac60 100644 --- a/.agents/skills/openclaw-parallels-smoke/SKILL.md +++ b/.agents/skills/openclaw-parallels-smoke/SKILL.md @@ -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. diff --git a/scripts/e2e/parallels-macos-smoke.sh b/scripts/e2e/parallels-macos-smoke.sh index 2a86be85296..792a1fc30e1 100644 --- a/scripts/e2e/parallels-macos-smoke.sh +++ b/scripts/e2e/parallels-macos-smoke.sh @@ -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." \ diff --git a/scripts/e2e/parallels-windows-smoke.sh b/scripts/e2e/parallels-windows-smoke.sh index 917aade83b5..05a29d0c2c0 100644 --- a/scripts/e2e/parallels-windows-smoke.sh +++ b/scripts/e2e/parallels-windows-smoke.sh @@ -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 <&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" diff --git a/src/agents/skills-install.download.test.ts b/src/agents/skills-install.download.test.ts index 7bccd0a6863..acbd0e15b73 100644 --- a/src/agents/skills-install.download.test.ts +++ b/src/agents/skills-install.download.test.ts @@ -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", }), diff --git a/src/agents/skills.buildworkspaceskillstatus.test.ts b/src/agents/skills.buildworkspaceskillstatus.test.ts index cb3a2b16c0e..c2e111dca3a 100644 --- a/src/agents/skills.buildworkspaceskillstatus.test.ts +++ b/src/agents/skills.buildworkspaceskillstatus.test.ts @@ -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: {}, diff --git a/src/cli/skills-cli.formatting.test.ts b/src/cli/skills-cli.formatting.test.ts index c5dd9499d5c..832660b29d1 100644 --- a/src/cli/skills-cli.formatting.test.ts +++ b/src/cli/skills-cli.formatting.test.ts @@ -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", }),