diff --git a/scripts/e2e/parallels/macos-smoke.ts b/scripts/e2e/parallels/macos-smoke.ts index c0b5debcd14..bc7d0589a0f 100755 --- a/scripts/e2e/parallels/macos-smoke.ts +++ b/scripts/e2e/parallels/macos-smoke.ts @@ -862,7 +862,7 @@ const config = JSON.parse(fs.readFileSync(configPath, "utf8")); config.update = { ...(config.update || {}), channel: "dev" }; fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\\n"); JS -/usr/bin/env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 ${guestNode} ${guestOpenClawEntry} update --channel dev --yes --json +/usr/bin/env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS=1 OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 ${guestNode} ${guestOpenClawEntry} update --channel dev --yes --json ${guestNode} ${guestOpenClawEntry} --version ${guestNode} ${guestOpenClawEntry} update status --json`, ); diff --git a/scripts/e2e/parallels/npm-update-scripts.ts b/scripts/e2e/parallels/npm-update-scripts.ts index 1a6c769a340..1071a4d5e43 100644 --- a/scripts/e2e/parallels/npm-update-scripts.ts +++ b/scripts/e2e/parallels/npm-update-scripts.ts @@ -65,7 +65,7 @@ wait_for_gateway() { } scrub_future_plugin_entries stop_openclaw_gateway_processes -OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 /opt/homebrew/bin/openclaw update --tag ${shellQuote(input.updateTarget)} --yes --json --no-restart +OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS=1 OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 /opt/homebrew/bin/openclaw update --tag ${shellQuote(input.updateTarget)} --yes --json --no-restart ${posixVersionCheck("/opt/homebrew/bin/openclaw", input.expectedNeedle)} start_openclaw_gateway wait_for_gateway @@ -111,6 +111,7 @@ function Stop-OpenClawGatewayProcesses { Remove-FuturePluginEntries Stop-OpenClawGatewayProcesses $env:OPENCLAW_DISABLE_BUNDLED_PLUGINS = '1' +$env:OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS = '1' $updateOutput = Invoke-OpenClaw update --tag ${psSingleQuote(input.updateTarget)} --yes --json --no-restart 2>&1 $updateExit = $LASTEXITCODE $updateOutput @@ -215,7 +216,7 @@ wait_for_gateway() { } scrub_future_plugin_entries stop_openclaw_gateway_processes -OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 openclaw update --tag ${shellQuote(input.updateTarget)} --yes --json --no-restart +OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS=1 OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 openclaw update --tag ${shellQuote(input.updateTarget)} --yes --json --no-restart ${posixVersionCheck("openclaw", input.expectedNeedle)} start_openclaw_gateway wait_for_gateway diff --git a/scripts/e2e/parallels/npm-update-smoke.ts b/scripts/e2e/parallels/npm-update-smoke.ts index 5a00c619762..1bc26546190 100755 --- a/scripts/e2e/parallels/npm-update-smoke.ts +++ b/scripts/e2e/parallels/npm-update-smoke.ts @@ -13,6 +13,7 @@ import { resolveHostIp, resolveLatestVersion, resolveProviderAuth, + resolveWindowsProviderAuth, run, say, startHostServer, @@ -145,6 +146,7 @@ function platformRecord(value: T): Record { class NpmUpdateSmoke { private auth: ProviderAuth; + private windowsAuth: ProviderAuth; private runDir = ""; private tgzDir = ""; private latestVersion = ""; @@ -168,6 +170,11 @@ class NpmUpdateSmoke { modelId: options.modelId, provider: options.provider, }); + this.windowsAuth = resolveWindowsProviderAuth({ + apiKeyEnv: options.apiKeyEnv, + modelId: options.modelId, + provider: options.provider, + }); } async run(): Promise { @@ -243,6 +250,7 @@ class NpmUpdateSmoke { env: NodeJS.ProcessEnv = {}, ): Job { const logPath = path.join(this.runDir, `${platform}-fresh.log`); + const auth = this.authForPlatform(platform); const args = [ "exec", "tsx", @@ -252,9 +260,9 @@ class NpmUpdateSmoke { "--provider", this.options.provider, "--model", - this.auth.modelId, + auth.modelId, "--api-key-env", - this.auth.apiKeyEnv, + auth.apiKeyEnv, "--target-package-spec", this.packageSpec, "--json", @@ -376,7 +384,7 @@ class NpmUpdateSmoke { private updateScript(platform: Platform): string { const input = { - auth: this.auth, + auth: this.authForPlatform(platform), expectedNeedle: this.updateExpectedNeedle, updateTarget: this.updateTargetEffective, }; @@ -391,6 +399,10 @@ class NpmUpdateSmoke { return die("unsupported platform"); } + private authForPlatform(platform: Platform): ProviderAuth { + return platform === "windows" ? this.windowsAuth : this.auth; + } + private spawnLogged( command: string, args: string[], diff --git a/scripts/e2e/parallels/provider-auth.ts b/scripts/e2e/parallels/provider-auth.ts index f2d0338bb4d..b1a3019f443 100644 --- a/scripts/e2e/parallels/provider-auth.ts +++ b/scripts/e2e/parallels/provider-auth.ts @@ -53,6 +53,25 @@ export function resolveProviderAuth(input: { return { ...resolved, apiKeyValue }; } +export function resolveWindowsProviderAuth(input: { + provider: Provider; + apiKeyEnv?: string; + modelId?: string; +}): ProviderAuth { + const auth = resolveProviderAuth(input); + if (input.provider !== "openai" || input.modelId) { + return auth; + } + const windowsModel = process.env.OPENCLAW_PARALLELS_WINDOWS_OPENAI_MODEL?.trim(); + if (windowsModel) { + return { ...auth, modelId: windowsModel }; + } + if (process.env.OPENCLAW_PARALLELS_OPENAI_MODEL?.trim()) { + return auth; + } + return { ...auth, modelId: "openai/gpt-4.1-mini" }; +} + export function parseProvider(value: string): Provider { if (value === "openai" || value === "anthropic" || value === "minimax") { return value; diff --git a/scripts/e2e/parallels/windows-smoke.ts b/scripts/e2e/parallels/windows-smoke.ts index 82db57d5f73..6b292d7d3cb 100755 --- a/scripts/e2e/parallels/windows-smoke.ts +++ b/scripts/e2e/parallels/windows-smoke.ts @@ -14,7 +14,7 @@ import { resolveHostIp, resolveHostPort, resolveLatestVersion, - resolveProviderAuth, + resolveWindowsProviderAuth, resolveSnapshot, run, runStreaming, @@ -241,7 +241,7 @@ class WindowsSmoke { }; constructor(private options: WindowsOptions) { - this.auth = resolveProviderAuth({ + this.auth = resolveWindowsProviderAuth({ apiKeyEnv: options.apiKeyEnv, modelId: options.modelId, provider: options.provider, @@ -805,6 +805,7 @@ if ($null -eq $config.update) { } $config.update | Add-Member -Force -MemberType NoteProperty -Name channel -Value 'dev' $config | ConvertTo-Json -Depth 100 | Set-Content -Path $configPath -Encoding utf8 +$env:OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS = '1' $env:OPENCLAW_DISABLE_BUNDLED_PLUGINS = '1' Invoke-OpenClaw update --channel dev --yes --json if ($LASTEXITCODE -ne 0) { throw "openclaw update failed with exit code $LASTEXITCODE" } diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 0a842abfcee..9e06e0f5382 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -87,7 +87,9 @@ describe("Parallels smoke model selection", () => { const providerAuth = readFileSync(TS_PATHS.providerAuth, "utf8"); expect(providerAuth).toContain("OPENCLAW_PARALLELS_OPENAI_MODEL"); + expect(providerAuth).toContain("OPENCLAW_PARALLELS_WINDOWS_OPENAI_MODEL"); expect(providerAuth).toContain("openai/gpt-5.5"); + expect(providerAuth).toContain("openai/gpt-4.1-mini"); expect(providerAuth).toContain('authChoice: "openai-api-key"'); expect(providerAuth).toContain('authChoice: "apiKey"'); expect(providerAuth).toContain('authChoice: "minimax-global-api"'); @@ -95,7 +97,7 @@ describe("Parallels smoke model selection", () => { for (const scriptPath of [...OS_TS_PATHS, TS_PATHS.npmUpdate]) { const script = readFileSync(scriptPath, "utf8"); - expect(script, scriptPath).toContain("resolveProviderAuth"); + expect(script, scriptPath).toMatch(/resolve(?:Windows)?ProviderAuth/u); expect(script, scriptPath).toContain("--model "); expect(script, scriptPath).toContain("modelId"); } @@ -243,6 +245,31 @@ console.log(resolveUbuntuVmName("Ubuntu missing")); }); }); + it("uses the faster OpenAI model for Windows smoke unless overridden", () => { + const source = ` +import { resolveWindowsProviderAuth } from "./${TS_PATHS.common}"; +const result = resolveWindowsProviderAuth({ + provider: "openai", +}); +console.log(JSON.stringify(result)); +`; + expect(JSON.parse(runTsEval(source, { OPENAI_API_KEY: "sk-openai" }))).toMatchObject({ + apiKeyEnv: "OPENAI_API_KEY", + modelId: "openai/gpt-4.1-mini", + }); + + expect( + JSON.parse( + runTsEval(source, { + OPENAI_API_KEY: "sk-openai", + OPENCLAW_PARALLELS_WINDOWS_OPENAI_MODEL: "openai/custom-windows", + }), + ), + ).toMatchObject({ + modelId: "openai/custom-windows", + }); + }); + it("rejects invalid providers and missing keys before touching guests", () => { const invalidProvider = spawnSync( "node", @@ -338,6 +365,8 @@ console.log(resolveUbuntuVmName("Ubuntu missing")); expect(macos).toContain('channel: "dev"'); expect(windows).toContain("Name channel -Value 'dev'"); + expect(macos).toContain("OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS=1"); + expect(windows).toContain("OPENCLAW_ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS"); }); it("passes aggregate model overrides into each OS fresh lane", () => { @@ -345,7 +374,8 @@ console.log(resolveUbuntuVmName("Ubuntu missing")); expect(script).toContain("scripts/e2e/parallels/${platform}-smoke.ts"); expect(script).toContain('"--model"'); - expect(script).toContain("this.auth.modelId"); + expect(script).toContain("auth.modelId"); + expect(script).toContain("authForPlatform"); expect(script).toContain("OPENCLAW_PARALLELS_LINUX_DISABLE_BONJOUR"); });