diff --git a/scripts/e2e/lib/clawhub-fixture-server.cjs b/scripts/e2e/lib/clawhub-fixture-server.cjs index ca2b53a4860..27fdf68d307 100644 --- a/scripts/e2e/lib/clawhub-fixture-server.cjs +++ b/scripts/e2e/lib/clawhub-fixture-server.cjs @@ -11,6 +11,15 @@ const JSZip = requireFromApp("jszip"); const packageName = "@openclaw/kitchen-sink"; const pluginId = "openclaw-kitchen-sink-fixture"; +const buildClawPackSummary = ({ sha256hash, manifestSha256, size }) => ({ + available: true, + specVersion: 1, + format: "clawpack.zip", + sha256: sha256hash, + size, + manifestSha256, +}); + const profiles = { "kitchen-sink-plugin": { version: "0.1.3", @@ -87,7 +96,8 @@ export default definePluginEntry({ properties: {}, }, }, - packageDetail(sha256hash) { + packageDetail(artifact) { + const clawpack = buildClawPackSummary(artifact); const packageDetail = { package: { name: packageName, @@ -121,6 +131,7 @@ export default definePluginEntry({ hasProvenance: false, scanStatus: "passed", }, + clawpack, }, }; return { @@ -136,10 +147,11 @@ export default definePluginEntry({ createdAt: 0, changelog: "Fixture package for kitchen-sink plugin prerelease CI.", distTags: ["latest"], - sha256hash, + sha256hash: artifact.sha256hash, compatibility: packageDetail.package.compatibility, capabilities: packageDetail.package.capabilities, verification: packageDetail.package.verification, + clawpack, }, }, betaStatus: 404, @@ -191,11 +203,12 @@ export default definePluginEntry({ properties: {}, }, }, - packageDetail(sha256hash) { + packageDetail(artifact) { const compatibility = { pluginApiRange: ">=2026.4.26", minGatewayVersion: "2026.4.26", }; + const clawpack = buildClawPackSummary(artifact); return { packageDetail: { package: { @@ -209,6 +222,7 @@ export default definePluginEntry({ createdAt: 0, updatedAt: 0, compatibility, + clawpack, }, }, versionDetail: { @@ -216,8 +230,9 @@ export default definePluginEntry({ version: this.version, createdAt: 0, changelog: "Kitchen-sink fixture package for Docker plugin E2E.", - sha256hash, + sha256hash: artifact.sha256hash, compatibility, + clawpack, }, }, }; @@ -237,13 +252,17 @@ async function main() { date: new Date(0), }); zip.file("package/index.js", fixture.indexJs, { date: new Date(0) }); - zip.file("package/openclaw.plugin.json", `${JSON.stringify(fixture.manifest, null, 2)}\n`, { - date: new Date(0), - }); + const manifestJson = `${JSON.stringify(fixture.manifest, null, 2)}\n`; + zip.file("package/openclaw.plugin.json", manifestJson, { date: new Date(0) }); const archive = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" }); const sha256hash = crypto.createHash("sha256").update(archive).digest("hex"); - const { packageDetail, versionDetail, betaStatus } = fixture.packageDetail(sha256hash); + const manifestSha256 = crypto.createHash("sha256").update(manifestJson).digest("hex"); + const { packageDetail, versionDetail, betaStatus } = fixture.packageDetail({ + sha256hash, + manifestSha256, + size: archive.length, + }); const json = (response, value, status = 200) => { response.writeHead(status, { "content-type": "application/json" }); @@ -283,6 +302,19 @@ async function main() { response.end(archive); return; } + if ( + url.pathname === + `/api/v1/packages/${encodeURIComponent(packageName)}/versions/${fixture.version}/clawpack` + ) { + response.writeHead(200, { + "content-type": "application/zip", + "content-length": String(archive.length), + "X-ClawHub-ClawPack-Sha256": sha256hash, + "X-ClawHub-ClawPack-Spec-Version": "1", + }); + response.end(archive); + return; + } response.writeHead(404, { "content-type": "text/plain" }); response.end(`not found: ${url.pathname}`); }); diff --git a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs index f2b9785629f..8e80d725038 100644 --- a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs +++ b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs @@ -389,6 +389,14 @@ function assertInstalled() { if (!record.version || !record.integrity || !record.resolvedAt) { throw new Error(`missing ClawHub resolution metadata: ${JSON.stringify(record)}`); } + if ( + !record.clawpackSha256 || + record.clawpackSpecVersion !== 1 || + !record.clawpackManifestSha256 || + typeof record.clawpackSize !== "number" + ) { + throw new Error(`missing kitchen-sink ClawPack metadata: ${JSON.stringify(record)}`); + } } if (typeof record.installPath !== "string" || record.installPath.length === 0) { throw new Error("missing kitchen-sink install path"); diff --git a/scripts/e2e/lib/plugins/assertions.mjs b/scripts/e2e/lib/plugins/assertions.mjs index 4a2b4a11575..7bfc8f77ed7 100644 --- a/scripts/e2e/lib/plugins/assertions.mjs +++ b/scripts/e2e/lib/plugins/assertions.mjs @@ -534,6 +534,14 @@ function assertClawHubInstalled() { if (typeof record.installPath !== "string" || record.installPath.length === 0) { throw new Error(`missing ClawHub install path for ${pluginId}`); } + if ( + !record.clawpackSha256 || + record.clawpackSpecVersion !== 1 || + !record.clawpackManifestSha256 || + typeof record.clawpackSize !== "number" + ) { + throw new Error(`missing ClawHub ClawPack metadata for ${pluginId}: ${JSON.stringify(record)}`); + } const installPath = record.installPath.replace(/^~(?=$|\/)/u, process.env.HOME); const extensionsRoot = path.join(process.env.HOME, ".openclaw", "extensions"); diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 3ae234a3049..e98ad6c59ec 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -307,5 +307,6 @@ describe("docker build helper", () => { expect(clawhub).toContain('plugins update "$CLAWHUB_PLUGIN_ID"'); expect(clawhub).toContain("clawhub:@openclaw/kitchen-sink"); expect(assertions).toContain("clawhub-updated"); + expect(assertions).toContain("record.clawpackSha256"); }); }); diff --git a/test/scripts/plugin-prerelease-test-plan.test.ts b/test/scripts/plugin-prerelease-test-plan.test.ts index e971ab3412e..e6eae538bfe 100644 --- a/test/scripts/plugin-prerelease-test-plan.test.ts +++ b/test/scripts/plugin-prerelease-test-plan.test.ts @@ -131,6 +131,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { expect(assertionsScript).toContain("assertCutoverPreinstalled"); expect(assertionsScript).toContain("record.source !== source"); expect(assertionsScript).toContain("record.clawhubPackage !== packageName"); + expect(assertionsScript).toContain("record.clawpackSha256"); expect(assertionsScript).toContain("assertClawHubExternalInstallContract"); expect(assertionsScript).toContain("expectedErrorMessages"); expect(assertionsScript).toContain( @@ -140,6 +141,9 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { expect(readFileSync("scripts/e2e/lib/clawhub-fixture-server.cjs", "utf8")).toContain( 'from "openclaw/plugin-sdk/plugin-entry"', ); + expect(readFileSync("scripts/e2e/lib/clawhub-fixture-server.cjs", "utf8")).toContain( + "X-ClawHub-ClawPack-Sha256", + ); expect(script).toContain("docker stats --no-stream"); expect(sweepScript).toContain("scan_logs_for_unexpected_errors"); });