diff --git a/.agents/skills/openclaw-testing/SKILL.md b/.agents/skills/openclaw-testing/SKILL.md index 5fdbf1537ba..a727751c5b4 100644 --- a/.agents/skills/openclaw-testing/SKILL.md +++ b/.agents/skills/openclaw-testing/SKILL.md @@ -200,7 +200,8 @@ gh workflow run openclaw-release-checks.yml \ `pnpm openclaw qa matrix` defaults to `--profile all`. Do not assume the CLI default is the fast release path. Use explicit profiles: -- `--profile fast --fail-fast`: release-critical Matrix transport contract +- `--profile fast`: release-critical Matrix transport contract; add + `--fail-fast` only when the target CLI supports it - `--profile transport|media|e2ee-smoke|e2ee-deep|e2ee-cli`: sharded full Matrix proof - `OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000`: CI-friendly no-reply quiet diff --git a/.github/workflows/openclaw-release-checks.yml b/.github/workflows/openclaw-release-checks.yml index 9cc9f1883cf..7cec1478856 100644 --- a/.github/workflows/openclaw-release-checks.yml +++ b/.github/workflows/openclaw-release-checks.yml @@ -407,15 +407,20 @@ jobs: output_dir=".artifacts/qa-e2e/matrix-live-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT" - pnpm openclaw qa matrix \ + matrix_args=( --repo-root . \ --output-dir "${output_dir}" \ --provider-mode live-frontier \ --model "${OPENCLAW_CI_OPENAI_MODEL}" \ --alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \ --profile fast \ - --fast \ - --fail-fast + --fast + ) + if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then + matrix_args+=(--fail-fast) + fi + + pnpm openclaw qa matrix "${matrix_args[@]}" - name: Upload Matrix QA artifacts if: always() diff --git a/.github/workflows/qa-live-transports-convex.yml b/.github/workflows/qa-live-transports-convex.yml index 9ff82805808..16524328298 100644 --- a/.github/workflows/qa-live-transports-convex.yml +++ b/.github/workflows/qa-live-transports-convex.yml @@ -259,15 +259,20 @@ jobs: output_dir=".artifacts/qa-e2e/matrix-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT" - pnpm openclaw qa matrix \ + matrix_args=( --repo-root . \ --output-dir "${output_dir}" \ --provider-mode live-frontier \ --model "${OPENCLAW_CI_OPENAI_MODEL}" \ --alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \ --profile "${INPUT_MATRIX_PROFILE}" \ - --fast \ - --fail-fast + --fast + ) + if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then + matrix_args+=(--fail-fast) + fi + + pnpm openclaw qa matrix "${matrix_args[@]}" - name: Upload Matrix QA artifacts if: always() @@ -336,15 +341,20 @@ jobs: output_dir=".artifacts/qa-e2e/matrix-live-${{ matrix.profile }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT" - pnpm openclaw qa matrix \ + matrix_args=( --repo-root . \ --output-dir "${output_dir}" \ --provider-mode live-frontier \ --model "${OPENCLAW_CI_OPENAI_MODEL}" \ --alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \ --profile "${{ matrix.profile }}" \ - --fast \ - --fail-fast + --fast + ) + if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then + matrix_args+=(--fail-fast) + fi + + pnpm openclaw qa matrix "${matrix_args[@]}" - name: Upload Matrix QA shard artifacts if: always() diff --git a/docs/ci.md b/docs/ci.md index bef43022619..93b2e7598d4 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -172,8 +172,9 @@ agentic packs. The `QA-Lab - All Lanes` workflow runs nightly on `main` and on manual dispatch; it fans out the mock parity gate, live Matrix lane, and live Telegram and Discord lanes as parallel jobs. The live jobs use the `qa-live-shared` environment, and Telegram/Discord use Convex leases. Matrix -uses `--profile fast --fail-fast` for scheduled and release gates while the CLI -default and manual workflow input remain `all`; manual `matrix_profile=all` +uses `--profile fast` for scheduled and release gates, adding `--fail-fast` only +when the checked-out CLI supports it. The CLI default and manual workflow input +remain `all`; manual `matrix_profile=all` dispatch always shards full Matrix coverage into `transport`, `media`, `e2ee-smoke`, `e2ee-deep`, and `e2ee-cli` jobs. `OpenClaw Release Checks` also runs the release-critical QA Lab lanes before release approval. diff --git a/extensions/qa-lab/src/gateway-rpc-client.test.ts b/extensions/qa-lab/src/gateway-rpc-client.test.ts index a7320bd2ac9..eab529a961f 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.test.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.test.ts @@ -10,7 +10,7 @@ const gatewayRpcMock = vi.hoisted(() => { }; }); -vi.mock("openclaw/plugin-sdk/gateway-runtime", () => ({ +vi.mock("openclaw/plugin-sdk/browser-node-runtime", () => ({ callGatewayFromCli: gatewayRpcMock.callGatewayFromCli, })); diff --git a/extensions/qa-lab/src/gateway-rpc-client.ts b/extensions/qa-lab/src/gateway-rpc-client.ts index 0a2583b4395..6fb138f989b 100644 --- a/extensions/qa-lab/src/gateway-rpc-client.ts +++ b/extensions/qa-lab/src/gateway-rpc-client.ts @@ -1,5 +1,5 @@ +import { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; -import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; import { formatQaGatewayLogsForError } from "./gateway-log-redaction.js"; type QaGatewayRpcRequestOptions = { diff --git a/extensions/qa-lab/src/runtime-api.ts b/extensions/qa-lab/src/runtime-api.ts index a02cf412a79..602832e034c 100644 --- a/extensions/qa-lab/src/runtime-api.ts +++ b/extensions/qa-lab/src/runtime-api.ts @@ -1,7 +1,7 @@ export type { Command } from "commander"; export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; -export { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime"; +export { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime"; export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store"; export { defaultQaRuntimeModelForMode } from "./model-selection.runtime.js"; export { diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index 05cee881b4c..066e54fb705 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -2314,7 +2314,7 @@ async function runInstalledBrowserOverrideImportSmoke(params) { cwd: packageRoot, env: { ...params.env, - OPENCLAW_BROWSER_CONTROL_MODULE: overridePath, + OPENCLAW_BROWSER_CONTROL_MODULE: pathToFileURL(overridePath).href, OPENCLAW_BROWSER_OVERRIDE_STARTED_PATH: startedPath, OPENCLAW_BROWSER_OVERRIDE_STOPPED_PATH: stoppedPath, }, diff --git a/test/scripts/npm-telegram-live.test.ts b/test/scripts/npm-telegram-live.test.ts index bd5bf18f18b..6f226fa7c1a 100644 --- a/test/scripts/npm-telegram-live.test.ts +++ b/test/scripts/npm-telegram-live.test.ts @@ -57,6 +57,14 @@ describe("package Telegram live Docker E2E", () => { it("keeps private QA harness imports local while using the installed package dist", () => { const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); + const gatewayRpcClient = readFileSync( + path.resolve(TEST_DIR, "../../extensions/qa-lab/src/gateway-rpc-client.ts"), + "utf8", + ); + const qaRuntimeApi = readFileSync( + path.resolve(TEST_DIR, "../../extensions/qa-lab/src/runtime-api.ts"), + "utf8", + ); expect(script).toContain('ln -sfnT "$openclaw_package_dir/dist" /app/dist'); expect(script).toContain('cp "$openclaw_package_dir/package.json" /app/package.json'); @@ -66,6 +74,9 @@ describe("package Telegram live Docker E2E", () => { expect(script).toContain('"./extensions/qa-channel/api.ts"'); expect(script).toContain('pkg.exports["./plugin-sdk/qa-channel-protocol"]'); expect(script).toContain('"./extensions/qa-channel/src/protocol.ts"'); + expect(gatewayRpcClient).toContain('from "openclaw/plugin-sdk/browser-node-runtime"'); + expect(qaRuntimeApi).toContain('from "openclaw/plugin-sdk/browser-node-runtime"'); + expect(gatewayRpcClient).not.toContain('from "openclaw/plugin-sdk/gateway-runtime"'); }); it("exposes installed package dependencies to the mounted QA harness", () => { @@ -76,7 +87,7 @@ describe("package Telegram live Docker E2E", () => { 'local source="/npm-global/lib/node_modules/openclaw/node_modules/$name"', ); expect(script).toContain('ln -sfn "$source" "$target"'); - expect(script).toContain("link_installed_package_dependency \"$dependency\""); + expect(script).toContain('link_installed_package_dependency "$dependency"'); expect(script).toContain("@modelcontextprotocol/sdk"); expect(script).toContain("yaml"); expect(script).toContain("zod"); diff --git a/test/scripts/openclaw-cross-os-release-checks.test.ts b/test/scripts/openclaw-cross-os-release-checks.test.ts index 283756e5085..a0763045651 100644 --- a/test/scripts/openclaw-cross-os-release-checks.test.ts +++ b/test/scripts/openclaw-cross-os-release-checks.test.ts @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { createServer as createNetServer } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -309,6 +309,9 @@ describe("scripts/openclaw-cross-os-release-checks", () => { expect(installedScript).toContain( 'from "file:///C:/Users/runner/AppData/Roaming/npm/node_modules/openclaw/dist/plugin-sdk/browser-node-runtime.js"', ); + expect(readFileSync("scripts/openclaw-cross-os-release-checks.ts", "utf8")).toContain( + "OPENCLAW_BROWSER_CONTROL_MODULE: pathToFileURL(overridePath).href", + ); }); it("normalizes Windows installed CLI paths to the cmd shim", () => { diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 7081665ff48..96b234b6167 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -124,6 +124,21 @@ describe("package artifact reuse", () => { ); }); + it("detects Matrix fail-fast support for older release refs", () => { + const releaseWorkflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8"); + const qaWorkflow = readFileSync(".github/workflows/qa-live-transports-convex.yml", "utf8"); + + expect(releaseWorkflow).toContain("matrix_args=("); + expect(releaseWorkflow).toContain( + 'pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"', + ); + expect(releaseWorkflow).toContain("matrix_args+=(--fail-fast)"); + expect(releaseWorkflow).toContain('pnpm openclaw qa matrix "${matrix_args[@]}"'); + expect(qaWorkflow).toContain( + 'pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"', + ); + }); + it("names package acceptance Telegram as artifact-backed package validation", () => { const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");