diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9a3f5660b..a23ffb96646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai - Plugins/source metadata: expose normalized install-source facts on provider and channel catalogs so onboarding can explain npm pinning, integrity state, and local availability before runtime loads. (#70951) Thanks @vincentkoc. - Plugins/catalog: pin the official external WeCom channel source to an exact npm release plus dist integrity, with a guard that official external sources stay integrity-pinned. (#70997) Thanks @vincentkoc. - Plugins/source metadata: warn when `openclaw.install.defaultChoice` is invalid or points at a missing source, keeping catalog diagnostics explicit without breaking existing plugins. Thanks @vincentkoc. +- Plugins/source metadata: warn when `openclaw.install.expectedIntegrity` is present without a valid npm source, keeping orphaned integrity metadata visible without rejecting existing plugins. Thanks @vincentkoc. - Diagnostics/OTEL: add a lightweight diagnostic trace-context carrier for future span correlation without adding OTEL SDK state to core. Thanks @vincentkoc. - Diagnostics/OTEL: attach diagnostic trace context to exported OTEL logs so log records can correlate with future spans without adding retained process state. Thanks @vincentkoc. - Diagnostics/OTEL: pass immutable per-run diagnostic trace context through agent and tool hook contexts, and parent exported diagnostic spans from validated context without retaining global trace state. Thanks @vincentkoc. diff --git a/docs/plugins/architecture-internals.md b/docs/plugins/architecture-internals.md index da4b2c9bbab..2b0747593e1 100644 --- a/docs/plugins/architecture-internals.md +++ b/docs/plugins/architecture-internals.md @@ -889,10 +889,11 @@ normalized install-source facts next to the raw `openclaw.install` block. The normalized facts identify whether the npm spec is an exact version or floating selector, whether expected integrity metadata is present, and whether a local source path is also available. They also warn when `defaultChoice` is invalid -or points at a source that is not available. Consumers should treat -`installSource` as an additive optional field so older hand-built entries and -compatibility shims do not have to synthesize it. This lets onboarding and -diagnostics explain source-plane state without importing plugin runtime. +or points at a source that is not available, and when npm integrity metadata is +present without a valid npm source. Consumers should treat `installSource` as +an additive optional field so older hand-built entries and compatibility shims +do not have to synthesize it. This lets onboarding and diagnostics explain +source-plane state without importing plugin runtime. Official external npm entries should prefer an exact `npmSpec` plus `expectedIntegrity`. Bare package names and dist-tags still work for diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index 7b3e315c564..a707b58be5b 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -597,9 +597,10 @@ closed if the fetched npm artifact no longer matches the pinned release. Interactive onboarding still offers trusted registry npm specs, including bare package names and dist-tags, for compatibility. Catalog diagnostics can distinguish exact, floating, integrity-pinned, missing-integrity, and invalid -default-choice sources. When `expectedIntegrity` is present, install/update -flows enforce it; when it is omitted, the registry resolution is recorded -without an integrity pin. +default-choice sources. They also warn when `expectedIntegrity` is present but +there is no valid npm source it can pin. When `expectedIntegrity` is present, +install/update flows enforce it; when it is omitted, the registry resolution is +recorded without an integrity pin. Channel plugins should provide `openclaw.setupEntry` when status, channel list, or SecretRef scans need to identify configured accounts without loading the full diff --git a/src/plugins/install-source-info.test.ts b/src/plugins/install-source-info.test.ts index 20c0647db86..34ad5b73025 100644 --- a/src/plugins/install-source-info.test.ts +++ b/src/plugins/install-source-info.test.ts @@ -177,4 +177,29 @@ describe("describePluginInstallSource", () => { warnings: ["invalid-npm-spec", "default-choice-missing-source"], }); }); + + it("warns when integrity metadata has no npm source", () => { + expect( + describePluginInstallSource({ + localPath: "extensions/demo", + expectedIntegrity: "sha512-demo", + }), + ).toEqual({ + local: { + path: "extensions/demo", + }, + warnings: ["npm-integrity-without-source"], + }); + }); + + it("warns when integrity metadata is attached to an invalid npm source", () => { + expect( + describePluginInstallSource({ + npmSpec: "github:vendor/demo", + expectedIntegrity: "sha512-demo", + }), + ).toEqual({ + warnings: ["invalid-npm-spec", "npm-integrity-without-source"], + }); + }); }); diff --git a/src/plugins/install-source-info.ts b/src/plugins/install-source-info.ts index d2421ebf75d..be5cb913965 100644 --- a/src/plugins/install-source-info.ts +++ b/src/plugins/install-source-info.ts @@ -6,6 +6,7 @@ export type PluginInstallSourceWarning = | "invalid-npm-spec" | "invalid-default-choice" | "default-choice-missing-source" + | "npm-integrity-without-source" | "npm-spec-floating" | "npm-spec-missing-integrity"; @@ -56,6 +57,7 @@ export function describePluginInstallSource( const npmSpec = normalizeOptionalString(install.npmSpec); const localPath = normalizeOptionalString(install.localPath); const defaultChoice = resolveDefaultChoice(install.defaultChoice); + const expectedIntegrity = normalizeOptionalString(install.expectedIntegrity); const warnings: PluginInstallSourceWarning[] = []; let npm: PluginInstallNpmSourceInfo | undefined; @@ -67,7 +69,6 @@ export function describePluginInstallSource( const parsed = parseRegistryNpmSpec(npmSpec); if (parsed) { const exactVersion = parsed.selectorKind === "exact-version"; - const expectedIntegrity = normalizeOptionalString(install.expectedIntegrity); const hasIntegrity = Boolean(expectedIntegrity); if (!exactVersion) { warnings.push("npm-spec-floating"); @@ -94,6 +95,9 @@ export function describePluginInstallSource( if (defaultChoice === "local" && !localPath) { warnings.push("default-choice-missing-source"); } + if (expectedIntegrity && !npm) { + warnings.push("npm-integrity-without-source"); + } return { ...(defaultChoice ? { defaultChoice } : {}),