From cce08881ecba48097959fdd90f7738e6636a7cdf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 23:25:52 +0100 Subject: [PATCH] test(release): harden Windows smoke model setup --- .../e2e/lib/doctor-install-switch/scenario.sh | 6 +-- scripts/e2e/parallels/npm-update-scripts.ts | 14 ++--- scripts/e2e/parallels/powershell.ts | 51 +++++++++++++++++++ scripts/e2e/parallels/windows-smoke.ts | 15 +++--- test/scripts/parallels-smoke-model.test.ts | 19 ++++--- 5 files changed, 81 insertions(+), 24 deletions(-) diff --git a/scripts/e2e/lib/doctor-install-switch/scenario.sh b/scripts/e2e/lib/doctor-install-switch/scenario.sh index 07cb8d5c2a4..b9e14176de4 100644 --- a/scripts/e2e/lib/doctor-install-switch/scenario.sh +++ b/scripts/e2e/lib/doctor-install-switch/scenario.sh @@ -129,7 +129,7 @@ run_flow() { local doctor_expected="$5" local install_log="/tmp/openclaw-doctor-switch-${name}-install.log" local doctor_log="/tmp/openclaw-doctor-switch-${name}-doctor.log" - local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-300s}" + local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" echo "== Flow: $name ==" openclaw_test_state_create "switch-${name}" empty @@ -175,7 +175,7 @@ run_proxy_env_flow() { local name="proxy-env-cleanup" local install_log="/tmp/openclaw-doctor-switch-${name}-install.log" local doctor_log="/tmp/openclaw-doctor-switch-${name}-doctor.log" - local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-300s}" + local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" echo "== Flow: $name ==" openclaw_test_state_create "switch-${name}" empty @@ -216,7 +216,7 @@ run_wrapper_flow() { local env_repair_log="/tmp/openclaw-doctor-switch-${name}-env-repair.log" local doctor_log="/tmp/openclaw-doctor-switch-${name}-doctor.log" local clear_log="/tmp/openclaw-doctor-switch-${name}-clear.log" - local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-300s}" + local command_timeout="${OPENCLAW_DOCKER_DOCTOR_SWITCH_COMMAND_TIMEOUT:-900s}" echo "== Flow: $name ==" openclaw_test_state_create "switch-${name}" empty diff --git a/scripts/e2e/parallels/npm-update-scripts.ts b/scripts/e2e/parallels/npm-update-scripts.ts index e6e76244d83..997a0e600dd 100644 --- a/scripts/e2e/parallels/npm-update-scripts.ts +++ b/scripts/e2e/parallels/npm-update-scripts.ts @@ -2,10 +2,13 @@ import { posixAgentWorkspaceScript, windowsAgentWorkspaceScript } from "./agent- import { shellQuote } from "./host-command.ts"; import { psSingleQuote, - windowsModelProviderTimeoutScript, + windowsAgentTurnConfigPatchScript, windowsOpenClawResolver, } from "./powershell.ts"; -import { modelProviderConfigBatchJson } from "./provider-auth.ts"; +import { + modelProviderConfigBatchJson, + resolveParallelsModelTimeoutSeconds, +} from "./provider-auth.ts"; import type { Platform, ProviderAuth } from "./types.ts"; export interface NpmUpdateScriptInput { @@ -201,10 +204,7 @@ if ($LASTEXITCODE -ne 0) { "gateway restart exited with code $LASTEXITCODE; probing readiness before failing" | Out-Host } Wait-OpenClawGateway -Invoke-OpenClaw models set ${psSingleQuote(input.auth.modelId)} -${windowsModelProviderTimeoutScript(input.auth.modelId)} -Invoke-OpenClaw config set agents.defaults.skipBootstrap true --strict-json -Invoke-OpenClaw config set tools.profile minimal +${windowsAgentTurnConfigPatchScript(input.auth.modelId)} $sessionPath = Join-Path $env:USERPROFILE '.openclaw\\agents\\main\\sessions\\parallels-npm-update-windows.jsonl' Remove-Item $sessionPath -Force -ErrorAction SilentlyContinue ${windowsAgentWorkspaceScript("Parallels npm update smoke test assistant.")} @@ -215,7 +215,7 @@ for ($attempt = 1; $attempt -le 2; $attempt++) { $sessionsDir = Join-Path $env:USERPROFILE '.openclaw\\agents\\main\\sessions' $sessionPath = Join-Path $sessionsDir "$sessionId.jsonl" Remove-Item $sessionPath -Force -ErrorAction SilentlyContinue - $output = Invoke-OpenClaw agent --local --agent main --session-id $sessionId --message 'Reply with exact ASCII text OK only.' --thinking minimal --json 2>&1 + $output = Invoke-OpenClaw agent --local --agent main --session-id $sessionId --model ${psSingleQuote(input.auth.modelId)} --message 'Reply with exact ASCII text OK only.' --thinking minimal --timeout ${resolveParallelsModelTimeoutSeconds("windows")} --json 2>&1 if ($null -ne $output) { $output | ForEach-Object { $_ } } if ($LASTEXITCODE -ne 0) { throw "agent failed with exit code $LASTEXITCODE" } if (($output | Out-String) -match '"finalAssistant(Raw|Visible)Text":\\s*"OK"') { diff --git a/scripts/e2e/parallels/powershell.ts b/scripts/e2e/parallels/powershell.ts index c0a0509073f..5c25c6d540e 100644 --- a/scripts/e2e/parallels/powershell.ts +++ b/scripts/e2e/parallels/powershell.ts @@ -1,5 +1,6 @@ import { configPathMapKey, + modelProviderConfigBatchJson, providerIdFromModelId, providerTimeoutConfigJson, } from "./provider-auth.ts"; @@ -49,6 +50,56 @@ Remove-Item $providerTimeoutBatchPath -Force -ErrorAction SilentlyContinue if ($providerTimeoutExit -ne 0) { throw "model provider timeout config set failed" }`; } +export function windowsAgentTurnConfigPatchScript(modelId: string): string { + const batchJson = modelProviderConfigBatchJson(modelId, "windows"); + const payloadJson = JSON.stringify({ + modelId, + operations: batchJson ? (JSON.parse(batchJson) as unknown) : [], + }); + return `$agentTurnConfigPatchPath = $env:OPENCLAW_CONFIG_PATH +if (-not $agentTurnConfigPatchPath) { $agentTurnConfigPatchPath = Join-Path $env:USERPROFILE '.openclaw\\openclaw.json' } +$env:OPENCLAW_PARALLELS_AGENT_CONFIG_PATCH = @' +${payloadJson} +'@ +$env:OPENCLAW_PARALLELS_AGENT_CONFIG_PATH = $agentTurnConfigPatchPath +node.exe -e @' +const fs = require("node:fs"); +const path = require("node:path"); +const configPath = process.env.OPENCLAW_PARALLELS_AGENT_CONFIG_PATH; +const payload = JSON.parse(process.env.OPENCLAW_PARALLELS_AGENT_CONFIG_PATCH || "{}"); +const cfg = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf8")) : {}; +cfg.agents = cfg.agents && typeof cfg.agents === "object" ? cfg.agents : {}; +cfg.agents.defaults = cfg.agents.defaults && typeof cfg.agents.defaults === "object" ? cfg.agents.defaults : {}; +cfg.agents.defaults.skipBootstrap = true; +const existingModel = cfg.agents.defaults.model && typeof cfg.agents.defaults.model === "object" ? cfg.agents.defaults.model : {}; +cfg.agents.defaults.model = { ...existingModel, primary: payload.modelId }; +cfg.agents.defaults.models = cfg.agents.defaults.models && typeof cfg.agents.defaults.models === "object" ? cfg.agents.defaults.models : {}; +cfg.tools = cfg.tools && typeof cfg.tools === "object" ? cfg.tools : {}; +cfg.tools.profile = "minimal"; +for (const op of payload.operations || []) { + const segments = String(op.path || "").match(/(?:[^.[\\]]+)|(?:\\["((?:\\\\.|[^"\\\\])*)"\\])/g) || []; + let cursor = cfg; + for (let i = 0; i < segments.length; i++) { + const raw = segments[i]; + const key = raw.startsWith("[") ? JSON.parse(raw.slice(1, -1)) : raw; + if (i === segments.length - 1) { + const existing = cursor[key] && typeof cursor[key] === "object" && !Array.isArray(cursor[key]) ? cursor[key] : {}; + cursor[key] = op.value && typeof op.value === "object" && !Array.isArray(op.value) ? { ...existing, ...op.value } : op.value; + } else { + cursor[key] = cursor[key] && typeof cursor[key] === "object" && !Array.isArray(cursor[key]) ? cursor[key] : {}; + cursor = cursor[key]; + } + } +} +fs.mkdirSync(path.dirname(configPath), { recursive: true }); +fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\\n", { mode: 0o600 }); +'@ +$agentTurnConfigPatchExit = $LASTEXITCODE +Remove-Item Env:OPENCLAW_PARALLELS_AGENT_CONFIG_PATCH -Force -ErrorAction SilentlyContinue +Remove-Item Env:OPENCLAW_PARALLELS_AGENT_CONFIG_PATH -Force -ErrorAction SilentlyContinue +if ($agentTurnConfigPatchExit -ne 0) { throw "agent turn config patch failed" }`; +} + export const windowsOpenClawResolver = String.raw`function Resolve-OpenClawCommand { if ($script:OpenClawResolvedCommand) { return $script:OpenClawResolvedCommand } $shimCandidates = @() diff --git a/scripts/e2e/parallels/windows-smoke.ts b/scripts/e2e/parallels/windows-smoke.ts index ed68edc6b14..3e88fc072df 100755 --- a/scripts/e2e/parallels/windows-smoke.ts +++ b/scripts/e2e/parallels/windows-smoke.ts @@ -14,6 +14,7 @@ import { resolveHostIp, resolveHostPort, resolveLatestVersion, + resolveParallelsModelTimeoutSeconds, resolveWindowsProviderAuth, resolveSnapshot, run, @@ -36,7 +37,7 @@ import { PhaseRunner } from "./phase-runner.ts"; import { encodePowerShell, psSingleQuote, - windowsModelProviderTimeoutScript, + windowsAgentTurnConfigPatchScript, windowsOpenClawResolver, } from "./powershell.ts"; import { ensureGuestGit, prepareMinGitZip } from "./windows-git.ts"; @@ -891,13 +892,7 @@ if ($LASTEXITCODE -ne 0) { throw "gateway ${action} failed with exit code $LASTE `$ErrorActionPreference = 'Continue' $PSNativeCommandUseErrorActionPreference = $false ${windowsPortableGitPathScript} -Invoke-OpenClaw models set ${psSingleQuote(this.auth.modelId)} -if ($LASTEXITCODE -ne 0) { throw "models set failed" } -${windowsModelProviderTimeoutScript(this.auth.modelId)} -Invoke-OpenClaw config set agents.defaults.skipBootstrap true --strict-json -if ($LASTEXITCODE -ne 0) { throw "config set failed" } -Invoke-OpenClaw config set tools.profile minimal -if ($LASTEXITCODE -ne 0) { throw "tools profile config set failed" } +${windowsAgentTurnConfigPatchScript(this.auth.modelId)} ${windowsAgentWorkspaceScript("Parallels Windows smoke test assistant.")} Set-Item -Path ('Env:' + ${psSingleQuote(this.auth.apiKeyEnv)}) -Value ${psSingleQuote(this.auth.apiKeyValue)} $agentOk = $false @@ -913,10 +908,14 @@ for ($attempt = 1; $attempt -le 2; $attempt++) { 'main', '--session-id', $sessionId, + '--model', + ${psSingleQuote(this.auth.modelId)}, '--message', 'Reply with exact ASCII text OK only.', '--thinking', 'minimal', + '--timeout', + '${resolveParallelsModelTimeoutSeconds("windows")}', '--json' ) $output = Invoke-OpenClaw @args 2>&1 diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 5d4b3ba57c0..c3aca0bb4e3 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -327,8 +327,10 @@ console.log(JSON.stringify(result)); expect(script, scriptPath).toContain("AgentWorkspaceScript"); expect(script, scriptPath).toContain("parallels-"); - expect(script, scriptPath).toContain("agents.defaults.skipBootstrap"); - expect(script, scriptPath).toContain("tools.profile"); + if (scriptPath !== TS_PATHS.windows) { + expect(script, scriptPath).toContain("agents.defaults.skipBootstrap"); + expect(script, scriptPath).toContain("tools.profile"); + } expect(script, scriptPath).toContain("--thinking"); expect(script, scriptPath).toContain("minimal"); expect(script, scriptPath).toContain("finalAssistant(Raw|Visible)Text"); @@ -337,8 +339,11 @@ console.log(JSON.stringify(result)); expect(readFileSync(TS_PATHS.macos, "utf8")).toContain("config set --batch-file"); expect(readFileSync(TS_PATHS.linux, "utf8")).toContain("modelProviderConfigBatchJson"); expect(readFileSync(TS_PATHS.linux, "utf8")).toContain("config set --batch-file"); - expect(readFileSync(TS_PATHS.windows, "utf8")).toContain("windowsModelProviderTimeoutScript"); - expect(readFileSync(TS_PATHS.powershell, "utf8")).toContain("config set --batch-file"); + expect(readFileSync(TS_PATHS.windows, "utf8")).toContain("windowsAgentTurnConfigPatchScript"); + const powershell = readFileSync(TS_PATHS.powershell, "utf8"); + expect(powershell).toContain("config set --batch-file"); + expect(powershell).toContain("agents.defaults.skipBootstrap"); + expect(powershell).toContain("tools.profile"); const npmUpdateScripts = readFileSync(TS_PATHS.npmUpdateScripts, "utf8"); expect(npmUpdateScripts).toContain("posixAgentWorkspaceScript"); @@ -347,7 +352,7 @@ console.log(JSON.stringify(result)); expect(npmUpdateScripts).toContain("--thinking minimal"); expect(npmUpdateScripts).toContain("finalAssistant(Raw|Visible)Text"); expect(npmUpdateScripts).toContain("posixAssertAgentOkScript"); - expect(npmUpdateScripts).toContain("windowsModelProviderTimeoutScript"); + expect(npmUpdateScripts).toContain("windowsAgentTurnConfigPatchScript"); expect(npmUpdateScripts).toContain("modelProviderConfigBatchJson"); expect(npmUpdateScripts).toContain("config set --batch-file"); }); @@ -476,7 +481,9 @@ console.log(JSON.stringify(result)); expect(script).toContain('guestPowerShellBackground(\n "agent-turn"'); expect(script).toContain("OPENCLAW_PARALLELS_WINDOWS_AGENT_TIMEOUT_S"); expect(script).toContain("OPENCLAW_PARALLELS_WINDOWS_AGENT_TIMEOUT_S || 1500"); - expect(script).toContain("windowsModelProviderTimeoutScript(this.auth.modelId)"); + expect(script).toContain("windowsAgentTurnConfigPatchScript(this.auth.modelId)"); + expect(script).toContain("--model"); + expect(script).toContain('resolveParallelsModelTimeoutSeconds("windows")'); expect(script).toContain("finalAssistant(Raw|Visible)Text"); expect(script).toContain("parallels-windows-smoke-retry-$attempt"); expect(script).toContain("agent turn attempt $attempt failed or finished without OK response");