From ddc2036956031f812485d9b6cde0c70a9d699f58 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 12:11:08 +0100 Subject: [PATCH 1/7] fix: stabilize Parallels plugin smoke paths --- CHANGELOG.md | 1 + extensions/bonjour/src/advertiser.test.ts | 10 +++++++ extensions/bonjour/src/advertiser.ts | 32 ++++++++++++----------- scripts/e2e/parallels-linux-smoke.sh | 14 ++++++++-- scripts/e2e/parallels-npm-update-smoke.sh | 2 +- src/plugins/installed-plugin-index.ts | 3 +++ 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c60adbbfcd..de08cba1708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Gateway/Bonjour: keep @homebridge/ciao cancellation handlers registered across advertiser restarts so late probing cancellations cannot crash Linux and other mDNS-churned gateways. Thanks @codex. - Plugins/startup: load the default `memory-core` slot during Gateway startup when permitted so active-memory recall can call `memory_search` and `memory_get` without requiring an explicit `plugins.slots.memory` entry, while preserving `plugins.slots.memory: "none"`. Thanks @codex. - Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet. - Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc. diff --git a/extensions/bonjour/src/advertiser.test.ts b/extensions/bonjour/src/advertiser.test.ts index 9f13037c7ad..7b1de8be9ed 100644 --- a/extensions/bonjour/src/advertiser.test.ts +++ b/extensions/bonjour/src/advertiser.test.ts @@ -490,6 +490,8 @@ describe("gateway bonjour advertiser", () => { const stateRef = { value: "announcing" }; const events: string[] = []; + const cleanupException = vi.fn(); + const cleanupRejection = vi.fn(); let advertiseCount = 0; const destroy = vi.fn().mockImplementation(async () => { events.push("destroy"); @@ -505,6 +507,8 @@ describe("gateway bonjour advertiser", () => { return Promise.resolve(); }); mockCiaoService({ advertise, destroy, stateRef }); + registerUncaughtExceptionHandler.mockImplementation(() => cleanupException); + registerUnhandledRejectionHandler.mockImplementation(() => cleanupRejection); const started = await startAdvertiser({ gatewayPort: 18789, @@ -513,6 +517,8 @@ describe("gateway bonjour advertiser", () => { expect(createService).toHaveBeenCalledTimes(1); expect(advertise).toHaveBeenCalledTimes(1); + expect(registerUncaughtExceptionHandler).toHaveBeenCalledTimes(1); + expect(registerUnhandledRejectionHandler).toHaveBeenCalledTimes(1); await vi.advanceTimersByTimeAsync(15_000); @@ -521,11 +527,15 @@ describe("gateway bonjour advertiser", () => { expect(advertise).toHaveBeenCalledTimes(2); expect(destroy).toHaveBeenCalledTimes(1); expect(shutdown).not.toHaveBeenCalled(); + expect(cleanupException).not.toHaveBeenCalled(); + expect(cleanupRejection).not.toHaveBeenCalled(); expect(events).toEqual(["advertise:1", "destroy", "advertise:2"]); await started.stop(); expect(destroy).toHaveBeenCalledTimes(2); expect(shutdown).toHaveBeenCalledTimes(1); + expect(cleanupException).toHaveBeenCalledTimes(1); + expect(cleanupRejection).toHaveBeenCalledTimes(1); }); it("treats probing-to-announcing churn as one unhealthy window", async () => { diff --git a/extensions/bonjour/src/advertiser.ts b/extensions/bonjour/src/advertiser.ts index 752e3a21028..0db1fcbed7f 100644 --- a/extensions/bonjour/src/advertiser.ts +++ b/extensions/bonjour/src/advertiser.ts @@ -50,8 +50,6 @@ type CiaoModule = { type BonjourCycle = { responder: BonjourResponder; services: Array<{ label: string; svc: BonjourService }>; - cleanupUncaughtException?: () => void; - cleanupUnhandledRejection?: () => void; }; type ServiceStateTracker = { @@ -179,6 +177,18 @@ export async function startGatewayBonjourAdvertiser( const { getResponder, Protocol } = await loadCiaoModule(); const restoreConsoleLog = installCiaoConsoleNoiseFilter(); let requestCiaoRecovery: ((classification: CiaoProcessErrorClassification) => void) | undefined; + let cleanupUnhandledRejection: (() => void) | undefined; + let cleanupUncaughtException: (() => void) | undefined; + let processHandlersCleaned = false; + + function cleanupProcessHandlers() { + if (processHandlersCleaned) { + return; + } + processHandlersCleaned = true; + cleanupUncaughtException?.(); + cleanupUnhandledRejection?.(); + } const handleCiaoProcessError = (reason: unknown): boolean => { const classification = classifyCiaoProcessError(reason); @@ -196,6 +206,8 @@ export async function startGatewayBonjourAdvertiser( } return true; }; + cleanupUnhandledRejection = deps.registerUnhandledRejectionHandler?.(handleCiaoProcessError); + cleanupUncaughtException = deps.registerUncaughtExceptionHandler?.(handleCiaoProcessError); try { const hostnameRaw = process.env.OPENCLAW_MDNS_HOSTNAME?.trim() || "openclaw"; @@ -259,16 +271,7 @@ export async function startGatewayBonjourAdvertiser( svc: gateway as unknown as BonjourService, }); - const cleanupUnhandledRejection = - services.length > 0 && deps.registerUnhandledRejectionHandler - ? deps.registerUnhandledRejectionHandler(handleCiaoProcessError) - : undefined; - const cleanupUncaughtException = - services.length > 0 && deps.registerUncaughtExceptionHandler - ? deps.registerUncaughtExceptionHandler(handleCiaoProcessError) - : undefined; - - return { responder, services, cleanupUncaughtException, cleanupUnhandledRejection }; + return { responder, services }; } async function stopCycle(cycle: BonjourCycle | null, opts?: { shutdownResponder?: boolean }) { @@ -288,9 +291,6 @@ export async function startGatewayBonjourAdvertiser( } } catch { /* ignore */ - } finally { - cycle.cleanupUncaughtException?.(); - cycle.cleanupUnhandledRejection?.(); } } @@ -483,10 +483,12 @@ export async function startGatewayBonjourAdvertiser( } await stopCycle(cycle, { shutdownResponder: true }); restoreConsoleLog(); + cleanupProcessHandlers(); }, }; } catch (err) { restoreConsoleLog(); + cleanupProcessHandlers(); throw err; } } diff --git a/scripts/e2e/parallels-linux-smoke.sh b/scripts/e2e/parallels-linux-smoke.sh index 4b0712a24be..2d4866048ad 100644 --- a/scripts/e2e/parallels-linux-smoke.sh +++ b/scripts/e2e/parallels-linux-smoke.sh @@ -42,6 +42,7 @@ TIMEOUT_ONBOARD_S=180 TIMEOUT_AGENT_S="${OPENCLAW_PARALLELS_LINUX_AGENT_TIMEOUT_S:-300}" TIMEOUT_GATEWAY_S=240 PHASE_STALE_WARN_S=60 +DISABLE_BONJOUR_FOR_GATEWAY=0 FRESH_MAIN_STATUS="skip" FRESH_MAIN_VERSION="skip" @@ -230,6 +231,11 @@ esac API_KEY_VALUE="${!API_KEY_ENV:-}" [[ -n "$API_KEY_VALUE" ]] || die "$API_KEY_ENV is required" +case "${OPENCLAW_PARALLELS_LINUX_DISABLE_BONJOUR:-}" in + 1|true|TRUE|yes|YES|on|ON) + DISABLE_BONJOUR_FOR_GATEWAY=1 + ;; +esac resolve_vm_name() { local json requested explicit @@ -725,12 +731,16 @@ EOF } start_gateway_background() { - local cmd api_key_value_q + local cmd api_key_value_q bonjour_env api_key_value_q="$(shell_quote "$API_KEY_VALUE")" + bonjour_env="" + if [[ "$DISABLE_BONJOUR_FOR_GATEWAY" -eq 1 ]]; then + bonjour_env=" OPENCLAW_DISABLE_BONJOUR=1" + fi cmd="$(cat </dev/null 2>&1 || true rm -f /tmp/openclaw-parallels-linux-gateway.log -setsid sh -lc 'exec env OPENCLAW_HOME=/root OPENCLAW_STATE_DIR=/root/.openclaw OPENCLAW_CONFIG_PATH=/root/.openclaw/openclaw.json ${API_KEY_ENV}=${api_key_value_q} openclaw gateway run --bind loopback --port 18789 --force >/tmp/openclaw-parallels-linux-gateway.log 2>&1' >/dev/null 2>&1 < /dev/null & +setsid sh -lc 'exec env OPENCLAW_HOME=/root OPENCLAW_STATE_DIR=/root/.openclaw OPENCLAW_CONFIG_PATH=/root/.openclaw/openclaw.json${bonjour_env} ${API_KEY_ENV}=${api_key_value_q} openclaw gateway run --bind loopback --port 18789 --force >/tmp/openclaw-parallels-linux-gateway.log 2>&1' >/dev/null 2>&1 < /dev/null & EOF )" guest_exec bash -lc "$cmd" diff --git a/scripts/e2e/parallels-npm-update-smoke.sh b/scripts/e2e/parallels-npm-update-smoke.sh index 7dfb84e3335..d37e8376f65 100755 --- a/scripts/e2e/parallels-npm-update-smoke.sh +++ b/scripts/e2e/parallels-npm-update-smoke.sh @@ -1944,7 +1944,7 @@ if platform_enabled windows; then fi if platform_enabled linux; then - bash "$ROOT_DIR/scripts/e2e/parallels-linux-smoke.sh" \ + OPENCLAW_PARALLELS_LINUX_DISABLE_BONJOUR=1 bash "$ROOT_DIR/scripts/e2e/parallels-linux-smoke.sh" \ --mode fresh \ --provider "$PROVIDER" \ --model "$MODEL_ID" \ diff --git a/src/plugins/installed-plugin-index.ts b/src/plugins/installed-plugin-index.ts index e8b4f4cc40f..5c98dd9d553 100644 --- a/src/plugins/installed-plugin-index.ts +++ b/src/plugins/installed-plugin-index.ts @@ -610,6 +610,9 @@ function buildInstalledPluginIndex( if (record.setupSource) { indexRecord.setupSource = record.setupSource; } + if (record.syntheticAuthRefs && record.syntheticAuthRefs.length > 0) { + indexRecord.syntheticAuthRefs = record.syntheticAuthRefs; + } if (candidate?.packageName) { indexRecord.packageName = candidate.packageName; } From 73affb491a70f9baa235d07386247a8bddc8abdc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 12:39:38 +0100 Subject: [PATCH 2/7] fix: bound dev update cleanup --- scripts/e2e/parallels-macos-smoke.sh | 1 + scripts/e2e/parallels-windows-smoke.sh | 3 +- src/infra/update-runner.test.ts | 100 ++++++++++++++++++++++++- src/infra/update-runner.ts | 23 +++--- 4 files changed, 114 insertions(+), 13 deletions(-) diff --git a/scripts/e2e/parallels-macos-smoke.sh b/scripts/e2e/parallels-macos-smoke.sh index 9659ed3eac9..9068463873a 100644 --- a/scripts/e2e/parallels-macos-smoke.sh +++ b/scripts/e2e/parallels-macos-smoke.sh @@ -1149,6 +1149,7 @@ run_dev_channel_update() { rm -rf $(shell_quote "$update_root") export PATH=$(shell_quote "$bootstrap_bin:$GUEST_EXEC_PATH") /usr/bin/env NODE_OPTIONS=--max-old-space-size=4096 \ + OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 \ $GUEST_NODE_BIN $GUEST_OPENCLAW_ENTRY update --channel dev --yes --json EOF )" "$update_log" "$update_done" "$TIMEOUT_UPDATE_DEV_S" "$update_runner" diff --git a/scripts/e2e/parallels-windows-smoke.sh b/scripts/e2e/parallels-windows-smoke.sh index 63088e20709..8af4617696f 100644 --- a/scripts/e2e/parallels-windows-smoke.sh +++ b/scripts/e2e/parallels-windows-smoke.sh @@ -2398,8 +2398,9 @@ New-Item -ItemType Directory -Path $stateDir -Force | Out-Null Remove-Item (Join-Path $workspace 'BOOTSTRAP.md') -Force -ErrorAction SilentlyContinue EOF )" + stop_gateway guest_run_openclaw "$API_KEY_ENV" "$API_KEY_VALUE" \ - agent --agent main --session-id parallels-windows-smoke --message "Reply with exact ASCII text OK only." --json + agent --local --agent main --session-id parallels-windows-smoke --message "Reply with exact ASCII text OK only." --json } capture_latest_ref_failure() { diff --git a/src/infra/update-runner.test.ts b/src/infra/update-runner.test.ts index 769051dec31..4a3d5cb6ec8 100644 --- a/src/infra/update-runner.test.ts +++ b/src/infra/update-runner.test.ts @@ -710,6 +710,7 @@ describe("runGatewayUpdate", () => { it("does not fail a good windows dev preflight only because worktree cleanup hit long paths", async () => { await setupGitPackageManagerFixture(); const calls: string[] = []; + const cleanupTimeouts: Array = []; const upstreamSha = "upstream123"; const doctorNodePath = await resolveStableNodePath(process.execPath); const doctorCommand = `${doctorNodePath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`; @@ -718,7 +719,7 @@ describe("runGatewayUpdate", () => { try { const runCommand = async ( argv: string[], - _options?: { env?: NodeJS.ProcessEnv; cwd?: string; timeoutMs?: number }, + options?: { env?: NodeJS.ProcessEnv; cwd?: string; timeoutMs?: number }, ) => { const key = argv.join(" "); calls.push(key); @@ -772,6 +773,7 @@ describe("runGatewayUpdate", () => { key.startsWith(`git -C ${tempDir} worktree remove --force `) && preflightPrefixPattern.test(key) ) { + cleanupTimeouts.push(options?.timeoutMs); return { stdout: "", stderr: "error: failed to delete worktree: Filename too long", @@ -798,6 +800,7 @@ describe("runGatewayUpdate", () => { expect(result.status).toBe("ok"); const cleanupStep = result.steps.find((step) => step.name === "preflight cleanup"); expect(cleanupStep?.exitCode).toBe(0); + expect(cleanupTimeouts[0]).toBeLessThanOrEqual(60_000); expect(cleanupStep?.stderrTail ?? "").toContain( "windows fallback cleanup removed preflight tree", ); @@ -806,6 +809,101 @@ describe("runGatewayUpdate", () => { } }); + it("falls back when dev preflight worktree cleanup times out", async () => { + await setupGitPackageManagerFixture(); + const calls: string[] = []; + const cleanupTimeouts: Array = []; + const upstreamSha = "upstream123"; + const doctorNodePath = await resolveStableNodePath(process.execPath); + const doctorCommand = `${doctorNodePath} ${path.join(tempDir, "openclaw.mjs")} doctor --non-interactive --fix`; + + const runCommand = async ( + argv: string[], + options?: { env?: NodeJS.ProcessEnv; cwd?: string; timeoutMs?: number }, + ) => { + const key = argv.join(" "); + calls.push(key); + + if (key === `git -C ${tempDir} rev-parse --show-toplevel`) { + return { stdout: tempDir, stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rev-parse HEAD`) { + return { stdout: "abc123", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rev-parse --abbrev-ref HEAD`) { + return { stdout: "main", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} status --porcelain -- :!dist/control-ui/`) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rev-parse --abbrev-ref --symbolic-full-name @{upstream}`) { + return { stdout: "origin/main", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} fetch --all --prune --tags`) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rev-parse @{upstream}`) { + return { stdout: upstreamSha, stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rev-list --max-count=10 ${upstreamSha}`) { + return { stdout: `${upstreamSha}\n`, stderr: "", code: 0 }; + } + if (key === "pnpm --version") { + return { stdout: "10.0.0", stderr: "", code: 0 }; + } + if ( + key.startsWith(`git -C ${tempDir} worktree add --detach /tmp/`) && + key.endsWith(` ${upstreamSha}`) && + preflightPrefixPattern.test(key) + ) { + return { stdout: `HEAD is now at ${upstreamSha}`, stderr: "", code: 0 }; + } + if ( + key.startsWith("git -C /tmp/") && + preflightPrefixPattern.test(key) && + key.includes(" checkout --detach ") && + key.endsWith(upstreamSha) + ) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === "pnpm install" || key === "pnpm build" || key === "pnpm lint") { + return { stdout: "", stderr: "", code: 0 }; + } + if ( + key.startsWith(`git -C ${tempDir} worktree remove --force `) && + preflightPrefixPattern.test(key) + ) { + cleanupTimeouts.push(options?.timeoutMs); + return { + stdout: "", + stderr: "Command timed out after 60000ms", + code: null, + }; + } + if (key === `git -C ${tempDir} worktree prune`) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === `git -C ${tempDir} rebase ${upstreamSha}`) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === doctorCommand) { + return { stdout: "", stderr: "", code: 0 }; + } + if (key === "pnpm ui:build") { + return { stdout: "", stderr: "", code: 0 }; + } + return { stdout: "", stderr: "", code: 0 }; + }; + + const result = await runWithCommand(runCommand, { channel: "dev" }); + + expect(result.status).toBe("ok"); + const cleanupStep = result.steps.find((step) => step.name === "preflight cleanup"); + expect(cleanupStep?.exitCode).toBe(0); + expect(cleanupTimeouts[0]).toBeLessThanOrEqual(60_000); + expect(cleanupStep?.stderrTail ?? "").toContain("fallback cleanup removed preflight tree"); + }); + it("adds heap headroom to windows pnpm build steps during dev updates", async () => { await setupGitPackageManagerFixture(); const upstreamSha = "upstream123"; diff --git a/src/infra/update-runner.ts b/src/infra/update-runner.ts index ac8da655674..2623382b8b9 100644 --- a/src/infra/update-runner.ts +++ b/src/infra/update-runner.ts @@ -138,6 +138,7 @@ const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME]); const PREFLIGHT_TEMP_PREFIX = process.platform === "win32" ? "ocu-pf-" : "openclaw-update-preflight-"; const PREFLIGHT_WORKTREE_DIRNAME = process.platform === "win32" ? "wt" : "worktree"; +const PREFLIGHT_CLEANUP_TIMEOUT_MS = 60_000; const WINDOWS_PREFLIGHT_BASE_DIR = "ocu"; const WINDOWS_BUILD_MAX_OLD_SPACE_MB = 4096; @@ -215,10 +216,7 @@ async function removePathRecursive(target: string) { .catch(() => {}); } -async function repairWindowsPreflightCleanup(worktreeDir: string, preflightRoot: string) { - if (process.platform !== "win32") { - return false; - } +async function repairPreflightCleanup(worktreeDir: string, preflightRoot: string) { try { await fs.rm(worktreeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 }); await fs.rm(preflightRoot, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 }); @@ -938,22 +936,25 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< break; } } finally { - const removeStep = await runStep( - step( + const removeStep = await runStep({ + ...step( "preflight cleanup", ["git", "-C", gitRoot, "worktree", "remove", "--force", worktreeDir], gitRoot, ), - ); + timeoutMs: Math.min(timeoutMs, PREFLIGHT_CLEANUP_TIMEOUT_MS), + }); if ( removeStep.exitCode !== 0 && - (await repairWindowsPreflightCleanup(worktreeDir, preflightRoot)) + (await repairPreflightCleanup(worktreeDir, preflightRoot)) ) { removeStep.exitCode = 0; + const fallbackMessage = + process.platform === "win32" + ? "windows fallback cleanup removed preflight tree" + : "fallback cleanup removed preflight tree"; removeStep.stderrTail = trimLogTail( - [removeStep.stderrTail, "windows fallback cleanup removed preflight tree"] - .filter(Boolean) - .join("\n"), + [removeStep.stderrTail, fallbackMessage].filter(Boolean).join("\n"), MAX_LOG_CHARS, ); } From 7e51866d235295c1338906fc35687d70a104567e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 12:44:01 +0100 Subject: [PATCH 3/7] fix: sync Parallels Linux clock --- scripts/e2e/parallels-linux-smoke.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/e2e/parallels-linux-smoke.sh b/scripts/e2e/parallels-linux-smoke.sh index 2d4866048ad..da66da93526 100644 --- a/scripts/e2e/parallels-linux-smoke.sh +++ b/scripts/e2e/parallels-linux-smoke.sh @@ -466,7 +466,18 @@ restore_snapshot() { wait_for_guest_ready || die "guest did not become ready in $VM_NAME" } +sync_guest_clock() { + local host_now + host_now="$(date -u '+%Y-%m-%d %H:%M:%S UTC')" + guest_exec date -u -s "$host_now" >/dev/null + guest_exec hwclock --systohc >/dev/null 2>&1 || true + guest_exec timedatectl set-ntp true >/dev/null 2>&1 || true + guest_exec systemctl restart systemd-timesyncd >/dev/null 2>&1 || true + guest_exec date -u +} + bootstrap_guest() { + sync_guest_clock guest_exec apt-get -o Acquire::Check-Date=false update guest_exec apt-get install -y curl ca-certificates } From 79ad635515eecafbd510e853a44e31af45bd88bf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 12:46:58 +0100 Subject: [PATCH 4/7] fix: pass Linux clock sync as epoch --- scripts/e2e/parallels-linux-smoke.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e/parallels-linux-smoke.sh b/scripts/e2e/parallels-linux-smoke.sh index da66da93526..d56a3a96d27 100644 --- a/scripts/e2e/parallels-linux-smoke.sh +++ b/scripts/e2e/parallels-linux-smoke.sh @@ -468,7 +468,7 @@ restore_snapshot() { sync_guest_clock() { local host_now - host_now="$(date -u '+%Y-%m-%d %H:%M:%S UTC')" + host_now="@$(date -u '+%s')" guest_exec date -u -s "$host_now" >/dev/null guest_exec hwclock --systohc >/dev/null 2>&1 || true guest_exec timedatectl set-ntp true >/dev/null 2>&1 || true From a87edd732d369a79308038c620e810469de58532 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 26 Apr 2026 16:12:41 +0100 Subject: [PATCH 5/7] fix: harden Windows Parallels smoke --- scripts/e2e/parallels-windows-smoke.sh | 199 ++++++++++++++++++++++++- scripts/install.ps1 | 4 +- test/scripts/install-ps1.test.ts | 38 +++++ 3 files changed, 232 insertions(+), 9 deletions(-) diff --git a/scripts/e2e/parallels-windows-smoke.sh b/scripts/e2e/parallels-windows-smoke.sh index 8af4617696f..ddd56da0377 100644 --- a/scripts/e2e/parallels-windows-smoke.sh +++ b/scripts/e2e/parallels-windows-smoke.sh @@ -614,6 +614,150 @@ EOF )" } +guest_run_agent_turn_process() { + local env_name_q env_value_q runner_basename runner_script_path runner_url runner_url_q + local runner_name stdout_name stderr_name done_name + local start_seconds poll_deadline startup_checked state_rc log_rc done_rc + local agent_combined done_status launcher_state + env_name_q="$(ps_single_quote "$API_KEY_ENV")" + env_value_q="$(ps_single_quote "$API_KEY_VALUE")" + runner_basename="openclaw-parallels-agent-runner-$RANDOM-$RANDOM.ps1" + runner_script_path="$MAIN_TGZ_DIR/$runner_basename" + runner_url="http://$HOST_IP:$HOST_PORT/$runner_basename" + runner_url_q="$(ps_single_quote "$runner_url")" + runner_name="openclaw-parallels-agent-$RANDOM-$RANDOM.ps1" + stdout_name="openclaw-parallels-agent-$RANDOM-$RANDOM.out.log" + stderr_name="openclaw-parallels-agent-$RANDOM-$RANDOM.err.log" + done_name="openclaw-parallels-agent-$RANDOM-$RANDOM.done" + start_seconds="$SECONDS" + poll_deadline=$((SECONDS + TIMEOUT_AGENT_S + 60)) + startup_checked=0 + + cat >"$runner_script_path" <<'EOF' +param( + [string]$StdoutPath, + [string]$StderrPath, + [string]$DonePath, + [string]$EnvName, + [string]$EnvValue +) +$ErrorActionPreference = 'Continue' +try { + if ($EnvName -ne '') { + Set-Item -Path ('Env:' + $EnvName) -Value $EnvValue + } + $node = Join-Path $env:ProgramFiles 'nodejs\node.exe' + if (-not (Test-Path $node)) { + $node = 'node' + } + $entry = Join-Path $env:APPDATA 'npm\node_modules\openclaw\openclaw.mjs' + & $node $entry agent --local --agent main --session-id 'parallels-windows-smoke' --message 'Reply with exact ASCII text OK only.' --json > $StdoutPath 2> $StderrPath + Set-Content -Path $DonePath -Value ([string]$LASTEXITCODE) + exit $LASTEXITCODE +} catch { + $_ | Out-String | Set-Content -Path $StderrPath + Set-Content -Path $DonePath -Value '1' + exit 1 +} +EOF + + guest_powershell_poll 20 "$(cat <