openclaw/docs/plugins/manifest.md
Ayaan Zaidi 7292689e7f
fix(signal): start accounts with spaced keys without Doctor (#145750)
## What Problem This Solves

Signal lists `Work Phone` as `work-phone` but misses its authored number/endpoint. Fresh startup reports unconfigured and never contacts that endpoint until Doctor renames the key. Runtime must work without the #138982 cleanup.

## Why This Change Was Made

The decision 'which stored key serves Signal account id X' is made by exactly one mechanism at `src/routing/account-lookup.ts:94`.

Signal declares its rule once in manifest metadata: exact keys win; normalized aliases use authored settings only with a nonempty own number. The task owner accepted own-number transport/settings, whole-root inheritance for aliases without a number, exact collision winners, and Doctor refusing cleanup that activates ignored settings.

Setup preserves restrictions in the canonical default row, even when empty, and restores its number to root. Promotion targets the runtime winner. The second correction removes command singleton/writer raw-key bypasses: selector-owned optional creation; existing selection for delete/clear. Global owner authority stays separate.

## User Impact

Own-number aliases start at their authored endpoint without Doctor. Root inheritance, exact winners and other channels' maps remain stable; adding an account preserves the active account's inherited credentials. No config keys, schema or protocol versions change. Public SDK exports remain. Code `8828dc61b2da6d7ed02b8765080ed8f474ae2567`: 76 files (+1,941/-360) against e76.

Credit: @marmar9615-cloud for the report; adversarial review found the incomplete repair.

## Evidence

Eight runtime scenarios cover both status paths, 35 startup cases and allowed/excluded senders across setup, reload and restart.

| Fresh alias, no Doctor | Pinned base `3f75b33d67` | Candidate `d4e41539b2aee717e105be61522463d76ad74490` |
|---|---|---|
| Both status paths: configured/running | false/false | true/true; connected |
| Authored endpoint | No requests | Account event stream |

Published v2026.9.4 CLI updated isolated authentic old state to merge `174bdff8057e193b01b3bf63ef969a5f676d737d`, exited 0, and a new Gateway passed both status paths/events. Independent acceptance verified the new process and cleanup. That cell permits updater Doctor; the fresh cell does not. Review of 134 later paths supports applicability through actual historical CI merge `4d2cdb6527d9b3e42f4f7085703a20f358b66c01`. Separate package `20090983b5683ff352ed31e50a15143468913084` is not that CI merge.

## Compatibility

| Consumer | Host/operation | Observed result |
|---|---|---|
| Published Signal 2026.9.4 npm artifact; API floor 2026.9.4 | Released 2026.9.4; install/canonical startup/both status paths | External plugin/events pass |
| Same published artifact | Candidate core; same flow | External source/host peer link pass |
| Matching candidate Signal/core | Fresh spaced account, optional Doctor, setup/reload/restart | Retained controls pass |
| Published v2026.9.4 CLI/authentic old state | Actual update/final driver output/new candidate process | Pass, including independent acceptance |

Preflights remain failed: `34683985589` found 1,113 native translation-ID mismatches; `34684811000` fixed those but found 126 different mismatches plus UI fallback drift. Neither compiled declarations. Focused `34686128844` passed candidate compilation but failed released Node-type enrollment; `34686394191` fixes only that environment and passes both. Sealed source/producer hashes remain; no locale source/release guard changed.

Unpublished candidate Signal imports a new SDK export and cannot run on 2026.9.4 SDK; this pairing is neither published nor claimed. Official release sync raises its API floor to matching host before publication; no fallback selector/invented release version.

## Consumers

Grouped paths use braces to enumerate exact filenames; no wildcard is implied.

Paths cover readers, writers, delivery/status, SDK facades and tests. Facades retain public signatures and delegate to the shared owner; their directory does not imply channel-specific use. Tests and Compatibility record execution and the published/candidate matrix. The pinned base `3f75b33d67` has 299 successful queries across 394 files. Historical CI integration `4d2cdb6527d9b3e42f4f7085703a20f358b66c01` has 147 queries across 401 files (2,385 references, 1,826 positions); its parents are main `67e1a8b0f2` and PR `ec1b82bc0c`. The correction at `8cbd800e3d` adds a 12-query supplement across 242 files (836 references, 767 positions) and two predecessor-local queries. All resolve. These supplements are source evidence, not a replacement CI-merge census. Complete raw outputs and omitted-project coverage are retained; scripts queries close the packed-consumer route and manual edges cover native assets and registrations.

Historical `census.json` stays bound to340. New `census-8828dc6-reviewed-row.json` uses the same pinned base and CI run34688444368 checkout `9fcd956a0f3dad9caed524a55968dbbc19f3346d`, tree `00e4c42d2982b3745f7db6175918155fcef51754`, main parent `cb25f00e24`; second parent is882 above. Prior5b census is preserved. Source-query applicability is separate from CI execution.

### Signal, shared owners, writers and final consumers

- `extensions/signal/api.ts` — Retained public operation export; transport writes use the selected key.
- `extensions/signal/config-doctor-api.ts` — Retained Doctor/compatibility registration; repair owner supplies persisted cleanup.
- `extensions/signal/runtime-api.ts` — Retained generic config/setup runtime exports; selection stays with shared owners.
- Under `extensions/signal/src/` (relative paths):
  - `account-key-repair.ts` — Changed Doctor eligibility to selected-key lookup; collision enumeration only reports.
  - `account-selection.ts` — Adapter passes manifest policy/routing normalizer to resolveAccountKey; resolveSignalAccountEntry projects that key. No selection loop.
  - `accounts.ts` — Changed merged account/reply-mode reads to the shared selector with Signal policy.
  - `channel.ts` — Retained registered threading/outbound calls with Signal channel/account identity.
  - `config-compat.ts` — Removed normalized-first selector; exhaustive migration reads winners through Signal adapter.
  - `config-schema.ts` — Changed default selection; validation still checks every authored row.
  - `doctor.ts` — Retained preview hook delegates warnings to key-repair owner; no selection.
  - `monitor.ts` — Retained receive/reply flow; threading/chunk reads use selected fields.
  - `send.ts` — Retained outbound formatting; Markdown uses selected account.
  - `setup-core.ts` — Changed selection/policy forwarding; default restoration keeps canonical winner/settings.
  - `setup-transport.ts` — Changed reserved-port/transport reads and writes to runtime-selected key.
  - `shared.ts` — Changed scoped adapter forwards Signal policy to runtime/writers; operation ownership stays.
- `extensions/signal/src/monitor/event-handler.ts` — Retained inbound reply/reaction/group policies use runtime-selected account.
- `extensions/signal/src/monitor/inbound-context.ts` — Retained inbound context consumes selected policy from shared owner.
- `scripts/fixtures/packed-plugin-sdk-setup-consumer.ts` — Identical released/candidate fixture calls promotion, setup/setup-runtime patch and allowFrom with full inline adapters/contextual callbacks. Compile results: Tests.
- `scripts/plugin-sdk-surface-report.mts` — Retained SDK budget/report owner; approved delta covers selector exports. No account state.
- `scripts/release-check.ts` — Copies both trusted-tooling fixtures, installs/compiles separate released/candidate consumers; packed smoke invokes/cleans up. Query success is not compilation.
- `src/agents/embedded-agent-runner/{compaction-session-execution.ts,run/attempt-history-prepare.ts}` — Retained history consumers use selected settings before unchanged truncation.
- `src/agents/embedded-agent-runner/history.ts` — Changed history-limit selection; precedence stays.
- `src/agents/{embedded-agent-runner/{model.configured-fallback.ts,model.configured-overrides.ts,model.inline-provider.ts,model.registry-resolution.ts},model-discovery-normalize.ts,provider-attribution.ts,provider-request-config.ts}` — Retained provider metadata readers/types; existing owner facts, not channel policy map.
- `src/agents/identity.ts` — Changed reaction/prefix reads to channel-aware selection.
- `src/agents/runtime-capabilities.ts` — Retained capability projection consumes selected account from shared owner.
- `src/{agents/embedded-agent-runner/model.static-catalog.ts,cli/{plugins-authoring-command.ts,plugins-feature-artifact.ts},commands/models/provider-aliases.ts,gateway/control-ui-plugin-assets.ts,plugins/{bundled-sources.ts,install.runtime.ts}}` — Retained plugin/provider/artifact manifest fields; no Signal key selection or policy interpretation.
- `src/agents/subagents/spawn/acp-spawn-parent-stream.ts` — Changed parent progress-stream selection.
- `src/auto-reply/chunk.ts` — Changed chunk size/mode reads carry channel identity.
- `src/auto-reply/command-auth.ts` — Changed explicit/default/sole fallback allowlists select through owner; no raw singleton return. Authorization/global-owner policy stays.
- `src/auto-reply/reply/block-streaming.ts` — Changed coalesce-setting selection.
- `src/auto-reply/reply/reply-threading.ts` — Changed generic reply-mode selection; registered Signal threading passes explicit policy.
- `src/channels/account-config-enabled.ts` — Changed enabled read uses selected entry.
- `src/channels/draft-streaming-chunking.ts` — Changed draft-stream chunk selection.
- `src/channels/join-intro/report-channel-room-join.ts` — Changed join-intro selection.
- Under `src/channels/plugins/` (relative paths):
  - `account-config-mutation.ts` — Active channels.add: normalize ID → shared promotion with execution adapter → applyAccountConfig. Existing lifecycle/config owners commit.
  - `account-helpers.ts` — Changed factory forwards channel key to shared merge; explicit normalizer stays.
  - `config-helpers.ts` — Changed enable creation uses owner-derived destination; delete/clear require selected existing key. No rejected raw-row fallback; unrelated entries stay.
  - `config-write-policy-shared.ts` — Changed configWrites selection before authorization.
  - `helpers.ts` — Changed DM guidance uses selected policy/stored path.
  - `read-only.ts` — Changed manifest-only reader uses selected record policy, never another snapshot owner.
  - `setup-adapter.types.ts` — Extended optional setup policy; existing implementations valid.
  - `setup-contract.ts` — Extended setup contract forwards policy to execution adapter.
  - `setup-helpers.ts` — Changed name/patch/promotion writes use selected spelling. Sole/default inference cannot restore rejected aliases; full-adapter and metadata-only inputs stay.
  - `setup-promotion-discovery.ts` — Retained lightweight discovery forwards promotion/policy declarations; no key selection.
  - `setup-promotion-helpers.ts` — Derives five promotion fields from ChannelSetupAdapter; duplicate callback shape removed. Owns movable fields/root preservation/named filtering, not destination selection.
  - `setup-wizard-helpers.ts` — Changed patch/allowFrom pass full adapter or derived promotion metadata into shared writing/promotion; destination is owner-selected.
  - `setup-wizard-legacy-compat.ts` — Active legacy wizard delegates policy/scoped patches to shared patch/promotion.
  - `setup-wizard-types.ts` — Retained setup type carries optional policy.
  - `setup-wizard.ts` — Active wizard promotes via owner, temporarily sets legacy defaultAccount, then restores operator default. Temporary scope does not select stored winners.
  - `types.adapters.ts` — Retained type barrel; public adapter identity stays.
  - `types.plugin.ts` — Retained plugin shape and setup ownership.
- `src/channels/status/account-state.ts` — Retained disabled-state check agrees with runtime selection.
- `src/cli/plugin-install-config-policy.ts` — Historical recovery-metadata caller now delegates manifest/request planning to src/plugins/install-config.ts; no account selection.
- `src/commands/doctor-config-flow.ts` — Retained Doctor binding orchestration delegates selection to repair owner.
- `src/commands/doctor/shared/legacy-config-binding-repair.ts` — Changed binding lookup; Doctor retains persistence.
- `src/commands/doctor/shared/plugin-metadata-snapshot-scope.ts` — Retained metadata rebase refreshes policy within its scope.
- Under `src/config/` (relative paths):
  - `channel-account-config.ts` — Shared merge forwards normalizer/channel/policy to one key decision; precedence stays.
  - `channel-alias-migration.ts` — Retained migration uses selected inherited-stream account.
  - `channel-capabilities.ts` — Changed capability selection.
  - `channel-doctor-helpers.ts` — Changed inherited-stream selection; full-map migration stays exhaustive.
  - `channel-groups.ts` — Changed merged-group read carries channel identity.
  - `context-visibility.ts` — Changed context-visibility selection.
  - `group-policy.ts` — Changed group path/policy selection before field precedence.
  - `group-scope-tree.ts` — Retained group-tree consumer of selected data.
  - `implicit-mentions.ts` — Changed implicit-mention selection.
  - `io.plugin-metadata.ts` — Retained config metadata rebase refreshes policy with existing facts.
  - `markdown-tables.ts` — Changed table-mode read carries channel identity.
  - `zod-schema.providers-whatsapp.ts` — Retained WhatsApp-only schema lookup; undeclared policy preserves old behavior.
- `src/cron/delivery-channel-validation.ts` — Changed enabled selection before delivery validation.
- `src/gateway/server-channels.ts` — Changed health-setting selection; undeclared compatibility forms stay and agree under declared policy. Manager still owns startup/runtime state.
- `src/gateway/server-chat.ts` — Retained heartbeat visibility consumer of selected policy.
- `src/gateway/server-runtime-state-prepare.ts` — Retained manager construction; startup/runtime state owner stays.
- `src/gateway/server-secrets-reload.ts` — Retained secrets-reload manager contract; no selector.
- `src/infra/event-session-routing.ts` — Changed event allowlist selection before routing.
- `src/infra/heartbeat-runner-execution.ts` — Retained heartbeat execution consumes visibility owner.
- `src/infra/heartbeat-visibility.ts` — Changed heartbeat policy selection.
- Under `src/infra/outbound/` (relative paths):
  - `deliver-core.ts` — Retained outbound chunk caller carries channel identity.
  - `message-account-selection.ts` — Retained disabled check agrees with runtime selection.
  - `message-action-params.ts` — Retained media-limit consumer of selected account.
  - `message-action-send.ts` — Retained response-prefix consumer of selected identity.
- `src/media/configured-max-bytes.ts` — Changed media-limit selection.
- Under `src/plugin-sdk/` (relative paths):
  - `{account-core.ts,account-helpers.ts,account-resolution-runtime.ts,account-resolution.ts,agent-runtime.ts,channel-core.ts,channel-feedback.ts,channel-ingress-runtime.ts,channel-outbound.ts,channel-plugin-common.ts,config-runtime.ts,context-visibility-runtime.ts,discord.ts,markdown-table-runtime.ts,reply-chunking.ts,reply-dispatch-runtime.ts,reply-runtime.ts,routing.ts,runtime-doctor-migrations.ts,setup-runtime.ts,setup.ts}` — Retained public SDK facades/types and argument/declaration identities; typed export/local-binding queries cover forwarding.
  - `allowlist-config-edit.ts` — Changed allowlist destination selection; persistence/validation owners stay.
  - `channel-config-helpers.ts` — Extended adapter/factory policy forwarding to enable/delete/setup writers.
  - `channel-dm-policy.ts` — Active DM getCurrent resolves account; setPolicy computes policy/allowFrom and invokes applyPatch or patchChannelConfigForAccount(setupSurface). Descriptive resolveConfigKeys keeps canonical IDs and has no production caller; live writer is active.
  - `channel-policy.ts` — Retained security exports/account wrappers; separate open-group severity change is not selection.
  - `channel-setup.ts` — Retained setup exports/types/missing-plugin builder; missing adapters reject writes.
  - `core.ts` — Retained exports and active security wrapper forward channelKey/policy to DM path owner.
  - `optional-channel-setup.ts` — Retained missing-plugin guidance; applyAccountConfig throws before account selection/mutation.
- Under `src/plugins/` (relative paths):
  - `bundled-channel-config-metadata.ts` — Retained manifest-bearing loader; no account/winner selection.
  - `bundled-plugin-metadata.ts` — Retained complete manifest transport; optional policy is not interpreted.
  - `discovery.ts` — Retained full PluginManifest transport; policy derivation downstream.
  - `discovery.types.ts` — Retained optional bundledManifest type accepts added metadata.
  - `manifest-capability-normalizers.ts` — Retained providerAuthAliases type use, unrelated to account policy.
  - `manifest-registry.ts` — Changed record builder preserves parsed policy; transport stays.
  - `manifest-registry.types.ts` — Retained manifest-derived types/record transport carry optional metadata.
  - `manifest-setup-normalizers.ts` — Validates declared channel/field shape, drops blocked keys and emits metadata; no account-map selection.
  - `manifest-types.ts` — Added optional channel-owned manifest policy.
  - `manifest.ts` — Changed parser adds normalized policy; existing fields stay.
  - `plugin-cache-files.types.ts` — Retained parsed-manifest cache type; lifecycle invalidation stays.
  - `plugin-metadata-snapshot.ts` — Changed installed-index owner-map derivation; existing rebase/project/load lifecycle rebuilds policy.
  - `plugin-metadata-snapshot.types.ts` — Added optional derived map; provider-reader fields stay.
  - `provider-model-compat.ts` — Retained provider-owner map reader; no channel-policy use.
- `src/plugins/contracts/inventory/bundled-capability-metadata.ts` — Retained type/setup/provider-env projection; no account-policy interpretation.
- `src/plugins/runtime/runtime-channel.ts` — Retained chunk/format runtime facade carries channel identity.
- `src/plugins/runtime/types-channel.ts` — Retained runtime type projection; arguments compatible.
- `src/routing/account-lookup.ts` — Entry helpers delegate to key owner. Declared policy gates aliases and optional allowMissing canonical creation; undeclared raw creation stays.
- `src/status/status-text.ts` — Retained Telegram-only rich-message lookup; Signal cannot reach it.
- `src/system-agent/plugin-artifact.ts` — Retained artifact identity/capability reader; incoming consolidation changes lazy config-owner import, not manifest interpretation.

### Retained non-Signal production consumers

Bundled non-Signal manifests omit Signal eligibility. Existing case-only/normalized reads and ordinary scoped/name/cleanup writes remain. Promotion now writes to the runtime exact-key winner; maps need not stay identical. Groups name readers, writers, delivery and forwarding files.

- `extensions/a2a/src/channel-base.ts` — Retained a2a fixed-root setup; no named account selection.
- `extensions/buzz/src/{gateway.ts,setup-core.ts,types.ts}` — Retained Buzz factory/Markdown delivery; setup preserves root during promotion.
- `extensions/clickclack/src/{accounts.ts,setup-core.ts}` — Retained exact-first normalized reader; registered promotion moves root credentials into runtime winner, preserving active account. Tests covers account-add.
- `extensions/discord/src/{accounts.ts,draft-chunking.ts,monitor/{agent-components.dispatch.ts,agent-components.runtime.ts,message-handler.context.ts,message-handler.process-reactions.ts,message-handler.process-reply-runtime.ts,native-command-agent-reply.ts,native-command-status.ts,native-command.ts,provider.ts},outbound-text.ts,send.outbound.ts,setup-adapter.ts,setup-core.ts,setup-surface.ts,shared.ts,token.ts}` — Retained case-only account/token reads, scoped setup/config, chunk/group/context/ack/outbound. Local calls bind Discord; generic runtime facade and lazy dispatch census stay.
- `extensions/feishu/{runtime-api.ts,src/{accounts.ts,bot.ts,channel.ts,comment-dispatcher.ts,outbound.ts,policy.ts,reply-dispatcher.ts,send.ts,setup-core.ts,setup-surface.ts}}` — Retained account/group/context, scoped setup/config, outbound/formatting. Local calls bind Feishu; generic exports, channel-only Markdown and account chunks stay.
- `extensions/googlechat/src/{accounts.ts,channel-base.ts,monitor-reply-delivery.ts,setup-core.ts,setup-surface.ts}` — Retained factory/setup/channel adapters; reply chunks receive resolved account; writes bind googlechat.
- `extensions/imessage/src/{accounts.ts,monitor/{deliver.runtime.ts,deliver.ts,inbound-processing.ts,monitor-provider.ts},send.ts,setup-core.ts,shared.ts}` — Retained factory/scoped writers/send/monitor. Chunk/ack/context bind iMessage; generic runtime forwarding stays.
- `extensions/irc/src/{accounts.ts,channel.ts,message-adapter.ts,send.ts,setup-core.ts}` — Retained factory/hybrid config/setup/send/message adapter; destructured chunks receive irc/accountId; no Signal policy.
- `extensions/line/{runtime-api.ts,src/{accounts.ts,bot-handlers.ts,config-adapter.ts,gateway.ts,group-keys.ts,outbound.ts,setup-core.ts}}` — Retained account/group/scoped writers/ingress/outbound; local calls bind line; generic exports and optional chunk limit stay.
- `extensions/matrix/src/{account-selection.ts,config-adapter.ts,matrix/{account-config.ts,monitor/{ack-config.ts,handler-context.ts,handler.ts},send/chunking.ts},setup-config.ts,setup-core.ts}` — Retained normalized selection/merge/writers/ack/context/formatting. Logical callback target resolves to runtime winner before promotion; Matrix owns bootstrap.
- `extensions/mattermost/{runtime-api.ts,src/{channel-config-shared.ts,mattermost/{accounts.ts,monitor-activation.ts,monitor-event-plan.ts,monitor-posts.ts,monitor-turn.ts,reply-delivery.ts,send.ts,slash-http.ts},setup-core.ts,setup-surface.ts}}` — Retained account/config/scoped setup/monitor/group/ack/outbound. Local calls bind mattermost/resolved account; generic exports stay.
- `extensions/msteams/src/{messenger.ts,monitor-handler/message-handler.ts,monitor.ts,reply-dispatcher.ts,send.ts,setup-core.ts}` — Retained Teams setup/context/ack/send/formatting; channel-only calls remain without new account argument or Signal policy.
- `extensions/nextcloud-talk/src/{accounts.ts,channel.adapters.ts,gateway.ts,send.runtime.ts,send.ts,setup-core.ts}` — Retained factory/scoped writers/gateway/send bind nextcloud-talk; send.runtime remains generic facade.
- `extensions/nostr/src/{gateway.ts,setup-adapter.ts,types.ts}` — Retained factory/setup/Markdown; both gateway calls keep their resolved account IDs.
- `extensions/qa-channel/src/{accounts.ts,channel-base.ts}` — Retained synthetic factory/fixed channel contract; no Signal policy.
- `extensions/raft/src/{accounts.ts,setup.ts}` — Retained factory/channel-bound setup/promotion; no Signal policy.
- `extensions/reef/src/setup.ts` — Retained fixed-channel setup/promotion declarations.
- `extensions/slack/src/{accounts.ts,actions.ts,config-adapter.ts,message-action-dispatch.ts,monitor/{config.runtime.ts,message-handler/{dispatch.ts,prepare.ts},provider.ts,slash-dispatch.runtime.ts,slash.ts},send.ts,setup-core.ts,setup-shared.ts,setup-surface.ts}` — Retained account/config/scoped setup/actions/ingress/send; slash chunk/Markdown bind slack/route.accountId; generic facades stay.
- `extensions/sms/src/{accounts.ts,channel.ts}` — Retained SMS-bound factory/channel account/config contract.
- `extensions/synology-chat/src/{accounts.ts,channel.ts,setup-surface.ts}` — Retained Synology factory/channel/setup binding and promotion.
- `extensions/telegram/{runtime-api.ts,src/{account-config.ts,account-selection.ts,bot-handlers.message-pipeline.ts,bot-message-context.session.ts,bot-message-context.ts,bot-message-dispatch.runtime.ts,bot-message-dispatch.ts,bot-message.ts,bot-native-command-dispatch.ts,bot-native-commands.runtime.ts,channel.ts,config-adapter.ts,draft-chunking.ts,send-edit.ts,send-message.ts,send.runtime.ts,setup-core.ts,setup-surface.helpers.ts,setup-surface.ts,text-chunk-limit.ts,token.ts}}` — Retained normalized account/token, scoped writers, message/native context, drafts/send/formatting. Calls bind telegram; generic facades/root-default precedence/token promotion stay.
- `extensions/tlon/src/{channel.ts,monitor/utils.ts,setup-core.ts,types.ts}` — Retained factory/hybrid config/scoped setup/name/monitor mentions bind tlon; URL/code promotion stays.
- `extensions/twitch/src/{config.ts,setup-surface.ts,token.ts}` — Retained normalized account/token/root-first default and scoped setup; access-token promotion stays.
- `extensions/whatsapp/src/{account-config.ts,account-ids.ts,auto-reply/{config.runtime.ts,monitor/{inbound-dispatch.ts,process-message.ts,runtime-api.ts}},send.ts,setup-core.ts,shared.ts}` — Retained listing/merge/account-only writers; alwaysUseAccounts/authDir promotion stays. Context/chunk/Markdown bind whatsapp; one inbound text-limit call still omits accountId; generic exports stay.
- `extensions/zalo/{runtime-api.ts,src/{accounts.ts,channel.ts,monitor.ts,setup-core.ts,test-support/lifecycle-test-support.ts,token.ts}}` — Retained factory/case-only token/scoped writers/monitor formatting bind zalo; generic setup exports stay.
- `extensions/zalouser/src/{accounts.ts,channel.adapters.ts,monitor.ts,setup-core.ts,setup-surface.ts,shared.ts}` — Retained profile factory/scoped writers/group-path/chunks bind zalouser/resolved account; empty extra promotion-key declaration stays.

- `extensions/clickclack/src/{channel.ts,channel.setup.ts,setup-core.test.ts}` — Both registered surfaces install ClickClack setup contract; account-add regression preserves exact ops credential (Tests).
- `extensions/matrix/src/setup-contract.ts` — Logical callback target is resolved by shared owner before inheritance copy; callback does not write map.

### Test and fixture consumers

These contract/test-support files assert behavior through production owners; none selects production stored keys. Tests records changed assertions, registered entry points, runtime proof and incomplete checks. Listing a retained test does not claim it was rerun.

- Signal: `extensions/signal/{doctor-contract-api.test.ts,src/{account-policy.test.ts,setup-transport.test.ts}}`.
- Other channel tests and fixtures: `extensions/{feishu/src/delivery-trace.test.ts,matrix/src/{channel.setup.test.ts,delivery-trace.test.ts,matrix/monitor/replies.formatting.test.ts},mattermost/src/delivery-trace.test.ts,msteams/src/delivery-trace.test.ts,slack/src/channel-actions-setup-status.contract.test.ts,telegram/src/{bot-native-command-executors.test-support.ts,bot-native-commands.test-helpers.ts,send.test-harness.ts,setup-surface.test.ts}}`.
- Agent tests and fixtures: `src/agents/{agent-command.compaction.test-support.ts,agent-command.live-model-switch.test.ts,ai-transport-runtime-host.test.ts,conversation-capability-profile.test.ts,embedded-agent-runner/{compact.hooks.harness.ts,history.test.ts,run/runtime-preparation.thinking.test.ts},identity.per-channel-prefix.test.ts,identity.test.ts,model-catalog-view.test.ts,models-config.providers.implicit.discovery-scope.test.ts,provider-auth-aliases.test.ts,provider-request-config.test.ts}`.
- Shared account, config and SDK tests: `src/{auto-reply/{chunk.test.ts,reply/commands-allowlist.test.ts},channels/plugins/{account-config-mutation.test.ts,account-helpers.test.ts,account-key-policy.test.ts,config-helpers.test.ts,helpers.test.ts,read-only.test.ts,setup-contract.test.ts,setup-helpers.test.ts,setup-promotion-helpers.test.ts,setup-wizard-helpers.test.ts,setup-wizard.test.ts},config/{channel-capabilities.test.ts,context-visibility.test.ts,group-policy.test.ts,implicit-mentions.test.ts,markdown-tables.test.ts},plugin-sdk/{channel-config-helpers.test.ts,channel-outbound.draft-chunking.test.ts,test-helpers/plugin-runtime-mock.ts},routing/account-lookup.test.ts}`.
- Command, Gateway and delivery tests: `src/{commands/{agents.providers.test.ts,channels.add.test.ts,channels.adds-non-default-telegram-account.test.ts,channels.remove.test.ts,doctor-config-flow.missing-default-account-bindings.integration.test.ts},flows/bundled-health-checks.test.ts,gateway/{server-channels.approval-bootstrap.test.ts,server-channels.test.ts,server-chat.agent-events.test.ts,server-methods/channels.start.test.ts,server-plugin-reload.activation.test-support.ts,server-plugin-reload.recovery.test-support.ts,server-plugin-reload.recovery.test.ts,server-plugin-reload.suspension.test-support.ts,server-reload-channel-restart.test.ts,server-reload-handlers.test.ts,server.chat.gateway-server-chat-b.test.ts},infra/heartbeat-visibility.test.ts}`.
- Metadata and release tests: `{src/plugins/{bundled-plugin-categories.test.ts,bundled-plugin-metadata.test.ts,contracts/runtime-import-side-effects.contract.test.ts,dashboard-capabilities.test.ts,loader.prefer-over.test.ts,manifest-backup-resources.test.ts,manifest-categories.test.ts,manifest-control-ui.test.ts,manifest-metadata-scan.test.ts,manifest-model-catalog.test.ts,manifest-registry-installed.test.ts,manifest-transcript-sources.test.ts,manifest.json5-tolerance.test.ts,manifest.reserved-id.test.ts,plugin-metadata-account-key-policies.test.ts,plugin-metadata.test-support.ts,plugin-policy-id.test.ts,provider-model-compat.prepared.test.ts,provider-model-routes.installed.test.ts},test/release-check.test.ts}`.

### Manual strings, registration and final pipeline edges

- Under `extensions/signal/src/` (relative paths):
  - `aliases.ts` — Retained aliases consume resolved Signal account.
  - `{approval-auth.ts,approval-native.ts,approval-handler.runtime.ts}` — Retained account-scoped approval auth/capability/handling; existing owner retains approval authority.
  - `{message-actions.ts,reaction-level.ts,send-reactions.ts}` — Retained action/reaction policy/RPC use selected account; no alternate key choice.
  - `{rpc-context.ts,signal-ingress.ts,client.ts}` — Selected number/endpoint reach ingress/RPC/event URL; recording endpoint proves final handoff.
  - `setup-surface.ts` — Retained setup status reads resolved account.
- `src/{gateway/server-methods/channels.ts,commands/channels/status.ts}` — Registered RPC/CLI status consume manager/adapters; UI/native receive canonical runtime maps, not authored maps. Both entrypoints have runtime proof.
- `src/commands/channels/add.ts` — Registered account-add calls account-config-mutation/shared promotion before persistence; Tests covers absent-default regression.
- `scripts/fixtures/packed-plugin-sdk-type-smoke.ts` — Relative import includes copied setup fixture in candidate smoke; same release-check copy owner.
- `test/scripts/release-check.test.ts` — Generated string calls fixture generator from separate tooling checkout; both copies checked against stale target fixtures.
- `.github/workflows/openclaw-npm-preflight.yml` — Four sparse tooling roots include scripts/both fixtures; copy owner/source stay.
- `src/plugins/install-config.ts` — Receives former CLI manifest read; manifest.id plans recovery and install metadata gates eligibility. No account-policy/route selection.

Full-map validation and migration enumeration in `extensions/signal/src/config-schema.ts`, `extensions/signal/src/config-compat.ts`, `extensions/signal/src/account-key-repair.ts`, and `src/config/channel-doctor-helpers.ts` inspect authored rows for validation, cleanup or diagnostics. Their selection-dependent operations use the shared owner; enumeration is not route selection.

### Pinned-base census dispositions

The pinned-base comparison includes incoming main changes. Rows trace removed declarations/properties; markers count files, except the worker path's two references. Unrelated `type` keywords and `meta` fields are not the removed local properties.

| Census row | Source disposition |
|---|---|
| `anyOf` | The local MCP union conversion moved to `packages/normalization-core/src/json-schema.ts:178`. `src/agents/mcp-json-schema-validator.ts:45` calls that shared normalizer. Remaining schema keywords and fixtures retain their JSON Schema contract. |
| `type` | The removed inventory property is the same MCP converter's `{ type: entry }`. The shared normalizer still emits it at `packages/normalization-core/src/json-schema.ts:178`. No global `type` contract was removed. |
| `AWS_SECRET_ACCESS_KEY_VALUE_PATTERN` | The old regex export and imports were removed. `src/logging/redact-patterns.ts:96,202,368` now supplies the matcher through the default pattern list. No old external reference remains. |
| `config/sessions/session-model-context.worker` | Both remaining references are the source/dist paths in `src/infra/runtime-process-entrypoints.ts:69-70`. Runtime URL resolution and `scripts/lib/runtime-process-core-build-entries.mts:4-24` consume this table; `tsdown.config.ts:423` spreads the derived build entries. The worker remains packaged; these two unchanged main-side readers are retained. |
| `currentModuleUrl` | The inline session-model worker property moved to `runtimeProcessEntrypoints.sessionModelContext`. Its source-relative path was adjusted for the table location. Runtime and build derive from that same table; other worker entries retain the property. |
| `sourceWorkerName` | The source worker name moved into the same table. Both URL resolution and build generation consume it. Other workers' names remain valid. |
| `distWorkerPath` | The same table retains `config/sessions/session-model-context.worker.js`. Runtime joins it under `dist`; build generation derives its output key. The artifact was not retired. |
| `DoctorConfigPreflightResult` | The type moved to `src/commands/doctor/shared/config-migration-result.ts:10`. `src/cli/program/config-guard.ts:6` and `doctor-config-preflight.ts:59` import the new owner. No old import remains. |
| `cronCodexRuntimePolicyTargets` | The moved type retains this field at `config-migration-result.ts:15`. Preflight still records/returns targets, and `doctor-config-flow.ts:332-347` consumes them for repair and persistence. |
| `stateMigrationStepReceipts` | Preflight still records/returns receipts. `config-migration-result.ts:54,61` forwards them; Doctor flow awaits/spreads the result. `doctor-health-contribution-runners.state.ts:149-150` retains the health handoff. |
| `postSessionPluginMigration` | Preflight still returns the prepared plan. `config-migration-result.ts:55,62` forwards it, and `doctor-health-contribution-runners.state.ts:142-143` supplies it to the transcript migration owner. |
| `postSessionPluginMigrationPlanBound` | The preflight flag remains on the moved type and is forwarded by `config-migration-result.ts:56,63`, the health runner at145-146, and the transcript migration entry point at `doctor-session-transcripts.ts:199-214`. |
| `lastTouchedVersion` | Doctor's inline read moved to `config-migration-result.ts:26-27`, before repairs. It still produces `sourceLastTouchedVersion`; the stored metadata key and native/config readers remain valid. |
| `meta` | The removed inventory field was Doctor's inline metadata cast. `config-migration-result.ts:26` now reads typed `snapshot.sourceConfig.meta`. Other metadata identities were not retired. |
| `resolveClaudeCliSessionFilePath` | The synchronous helper remains private at `cli-session-history.claude.ts:455` for its internal synchronous readers. The external snapshot reader imports/awaits the async owner at `cli-session-history.claude-snapshot.ts:15,135`. No stale external import remains. |
| `runSerializedPreparedModelRuntimeTask` | The helper and its only caller/import were removed. `prepared-model-runtime.build.ts` now uses `createFullModelCatalogAccess`; its owner at `prepared-model-runtime.catalog-access.ts:420-440` retains the generation, limits discovery concurrency, and checks the current generation around awaited work. No old caller remains. |

The exact source-dispositioned owner/consumer files counted by each marker are:

| Symbol | Files |
|---|---|
| `anyOf` | `src/agents/mcp-json-schema-validator.ts`; `packages/normalization-core/src/json-schema.ts` |
| `type` | `src/agents/mcp-json-schema-validator.ts`; `packages/normalization-core/src/json-schema.ts` |
| `currentModuleUrl` | `{src/{config/sessions/session-model-context-worker-runtime.ts,infra/{runtime-process-entrypoints.ts,runtime-worker-url.ts}},scripts/lib/runtime-process-core-build-entries.mts}` |
| `sourceWorkerName` | `{src/{config/sessions/session-model-context-worker-runtime.ts,infra/{runtime-process-entrypoints.ts,runtime-worker-url.ts}},scripts/lib/runtime-process-core-build-entries.mts}` |
| `distWorkerPath` | `{src/{config/sessions/session-model-context-worker-runtime.ts,infra/{runtime-process-entrypoints.ts,runtime-worker-url.ts}},scripts/lib/runtime-process-core-build-entries.mts}` |
| `DoctorConfigPreflightResult` | `src/{commands/{doctor/shared/config-migration-result.ts,doctor-config-preflight.ts},cli/program/config-guard.ts}` |
| `cronCodexRuntimePolicyTargets` | `src/commands/{doctor/shared/config-migration-result.ts,doctor-config-preflight.ts,doctor-config-flow.ts}` |
| `stateMigrationStepReceipts` | `src/{commands/{doctor/shared/config-migration-result.ts,doctor-config-preflight.ts,doctor-config-flow.ts},flows/doctor-health-contribution-runners.state.ts}` |
| `postSessionPluginMigration` | `src/{commands/{doctor/shared/config-migration-result.ts,doctor-config-preflight.ts,doctor-config-flow.ts,doctor-session-transcripts.ts},flows/doctor-health-contribution-runners.state.ts}` |
| `postSessionPluginMigrationPlanBound` | `src/{commands/{doctor/shared/config-migration-result.ts,doctor-config-preflight.ts,doctor-config-flow.ts,doctor-session-transcripts.ts},flows/doctor-health-contribution-runners.state.ts}` |
| `lastTouchedVersion` | `src/commands/{doctor/shared/config-migration-result.ts,doctor-config-flow.ts}` |
| `meta` | `src/commands/{doctor/shared/config-migration-result.ts,doctor-config-flow.ts}` |

census: generic `config/sessions/session-model-context.worker` reviewed — 2 callers listed
census: generic anyOf reviewed — 2 callers listed
census: generic type reviewed — 2 callers listed
census: generic currentModuleUrl reviewed — 4 callers listed
census: generic sourceWorkerName reviewed — 4 callers listed
census: generic distWorkerPath reviewed — 4 callers listed
census: generic DoctorConfigPreflightResult reviewed — 3 callers listed
census: generic cronCodexRuntimePolicyTargets reviewed — 3 callers listed
census: generic stateMigrationStepReceipts reviewed — 4 callers listed
census: generic postSessionPluginMigration reviewed — 5 callers listed
census: generic postSessionPluginMigrationPlanBound reviewed — 5 callers listed
census: generic lastTouchedVersion reviewed — 2 callers listed
census: generic meta reviewed — 2 callers listed

### Additional normalization consumers

The source supplement adds 126 files to 401 typed: 527 total. Matrix setup-contract was already named; these 125 were absent. These are existing normalizer consumers, not new production changes; braces enumerate paths.

- `extensions/a2a/src/accounts.ts` — Retained account-context identity normalization.
- `extensions/discord/src/{account-inspect.ts,actions/runtime.messaging.shared.ts,client.ts,directory-cache.ts,directory-config.ts,monitor/{model-picker-preferences.ts,thread-bindings.config.ts,thread-bindings.manager.ts,thread-bindings.session-shared.ts,thread-bindings.state.ts},secret-config-contract.ts,setup-account-state.ts,voice/transcripts-source.ts}` — Retained inspection/setup/client/directory identity, message comparisons, voice secret/transcript scope, model/thread cache/manager/persisted keys.
- `extensions/feishu/src/{bot-identity-cache.ts,config-schema.ts,dynamic-agent.ts,secret-contract.ts,thread-bindings.ts}` — Retained bot/dynamic-agent/thread keys, default schema and secret identity.
- `extensions/googlechat/src/secret-contract.ts` — Retained account secret-owner identity.
- `extensions/imessage/src/approval-native.ts` — Retained approval-target identity/same-account suppression.
- `extensions/matrix/{doctor-contract-api.ts,src/{approval-handler.runtime.ts,approval-reactions.ts,auth-precedence.ts,cli-account.ts,cli-shared.ts,env-vars.ts,matrix/{accounts.ts,client/{config.ts,env-auth.ts,storage.ts},config-paths.ts,config-update.ts,credentials-read.ts,credentials-state.ts,credentials.ts,monitor/reaction-events.ts,read-policy.ts,session-store-metadata.ts},onboarding.ts,profile-update.ts,secret-contract.ts,session-route.ts,setup-dm-policy.ts,storage-paths.ts}}` — Retained Doctor credential identity; account/auth/config/CLI/onboarding/profile/env scope; storage/credential compatibility; session/read routes; approval-reaction registration/lookup/cleanup.
- `extensions/mattermost/src/mattermost/read.ts` — Retained requester-account comparisons for message reads.
- `extensions/msteams/src/approval-native.ts` — Retained named-account approval transport gating.
- `extensions/nostr/src/{channel.setup.ts,secret-contract.ts}` — Retained setup/default-account and secret-assignment identity.
- `extensions/policy/src/doctor/strictness.ts` — Retained Doctor routing-policy canonicalization.
- `extensions/signal/src/{approval-reaction-routes.ts,approval-reactions.ts,question-reactions.ts}` — Retained approval-route matching and delivered approval/question account identity.
- `extensions/slack/src/{account-inspect.ts,action-runtime.ts,channel-migration.ts,directory-config.ts,group-policy.ts,installation-identity-state.ts,monitor/enterprise-install.ts}` — Retained account/directory/group identity, requester checks, migration and installation/enterprise keys.
- `extensions/telegram/src/{account-inspect.ts,accounts.ts,bot/helpers.ts,directory-config.ts,dm-session-key.ts,group-migration.ts,message-topic-binding.ts,miniapp/{command.ts,routes.ts},poll-registry.ts,thread-bindings.ts}` — Retained account/token/action gates; directory/group/DM routes; requester/topic matching; Mini App scope; poll/thread keys.
- `extensions/whatsapp/{auth-presence.ts,src/{accounts.ts,agent-tools-call.ts,auto-reply/monitor/group-activation.ts,group-session-key.ts}}` — Retained auth/call storage, group activation and account session keys.
- `extensions/zalouser/runtime-api.ts` — Retained public normalizer forwarding export.
- `src/agents/agent-tools.policy.ts` — Retained normalization/account matching. Historical merge4d throws for unavailable accounts; prepush8c denies all. Separate base/source behavior, not promotion repair or new CI proof.
- `src/acp/persistent-bindings.types.ts` — Retained persistent-binding/session account identity.
- `src/agents/{subagents/announce/subagent-announce-origin.ts,tools/{message-tool-discovery.ts,message-tool-execution.ts,message-tool-group-thread.ts,sessions-send-tool.ts}}` — Retained announcement origin, message discovery/echo/group-thread and session-send matching.
- `src/auto-reply/{group-thread-dispatch.ts,group-thread.ts,reply/{route-reply.ts,source-turn-id.ts}}` — Retained group dispatch/source-turn/reply-source identity.
- `src/channels/{message/outbound-echo.ts,plugins/{configured-binding-match.ts,media-limits.ts,message-action-dispatch.ts},thread-bindings-policy.ts}` — Retained echo/binding keys, media account input, conversation comparisons and thread policy.
- `src/cli/message-secret-scope.ts` — Retained message secret-account scope.
- `src/commands/{agents.providers.ts,channels/{add-mutators.ts,remove.ts},doctor/shared/{allowlist-policy-repair.ts,default-account-warnings.ts,legacy-config-migrations.runtime.config-tranche.ts}}` — Retained provider indexing, add/remove identity, Doctor allowlist/default/migration scope.
- `src/cron/isolated-agent/delivery-target.ts` — Retained isolated delivery account identity.
- `src/flows/{channel-setup.prompts.ts,channel-setup.ts}` — Retained setup/removal account input normalization.
- `src/gateway/{conversation-route-ownership.ts,server-methods/{cron-caller-scope.ts,send.ts}}` — Retained conversation ownership, cron caller/declaration matching and message routes.
- `src/infra/outbound/{account-scoped-conversation-bindings.ts,session-binding-normalization.ts,source-reply-mirror.ts,targets.ts}` — Retained conversation/session keys, reply matching and heartbeat account/secret scope.
- `src/pairing/pairing-challenge.ts` — Retained optional challenge account scope.
- `src/plugin-sdk/{account-id.ts,approval-client-helpers.ts,approval-native-helpers.ts,pairing-access.ts}` — Retained normalizer export/approval-recipient/native route/pairing comparisons.
- `src/routing/{account-id.test.ts,account-id.ts,binding-scope.ts,channel-route-targets.ts,resolve-route.ts,session-key.ts}` — Retained normalizer/test, bindings/routes/account lists and peer/group session keys.
- `src/secrets/channel-secret-basic-runtime.ts` — Retained account secret-owner and assignment identity.
- `src/state/openclaw-agent-db-session-migrations.ts` — Retained migrated conversation account identity.
- `src/tts/tts-config.ts` — Retained account normalization supplied to TTS override lookup.

Of these 125 files, 124 match the historical integration byte-for-byte. The agent tool-policy source difference is stated above; its normalization and account matching remain. The normalizer still owns lowercase/safe account IDs and default handling. These callers retain their existing lookup, comparison, storage, routing or test duties; they add no Signal eligibility decision.

### Complete fallback audit and authorization consumers

Audit before correction: 73 calls/54 production files (four owner delegations); 160 lexical references/67 files include facades/types/comments/local names. Factory/Signal aliases were traced separately. Coordinates below identify the audited340 source. Commit5b50754 fixes both families; registered red/green is recorded under Tests.

- **Owner/facades:** `src/routing/account-lookup.ts:34,44,60,72` and `src/config/channel-account-config.ts:76,83` consume the selected key; no rescan. `src/channels/plugins/account-helpers.ts:115` forwards generated resolvers. Public `src/plugin-sdk/{account-core,account-helpers,account-resolution,account-resolution-runtime,routing}.ts` retain owner forwarding.
- **Correction sites:** `src/auto-reply/command-auth.ts:454,471-472` now selects inferred sole accounts through the owner. `src/channels/plugins/config-helpers.ts:88,126,177,184` remove raw fallback: delete/clear need existing selection; optional allowMissing creation belongs to selector. Public helpers accept raw IDs; Signal CRUD normalizes at `src/plugin-sdk/channel-config-helpers.ts:227-238`. Undeclared-channel raw creation remains the shipped contract.
- **Retained writer creation:** `src/channels/plugins/setup-helpers.ts:50,93,311,370` and `src/plugin-sdk/allowlist-config-edit.ts:197` normalize creation intent. Promotion selects inferred/default candidates; only explicit callback targets may create a missing normalized key. `src/channels/plugins/helpers.ts:57,63` reads selected/default values; path descriptions do not read raw rows.
- **Root/default field inheritance only:** `src/media/configured-max-bytes.ts:39`; `src/channels/{account-config-enabled.ts:13,draft-streaming-chunking.ts:33,join-intro/report-channel-room-join.ts:74}`; `src/channels/plugins/{config-write-policy-shared.ts:71,read-only.ts:209}`; `src/config/{channel-groups.ts:32,channel-capabilities.ts:51,markdown-tables.ts:57,group-policy.ts:75,104,context-visibility.ts:54,implicit-mentions.ts:29}`; `src/infra/{event-session-routing.ts:113,heartbeat-visibility.ts:54}`; `src/auto-reply/{chunk.ts:49,92,reply/reply-threading.ts:51,reply/block-streaming.ts:60}`; `src/agents/{identity.ts:33,117,subagents/spawn/acp-spawn-parent-stream.ts:124,embedded-agent-runner/history.ts:192}`. No raw retry; group/peer scans stay inside selected maps.
- **Selection or validation only:** `src/cron/delivery-channel-validation.ts:99` and `src/commands/doctor/shared/legacy-config-binding-repair.ts:99` consume selected enabled flags; `src/config/channel-doctor-helpers.ts:196` selects inheritance, migrates all rows. `src/config/zod-schema.providers-whatsapp.ts:126` selects default, validates all rows. `src/status/status-text.ts:97` is Telegram-only. `src/gateway/server-channels.ts:407,415` retains two undeclared forms; declared policy gives the same winner.
- **Plugin direct calls:** `extensions/{zalo/src/token.ts:42,discord/src/token.ts:70,discord/src/accounts.ts:52,line/src/accounts.ts:88,line/src/group-keys.ts:52,imessage/src/accounts.ts:47,slack/src/accounts.ts:93,sms/src/accounts.ts:103,googlechat/src/accounts.ts:71,clickclack/src/accounts.ts:86,122,feishu/src/policy.ts:303,whatsapp/src/account-config.ts:13,33,telegram/src/token.ts:125,telegram/src/account-config.ts:15,matrix/src/matrix/account-config.ts:81,117,matrix/src/account-selection.ts:130,twitch/src/token.ts:64,twitch/src/config.ts:68,116,signal/src/account-selection.ts:10,signal/src/accounts.ts:69}`. Selected values inherit root/shared-default/environment or stop. Twitch reuses selected default; Signal reply/setup/transport/Doctor wrappers use explicit policy (Consumers).
- **Factory aliases:** active: `extensions/{discord,clickclack,line,imessage,slack,googlechat,sms,zalo,zalouser,qa-channel,nextcloud-talk,irc,feishu,raft,synology-chat}/src/accounts.ts`, `extensions/mattermost/src/mattermost/accounts.ts`, and `extensions/tlon/src/types.ts`; factory-owned. List/default-only: `extensions/{nostr/src/types.ts,buzz/src/types.ts,whatsapp/src/account-ids.ts,signal/src/accounts.ts,twitch/src/config.ts,telegram/src/account-selection.ts}`.
- **Separate source-only follow-ups:** `src/channels/thread-bindings-policy.ts:123` and `extensions/discord/src/monitor/thread-bindings.config.ts:18,31` retain exact reads; Signal lacks that field. `extensions/synology-chat/src/accounts.ts:51,113` retains exact webhook/policy reads beside merged config. Both are source-only follow-ups. Runtime maps, all-row scans and pairing's resolved/configured singleton do not restore rejected config.

Authorization scope closes the full additional inventory:

- **Account-derived `isAuthorizedSender` pipeline:** `src/auto-reply/reply/{commands-context,commands-core,commands-handlers.runtime,get-reply-directives,get-reply-native-slash-fast-path,fast-approve,dispatch-from-config.context,dispatch-from-config.prepare-operation,session-reset-command,session,commands-reset,abort-operation,abort}.ts`. Dispatch/reset/abort share corrected auth owner.
- **Flag consumers/handlers:** `src/auto-reply/reply/{command-gates,get-reply-directives-apply,get-reply-directives-routing,get-reply-inline-actions,get-reply-run-context,get-reply,commands-status,commands-plugin,commands-system-agent,commands-diagnostics,commands-session,commands-acp,commands-bash,commands-approve,commands-login}.ts`. `/acp help` proves the flag gate; approve/login/privileged actions retain independent approval/global-owner gates.
- **Facades/type projections:** `src/auto-reply/reply/{commands,abort.runtime,fast-approve.runtime,get-reply-run.types,commands-types}.ts`; no local account selection.
- **Global `senderIsOwner` consumers:** `src/gateway/{talk-client-agent-consult,server-methods/chat-send-message-injection}.ts` and `src/auto-reply/reply/dispatch-from-config.prepare-operation.ts:235-253`. Alias fallback never grants this field; last file also dispatches fast account-authorized commands.
- **Retained tests/fixtures:** `src/auto-reply/{command-control.test,command-auth.owner-default.test}.ts`; `src/auto-reply/test-helpers/{command-auth-registry-fixture,command-auth-registry-fixture.test}.ts`; `src/auto-reply/reply/{commands.test-harness,get-reply.test-mocks,get-reply.test-fixtures,directive-handling.mixed-inline.test-helpers,commands-export-trajectory.test-support,commands-compact.test-support,commands-login.harness-test-support}.ts`. `src/gateway/node-invoke-plugin-policy.ts:291` is an unrelated local closure. `src/gateway/server-plugin-reload.recovery.test-support.ts` was already inventoried.

### Round-2 typed authorization supplement

Candidate `5b5075411706bf8ee1992125335c0ac84a30a6b2`, tree `7029d0d267619b6511f3a7c29c225375cf7dd5ff`: 69 queries (29 root, 18 scripts, 18 test-root, 4 locals/copied CommandContext properties). Eight separate pinned-base auth/context queries supplement the historical 299/147/12 batches; all five new batches exit 0. Candidate: 1,257 references/708 positions/160 files; base supplement: 443/443/87. Added files: 84 (37 production, 47 tests/fixtures), preserving 527 for a union of 611. These are source queries, not a new CI-merge census. Full source evidence stays private; braces enumerate all added paths.

- `extensions/slack/src/monitor/events/interactions.block-actions.ts` — Shared admission when commands.allowFrom is set; retains Slack policy.
- `extensions/telegram/src/{bot-handlers.inbound-authorization,bot-native-command-login}.ts` — Callbacks use command admission when configured, otherwise channel admission; login requires current admission AND global owner.
- Under `src/auto-reply/reply/`:
  - `abort-operation.ts`, `commands-{acp,bash,session,status,system-agent}.ts`, `get-reply-native-slash-fast-path.ts` — Admission before abort, ACP, bang alias, session, both status branches, system-agent and native fast routing.
  - `command-gates.ts` — Admission and global-owner checks remain distinct.
  - `commands-{diagnostics,plugin}.ts` — Gate admission; forward both facts.
  - `commands-approve.ts` — Admission or existing explicit approval authority.
  - `commands-login.ts` — Recheck current admission AND global owner.
  - `commands-plugins.ts` — Privileged work still requires global owner or Gateway admin.
  - `commands-{btw,compact,learn}.ts`, `commands-acp/diagnostics.ts` — Forward global-owner fact; ACP diagnostics use it for visible/current entries.
  - `commands-{context,types}.ts` — Resolver copies two distinct facts into CommandContext; both receiving declarations were queried.
  - `commands-reset.ts`, `session-reset-command.ts` — Shared reset admission, including upstream commandAuthorized.
  - `dispatch-from-config.prepare-operation.ts` — Resolve current authorization; forward owner fact and dispatch fast commands.
  - `get-reply-directives{,-apply}.ts` — Gate directives/status on admission; retain literal-command suppression and separate owner branches/forwarding.
  - `get-reply-inline-actions.ts` — Gate skill/commands on admission; forward owner fact.
  - `get-reply-run-context.ts` — Upstream commandAuthorized and resolved admission both apply.
  - `get-reply-run-execute.ts` — Forward owner fact; retain existing Gateway-admin alternative.
  - `get-reply.ts` — Reset hooks need admission; ordinary replies forward owner fact.
- `src/gateway/{server-methods/chat-send-message-injection,talk-client-agent-consult}.ts`, `src/system-agent/rescue-message.ts` — Derive/forward global-owner fact; account access cannot grant it.
- `src/plugin-sdk/command-auth{,-native}.ts` — Re-export shared resolver. `src/plugin-sdk/command-status.runtime.ts` constructs CommandContext from separately supplied flags.

All 47 tests/fixtures below retain admission/global-owner inputs or assertions; none owns production selection. The new account-policy test covers registered command/reset regression. Inventory does not claim all listed tests reran.

- `extensions/discord/src/monitor/monitor.test.ts`
- `src/acp/control-plane/spawn.test.ts`
- `src/auto-reply/{command-auth.owner-default.test,command-control.test}.ts`
- `src/auto-reply/reply/{abort.target-owner.test,directive-handling.mixed-inline.test-helpers,directive-handling.model.test,get-reply-directives-apply.test,get-reply-inline-actions.skip-when-config-empty.test,get-reply-native-compact-authorization.test,get-reply-run.media-only.test,session.test,commands-{abort-trigger.test,account-policy.test,acp.test,approve.test,bash-alias.test,btw.test,compact.test,diagnostics.test,export-trajectory.test-support,gating.test,handlers.registration.test,info.test,learn.test,login.consent.test,login.harness-test-support,login.test,loop.test,mcp.test,plugin.test,plugins.install.test,plugins.test,reset-hooks.test,session-lifecycle.test,session-restart.test,steer.test,stop-target.test,subagents-routing.test,update.test}}.ts`
- `src/gateway/server-methods/{chat-send-user-turn.test,gateway-client-identity.test,talk.test}.ts`
- `src/plugin-sdk/command-auth.test.ts`
- `src/system-agent/{rescue-channel.live.test,rescue-message.test}.ts`
- `src/tts/tts-entry-delivery.test.ts`

Seven manual scope paths stay outside typed611: `scripts/check-ingress-agent-owner-context.mts` is diagnostic text; `scripts/dev/test-device-pair-telegram.ts` supplies literal plugin-command admission. `test/{helpers/agents/happy-path-prompt-snapshots.ts,transcripts-tool.discord-lifecycle.integration.test.ts,loopback-ask-user-telegram-channel.test.ts,canonical-descendant.integration.test.ts}` supply synthetic prompt/speaker/tool-authority/descendant owner facts. `scripts/e2e/system-agent-rescue-docker-client.ts` supplies literal flags in a client excluded from the scripts project. No competing selector or new execution claim.

## Invalidation

Selection is synchronous, uncached and reads supplied config. Doctor retains scoped metadata, preview without writes, backed-up repair and idempotence. Existing Signal reload prefix/restart refresh account state. Installed-record load/rebase/project rebuild policy; publication, retirement and caches own lifetime. Manifest adapters retain their selected record. Runtime proof covers Doctor preview/repair/repeat, restart, endpoint reload and old-source config boot without Doctor.

## Contention

No new lock, queue, transaction, async wait, listener or state store. Existing config/lifecycle owners retain effects.

## Tests

- Round 2: real admitted-group `buildCommandContext`→`handleCommands` `/acp help` and `resolveAuthorizedSessionResetCommand` prove the optional-resolveAllowFrom path in `src/auto-reply/reply/commands-account-policy.test.ts`. On source340, Alice loses help/reset and Bob gains both, with/without defaultAccount; senderIsOwner stays false. Four dispatch/reset cases fail while all eight eligible sole/default, undeclared, exact/case and global-owner controls pass. Split enable/delete/clear tests add three intended failures: final red 7 failed/27 passed. Source `5b5075411706bf8ee1992125335c0ac84a30a6b2` passes 146 tests (106 focused + 40 sibling), syntax lint/format/max-lines/assertion/SDK surface/diff checks. Native review passes. CI34688067951 found a test cleanup returning number;8828dc6 returns void, with14 command/abort tests passing. Assertions/production stay unchanged; CI/review receipts remain separate.
- Reset uses the actual session reset gate/result. Fast abort at `src/auto-reply/reply/abort-operation.ts:183-190` calls the same authorization owner and rejects before target/cancellation work. This is shared-entry authorization coverage, not executed cancellation side effects or a callback-backed Signal/global-owner escalation claim.
- Promotion: real `channelsAddCommand` plus cold contract tables reproduce root credentials/ignored access moving into absent-default spaced aliases. Corrected controls preserve root identity/restrictions/ignored rows across absent/present defaults, exact/case keys, explicit callback creation, `Default` and collisions: 125/125; earlier broader suite 143/143. Signal's adapter suppresses this generic promotion. `src/channels/plugins/setup-contract.test.ts` covers full/metadata-only contracts; `src/commands/channels.add.test.ts` covers registered add. No test deletion.
- SDK: [run 34686394191](https://github.com/openclaw/openclaw/actions/runs/34686394191), jobs `103534002152`/`103534002115`, compiles released 2026.9.4 and sealed candidate. Identical full inline adapters/contextual callbacks exercise promotion, setup/setup-runtime patch and allowFrom. TypeScript 6.0.3: NodeNext, strict/noEmit, skipLibCheck:false, types:[node]. Package source `20090983b5683ff352ed31e50a15143468913084`, SHA-256 `0b25b612693be3b4629231ff4445342f859bc0399f207d98b5d884c19caa6e6c`; checker `340142aaf7`. Runtime-only promotion correction retains signatures/fixture bytes. Both release-check files pass 60 cases; `test/scripts/release-check.test.ts` proves trusted-tooling copies against stale targets. Declaration proof is not runtime/release qualification. Round2 keeps this fixture/checker and public setup signatures unchanged. Its selector gains an optional final creation argument; prior arguments/inference/returns stay valid. The writer regression executes that mode and new-head CI owns its typecheck; the sealed200 compiler run is not represented as compiling the new mode.
- `extensions/clickclack/src/setup-core.test.ts:484`: registered setup+real reader preserve active exact ops credentials; pinned base fails while 19 controls pass, candidate 20/20.
- `extensions/signal/src/account-selection.test.ts`: registered config/threading/setup/delete cover own-number/inheritance, collision reads/writes, container URL, managed-port and Doctor default-transport collisions in both orders. `src/channels/plugins/account-key-policy.test.ts`: manifest adapter, scoped binding repair, outbound media collision, explicit SDK merge. Metadata tests cover persisted reconstruction, enablement, projection, replacement with/without declaration and no runtime execution.
- Retained receipts: 200 Signal-owner passes; routing/Gateway/metadata/policy/startup checks; final 35-case startup corpus; CI fixture 25/25 and 27/27 with provider-alias/Doctor sequencing assertions retained; setup metadata 142; SDK surface 10 (two exports/one callable); restored two released full-adapter callbacks; setup/contract 83 before metadata-only addition; revised cold-promotion/tooling tests. CI owns typechecking. Two macOS read-only `/var` versus `/private/var` assertions fail identically on base/candidate; neither assertions nor production changed.
- Test ledger: no baseline test deleted. `extensions/signal/src/setup-core.test.ts:144` and `:355` retire deletion of an emptied default row; the canonical empty row now preserves the exact winner. `extensions/signal/src/account-selection.test.ts:16` replaces that cleanup shape with effective identity/restriction continuity for exact and alias winners, including empty collision winners. `src/channels/plugins/setup-contract.test.ts:20` covers cold contract-to-promotion policy forwarding. `extensions/signal/doctor-contract-api.test.ts:45`: Doctor's former own-number inheritance fixture now omits the number per the accepted contract; `extensions/signal/doctor-contract-api.test.ts:87`: its own-number default case now expects optional key cleanup while keeping root transport authoritative. `src/plugins/loader.prefer-over.test.ts:110`: existing preferred-plugin assertions remain and cover both policy variants. `src/agents/provider-auth-aliases.test.ts` retains real installed-record selection in its I/O mock; `src/commands/doctor/repair-sequencing.test.ts` uses existing complete snapshots through `runDoctorRepairSequence`, removing two fixture casts without changing assertions. New metadata coverage uses a separate file; an intermediate clone-only assertion was removed because persisted-index reconstruction already covers the meaningful boundary.
- Runtime controls: exact collision before/after Doctor, own-number plus root, unchanged no-own-number base/head inheritance, optional Doctor/unchanged Telegram map, endpoint reload/restart, legacy default collision, and old config from `3a9d69db30` booting without Doctor. Managed-native inheritance lacks signal-cli; base/head share configured state/fallback/spawn error while root external transport runs. No live Signal messages; recording endpoints prove transport.
- Fresh external npm install on sealed200 passes maintained install/inspect, new Gateway, both status paths, account events/probes and cleanup. Source comparison to actual later merge `4b2f53900bc91b72b4afddaeb219251cda804f26` supports ordinary install/fresh startup only; new batch deferral is unused. Not merge4b package execution. Cold-start event-loop degraded metrics and disabled unrelated config warning remain recorded; no steady-state/live-service/batch/hot-reload claim. Historical merge4b lifecycle job103534548382 passed all nine reload tests, including watcher restart followed by settings persistence after reload closes. Its complete receipt is retained; this is not future-merge execution.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-12 16:35:19 +05:30

51 KiB

summary read_when title
Plugin manifest + JSON schema requirements (strict config validation)
You are building an OpenClaw plugin
You need to ship a plugin config schema or debug plugin validation errors
Plugin manifest

This page covers the native OpenClaw plugin manifest, openclaw.plugin.json. For compatible bundle layouts (Agent Plugins, Codex, Claude, Cursor), see Plugin bundles.

Compatible bundle formats use their own manifest files instead:

  • Agent Plugins bundle: plugin.json at the package root, per the open Agent Plugins standard
  • Codex bundle: .codex-plugin/plugin.json
  • Claude bundle: .claude-plugin/plugin.json, or the default Claude component layout with no manifest
  • Cursor bundle: .cursor-plugin/plugin.json

OpenClaw auto-detects those layouts but does not validate them against the openclaw.plugin.json schema below. For a compatible bundle, OpenClaw reads bundle metadata, declared skill roots, Claude command roots, Claude settings.json defaults, Claude LSP defaults, and supported hook packs, when the layout matches OpenClaw's runtime expectations.

Every native OpenClaw plugin must ship openclaw.plugin.json in the plugin root. OpenClaw reads it to validate configuration without executing plugin code. A missing or invalid manifest blocks config validation and is treated as a plugin error.

See Plugins for the full plugin system guide, and Capability model for the native capability model and current external-compatibility guidance.

What this file does

openclaw.plugin.json is metadata OpenClaw reads before loading your plugin code. Everything in it must be cheap enough to inspect without booting plugin runtime.

Use it for:

  • plugin identity, config validation, and config UI hints
  • auth, onboarding, and setup metadata (alias, auto-enable, provider env vars, auth choices)
  • activation hints for control-plane surfaces
  • root CLI command names, descriptions, and subcommand markers (cliCommands)
  • shorthand model-family ownership
  • static capability-ownership snapshots (contracts)
  • dashboard widget data bindings and action verbs
  • static MCP servers that should exist while the plugin is enabled
  • durable and regenerable state- or agent-relative backup resources
  • QA runner metadata the shared openclaw qa host can inspect
  • channel-specific config metadata merged into catalog and validation surfaces

Do not use it for: registering native runtime hooks, declaring the full plugin runtime entrypoint, or npm install metadata. Those belong in your plugin code and package.json.

Where each field is documented

Every manifest field is documented on this page or on one of the seven child pages below. The anchors from the single-page version still resolve here.

Model fields

Manifest model fields — Manifest model catalog, shorthand family, id normalization, and pricing fields.

Provider fields

Manifest provider fields — Manifest generation, media-understanding, endpoint, and request provider metadata.

Setup and auth fields

Manifest setup and auth fields — Manifest setup descriptors, auth choices, conversation discovery, and config UI hints.

Capability fields

Manifest capability fields — Manifest capability ownership, tool availability metadata, and activation planning.

Host surface fields

Manifest host surface fields — Manifest fields for icons, CLI, MCP, Control UI, dashboard, QA, channel, and backup surfaces.

Config and secret fields

Manifest config and secret fields — Manifest dangerous-flag, SecretRef migration, and secret provider preset metadata.

Manifest and package.json fields

Manifest versus package.json — Which pre-runtime metadata lives in package.json, and which duplicate plugin id wins.

Minimal example

{
  "id": "voice-call",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {}
  }
}

Rich example

{
  "id": "openrouter",
  "name": "OpenRouter",
  "description": "OpenRouter provider plugin",
  "version": "1.0.0",
  "providers": ["openrouter"],
  "modelSupport": {
    "modelPrefixes": ["router-"]
  },
  "modelIdNormalization": {
    "providers": {
      "openrouter": {
        "prefixWhenBare": "openrouter"
      }
    }
  },
  "providerEndpoints": [
    {
      "endpointClass": "openrouter",
      "hostSuffixes": ["openrouter.ai"]
    }
  ],
  "providerRequest": {
    "providers": {
      "openrouter": {
        "family": "openrouter"
      }
    }
  },
  "cliBackends": ["openrouter-cli"],
  "syntheticAuthRefs": ["openrouter-cli"],
  "setup": {
    "providers": [
      {
        "id": "openrouter",
        "envVars": ["OPENROUTER_API_KEY"]
      }
    ]
  },
  "providerAuthAliases": {
    "openrouter-coding": "openrouter"
  },
  "providerAuthChoices": [
    {
      "provider": "openrouter",
      "method": "api-key",
      "choiceId": "openrouter-api-key",
      "choiceLabel": "OpenRouter API key",
      "groupId": "openrouter",
      "groupLabel": "OpenRouter",
      "optionKey": "openrouterApiKey",
      "cliFlag": "--openrouter-api-key",
      "cliOption": "--openrouter-api-key <key>",
      "cliDescription": "OpenRouter API key",
      "onboardingScopes": ["text-inference"]
    }
  ],
  "uiHints": {
    "apiKey": {
      "label": "API key",
      "placeholder": "sk-or-v1-...",
      "sensitive": true
    }
  },
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "apiKey": {
        "type": "string"
      }
    }
  }
}

Top-level field reference

Field Required Type What it means
id Yes string Canonical plugin id. This is the id used in plugins.entries.<id>. Exception: a package whose package.json declares multiple plugin entries registers each entry as <id>/<entry-basename> (for example pack/one), and that entry-scoped id is the plugins.entries key for that entry. Entry basenames must be unique within the package; colliding basenames are rejected at discovery.
configSchema Yes object Inline JSON Schema for this plugin's config.
requiresPlugins No string[] Plugin ids that must also be installed for this plugin to have an effect. Discovery keeps the plugin loadable but warns when any required plugin is missing.
enabledByDefault No true Marks a bundled plugin as enabled by default. Omit it, or set any non-true value, to leave the plugin disabled by default.
enabledByDefaultOnPlatforms No string[] Marks a bundled plugin as enabled by default only on the listed Node.js platforms, for example ["darwin"]. Explicit config still wins.
legacyPluginIds No string[] Legacy ids that normalize to this canonical plugin id.
autoEnableWhenConfiguredProviders No string[] Provider ids that should auto-enable this plugin when auth, config, or model refs mention them.
kind No PluginKind | PluginKind[] Declares one or more exclusive plugin kinds ("memory", "context-engine") used by plugins.slots.*. A plugin that owns both slots declares both kinds in one array.
channels No string[] Channel ids owned by this plugin. Used for discovery and config validation.
providers No string[] Provider ids owned by this plugin.
providerCatalogEntry No string Lightweight provider-catalog module path, relative to the plugin root, for manifest-scoped provider catalog metadata that can be loaded without activating the full plugin runtime.
capabilityCatalogEntry No string Lightweight module of typed speech, realtime transcription, and realtime voice provider descriptors, relative to the plugin root. See Capability catalogs.
modelSupport No object Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime.
modelCatalog No object Declarative model catalog metadata for providers owned by this plugin. This is the control-plane contract for future read-only listing, onboarding, model pickers, aliases, and suppression without loading plugin runtime.
modelPricing No object Provider-owned hosted-pricing publication policy. Use it to opt local/self-hosted providers out of published pricing or map provider refs to supported public pricing catalogs without hardcoding provider ids in core.
modelIdNormalization No object Provider-owned model-id alias/prefix cleanup that must run before provider runtime loads.
providerEndpoints No object[] Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads.
providerRequest No object Cheap provider-family and request-compatibility metadata used by generic request policy before provider runtime loads.
secretProviderIntegrations No Record<string, object> Declarative SecretRef exec provider presets that setup or install surfaces can offer without hardcoding provider-specific integrations in core.
cliBackends No string[] CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs.
syntheticAuthRefs No string[] Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads.
nonSecretAuthMarkers No string[] Bundled-plugin-owned placeholder API key values that represent non-secret local, OAuth, or ambient credential state.
commandAliases No object[] Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads.
cliCommands No object[] Root CLI commands shown in openclaw --help before plugin code loads. Each row requires name, description, and hasSubcommands.
providerUsageAuthEnvVars No Record<string, string[]> Usage/billing-only provider credentials. OpenClaw uses these names for usage discovery and secret scrubbing but never for inference auth.
providerAuthAliases No Record<string, AuthAlias> Provider ids that reuse another provider for auth lookup. A baseUrls condition applies only when that provider's configured endpoint matches; stored credentials retain their provider identity.
providerAuthChoices No object[] Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring.
activation No object Cheap activation planner metadata for startup, provider, command, channel, route, and capability-triggered loading. Metadata only; plugin runtime still owns actual behavior.
backupResources No object[] Manifest-owned durable or regenerable state- or agent-relative backup resources. Applied only for effectively activated, loadable plugins without executing their runtime. See backupResources reference.
setup No object Cheap setup/onboarding descriptors that discovery and setup surfaces can inspect without loading plugin runtime.
doctorContract No object Declares which dynamic doctor-contract surfaces the plugin artifact exports so doctor loads only relevant modules.
doctorHealthChecks No boolean Declares health-check registration in the selected plugin's public API. Read by the Codex doctor health API.
sessionRouteStateOwners No object[] Static session-route ownership for doctor cleanup. Each entry declares an id, label, and optional providerIds, runtimeIds, cliSessionKeys, and authProfilePrefixes.
qaRunners No object[] Cheap QA runner descriptors used by the shared openclaw qa host before plugin runtime loads.
dashboard No object Dashboard widget data bindings and action verbs. Each entry is validated against a Gateway method registered by this plugin with the required read or write scope. See dashboard reference.
mcpServers No Record<string, object> Static MCP server definitions contributed while this plugin is enabled. Relative command arguments and working directories resolve from the plugin root. Operator mcp.servers entries override or disable definitions with the same name. See MCP server reference.
contracts No object Static capability ownership snapshot for external auth hooks, embeddings, speech, realtime transcription, realtime voice, media-understanding, image/video/music generation, web fetch, web search, worker providers, document/web-content extraction, and tool ownership.
transcriptSources No Record<string, object> Static transcript source names and auto-start locator requirements for IDs declared in contracts.transcriptSourceProviders. See Transcript sources reference.
configContracts No object Manifest-owned config behavior consumed by generic core helpers: dangerous-flag detection, SecretRef migration targets, and legacy config-path narrowing. See configContracts reference.
mediaUnderstandingProviderMetadata No Record<string, object> Cheap media-understanding defaults for provider ids declared in contracts.mediaUnderstandingProviders.
imageGenerationProviderMetadata No Record<string, object> Cheap image-generation auth metadata for provider ids declared in contracts.imageGenerationProviders, including provider-owned auth aliases and base-url guards.
videoGenerationProviderMetadata No Record<string, object> Cheap video-generation auth metadata for provider ids declared in contracts.videoGenerationProviders, including provider-owned auth aliases and base-url guards.
musicGenerationProviderMetadata No Record<string, object> Cheap music-generation auth metadata for provider ids declared in contracts.musicGenerationProviders, including provider-owned auth aliases and base-url guards.
toolMetadata No Record<string, object> Cheap availability metadata for plugin-owned tools declared in contracts.tools. Use it when a tool should not load runtime unless config, env, or auth evidence exists.
channelConfigs No Record<string, object> Manifest-owned channel config metadata merged into discovery and validation surfaces before runtime loads.
channelAccountKeyPolicies No Record<string, object> Stored account-key selection rules for declared channels. See account-key policies.
skills No string[] Skill directories to load, relative to the plugin root.
name No string Human-readable plugin name.
description No string Short summary shown in plugin surfaces.
catalog No object Optional presentation hints for plugin catalog surfaces. This metadata does not install, enable, or grant trust to a plugin.
categories No string[] One to three controlled catalog category slugs, ordered with the primary category first. Bundled plugins must declare exactly one active category.
version No string Informational plugin version.
uiHints No Record<string, object> UI labels, placeholders, and sensitivity hints for config fields.

An AuthAlias is either a provider id string or an object with provider and baseUrls. An object alias applies only to the configured model-provider endpoint after trimming whitespace and trailing slashes. It does not rename stored credential providers or contribute a new setup provider. Existing profile order, explicit bindings, and plugin trust checks still apply.

Catalog categories

Choose the one category that best describes why someone would install the plugin. Use its main user purpose, not every tool, provider, or runtime capability it exposes. For example, an agent execution backend belongs in agent-runtimes, document extraction belongs in documents-files, and a messaging adapter belongs in channels even when it also provides workspace tools.

Bundled OpenClaw plugins declare exactly one active category. New ClawHub publications also accept exactly one declared category, using the same array shape, or omit the field for ClawHub to generate a category.

OpenClaw's manifest reader continues to accept one to three unique, ordered categories so previously installed and published packages remain readable. When reading older multiple-category declarations, the first remains primary and all remain searchable. The stricter new-publication rule does not invalidate an installed plugin's manifest.

The active categories below are listed in browse order:

Slug Use for
channels Human-agent messaging transports and channel adapters. Choose this when the main purpose is letting people talk to the agent through a messaging service, even if the adapter also exposes workspace tools.
models General model providers, inference backends, and model routing. Agent execution engines belong in Agent runtimes; specialized speech or media generators belong in Voice or Media when that is their main purpose.
agent-runtimes Agent execution engines and backends that run model/tool loops and manage native sessions, including Codex, ACP, and Copilot runtimes. Context assembly belongs in Context; coordinating work across agents belongs in Agent orchestration.
memory Durable agent memory, embeddings, and retrieval across conversations. Building or compacting the active conversation context belongs in Context.
context Building, selecting, compacting, or managing the active conversation context. Durable memory belongs in Memory; an engine that runs the agent and owns its native sessions belongs in Agent runtimes.
voice Speech synthesis, transcription, voice calls, and spoken interaction. Music and general media creation or analysis belong in Media.
web General web search, browser control, and fetching web pages. A tool whose main purpose is a specific research or business workflow belongs in that workflow's category.
media Creating, transforming, or understanding images, video, music, and other media. Spoken interaction and transcription belong in Voice.
security Protecting access and enforcing trust through authentication, authorization, credential controls, security auditing, or policy. Authentication incidental to another purpose does not belong here.
integrations General connectors, API bridges, and service integration platforms without a more specific user purpose. A connector to a particular workflow belongs in that workflow's category; exposing tools or MCP is not enough.
developer-tools Writing, reviewing, testing, and debugging software, development environments, and coding workflows. Plugins whose main purpose is providing the agent execution engine belong in Agent runtimes.
infrastructure Deploying, hosting, monitoring, and operating systems, networks, services, and execution environments. Engines that run the agent loop belong in Agent runtimes; coordinating agents belongs in Agent orchestration.
documents-files Reading, creating, extracting, transferring, and managing documents and files. Software code review belongs in Developer tools; task and project management belongs in Productivity.
inbox-collaboration Managing email, inboxes, team communication, and collaborative workspaces. Providing a transport for people to talk to the agent belongs in Channels.
productivity Managing tasks, notes, projects, plans, and personal or team work. Appointments and availability belong in Scheduling; document processing belongs in Documents & files.
scheduling Calendars, appointments, availability, and booking. Technical job scheduling belongs with the workflow it supports, or Infrastructure for general system scheduling.
finance-payments Payments, billing, accounting, banking, trading, and financial workflows. General business reporting belongs in Data & analytics.
sales-marketing Customer relationships, sales, customer support, outreach, campaigns, and marketing operations. General email or chat management belongs in Inbox & collaboration.
data-analytics Querying databases, processing datasets, analysis, reporting, and business intelligence. Agent memory storage belongs in Memory; operational telemetry belongs in Infrastructure.
agent-orchestration Coordinating agents, delegating work, and running multi-step agent workflows. Engines and backends that execute the agent loop and manage its native sessions belong in Agent runtimes.
research Investigating topics, evaluating sources, working with scientific literature, and synthesizing evidence. General web search, browsing, and page fetching belong in Web.
other Use only when the plugin's main purpose does not fit another category or the available evidence is insufficient. Do not use this just because a plugin has several capabilities.

Legacy tools, runtime, and gateway declarations remain valid so existing packages keep loading. They are retired from the active browse taxonomy. Choose active categories for new declarations; legacy values are not automatically translated into a different category.

Omission remains valid for external plugin compatibility. When an external catalog supplies a derived fallback, an explicit package declaration takes precedence. Bundled OpenClaw plugins must declare exactly one active category.

JSON Schema requirements

  • Every plugin must ship a JSON Schema, even if it accepts no config.
  • An empty schema is acceptable (for example, { "type": "object", "additionalProperties": false }).
  • Config is validated against the manifest schema at config read/write time and before the plugin loads.
  • When extending or forking a bundled plugin with new config keys, update that plugin's openclaw.plugin.json configSchema at the same time. Bundled plugin schemas are strict, so adding plugins.entries.<id>.config.myNewKey in user config without adding myNewKey to configSchema.properties will be rejected before the plugin runtime loads.

Example schema extension:

{
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "myNewKey": {
        "type": "string"
      }
    }
  }
}

Validation behavior

Capability catalogs

capabilityCatalogEntry declares a lightweight module relative to the selected plugin root, for example "./capability-catalog.ts". It exports actual speech, realtime transcription, or realtime voice provider descriptors without importing the full plugin entry. See the typed SDK contract.

Each supplied family is authoritative, including an empty array. An omitted family, or a plugin without this declaration, retains the existing register() discovery contract for installed plugins. A malformed, missing, or broken declared entry fails with a repair diagnostic; it does not fall through to full registration. Already registered runtime providers remain authoritative, including live broker and readiness closures.

The entry uses the same plugin-root boundary checks, installed-owner precedence, prepared metadata generation, and source/built artifact policy as other plugin surfaces. Repository builds include declared entries and rewrite emitted manifest paths to the corresponding JavaScript artifacts. Plugin reload owns invalidation; catalog requests do not poll files for changes.

Configuration validation

  • Required-field errors identify every missing field after schema defaults are applied. For dependencies on multiple fields, the error reports the dependency condition without claiming that fields already present are missing.
  • Unknown channels.* keys are errors, unless the channel id is declared by a plugin manifest. If the same id also appears in plugins.allow, plugins.entries, or plugins.installs (a plugin that is referenced but not currently discoverable), OpenClaw downgrades this to a warning instead.
  • plugins.entries.<id>, plugins.allow, and plugins.deny referencing unknown plugin ids are warnings ("stale config entry ignored"), not errors, so upgrades and removed/renamed plugins do not block gateway startup. An exact { enabled: false } plugin entry is an intentional uninstall marker, so validation and Doctor keep it without a stale-config warning.
  • plugins.slots.memory referencing an unknown plugin id is an error, except for the known memory-lancedb official external plugin, which warns instead.
  • If a plugin is installed but has a broken or missing manifest or schema, validation fails and Doctor reports the plugin error.
  • If plugin config exists but the plugin is disabled, the config is kept and a warning is surfaced in Doctor + logs.

See Configuration reference for the full plugins.* schema.

Notes

  • The manifest is required for native OpenClaw plugins, including local filesystem loads. Runtime still loads the plugin module separately; the manifest is only for discovery + validation.
  • Native manifests are parsed with JSON5, so comments, trailing commas, and unquoted keys are accepted as long as the final value is still an object.
  • Only documented manifest fields are read by the manifest loader. Avoid custom top-level keys.
  • channels, providers, cliBackends, and skills can all be omitted when a plugin does not need them.
  • providerCatalogEntry must stay lightweight and should not import broad runtime code; use it for static provider catalog metadata or narrow discovery descriptors, not request-time execution.
  • Exclusive plugin kinds are selected through plugins.slots.*: kind: "memory" via plugins.slots.memory (default memory-core), kind: "context-engine" via plugins.slots.contextEngine (default legacy).
  • Declare exclusive plugin kind in this manifest. Bundled plugins use manifest kinds without loading their runtime during enablement. Runtime-entry OpenClawPluginDefinition.kind was deprecated on 2026-07-25 and remains only as a compatibility fallback for older external plugins; its removal gate is 2026-10-01. See the compatibility policy.
  • Env-var metadata in setup.providers[].envVars is declarative only. Status, audit, cron delivery validation, and other read-only surfaces still apply plugin trust and effective activation policy before treating an env var as configured.
  • For runtime wizard metadata that requires provider code, see Provider runtime hooks.
  • If your plugin depends on native modules, document the build steps and any package-manager allowlist requirements (for example, pnpm allow-build-scripts + pnpm rebuild <package>).
Getting started with plugins. Internal architecture and capability model. Plugin SDK reference and subpath imports. Manifest model catalog, shorthand family, id normalization, and pricing fields. Manifest generation, media-understanding, endpoint, and request provider metadata. Manifest setup descriptors, auth choices, conversation discovery, and config UI hints. Manifest capability ownership, tool availability metadata, and activation planning. Manifest fields for icons, CLI, MCP, Control UI, dashboard, QA, channel, and backup surfaces. Manifest dangerous-flag, SecretRef migration, and secret provider preset metadata. Which pre-runtime metadata lives in package.json, and which duplicate plugin id wins. Packaging and config schemas that consume this manifest. `definePluginEntry` and the other entry helpers a plugin's code exports. Declaring `contracts.tools` for agent tools. Installing and enabling the plugins this manifest describes. The `backupResources` surface declared here.