fix: keep whatsapp and acpx bundled, show install hint for missing channel plugins [AI-assisted]#52846
Conversation
Greptile SummaryThis PR improves the UX for users upgrading from builds that included WhatsApp/ACPX as bundled channels: instead of the misleading "stale config entry" message, they now receive an actionable install hint ( The implementation is tightly scoped — the new branch is guarded by both Key changes:
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/config/validation.ts
Line: 527
Comment:
**Module-level constant preferred**
`KNOWN_CHANNEL_ID_SET` is derived entirely from `CHANNEL_IDS`, which is a compile-time constant. Rebuilding the `Set` inside the function on every call to `validateConfigObjectWithPluginsBase` is harmless (it's a tiny array), but hoisting it to module scope makes the intent clearer and avoids unnecessary allocations:
```suggestion
const CHANNEL_IDS_SET: ReadonlySet<string> = new Set(CHANNEL_IDS);
```
…or better yet, declare it at module scope alongside `LEGACY_REMOVED_PLUGIN_IDS`:
```ts
const KNOWN_CHANNEL_ID_SET: ReadonlySet<string> = new Set(CHANNEL_IDS as readonly string[]);
```
Then remove the local declaration and reference the module-level constant inside the function.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/config/config.plugin-validation.test.ts
Line: 538
Comment:
**Test doesn't assert `res.ok === true`**
The comment says "Config should still be valid (warnOnly path)" but the assertion is never made. If a future change accidentally starts emitting issues instead of warnings (making `res.ok === false`), this test would still pass because `res.warnings` is present on both result shapes. Adding an explicit `ok` assertion closes that gap:
```suggestion
expect(res.ok).toBe(true);
// Config should still be valid (warnOnly path) but with an install hint.
expect(res.warnings).toContainEqual({
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: show install hint when channel plug..." | Re-trigger Greptile |
src/config/validation.ts
Outdated
| const { registry } = ensureRegistry(); | ||
| const knownIds = ensureKnownIds(); | ||
| const normalizedPlugins = ensureNormalizedPlugins(); | ||
| const KNOWN_CHANNEL_ID_SET: ReadonlySet<string> = new Set(CHANNEL_IDS as readonly string[]); |
There was a problem hiding this comment.
Module-level constant preferred
KNOWN_CHANNEL_ID_SET is derived entirely from CHANNEL_IDS, which is a compile-time constant. Rebuilding the Set inside the function on every call to validateConfigObjectWithPluginsBase is harmless (it's a tiny array), but hoisting it to module scope makes the intent clearer and avoids unnecessary allocations:
| const KNOWN_CHANNEL_ID_SET: ReadonlySet<string> = new Set(CHANNEL_IDS as readonly string[]); | |
| const CHANNEL_IDS_SET: ReadonlySet<string> = new Set(CHANNEL_IDS); |
…or better yet, declare it at module scope alongside LEGACY_REMOVED_PLUGIN_IDS:
const KNOWN_CHANNEL_ID_SET: ReadonlySet<string> = new Set(CHANNEL_IDS as readonly string[]);Then remove the local declaration and reference the module-level constant inside the function.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/validation.ts
Line: 527
Comment:
**Module-level constant preferred**
`KNOWN_CHANNEL_ID_SET` is derived entirely from `CHANNEL_IDS`, which is a compile-time constant. Rebuilding the `Set` inside the function on every call to `validateConfigObjectWithPluginsBase` is harmless (it's a tiny array), but hoisting it to module scope makes the intent clearer and avoids unnecessary allocations:
```suggestion
const CHANNEL_IDS_SET: ReadonlySet<string> = new Set(CHANNEL_IDS);
```
…or better yet, declare it at module scope alongside `LEGACY_REMOVED_PLUGIN_IDS`:
```ts
const KNOWN_CHANNEL_ID_SET: ReadonlySet<string> = new Set(CHANNEL_IDS as readonly string[]);
```
Then remove the local declaration and reference the module-level constant inside the function.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| entries: { whatsapp: { enabled: true } }, | ||
| }, | ||
| }); | ||
| // Config should still be valid (warnOnly path) but with an install hint. |
There was a problem hiding this comment.
Test doesn't assert
res.ok === true
The comment says "Config should still be valid (warnOnly path)" but the assertion is never made. If a future change accidentally starts emitting issues instead of warnings (making res.ok === false), this test would still pass because res.warnings is present on both result shapes. Adding an explicit ok assertion closes that gap:
| // Config should still be valid (warnOnly path) but with an install hint. | |
| expect(res.ok).toBe(true); | |
| // Config should still be valid (warnOnly path) but with an install hint. | |
| expect(res.warnings).toContainEqual({ |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/config/config.plugin-validation.test.ts
Line: 538
Comment:
**Test doesn't assert `res.ok === true`**
The comment says "Config should still be valid (warnOnly path)" but the assertion is never made. If a future change accidentally starts emitting issues instead of warnings (making `res.ok === false`), this test would still pass because `res.warnings` is present on both result shapes. Adding an explicit `ok` assertion closes that gap:
```suggestion
expect(res.ok).toBe(true);
// Config should still be valid (warnOnly path) but with an install hint.
expect(res.warnings).toContainEqual({
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d8a426f8d5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/config/validation.ts
Outdated
| }); | ||
| return; | ||
| } | ||
| // When a missing plugin corresponds to a known channel that is configured, | ||
| // it is almost certainly an optional bundled plugin that needs to be | ||
| // installed separately rather than a stale leftover. | ||
| if (opts?.warnOnly && KNOWN_CHANNEL_ID_SET.has(pluginId)) { |
There was a problem hiding this comment.
Include extension-backed optional channels in this install-hint check
Because KNOWN_CHANNEL_ID_SET is built only from CHANNEL_IDS, this branch only recognizes the built-in chat IDs from src/channels/ids.ts:4-14. Most optional bundled channel plugins that were moved out of the default build (matrix, msteams, nostr, tlon, twitch, zalouser) are extension channels listed in scripts/lib/optional-bundled-clusters.mjs:1-14, and their config still lives under channels.* (src/config/zod-schema.providers.ts:25-46). Those upgrades will continue to fall through to the old unknown channel id/stale-entry behavior instead of getting the new install guidance, so this fix only covers a small subset of the affected channels.
Useful? React with 👍 / 👎.
src/config/validation.ts
Outdated
| warnings.push({ | ||
| path, | ||
| message: `plugin not installed: ${pluginId} — channel is configured but the plugin is not bundled in this build. Install it with: openclaw plugins install ${pluginId}`, | ||
| }); |
There was a problem hiding this comment.
Use the declared npm install spec in the warning
This message hard-codes openclaw plugins install ${pluginId}, but the official install surface for repo-owned channel plugins is the package's declared install.npmSpec (for example @openclaw/whatsapp in extensions/whatsapp/package.json:36-40), and ensureChannelSetupPluginInstalled installs that exact spec in src/commands/channel-setup/plugin-install.ts:185-199. For channels whose package name is scoped, users following this new warning on stable/beta builds can still fail to install the plugin or resolve a different package than the one OpenClaw expects.
Useful? React with 👍 / 👎.
|
Updated the parent issue (#52838) with a full audit of all 13 optional bundled clusters. 6 of 13 are completely unavailable on 2026.3.22 (not bundled, not on npm) — including This PR handles the UX side (better error message → install hint), but the root issue is that the npm release workflow doesn't set See the full breakdown and workaround in the issue comment: #52838 (comment) |
81bb97c to
80b0ca6
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 80b0ca6e7b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
src/config/validation.ts
Outdated
| // When a missing plugin corresponds to a known channel that is configured, | ||
| // it is almost certainly an optional bundled plugin that needs to be | ||
| // installed separately rather than a stale leftover. | ||
| if (opts?.warnOnly && KNOWN_CHANNEL_ID_SET.has(pluginId)) { |
There was a problem hiding this comment.
Downgrade missing allowlisted optional channels to install hints
When a built-in channel becomes optional-bundled (for example googlechat in scripts/lib/optional-bundled-clusters.mjs:8-19), upgrades with a non-empty plugins.allow still fail validation. enablePluginInConfig appends built-in channel IDs to plugins.allow whenever an allowlist exists (src/plugins/enable.ts:21-22, src/plugins/enable.test.ts:43-52), but this new branch only runs on the warnOnly path used for plugins.entries.*; the later plugins.allow check still emits a hard plugin not found error. That leaves existing allowlisted channel installs unable to start instead of getting the migration guidance this patch is supposed to add.
Useful? React with 👍 / 👎.
src/config/validation.ts
Outdated
| warnings.push({ | ||
| path, | ||
| message: `plugin not installed: ${pluginId} — channel is configured but the plugin is not bundled in this build. Install it with: openclaw plugins install ${pluginId}`, | ||
| }); |
There was a problem hiding this comment.
Emit the install hint for channel-only configs as well
This hint is only reached after an unknown plugin ID is found under plugins.entries, but bundled chat channels can be enabled from channels.<id>.enabled=true alone (src/plugins/config-state.test.ts:171-176, src/plugins/loader.test.ts:753-766). For optional-bundled built-ins like googlechat (scripts/lib/optional-bundled-clusters.mjs:8-19), a config that only sets channels.googlechat.enabled=true never creates plugins.entries.googlechat, so validation now succeeds with no warning at all even though the channel plugin is absent and will not load.
Useful? React with 👍 / 👎.
|
Can confirm the root cause analysis here is spot on. I filed #52857 independently with the same findings (npm 404 for @openclaw/whatsapp, missing dist/extensions/whatsapp/ in the published tarball, runtime files orphaned in dist/). The two-part fix makes sense: keeping whatsapp and acpx bundled until proper npm packages exist is the right call, and the install hint for missing channel plugins is a solid UX improvement over the misleading "stale config entry" message. Tested the regression on a live VPS upgrade from v2026.3.13 to v2026.3.22 — WhatsApp silently disappeared, Telegram recovered fine. Rolling back restored WhatsApp. This PR addresses the packaging root cause correctly. |
|
Agree on kepping whatsapp in default builds. Whatsapp is a rather common channel used, it should not be marked as optional as it does not appear to be only of regional minor usage, despite its bulkiness. |
80b0ca6 to
bdd5ddb
Compare
bdd5ddb to
274029f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 274029ffea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
extensions/whatsapp/package.json
Outdated
| "./index.ts", | ||
| "./light-runtime-api.ts", | ||
| "./runtime-api.ts", | ||
| "./api.ts", | ||
| "./auth-presence.ts", |
There was a problem hiding this comment.
Keep WhatsApp helper barrels out of
openclaw.extensions
openclaw.extensions is consumed as the set of loadable plugin entrypoints in src/plugins/discovery.ts:584-603 and src/plugins/discovery.ts:715-730, not as a generic “files to publish” list. Once this array has more than one entry, deriveIdHint() switches the primary candidate to whatsapp/index (src/plugins/discovery.ts:339-365), which src/plugins/manifest-registry.ts:388-394 immediately reports as a plugin id mismatch for manifest id whatsapp. That warning is then surfaced during config validation and normal gateway startup (src/config/validation.ts:411-418, src/gateway/server-plugins.ts:429-444), so this change makes every bundled/workspace WhatsApp load look broken even when the plugin is fine.
Useful? React with 👍 / 👎.
| if (isConfiguredChannel(pluginId)) { | ||
| warnings.push({ | ||
| path, | ||
| message: `plugin not installed: ${pluginId} — channel is configured but the plugin is not bundled in this build. Install it with: openclaw plugins install ${pluginId}`, |
There was a problem hiding this comment.
Extend the install hint to plugin-entry-enabled channel configs
This new branch only looks at channels.<id> before deciding to show the install guidance. An explicit plugins.entries.<id>.enabled = true is also a supported way to enable a channel plugin — resolveEnableState() treats entry.enabled === true as enabled even without any channels.<id> section (src/plugins/config-state.ts:286-301) — so those plugin-centric configs still fall through to the old stale config entry ignored warning. In practice, upgrades that enable a channel through plugins.entries.whatsapp will still get told to remove the config instead of being shown how to install the missing plugin.
Useful? React with 👍 / 👎.
274029f to
8b8ae88
Compare
…annel plugins Two changes to ease the transition to channels-as-plugins: 1. Remove whatsapp and acpx from optionalBundledClusters so they ship in npm and Docker releases. Both are core functionality with no published npm package yet, so excluding them leaves users with no install path. Once @openclaw/whatsapp and @openclaw/acpx are published and stable, they can move back to optional with proper migration guidance. 2. When a missing plugin matches a known channel with active config, show an actionable install hint instead of the misleading 'stale config entry' message. This covers both plugins.entries and plugins.allow paths, helping users of remaining optional channels (googlechat, matrix, msteams, etc.) find the right fix. Also adds the WhatsApp runtime API entries (light-runtime-api, runtime-api, etc.) to openclaw.extensions so they are compiled into dist/extensions/whatsapp/ — without these, tarball installs crash with 'missing light-runtime-api for plugin whatsapp'. Fixes openclaw#52838
8b8ae88 to
160c0c0
Compare
|
Cmon @steipete fix it man <3 |
Addressed Codex review feedback (latest push)P2: Keep WhatsApp helper barrels out of
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 160c0c0c0a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const file of fs.readdirSync(pluginDir)) { | ||
| if (!file.endsWith(".ts") || file.endsWith(".test.ts") || file.endsWith(".runtime.ts")) { | ||
| continue; | ||
| } | ||
| const baseName = file.replace(/\.[^.]+$/u, ""); | ||
| if (declaredEntries.has(baseName)) { | ||
| continue; | ||
| } | ||
| const sourceFile = path.join(pluginDir, file); | ||
| const distFile = path.join(distPluginDir, file); | ||
| if (fs.statSync(sourceFile).isFile()) { | ||
| fs.copyFileSync(sourceFile, distFile); |
There was a problem hiding this comment.
Build the runtime shims instead of copying raw
.ts files
This postbuild only copies top-level shim sources into dist/extensions/<plugin>, but the shims it is trying to rescue still immediately re-export ./src/*.js (for example extensions/whatsapp/runtime-api.ts and light-runtime-api.ts). tsdown.config.ts only emits declared openclaw.extensions/setupEntry entrypoints, so no dist/extensions/whatsapp/src/* tree is generated alongside these copied files. In packaged installs, src/plugins/runtime/runtime-whatsapp-boundary.ts resolves dist/extensions/whatsapp/runtime-api.ts, Jiti has no sibling sources to rewrite against, and the WhatsApp runtime still fails to load.
Useful? React with 👍 / 👎.
| // WhatsApp and ACPX were removed from this list to ease the transition to | ||
| // channels-as-plugins. Both are core functionality (primary messaging channel | ||
| // and ACP/Codex runtime) and have no published npm package yet, so excluding | ||
| // them from the default build leaves users with no install path. | ||
| export const optionalBundledClusters = [ |
There was a problem hiding this comment.
Keep ACPX optional until its bundled dependency is staged
Removing acpx from optionalBundledClusters makes every default build claim ACPX is bundled, but scripts/stage-bundled-plugin-runtime-deps.mjs only installs bundled plugin dependencies when openclaw.bundle.stageRuntimeDependencies === true, and extensions/acpx/package.json does not opt in. That means the default package still ships without dist/extensions/acpx/node_modules/.bin/acpx, so resolveAcpxPluginConfig() falls back to a runtime npm install [email protected]; on offline or registry-restricted hosts the newly bundled ACP backend still comes up broken.
Useful? React with 👍 / 👎.
|
Thanks but we are working on a fix ourselves and will be released very shortly. |
Problem
Upgrading from 2026.3.13 to 2026.3.22 silently breaks WhatsApp and ACPX. Both were added to
optionalBundledClustersbut:@openclaw/whatsapp,@openclaw/acpxreturn 404)[email protected]package is broken (missingopenclaw.extensionsmetadata)6 of 13 optional bundled plugins are completely unavailable on 2026.3.22 (not bundled, not on npm):
whatsapp,acpx,googlechat,diffs,memory-lancedb,ui.Fix
Single commit with three targeted changes to ease the transition to channels-as-plugins:
1. Keep whatsapp and acpx in default builds (
scripts/lib/optional-bundled-clusters.mjs)Remove
whatsappandacpxfrom the optional list. Both are core functionality — WhatsApp is a primary messaging channel (bundled since day one) and ACPX is the ACP/Codex runtime. Once their npm packages are published and stable, they can move back to optional with proper migration guidance.2. Include WhatsApp runtime API entries in the extension build (
extensions/whatsapp/package.json)The WhatsApp plugin uses a lazy boundary pattern that resolves
light-runtime-api,runtime-api, and other modules at runtime. These were not listed inopenclaw.extensions, so tsdown never compiled them intodist/extensions/whatsapp/. Without this, tarball installs crash with:3. Show install hint for configured-but-missing channel plugins (
src/config/validation.ts)When a missing plugin matches a known channel ID with active configuration, show an actionable install command. This covers both
plugins.entriesandplugins.allowpaths.Before:
After:
Changes
scripts/lib/optional-bundled-clusters.mjswhatsappandacpxfrom optional listextensions/whatsapp/package.jsonopenclaw.extensionssrc/plugins/bundled-plugin-metadata.generated.tssrc/config/validation.tssrc/config/config.plugin-validation.test.tsTesting
pnpm vitest run src/config/config.plugin-validation.test.ts— 16/16 passpnpm test:extension whatsapp— 297/305 pass (8 pre-existing failures inlogin.test.tstimer mocks, unrelated to this change)pnpm build→ verifieddist/extensions/whatsapp/includeslight-runtime-api.js,runtime-api.js, etc.npm pack→npm install -g openclaw-2026.3.23.tgz→ gateway restart → WhatsApp connected and processing messages (not a symlink — real tarball install)Review feedback addressed
KNOWN_CHANNEL_ID_SETto module scope (greptile P2)expect(res.ok).toBe(true)assertion (greptile P2)plugins.allowentries too, not justplugins.entries(codex P1)isConfiguredChannel()helper (codex P1)AI-assisted
Fixes #52838