openclaw - ✅(Solved) Fix Externalize extensions: manifest-driven discovery + on-demand install via `openclaw plugins install` [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70150Fetched 2026-04-23 07:28:40
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
closed ×1cross-referenced ×1labeled ×1referenced ×1

Fix Action

Fixed

PR fix notes

PR #70176: feat(extensions): phase-1 extension manifest and plugin install progress

Description (problem / solution / changelog)

feat(extensions): phase-1 extension manifest and plugin install progress

The approach adds a manifest.json under extension to declare which external plugins are available. The entries in the manifest are loaded into the onboarding UI based on their type, and when a user selects one, it is automatically installed via NPM with progress feedback shown during the process. From an architecture perspective, the file structure also leaves room for future extensions such as remote registry support and plugin marketplace-style expansion.

The PR uses wecom as an example and has been tested. Review would be appreciated.

https://github.com/openclaw/openclaw/issues/70150

Changed files

  • extensions/manifest.json (added, +31/-0)
  • extensions/manifest.schema.json (added, +116/-0)
  • src/channels/bundled-channel-catalog-read.ts (modified, +47/-8)
  • src/channels/ids.test.ts (modified, +4/-8)
  • src/channels/plugins/catalog.extension-manifest.test.ts (added, +58/-0)
  • src/channels/plugins/catalog.ts (modified, +36/-1)
  • src/commands/channel-setup/install-plan-progress.test.ts (added, +146/-0)
  • src/commands/channel-setup/install-plan-progress.ts (added, +162/-0)
  • src/plugins/extension-registry/bundled-source.test.ts (added, +82/-0)
  • src/plugins/extension-registry/bundled-source.ts (added, +84/-0)
  • src/plugins/extension-registry/index.test.ts (added, +117/-0)
  • src/plugins/extension-registry/index.ts (added, +146/-0)
  • src/plugins/extension-registry/local-override-source.ts (added, +63/-0)
  • src/plugins/extension-registry/remote-source.ts (added, +32/-0)
  • src/plugins/extension-registry/types.ts (added, +65/-0)

Code Example

openclaw plugins install @wecom/wecom-openclaw-plugin

---

{
  "schemaVersion": 1,
  "updatedAt": "2026-04-22T00:00:00Z",
  "plugins": [
    {
      "id": "wecom",
      "kind": "channel",                     // "channel" | "provider" | "tool" | "memory" | ...
      "name": "WeCom",
      "description": "WeCom (企业微信) channel plugin",
      "tags": ["channel", "im", "enterprise"],
      "npmSpec": "@wecom/wecom-openclaw-plugin",
      "clawhubPackage": "wecom-openclaw-plugin",   // optional
      "minHostVersion": "2026.4.0",
      "homepage": "https://github.com/wecom/wecom-openclaw-plugin",
      "verified": true,                             // first-party / signed
      "enabledByDefault": false,
      "onboardingGroup": "channels.enterprise",
      "search": { "keywords": ["wecom", "qiwei", "企业微信"] }
    }
  ]
}

---

┌─────────────────┐   discover   ┌──────────────────────┐
│ onboard wizard  │ ───────────▶ │ ExtensionRegistry    │ ◀─┐
└─────────────────┘               (merges sources)      │  │ same shape
         │ user picks plugin     └──────────────────────┘  │
         ▼                          ▲          ▲           │
┌─────────────────┐                 │          │           │
InstallManager  │─────────────────┤          │           │
 (progress bar)  │ reuses existing │          │           │
└─────────────────┘ `openclaw       │          │           │
         │          plugins install`│          │           │
         ▼                          │          │           │
┌─────────────────┐   ┌─────────────┴──┐  ┌────┴────────┐ ┌─┴──────────┐
Plugin runtime  │   │ Bundled        │  │ Remote      │ │ Local (unchanged SDK) │   │ manifest       │  │ manifest    │ │ override   │
└─────────────────┘    (in-repo) (CDN/HTTP) (user file)                      └────────────────┘  └─────────────┘ └────────────┘
RAW_BUFFERClick to expand / collapse

Background

The extensions/ directory currently ships ~115 bundled plugins (providers, channels, etc.) directly inside this repo. That is becoming a problem:

  • Repo weight (clone size, CI time, bundle size, reviewer load) grows linearly with every new integration.
  • Community / vendor plugins (e.g. WeCom, other 3rd-party channels) cannot be added without landing code here, so the repo ends up owning plugins that should live elsewhere.
  • The existing install surface (openclaw plugins install ..., ClawHub, npm, marketplace) is already mature and solves the distribution problem — we just haven't taken the step of unbundling.

Goal: make every extension an npm package that users install on demand via

openclaw plugins install @wecom/wecom-openclaw-plugin

…while keeping the onboarding UX unchanged: users still pick plugins from a list during openclaw onboard, and the CLI installs what they pick, with a visible progress indicator.

Non-goals (for this issue)

  • We are not migrating all extensions out of the repo in one go.
  • We are not breaking the existing bundled-plugin runtime in phase 1.
  • We are not redesigning the Plugin SDK contracts in phase 1 (that's phase 2/3).

Current state (summary of relevant code paths)

Already in place — we are building on these, not replacing them:

  • extensions/<id>/openclaw.plugin.json — per-plugin manifest (id, providers, configSchema, auth choices, …).
  • extensions/<id>/package.json with openclaw.extensions — entry points.
  • src/plugins/bundled-plugin-metadata.ts / bundled-plugin-scan.ts / bundled-sources.ts — discovers bundled plugins by scanning extensions/.
  • src/plugins/discovery.ts, manifest.ts, manifest-registry.ts — merges bundled + installed + linked plugins.
  • src/plugins/install.ts, clawhub.ts, marketplace.ts — the install pipeline (ClawHub → npm → path → archive → marketplace).
  • src/auto-reply/reply/commands-plugins.ts + src/commands/onboard-*.ts + src/wizard/setup.ts — the user-facing wiring.
  • docs/plugins/manifest.md + docs/cli/plugins.md — public contracts.

The existing openclaw plugins install <spec> pipeline already supports ClawHub, npm registry, local path, archive, linked path, and Claude-style marketplaces, with integrity checks and a dangerous-code scanner. We will reuse it as the execution engine.


Proposed design

Key idea: "remote manifest" becomes the source of truth for onboarding

Right now onboarding discovers what to offer by scanning locally bundled plugins. In the new model, onboarding discovers from a Plugin Manifest (a list of plugins and their metadata), which has three sources with identical shape and priority order:

  1. Bundled manifest (in-repo, fallback, offline) — ships in extensions/manifest.json.
  2. Remote manifest (fetched from a stable URL, e.g. ClawHub / a signed JSON on a CDN).
  3. Local override (config file under ~/.openclaw/..., for air-gapped / enterprise pinning).

The manifest entry for a plugin is intentionally small and does not include runtime code — it describes how to obtain and categorize the plugin:

{
  "schemaVersion": 1,
  "updatedAt": "2026-04-22T00:00:00Z",
  "plugins": [
    {
      "id": "wecom",
      "kind": "channel",                     // "channel" | "provider" | "tool" | "memory" | ...
      "name": "WeCom",
      "description": "WeCom (企业微信) channel plugin",
      "tags": ["channel", "im", "enterprise"],
      "npmSpec": "@wecom/wecom-openclaw-plugin",
      "clawhubPackage": "wecom-openclaw-plugin",   // optional
      "minHostVersion": "2026.4.0",
      "homepage": "https://github.com/wecom/wecom-openclaw-plugin",
      "verified": true,                             // first-party / signed
      "enabledByDefault": false,
      "onboardingGroup": "channels.enterprise",
      "search": { "keywords": ["wecom", "qiwei", "企业微信"] }
    }
  ]
}

Architecture

┌─────────────────┐   discover   ┌──────────────────────┐
│ onboard wizard  │ ───────────▶ │ ExtensionRegistry    │ ◀─┐
└─────────────────┘              │ (merges sources)      │  │ same shape
         │ user picks plugin     └──────────────────────┘  │
         ▼                          ▲          ▲           │
┌─────────────────┐                 │          │           │
│ InstallManager  │─────────────────┤          │           │
│ (progress bar)  │ reuses existing │          │           │
└─────────────────┘ `openclaw       │          │           │
         │          plugins install`│          │           │
         ▼                          │          │           │
┌─────────────────┐   ┌─────────────┴──┐  ┌────┴────────┐ ┌─┴──────────┐
│ Plugin runtime  │   │ Bundled        │  │ Remote      │ │ Local      │
│ (unchanged SDK) │   │ manifest       │  │ manifest    │ │ override   │
└─────────────────┘   │ (in-repo)      │  │ (CDN/HTTP)  │ │ (user file)│
                      └────────────────┘  └─────────────┘ └────────────┘

What changes in each layer

extensions/

  • Add extensions/manifest.json — the bundled/default manifest. Phase 1 reuses the exact same per-plugin metadata we already have in each openclaw.plugin.json, just aggregated.
  • Keep all extensions in-repo in phase 1. Nothing moves out yet.
  • Add a lint/CI step: every plugin dir must have an entry in extensions/manifest.json, and fields must match.

src/plugins/extension-registry/ (new module)

  • manifest-source.ts: typed interface ExtensionManifestSource with list() / get(id) / refresh().
  • Three implementations:
    • BundledManifestSource — reads extensions/manifest.json.
    • RemoteManifestSourcefetchWithSsrfGuard + cache with ETag + signature field (reserve signature in schema, no verification in phase 1).
    • LocalOverrideManifestSource — from ~/.openclaw/extensions-manifest.json.
  • registry.ts: merges sources by id with priority local > remote > bundled, emits a unified ExtensionEntry[].
  • Gracefully degrades: if remote fetch fails → fall back to bundled; all network paths are offline-safe.

src/wizard/ (onboard UI)

  • setup.extensions.ts (new): prompts a filterable list grouped by kind (Channel / Model / Tool / …). Search uses ExtensionEntry.search.keywords.
  • User selection produces an install plan: a list of { id, installSpec } where installSpec is derived from manifest fields (clawhubPackageclawhub:..., else npmSpec, else local bundled path for not-yet-extracted plugins).
  • Existing channel / provider prompts continue to work; setup.extensions.ts runs before them so newly-installed plugins are visible to downstream steps in the same wizard session.

Install pipeline (reuse, don't rewrite)

  • Call existing installPluginFromClawHub / installPluginFromNpmSpec / installPluginFromPath in src/plugins/install.ts + clawhub.ts.
  • Add a thin runInstallPlanWithProgress(plan, prompter) wrapper that reports per-step progress to the wizard's prompter:
    1. Resolving spec (clawhub/npm resolve)
    2. Downloading (bytes progress from downloadClawHubPackageArchive / npm tarball)
    3. Verifying integrity
    4. Security scan
    5. Extracting
    6. Registering manifest
    • Emits structured events to both an interactive spinner and --non-interactive JSON output.
  • Add a --plan flag to openclaw plugins install so the wizard can batch multiple installs and show aggregate progress.

Backward compatibility / migration path

  • Phase 1 (this issue): bundled manifest + registry + wizard integration + install-with-progress. No plugin moves out. User workflow is unchanged; only the code path that feeds the onboarding list changes.
  • Phase 2: start externalizing plugins one at a time. Flow per plugin:
    1. Publish @openclaw/<id> (or keep community scope) to ClawHub + npm.
    2. Flip its manifest entry source: "bundled"source: "external".
    3. Remove directory from extensions/ in a follow-up PR, gated behind min-host-version.
  • Phase 3: SDK cleanup — stabilize the plugin-facing surface so external plugins no longer need workspace:* deps, dedicated tsconfig.package-boundary.*, or private internals. That work is tracked separately; this issue just makes sure phase 1 does not widen the SDK surface.

Manifest forward-compat rules

To survive later SDK changes without breaking older hosts / older manifests:

  • schemaVersion: 1 is mandatory; older hosts reject unknown major versions but ignore unknown fields.
  • All new manifest fields in future versions must be optional and must have a sane default interpretation on old hosts.
  • minHostVersion is honoured on install and at discovery time (hidden with a reason when incompatible).
  • Every ExtensionEntry carries a source tag (bundled / remote / local) so diagnostics (openclaw plugins doctor) can attribute problems correctly.
  • Reserve but don't yet consume: signature, publisher, sbom, capabilities (for future declarative activation).

Deliverables (phase 1 = this issue)

Must

  • Schema: extensions/manifest.schema.json + ExtensionManifest TS type.
  • Aggregate extensions/manifest.json generated from existing per-plugin openclaw.plugin.json (with a pnpm build:extensions-manifest script + CI check that it is up to date).
  • src/plugins/extension-registry/ module with bundled + remote + local sources and merge logic, fully unit-tested (offline-safe).
  • Onboard wizard step: setup.extensions.ts, wired into runSetupWizard before channel/provider setup.
  • Install-with-progress wrapper on top of existing installPluginFromClawHub / installPluginFromNpmSpec / installPluginFromPath, surfaced through the clack prompter and through --json for non-interactive runs.
  • Docs: new docs/plugins/extension-manifest.md; update docs/cli/plugins.md and docs/plugins/manifest.md with a "extension manifest vs plugin manifest" explainer.
  • Tests: registry merge, remote-fallback-to-bundled, install progress events, onboard wizard selection → install plan → install call.

Should

  • openclaw plugins search <query> command that queries the extension registry (bundled + remote).
  • openclaw plugins manifest refresh to explicitly refresh the cached remote manifest.
  • Telemetry hook (opt-in) for "which plugins were offered / installed from onboarding" so we can prioritize what to externalize first.

Nice to have

  • Signature verification stub (feature-flagged off).
  • Offline cache of last-known-good remote manifest under the OpenClaw state dir.

Acceptance criteria

  1. Running openclaw onboard on a fresh install shows the same (or a superset of) plugin choices it shows today, sourced from ExtensionRegistry, not from a direct extensions/ scan in the wizard.
  2. Selecting a plugin in onboarding kicks off the same code path as openclaw plugins install <spec>, with a live progress indicator per step.
  3. With OPENCLAW_EXTENSIONS_MANIFEST_URL pointing to a test manifest, the wizard offers the remote-only plugins and installs them via ClawHub/npm — and with the network blocked, it still works from the bundled manifest.
  4. No regression: openclaw plugins list, inspect, enable/disable, update, uninstall, marketplace install, and existing channel/provider plugin flows continue to pass their current test suites.
  5. extensions/manifest.json is regenerated and validated by CI; a missing or drifted entry fails the build.

Out of scope (tracked in follow-ups)

  • Actually removing plugins from extensions/ (phase 2, per-plugin issues).
  • Plugin-SDK stabilization / dropping workspace:* for out-of-repo plugins (phase 3).
  • UI for managing plugins outside onboarding (post-phase-3).

/cc maintainers — happy to split this into smaller PRs (manifest schema + registry first, wizard integration second, progress wrapper third) if that's easier to review.

extent analysis

TL;DR

To fix the issue of bundled plugins in the extensions/ directory, implement a plugin manifest system that allows for on-demand installation of plugins via openclaw plugins install while keeping the onboarding UX unchanged.

Guidance

  1. Create a plugin manifest: Design a manifest file (extensions/manifest.json) that lists all available plugins, including their metadata, and serves as the source of truth for onboarding.
  2. Implement the ExtensionRegistry: Develop a registry module (src/plugins/extension-registry/) that merges plugin sources (bundled, remote, local) and provides a unified list of plugins for onboarding.
  3. Update the onboard wizard: Modify the onboard wizard to use the ExtensionRegistry and display a filterable list of plugins, allowing users to select and install plugins on demand.
  4. Add install progress wrapper: Create a wrapper around the existing install pipeline to display progress indicators for each installation step.
  5. Test and validate: Ensure that the new system works as expected, including testing the registry merge, remote manifest fallback, and install progress events.

Example

// extensions/manifest.json
{
  "schemaVersion": 1,
  "plugins": [
    {
      "id": "wecom",
      "kind": "channel",
      "name": "WeCom",
      "npmSpec": "@wecom/wecom-openclaw-plugin"
    }
  ]
}

Notes

  • This solution focuses on phase 1 of the proposed design, which introduces the plugin manifest system and updates the onboard wizard without removing any plugins from the extensions/ directory.
  • Future phases will involve externalizing plugins and stabilizing the plugin SDK.

Recommendation

Apply the workaround by implementing the plugin manifest system and updating the onboard wizard to use the ExtensionRegistry, as this will allow for on-demand installation of plugins while maintaining the existing onboarding UX.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Externalize extensions: manifest-driven discovery + on-demand install via `openclaw plugins install` [1 pull requests, 1 participants]