diff --git a/scripts/lib/docker-e2e-plan.mjs b/scripts/lib/docker-e2e-plan.mjs index a94644f6ca2..9bcc238cc2b 100644 --- a/scripts/lib/docker-e2e-plan.mjs +++ b/scripts/lib/docker-e2e-plan.mjs @@ -147,6 +147,33 @@ function parseUpgradeSurvivorScenarios(raw) { ]; } +function parsePublishedReleaseVersion(spec) { + const match = /^openclaw@([0-9]{4})\.([0-9]+)\.([0-9]+)/u.exec(String(spec ?? "")); + if (!match) { + return null; + } + return { + year: Number(match[1]), + month: Number(match[2]), + day: Number(match[3]), + }; +} + +function comparePublishedReleaseVersion(a, b) { + return a.year - b.year || a.month - b.month || a.day - b.day; +} + +function supportsUpgradeSurvivorPluginDependencyCleanup(baselineSpec) { + if (!baselineSpec) { + return true; + } + const version = parsePublishedReleaseVersion(baselineSpec); + if (!version) { + return true; + } + return comparePublishedReleaseVersion(version, { year: 2026, month: 4, day: 23 }) >= 0; +} + function expandUpgradeSurvivorBaselineLanes(poolLanes, rawBaselineSpecs, rawScenarios = "") { const baselineSpecs = parseUpgradeSurvivorBaselineSpecs(rawBaselineSpecs); const scenarios = parseUpgradeSurvivorScenarios(rawScenarios); @@ -160,30 +187,38 @@ function expandUpgradeSurvivorBaselineLanes(poolLanes, rawBaselineSpecs, rawScen const matrixBaselines = baselineSpecs.length > 0 ? baselineSpecs : [undefined]; const matrixScenarios = scenarios.length > 0 ? scenarios : [undefined]; return matrixBaselines.flatMap((baselineSpec) => - matrixScenarios.map((scenario) => { - const suffixParts = [ - baselineSpec ? sanitizeLaneNameSuffix(baselineSpec) : "", - scenario && scenario !== "base" ? sanitizeLaneNameSuffix(scenario) : "", - ].filter(Boolean); - const suffix = suffixParts.join("-"); - const name = suffix ? `${poolLane.name}-${suffix}` : poolLane.name; - const commandPrefix = [ - `OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR="$PWD/.artifacts/upgrade-survivor/${name}"`, - baselineSpec ? `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${shellQuote(baselineSpec)}` : "", - scenario ? `OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${shellQuote(scenario)}` : "", - ] - .filter(Boolean) - .join(" "); - return Object.assign({}, poolLane, { - cacheKey: poolLane.cacheKey - ? suffix - ? `${poolLane.cacheKey}-${suffix}` - : poolLane.cacheKey - : name, - command: commandPrefix ? `${commandPrefix} ${poolLane.command}` : poolLane.command, - name, - }); - }), + matrixScenarios + .filter( + (scenario) => + scenario !== "plugin-deps-cleanup" || + supportsUpgradeSurvivorPluginDependencyCleanup(baselineSpec), + ) + .map((scenario) => { + const suffixParts = [ + baselineSpec ? sanitizeLaneNameSuffix(baselineSpec) : "", + scenario && scenario !== "base" ? sanitizeLaneNameSuffix(scenario) : "", + ].filter(Boolean); + const suffix = suffixParts.join("-"); + const name = suffix ? `${poolLane.name}-${suffix}` : poolLane.name; + const commandPrefix = [ + `OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR="$PWD/.artifacts/upgrade-survivor/${name}"`, + baselineSpec + ? `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC=${shellQuote(baselineSpec)}` + : "", + scenario ? `OPENCLAW_UPGRADE_SURVIVOR_SCENARIO=${shellQuote(scenario)}` : "", + ] + .filter(Boolean) + .join(" "); + return Object.assign({}, poolLane, { + cacheKey: poolLane.cacheKey + ? suffix + ? `${poolLane.cacheKey}-${suffix}` + : poolLane.cacheKey + : name, + command: commandPrefix ? `${commandPrefix} ${poolLane.command}` : poolLane.command, + name, + }); + }), ); }); } diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index 55b70271c84..0e621ff8cb7 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -34,6 +34,11 @@ const SUPPORTED_SUITES = new Set([ "dev-update", ]); +export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = parsePositiveIntegerEnv( + "OPENCLAW_CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS", + 1200, +); + const providerConfig = { openai: { extensionId: "openai", @@ -41,7 +46,7 @@ const providerConfig = { authChoice: "openai-api-key", model: "openai/gpt-5.4", baseUrl: "https://api.openai.com/v1", - timeoutSeconds: 600, + timeoutSeconds: CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS, }, anthropic: { extensionId: "anthropic", @@ -112,7 +117,6 @@ export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS = CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000; export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_000; export const CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000; -export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = 600; export const CROSS_OS_RELEASE_SMOKE_TOOLS_PROFILE = "minimal"; if (isMainModule()) { @@ -151,6 +155,18 @@ export function parseArgs(argv) { return parsed; } +function parsePositiveIntegerEnv(name, fallback) { + const raw = process.env[name]?.trim(); + if (!raw) { + return fallback; + } + const value = Number(raw); + if (!Number.isSafeInteger(value) || value <= 0) { + throw new Error(`${name} must be a positive integer. Got: ${JSON.stringify(raw)}`); + } + return value; +} + export function looksLikeReleaseVersionRef(ref) { const trimmed = normalizeRequestedRef(ref); return /^v?[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[1-9][0-9]*)|[-.](?:beta|rc)[-.]?[0-9]+)?$/iu.test( diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index d76fd267e35..d067495e90e 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -56,12 +56,27 @@ const DEFAULT_MODEL = // The cron/MCP live probe now tolerates more cancelled tool-call retries in CI, // so the outer test budget needs enough headroom to finish those retries. const CLI_BACKEND_LIVE_TIMEOUT_MS = 20 * 60_000; -const CLI_BACKEND_REQUEST_TIMEOUT_MS = 600_000; +const CLI_BACKEND_REQUEST_TIMEOUT_MS = parsePositiveIntegerEnv( + "OPENCLAW_LIVE_CLI_BACKEND_REQUEST_TIMEOUT_MS", + 15 * 60_000, +); const CLI_BACKEND_AGENT_TIMEOUT_SECONDS = Math.max( 1, Math.ceil(CLI_BACKEND_REQUEST_TIMEOUT_MS / 1000) - 10, ); +function parsePositiveIntegerEnv(name: string, fallback: number): number { + const raw = process.env[name]?.trim(); + if (!raw) { + return fallback; + } + const value = Number(raw); + if (!Number.isSafeInteger(value) || value <= 0) { + throw new Error(`${name} must be a positive integer. Got: ${JSON.stringify(raw)}`); + } + return value; +} + function logCliBackendLiveStep(step: string, details?: Record): void { if (!CLI_DEBUG) { return; diff --git a/test/scripts/docker-e2e-plan.test.ts b/test/scripts/docker-e2e-plan.test.ts index 983746f53e4..a1e6c173ee1 100644 --- a/test/scripts/docker-e2e-plan.test.ts +++ b/test/scripts/docker-e2e-plan.test.ts @@ -358,6 +358,28 @@ describe("scripts/lib/docker-e2e-plan", () => { ]); }); + it("skips plugin dependency cleanup for baselines without packaged plugin dirs", () => { + const plan = planFor({ + selectedLaneNames: ["published-upgrade-survivor"], + upgradeSurvivorBaselines: "2026.4.29 2026.3.13", + upgradeSurvivorScenarios: "reported-issues", + }); + + expect(plan.lanes.map((lane) => lane.name)).toEqual([ + "published-upgrade-survivor-2026.4.29", + "published-upgrade-survivor-2026.4.29-feishu-channel", + "published-upgrade-survivor-2026.4.29-bootstrap-persona", + "published-upgrade-survivor-2026.4.29-plugin-deps-cleanup", + "published-upgrade-survivor-2026.4.29-tilde-log-path", + "published-upgrade-survivor-2026.4.29-versioned-runtime-deps", + "published-upgrade-survivor-2026.3.13", + "published-upgrade-survivor-2026.3.13-feishu-channel", + "published-upgrade-survivor-2026.3.13-bootstrap-persona", + "published-upgrade-survivor-2026.3.13-tilde-log-path", + "published-upgrade-survivor-2026.3.13-versioned-runtime-deps", + ]); + }); + it("expands update migration across baselines and cleanup scenarios", () => { const plan = planFor({ selectedLaneNames: ["update-migration"],