diff --git a/scripts/e2e/parallels/linux-smoke.ts b/scripts/e2e/parallels/linux-smoke.ts index ccfe507fc95..a0feb515159 100755 --- a/scripts/e2e/parallels/linux-smoke.ts +++ b/scripts/e2e/parallels/linux-smoke.ts @@ -14,6 +14,7 @@ import { parseProvider, readPositiveIntEnv, modelProviderConfigBatchJson, + posixCodexPlatformPackageRepairFunction, posixProviderOnlyPluginIsolationScript, repoRoot, resolveParallelsModelTimeoutSeconds, @@ -741,7 +742,8 @@ rm -f "$provider_config_batch"`); this.restrictAgentTurnPlugins(); this.prepareAgentWorkspace(); this.guestBash( - `agent_ok=false + `${posixCodexPlatformPackageRepairFunction()} +agent_ok=false for attempt in 1 2; do session_id="parallels-linux-smoke" if [ "$attempt" -gt 1 ]; then session_id="parallels-linux-smoke-retry-$attempt"; fi @@ -755,6 +757,11 @@ for attempt in 1 2; do set -e cat "$output_file" if [ "$rc" -ne 0 ]; then + if [ "$attempt" -lt 2 ] && repair_missing_codex_platform_package "$output_file"; then + rm -f "$output_file" + echo "agent turn attempt $attempt hit a missing Codex platform package; retrying" + continue + fi rm -f "$output_file" exit "$rc" fi diff --git a/scripts/e2e/parallels/macos-smoke.ts b/scripts/e2e/parallels/macos-smoke.ts index 4a71e9caf99..72513b7ce33 100755 --- a/scripts/e2e/parallels/macos-smoke.ts +++ b/scripts/e2e/parallels/macos-smoke.ts @@ -16,6 +16,7 @@ import { parseMode, parseProvider, modelProviderConfigBatchJson, + posixCodexPlatformPackageRepairFunction, posixProviderOnlyPluginIsolationScript, parsePositiveInt, readPositiveIntEnv, @@ -1108,6 +1109,7 @@ rm -f "$provider_config_batch"`); this.restrictAgentTurnPlugins(); this.guestSh( `${posixAgentWorkspaceScript("Parallels macOS smoke test assistant.")} +${posixCodexPlatformPackageRepairFunction()} agent_ok=false for attempt in 1 2; do session_id="parallels-macos-smoke" @@ -1122,6 +1124,11 @@ for attempt in 1 2; do set -e cat "$output_file" if [ "$rc" -ne 0 ]; then + if [ "$attempt" -lt 2 ] && repair_missing_codex_platform_package "$output_file"; then + rm -f "$output_file" + echo "agent turn attempt $attempt hit a missing Codex platform package; retrying" + continue + fi rm -f "$output_file" exit "$rc" fi diff --git a/scripts/e2e/parallels/npm-update-scripts.ts b/scripts/e2e/parallels/npm-update-scripts.ts index 639c1e8296d..60e67ab7b39 100644 --- a/scripts/e2e/parallels/npm-update-scripts.ts +++ b/scripts/e2e/parallels/npm-update-scripts.ts @@ -1,7 +1,11 @@ // Npm Update Scripts script supports OpenClaw repository automation. import { posixAgentWorkspaceScript, windowsAgentWorkspaceScript } from "./agent-workspace.ts"; import { shellQuote } from "./host-command.ts"; -import { posixProviderOnlyPluginIsolationScript } from "./plugin-isolation.ts"; +import { + posixCodexPlatformPackageRepairFunction, + posixProviderOnlyPluginIsolationScript, + windowsCodexPlatformPackageRepairFunction, +} from "./plugin-isolation.ts"; import { psSingleQuote, windowsAgentTurnConfigPatchScript, @@ -72,6 +76,7 @@ function posixAssertAgentOkScript(command: string, input: NpmUpdateScriptInput, fallbackPluginId: input.auth.modelId.split("/", 1)[0] || "openai", modelId: input.auth.modelId, })} +${posixCodexPlatformPackageRepairFunction()} agent_ok=false for attempt in 1 2; do session_id=${shellQuote(sessionId)} @@ -84,6 +89,11 @@ for attempt in 1 2; do set -e print_log_tail "$output_file" if [ "$rc" -ne 0 ]; then + if [ "$attempt" -lt 2 ] && repair_missing_codex_platform_package "$output_file"; then + rm -f "$output_file" + echo "agent turn attempt $attempt hit a missing Codex platform package; retrying" + continue + fi rm -f "$output_file" exit "$rc" fi @@ -138,6 +148,7 @@ Wait-OpenClawGateway`; function windowsAssertAgentOkScript(input: NpmUpdateScriptInput): string { return `${windowsAgentTurnConfigPatchScript(input.auth.modelId)} +${windowsCodexPlatformPackageRepairFunction()} $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.")} @@ -149,16 +160,21 @@ for ($attempt = 1; $attempt -le 2; $attempt++) { $sessionPath = Join-Path $sessionsDir "$sessionId.jsonl" Remove-Item $sessionPath -Force -ErrorAction SilentlyContinue $output = Invoke-OpenClaw agent --local --agent main --session-id $sessionId --model ${psSingleQuote(input.auth.modelId)} --message 'Reply with exact ASCII text OK only.' --thinking off --timeout ${resolveParallelsModelTimeoutSeconds("windows")} --json 2>&1 + $agentExitCode = $LASTEXITCODE 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"') { + if ($agentExitCode -eq 0 -and ($output | Out-String) -match '"finalAssistant(Raw|Visible)Text":\\s*"OK"') { $agentOk = $true break } + if ($agentExitCode -ne 0 -and $attempt -lt 2 -and (Repair-MissingCodexPlatformPackage -Output $output)) { + Write-Host "agent turn attempt $attempt hit a missing Codex platform package; retrying" + continue + } if ($attempt -lt 2) { Write-Host "agent turn attempt $attempt finished without OK response; retrying" Start-Sleep -Seconds 3 } + if ($agentExitCode -ne 0) { throw "agent failed with exit code $agentExitCode" } } if (-not $agentOk) { throw 'openclaw agent finished without OK response' }`; } diff --git a/scripts/e2e/parallels/plugin-isolation.ts b/scripts/e2e/parallels/plugin-isolation.ts index dbf7da57d10..d2642d3f699 100644 --- a/scripts/e2e/parallels/plugin-isolation.ts +++ b/scripts/e2e/parallels/plugin-isolation.ts @@ -9,6 +9,84 @@ interface PluginIsolationOptions { nodeCommand?: string; } +export function posixCodexPlatformPackageRepairFunction(): string { + return `repair_missing_codex_platform_package() { + output_file="$1" + grep -F 'Missing optional dependency @openai/codex-' "$output_file" >/dev/null 2>&1 || return 1 + state_home="\${OPENCLAW_PARALLELS_HOME:-\${HOME:-}}" + codex_manifest="" + for candidate in "$state_home"/.openclaw/npm/projects/*/node_modules/@openclaw/codex/package.json; do + [ -f "$candidate" ] || continue + codex_manifest="$candidate" + break + done + if [ -z "$codex_manifest" ]; then + echo "codex-platform-repair: managed Codex project not found" >&2 + return 1 + fi + project_root="\${codex_manifest%/node_modules/@openclaw/codex/package.json}" + cache_dir="$(mktemp -d "\${TMPDIR:-/tmp}/openclaw-npm-cache.XXXXXX")" + echo "codex-platform-repair: retrying managed npm install once with a fresh cache" >&2 + repair_rc=0 + ( + cd "$project_root" + NPM_CONFIG_CACHE="$cache_dir" npm_config_cache="$cache_dir" npm install --omit=dev --omit=peer --legacy-peer-deps --ignore-scripts --no-audit --no-fund + ) || repair_rc=$? + rm -rf "$cache_dir" + if [ "$repair_rc" -ne 0 ]; then + echo "codex-platform-repair: npm install failed with exit code $repair_rc" >&2 + return "$repair_rc" + fi + echo "codex-platform-repair: managed npm install completed" >&2 +}`; +} + +export function windowsCodexPlatformPackageRepairFunction(): string { + return String.raw`function Repair-MissingCodexPlatformPackage { + param([object[]] $Output) + $outputText = $Output | Out-String + if ($outputText -notmatch [regex]::Escape('Missing optional dependency @openai/codex-')) { + return $false + } + $projectsRoot = Join-Path $env:USERPROFILE '.openclaw\npm\projects' + $codexManifest = Get-ChildItem -Path $projectsRoot -Filter package.json -File -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -match 'node_modules[\\/]@openclaw[\\/]codex[\\/]package\.json$' } | + Select-Object -First 1 + if (-not $codexManifest) { + Write-Warning 'codex-platform-repair: managed Codex project not found' + return $false + } + $projectRoot = $codexManifest.Directory.Parent.Parent.Parent.FullName + $cacheDir = Join-Path ([System.IO.Path]::GetTempPath()) ('openclaw-npm-cache-' + [guid]::NewGuid().ToString('N')) + $oldUpperCache = [Environment]::GetEnvironmentVariable('NPM_CONFIG_CACHE', 'Process') + $oldLowerCache = [Environment]::GetEnvironmentVariable('npm_config_cache', 'Process') + $pushedLocation = $false + $repairExit = 1 + try { + New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null + [Environment]::SetEnvironmentVariable('NPM_CONFIG_CACHE', $cacheDir, 'Process') + [Environment]::SetEnvironmentVariable('npm_config_cache', $cacheDir, 'Process') + Push-Location $projectRoot + $pushedLocation = $true + Write-Host 'codex-platform-repair: retrying managed npm install once with a fresh cache' + $repairOutput = & npm.cmd install --omit=dev --omit=peer --legacy-peer-deps --ignore-scripts --no-audit --no-fund 2>&1 + $repairExit = $LASTEXITCODE + if ($null -ne $repairOutput) { $repairOutput | ForEach-Object { Write-Host $_ } } + } finally { + if ($pushedLocation) { Pop-Location } + [Environment]::SetEnvironmentVariable('NPM_CONFIG_CACHE', $oldUpperCache, 'Process') + [Environment]::SetEnvironmentVariable('npm_config_cache', $oldLowerCache, 'Process') + Remove-Item $cacheDir -Force -Recurse -ErrorAction SilentlyContinue + } + if ($repairExit -ne 0) { + Write-Warning "codex-platform-repair: npm install failed with exit code $repairExit" + return $false + } + Write-Host 'codex-platform-repair: managed npm install completed' + return $true +}`; +} + export function providerOnlyPluginId(modelId: string, fallbackPluginId: string): string { return providerIdFromModelId(modelId) || fallbackPluginId; } diff --git a/scripts/e2e/parallels/windows-smoke.ts b/scripts/e2e/parallels/windows-smoke.ts index f6bf8218f0d..a5baa34379b 100755 --- a/scripts/e2e/parallels/windows-smoke.ts +++ b/scripts/e2e/parallels/windows-smoke.ts @@ -33,7 +33,10 @@ import { runWindowsBackgroundPowerShell, WindowsGuest } from "./guest-transports import { startHostServer } from "./host-server.ts"; import { ensureVmRunning } from "./parallels-vm.ts"; import { PhaseRunner } from "./phase-runner.ts"; -import { windowsProviderOnlyPluginIsolationScript } from "./plugin-isolation.ts"; +import { + windowsCodexPlatformPackageRepairFunction, + windowsProviderOnlyPluginIsolationScript, +} from "./plugin-isolation.ts"; import { psSingleQuote, windowsAgentTurnConfigPatchScript, @@ -725,6 +728,7 @@ $PSNativeCommandUseErrorActionPreference = $false ${windowsPortableGitPathScript} ${windowsAgentTurnConfigPatchScript(this.auth.modelId)} ${windowsAgentWorkspaceScript("Parallels Windows smoke test assistant.")} +${windowsCodexPlatformPackageRepairFunction()} Set-Item -Path ('Env:' + ${psSingleQuote(this.auth.apiKeyEnv)}) -Value ${psSingleQuote(this.auth.apiKeyValue)} $agentOk = $false for ($attempt = 1; $attempt -le 2; $attempt++) { @@ -754,6 +758,10 @@ for ($attempt = 1; $attempt -le 2; $attempt++) { $agentOk = $true break } + if ($agentExitCode -ne 0 -and $attempt -lt 2 -and (Repair-MissingCodexPlatformPackage -Output $output)) { + Write-Host "agent turn attempt $attempt hit a missing Codex platform package; retrying" + continue + } if ($attempt -lt 2) { Write-Host "agent turn attempt $attempt failed or finished without OK response; retrying" Start-Sleep -Seconds 3 diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index b7b0ec6bdb5..445fce8767c 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -42,6 +42,10 @@ import { parseArgs as parseLinuxSmokeArgs } from "../../scripts/e2e/parallels/li import { parseArgs as parseMacosSmokeArgs } from "../../scripts/e2e/parallels/macos-smoke.ts"; import { parseArgs as parseNpmUpdateSmokeArgs } from "../../scripts/e2e/parallels/npm-update-smoke.ts"; import { PhaseRunner } from "../../scripts/e2e/parallels/phase-runner.ts"; +import { + posixCodexPlatformPackageRepairFunction, + windowsCodexPlatformPackageRepairFunction, +} from "../../scripts/e2e/parallels/plugin-isolation.ts"; import { parseArgs as parseWindowsSmokeArgs } from "../../scripts/e2e/parallels/windows-smoke.ts"; import { withEnv } from "../../src/test-utils/env.js"; import { spawnNodeEvalSync } from "../../src/test-utils/node-process.js"; @@ -275,6 +279,20 @@ describe("Parallels smoke model selection", () => { } }); + it("repairs only the exact missing Codex platform package failure with a fresh npm cache", () => { + const posixRepair = posixCodexPlatformPackageRepairFunction(); + const windowsRepair = windowsCodexPlatformPackageRepairFunction(); + + for (const repair of [posixRepair, windowsRepair]) { + expect(repair).toContain("Missing optional dependency @openai/codex-"); + expect(repair).toContain("NPM_CONFIG_CACHE"); + expect(repair).toContain("--ignore-scripts"); + expect(repair).toContain("codex-platform-repair: managed npm install completed"); + } + expect(posixRepair).toContain("repair_missing_codex_platform_package"); + expect(windowsRepair).toContain("Repair-MissingCodexPlatformPackage"); + }); + it("writes full model ids as config map keys in provider batches", () => { const batch = JSON.parse(modelProviderConfigBatchJson("openai/gpt-5.5", "windows")) as Array<{ path: string;