






















@@ -346,6 +346,185 @@ jobs:
346346OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
347347run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
348348349+plugin-prerelease-inspector:
350+permissions:
351+contents: read
352+name: plugin-prerelease-inspector
353+needs: [preflight]
354+if: needs.preflight.outputs.run_plugin_prerelease_suite == 'true'
355+continue-on-error: true
356+runs-on: ubuntu-24.04
357+timeout-minutes: 30
358+steps:
359+ - name: Checkout
360+uses: actions/checkout@v6
361+with:
362+ref: ${{ needs.preflight.outputs.checkout_revision }}
363+fetch-depth: 1
364+fetch-tags: false
365+persist-credentials: false
366+submodules: false
367+368+ - name: Setup Node environment
369+uses: ./.github/actions/setup-node-env
370+with:
371+install-bun: "false"
372+373+ - name: Run plugin inspector advisory sweep
374+env:
375+OPENCLAW_PLUGIN_INSPECTOR_VERSION: "0.3.10"
376+OPENCLAW_PLUGIN_INSPECTOR_ROOT: .artifacts/plugin-inspector
377+shell: bash
378+run: |
379+ set -euo pipefail
380+ mkdir -p "$OPENCLAW_PLUGIN_INSPECTOR_ROOT"
381+ set +e
382+ node --input-type=module <<'EOF'
383+ import { existsSync } from "node:fs";
384+ import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
385+ import path from "node:path";
386+387+ const artifactRoot = process.env.OPENCLAW_PLUGIN_INSPECTOR_ROOT;
388+ if (!artifactRoot) {
389+ throw new Error("OPENCLAW_PLUGIN_INSPECTOR_ROOT is required");
390+ }
391+392+ const readJson = async (filePath) => JSON.parse(await readFile(filePath, "utf8"));
393+ const inferSeams = (pluginManifest, packageJson) => {
394+ const contracts = Object.keys(pluginManifest?.contracts ?? {});
395+ if (contracts.includes("tools")) {
396+ return ["dynamic-tool"];
397+ }
398+ const openclawPackage = packageJson?.openclaw ?? {};
399+ if (openclawPackage.extensions || openclawPackage.runtimeExtensions) {
400+ return ["plugin-runtime"];
401+ }
402+ return ["plugin-metadata"];
403+ };
404+405+ const extensionRoot = path.resolve("extensions");
406+ const fixtures = [];
407+ for (const entry of await readdir(extensionRoot, { withFileTypes: true })) {
408+ if (!entry.isDirectory()) {
409+ continue;
410+ }
411+ const relativePath = `extensions/${entry.name}`;
412+ const packagePath = path.join(extensionRoot, entry.name, "package.json");
413+ const manifestPath = path.join(extensionRoot, entry.name, "openclaw.plugin.json");
414+ if (!existsSync(packagePath) || !existsSync(manifestPath)) {
415+ continue;
416+ }
417+ const packageJson = await readJson(packagePath);
418+ const pluginManifest = await readJson(manifestPath);
419+ fixtures.push({
420+ id: entry.name,
421+ name: pluginManifest.name ?? packageJson.name ?? entry.name,
422+ path: relativePath,
423+ priority: "high",
424+ repo: "local",
425+ seams: inferSeams(pluginManifest, packageJson),
426+ why: "bundled OpenClaw plugin prerelease advisory fixture",
427+ });
428+ }
429+ fixtures.sort((left, right) => left.id.localeCompare(right.id));
430+ if (fixtures.length === 0) {
431+ throw new Error("No bundled plugin fixtures found under extensions/");
432+ }
433+434+ await mkdir(artifactRoot, { recursive: true });
435+ const config = `${JSON.stringify(
436+ {
437+ version: 1,
438+ submoduleRoot: ".",
439+ openclaw: {
440+ defaultCheckoutPath: ".",
441+ },
442+ fixtures,
443+ },
444+ null,
445+ 2,
446+ )}\n`;
447+ await writeFile("plugin-inspector.config.json", config, "utf8");
448+ await writeFile(path.join(artifactRoot, "plugin-inspector.config.json"), config, "utf8");
449+ EOF
450+ config_status=$?
451+ set -e
452+ echo "$config_status" > "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/config-exit-code.txt"
453+454+ if [ "$config_status" -eq 0 ]; then
455+ set +e
456+ npm exec --yes "@openclaw/plugin-inspector@${OPENCLAW_PLUGIN_INSPECTOR_VERSION}" -- ci \
457+ --config plugin-inspector.config.json \
458+ --openclaw "$PWD" \
459+ --out "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/reports" \
460+ --json \
461+ > "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/plugin-inspector-stdout.json" \
462+ 2> "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/plugin-inspector-stderr.log"
463+ inspector_status=$?
464+ set -e
465+ else
466+ inspector_status=127
467+ echo "Skipped plugin-inspector because config generation failed." \
468+ > "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/plugin-inspector-stderr.log"
469+ fi
470+ echo "$inspector_status" > "$OPENCLAW_PLUGIN_INSPECTOR_ROOT/exit-code.txt"
471+472+ node --input-type=module <<'EOF'
473+ import { existsSync } from "node:fs";
474+ import { appendFile, readFile, writeFile } from "node:fs/promises";
475+ import path from "node:path";
476+477+ const artifactRoot = process.env.OPENCLAW_PLUGIN_INSPECTOR_ROOT;
478+ const summaryPath = path.join(artifactRoot, "reports/plugin-inspector-ci-summary.json");
479+ const markdownPath = path.join(artifactRoot, "reports/plugin-inspector-ci-summary.md");
480+ const configExitCode = (await readFile(path.join(artifactRoot, "config-exit-code.txt"), "utf8")).trim();
481+ const exitCode = (await readFile(path.join(artifactRoot, "exit-code.txt"), "utf8")).trim();
482+ const lines = [
483+ "## Plugin Inspector Advisory",
484+ "",
485+ `Inspector: @openclaw/plugin-inspector@${process.env.OPENCLAW_PLUGIN_INSPECTOR_VERSION}`,
486+ `Config exit code: ${configExitCode}`,
487+ `Exit code: ${exitCode}`,
488+ ];
489+490+ if (existsSync(summaryPath)) {
491+ const summary = JSON.parse(await readFile(summaryPath, "utf8"));
492+ lines.push(
493+ `Status: ${String(summary.status ?? "unknown").toUpperCase()}`,
494+ "",
495+ "| Metric | Count |",
496+ "| --- | ---: |",
497+ `| Hard breakages | ${summary.summary?.breakages ?? 0} |`,
498+ `| Issues | ${summary.summary?.issues ?? 0} |`,
499+ `| P0 issues | ${summary.summary?.p0Issues ?? 0} |`,
500+ `| P1 issues | ${summary.summary?.p1Issues ?? 0} |`,
501+ `| Compat gaps | ${summary.summary?.compatGaps ?? 0} |`,
502+ `| Inspector gaps | ${summary.summary?.inspectorGaps ?? 0} |`,
503+ "",
504+ "This job is informational; Plugin Prerelease blocking status is unchanged.",
505+ );
506+ await writeFile(path.join(artifactRoot, "advisory-summary.md"), `${lines.join("\n")}\n`, "utf8");
507+ if (existsSync(markdownPath)) {
508+ lines.push("", "### Full inspector summary", "");
509+ lines.push(await readFile(markdownPath, "utf8"));
510+ }
511+ } else {
512+ lines.push("", "No plugin-inspector CI summary was produced.", "");
513+ lines.push("This job is informational; inspect the uploaded stdout/stderr artifacts.");
514+ await writeFile(path.join(artifactRoot, "advisory-summary.md"), `${lines.join("\n")}\n`, "utf8");
515+ }
516+517+ await appendFile(process.env.GITHUB_STEP_SUMMARY, `${lines.join("\n")}\n`, "utf8");
518+ EOF
519+520+ - name: Upload plugin inspector advisory artifacts
521+if: always()
522+uses: actions/upload-artifact@v7
523+with:
524+name: plugin-inspector-advisory
525+path: .artifacts/plugin-inspector/**
526+if-no-files-found: warn
527+349528plugin-prerelease-docker-suite:
350529name: plugin-prerelease-docker-suite
351530needs: [preflight]
@@ -375,6 +554,7 @@ jobs:
375554 - plugin-prerelease-static-shard
376555 - plugin-prerelease-node-shard
377556 - plugin-prerelease-extension-shard
557+ - plugin-prerelease-inspector
378558 - plugin-prerelease-docker-suite
379559if: ${{ !cancelled() && always() && needs.preflight.outputs.run_plugin_prerelease_suite == 'true' }}
380560runs-on: ubuntu-24.04
@@ -389,6 +569,7 @@ jobs:
389569STATIC_RESULT: ${{ needs.plugin-prerelease-static-shard.result }}
390570NODE_RESULT: ${{ needs.plugin-prerelease-node-shard.result }}
391571EXTENSIONS_RESULT: ${{ needs.plugin-prerelease-extension-shard.result }}
572+INSPECTOR_RESULT: ${{ needs.plugin-prerelease-inspector.result }}
392573DOCKER_RESULT: ${{ needs.plugin-prerelease-docker-suite.result }}
393574shell: bash
394575run: |
@@ -411,4 +592,5 @@ jobs:
411592 check_required "plugin-prerelease-node" "$RUN_NODE" "$NODE_RESULT"
412593 check_required "plugin-prerelease-extensions" "$RUN_EXTENSIONS" "$EXTENSIONS_RESULT"
413594 check_required "plugin-prerelease-docker" "$RUN_DOCKER" "$DOCKER_RESULT"
595+ echo "plugin-prerelease-inspector advisory result: ${INSPECTOR_RESULT}"
414596 exit "$failed"
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。