feat(channels): add channel-owned setup contracts#112176
Merged
Merged
Conversation
steipete
force-pushed
the
jesse/channel-owned-setup-contract
branch
from
July 22, 2026 20:53
cd82dbb to
4433fcc
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a channel-owned setup contract and metadata projection so openclaw channels add can present help and parse flags only for the selected channel (without loading plugin runtime). It also migrates many bundled channels to publish their setup fields via package.json metadata and aligns Signal transport ownership with the channel-owned contract.
Changes:
- Add a typed
setupContractruntime surface plus serializableopenclaw.channel.setupmetadata for lazy CLI discovery. - Migrate bundled channels to define
setupContractand publishsetup.fieldsin theirpackage.json(replacing/retiringcliAddOptionsin many cases). - Harden post-update plugin-doctor validation and update Signal transport normalization/probing/doctor behavior.
Reviewed changes
Copilot reviewed 178 out of 179 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/plugins/manifest.ts | Extends package channel metadata types to include setup and negatedFlags. |
| src/plugins/manifest-registry-installed.test.ts | Adds coverage for normalizing/dropping invalid setup field metadata during registry hydration. |
| src/plugins/bundled-package-channel-metadata.ts | Adds listPackageChannelMetadata() for lazy CLI setup discovery across bundled+installed. |
| src/plugin-sdk/core.ts | Makes setup optional in plugin base creation and adds optional setupContract. |
| src/plugin-sdk/channel-setup.ts | Re-exports defineChannelSetupContract and setup types via the SDK subpath. |
| src/config/zod-schema.signal-update.test.ts | Adds tests for the temporary Signal schema validation window during updates. |
| src/config/channel-configured.test.ts | Updates Signal configured-shape assertions to the new transport object. |
| src/commands/channels/add-mutators.ts | Routes config mutation through resolveChannelSetupExecutionAdapter to support setupContract. |
| src/cli/update-cli/update-command.test.ts | Adds tests for post-plugin config validation behavior. |
| src/cli/update-cli/update-command-resume.ts | Uses completePostCorePluginUpdate to finalize plugin update + validation during resume. |
| src/cli/update-cli/update-command-post-update.ts | Uses completePostCorePluginUpdate and expands freshDoctorRequired conditions to include core-install changes. |
| src/channels/plugins/types.plugin.ts | Adds setupContract to the channel plugin type and deprecates setup for new plugins. |
| src/channels/plugins/types.adapters.ts | Makes ChannelSetupAdapter generic over the input type. |
| src/channels/plugins/setup-wizard.ts | Uses resolveChannelSetupExecutionAdapter and validates contract input for contract-backed plugins. |
| src/channels/plugins/setup-wizard-types.ts | Threads optional setupContract through the setup wizard plugin shape. |
| src/channels/plugins/contracts/plugin-shape.contract.test.ts | Adds parity test asserting runtime setupContract.metadata equals package setup metadata for bundled channels. |
| scripts/plugin-sdk-surface-report.mjs | Updates SDK surface budgets for the new contract factory export. |
| scripts/lib/official-external-channel-catalog.json | Updates official external channel catalog setup metadata (Signal transport selector addition). |
| extensions/zalouser/src/shared.ts | Wires optional setupContract through Zalouser plugin base builder. |
| extensions/zalouser/src/setup-core.ts | Defines Zalouser setupContract (empty fields + legacy adapter). |
| extensions/zalouser/src/channel.ts | Registers Zalouser setupContract on the runtime plugin. |
| extensions/zalouser/src/channel.setup.ts | Registers Zalouser setupContract on the setup plugin entry. |
| extensions/zalouser/package.json | Publishes Zalouser setup metadata projection (setup.fields). |
| extensions/zalo/src/setup-core.ts | Defines Zalo setupContract fields for token/token-file/use-env. |
| extensions/zalo/src/channel.ts | Registers Zalo setupContract. |
| extensions/zalo/package.json | Publishes Zalo setup metadata projection (setup.fields). |
| extensions/whatsapp/src/shared.ts | Wires optional setupContract through WhatsApp plugin base builder. |
| extensions/whatsapp/src/setup-core.ts | Defines WhatsApp setupContract field for authDir. |
| extensions/whatsapp/src/channel.ts | Registers WhatsApp setupContract. |
| extensions/whatsapp/src/channel.setup.ts | Registers WhatsApp setupContract on the setup plugin entry. |
| extensions/whatsapp/package.json | Migrates WhatsApp from cliAddOptions to setup.fields metadata. |
| extensions/twitch/src/setup-surface.ts | Defines empty Twitch setupContract (wizard-only storage). |
| extensions/twitch/src/plugin.ts | Registers Twitch setupContract. |
| extensions/twitch/package.json | Publishes empty Twitch setup metadata projection. |
| extensions/tlon/src/setup-core.ts | Defines Tlon setupContract fields (ship/url/code/etc). |
| extensions/tlon/src/channel.ts | Registers Tlon setupContract. |
| extensions/tlon/package.json | Migrates Tlon from cliAddOptions to setup.fields metadata. |
| extensions/telegram/src/shared.ts | Wires optional setupContract through Telegram plugin base builder. |
| extensions/telegram/src/setup-core.ts | Defines Telegram setupContract (token/token-file/use-env). |
| extensions/telegram/src/channel.ts | Registers Telegram setupContract. |
| extensions/telegram/src/channel.setup.ts | Registers Telegram setupContract on the setup plugin entry. |
| extensions/telegram/package.json | Publishes Telegram setup metadata projection (setup.fields). |
| extensions/synology-chat/src/setup-surface.ts | Defines Synology Chat setupContract (token/url/webhookPath/useEnv). |
| extensions/synology-chat/src/channel.ts | Registers Synology Chat setupContract. |
| extensions/synology-chat/package.json | Migrates Synology Chat from cliAddOptions to setup.fields metadata. |
| extensions/sms/src/channel.ts | Defines and registers SMS setupContract fields (Twilio + policy fields). |
| extensions/sms/package.json | Publishes SMS setup metadata projection (setup.fields). |
| extensions/slack/src/shared.ts | Wires optional setupContract through Slack plugin base builder. |
| extensions/slack/src/setup-core.ts | Defines Slack setupContract fields (tokens/secrets/mode/identity/useEnv). |
| extensions/slack/src/channel.ts | Registers Slack setupContract. |
| extensions/slack/src/channel.setup.ts | Registers Slack setupContract on the setup plugin entry. |
| extensions/slack/package.json | Migrates Slack from cliAddOptions to setup.fields metadata. |
| extensions/signal/src/transport-url.ts | Adds canonicalization helpers for Signal transport URLs/hosts. |
| extensions/signal/src/transport-probes.runtime.ts | Adds a lazy runtime boundary for transport probes. |
| extensions/signal/src/transport-policy.test.ts | Adds regression coverage for managed-native URL/host alignment behavior. |
| extensions/signal/src/transport-detection.ts | Adds setup-only detection that probes native/container endpoints. |
| extensions/signal/src/transport-detection.runtime.ts | Adds a lazy runtime boundary for detection. |
| extensions/signal/src/sse-reconnect.ts | Renames/threads transport kind through the SSE reconnect loop. |
| extensions/signal/src/shared.ts | Moves Signal base plugin to require setupContract and registers signalDoctor. |
| extensions/signal/src/send-reactions.ts | Switches reaction RPC routing from apiMode to transport-kind selection. |
| extensions/signal/src/send-reactions.test.ts | Updates/adds tests to assert transport-kind behavior in reactions RPC calls. |
| extensions/signal/src/runtime-api.ts | Exposes additional Signal runtime API exports/types for transports. |
| extensions/signal/src/probe.ts | Switches probing to transport-kind; keeps deprecated apiMode compatibility. |
| extensions/signal/src/monitor.tool-result.test-harness.ts | Updates monitor test harness config to the new transport shape. |
| extensions/signal/src/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.test.ts | Updates expectations for container transport config + probe args. |
| extensions/signal/src/monitor.tool-result.autostart.test.ts | Updates autostart tests for managed transport config semantics. |
| extensions/signal/src/doctor.ts | Adds Signal doctor adapter that runs transport migration/detection. |
| extensions/signal/src/config-ui-hints.ts | Updates UI hints to document the new transport subtree. |
| extensions/signal/src/channel.ts | Registers Signal setupContract and passes transport-kind into probe. |
| extensions/signal/src/channel.setup.ts | Registers Signal setupContract on the setup plugin entry. |
| extensions/signal/src/account-types.ts | Refactors Signal account config types and exports transport config type. |
| extensions/signal/package.json | Migrates Signal to setup.fields metadata including transport selector. |
| extensions/signal/doctor-contract-api.ts | Adds legacy config rules + compatibility normalization for retired transport fields. |
| extensions/signal/api.ts | Exports setup/transport helpers/types for external consumers. |
| extensions/reef/src/setup.ts | Defines Reef setupContract (empty) and preserves adapter. |
| extensions/reef/src/channel.ts | Registers Reef setupContract. |
| extensions/reef/package.json | Publishes empty Reef setup metadata projection. |
| extensions/raft/src/setup.ts | Defines and registers Raft setupContract (profile). |
| extensions/raft/src/channel.ts | Threads Raft setupContract into the runtime plugin. |
| extensions/raft/package.json | Publishes Raft setup metadata projection (setup.fields). |
| extensions/qqbot/src/channel.ts | Registers QQBot setupContract. |
| extensions/qqbot/src/channel.setup.ts | Registers QQBot setupContract on the setup plugin entry. |
| extensions/qqbot/src/bridge/config-shared.ts | Defines QQBot setupContract (token/token-file/use-env). |
| extensions/qqbot/package.json | Publishes QQBot setup metadata projection (setup.fields). |
| extensions/qa-lab/src/crabline-transport.ts | Normalizes retired Signal transport fields to canonical transport shape at the adapter boundary. |
| extensions/qa-lab/src/crabline-transport.test.ts | Updates assertions to the canonical Signal transport object and absence of retired fields. |
| extensions/qa-channel/src/channel-base.ts | Defines QA Channel setupContract and registers it. |
| extensions/qa-channel/package.json | Migrates QA Channel from cliAddOptions to setup.fields metadata. |
| extensions/nostr/src/setup-surface.ts | Threads Nostr setup contract factory through setup surface. |
| extensions/nostr/src/setup-adapter.ts | Defines Nostr setupContract helper that wraps the legacy adapter. |
| extensions/nostr/src/channel.ts | Registers Nostr setupContract. |
| extensions/nostr/src/channel.setup.ts | Registers Nostr setupContract on the setup plugin entry. |
| extensions/nostr/package.json | Migrates Nostr from cliAddOptions to setup.fields metadata. |
| extensions/nextcloud-talk/src/setup-core.ts | Defines Nextcloud Talk setupContract including legacy aliases. |
| extensions/nextcloud-talk/src/channel.ts | Registers Nextcloud Talk setupContract. |
| extensions/nextcloud-talk/package.json | Migrates Nextcloud Talk from cliAddOptions to setup.fields metadata. |
| extensions/msteams/src/setup-core.ts | Defines empty MSTeams setupContract (adapter-only). |
| extensions/msteams/src/channel.ts | Registers MSTeams setupContract. |
| extensions/msteams/src/channel.setup.ts | Registers MSTeams setupContract on the setup plugin entry. |
| extensions/msteams/package.json | Publishes empty MSTeams setup metadata projection. |
| extensions/mattermost/src/setup-core.ts | Defines Mattermost setupContract fields (token/httpUrl/useEnv). |
| extensions/mattermost/src/channel.ts | Registers Mattermost setupContract. |
| extensions/mattermost/src/channel.setup.ts | Registers Mattermost setupContract on the setup plugin entry. |
| extensions/mattermost/package.json | Migrates Mattermost from cliAddOptions to setup.fields metadata. |
| extensions/matrix/src/setup-core.ts | Defines Matrix setupContract fields (homeserver/userId/tokens/etc). |
| extensions/matrix/src/channel.ts | Registers Matrix setupContract (and threads promotion keys). |
| extensions/matrix/src/channel.setup.ts | Registers Matrix setupContract on the setup plugin entry. |
| extensions/matrix/package.json | Migrates Matrix from cliAddOptions to setup.fields metadata. |
| extensions/line/src/setup-core.test.ts | Adds regression coverage for LINE’s shipped --token alias behavior. |
| extensions/line/src/channel.ts | Registers LINE setupContract. |
| extensions/line/src/channel.setup.ts | Registers LINE setupContract on the setup plugin entry. |
| extensions/line/package.json | Migrates LINE from cliAddOptions to setup.fields metadata (including alias fields). |
| extensions/irc/src/setup-core.ts | Defines IRC setupContract fields (host/port/tls/etc). |
| extensions/irc/src/channel.ts | Registers IRC setupContract. |
| extensions/irc/package.json | Migrates IRC from cliAddOptions to setup.fields metadata. |
| extensions/imessage/src/shared.ts | Wires optional setupContract through iMessage plugin base builder. |
| extensions/imessage/src/setup-core.ts | Defines iMessage setupContract fields (cliPath/dbPath/service/region). |
| extensions/imessage/src/channel.ts | Registers iMessage setupContract. |
| extensions/imessage/src/channel.setup.ts | Registers iMessage setupContract on the setup plugin entry. |
| extensions/imessage/package.json | Migrates iMessage from cliAddOptions to setup.fields metadata. |
| extensions/googlechat/src/setup-core.ts | Defines Google Chat setupContract fields (token/token-file/audience/etc). |
| extensions/googlechat/src/channel-base.ts | Registers Google Chat setupContract. |
| extensions/googlechat/package.json | Migrates Google Chat from cliAddOptions to setup.fields metadata. |
| extensions/feishu/src/setup-core.ts | Defines empty Feishu setupContract (adapter-only). |
| extensions/feishu/src/channel.ts | Registers Feishu setupContract. |
| extensions/feishu/package.json | Publishes empty Feishu setup metadata projection. |
| extensions/discord/src/shared.ts | Wires optional setupContract through Discord plugin base builder. |
| extensions/discord/src/setup-adapter.ts | Defines Discord setupContract (token/useEnv). |
| extensions/discord/src/channel.ts | Registers Discord setupContract. |
| extensions/discord/src/channel.setup.ts | Registers Discord setupContract on the setup plugin entry. |
| extensions/discord/package.json | Publishes Discord setup metadata projection (setup.fields). |
| extensions/clickclack/src/setup-core.ts | Defines ClickClack setupContract fields (code/token/workspace/etc). |
| extensions/clickclack/src/channel.ts | Registers ClickClack setupContract. |
| extensions/clickclack/src/channel.setup.ts | Registers ClickClack setupContract on the setup plugin entry. |
| extensions/clickclack/package.json | Migrates ClickClack from cliAddOptions to setup.fields metadata. |
| docs/start/wizard-cli-reference.md | Updates docs for Signal wizard write path (channels.signal.transport.cliPath). |
| docs/reference/wizard.md | Updates docs for Signal wizard write path (channels.signal.transport.cliPath). |
| docs/reference/rpc.md | Updates docs for Signal lifecycle ownership based on managed transport kind. |
| docs/plugins/sdk-subpaths.md | Documents new setup contract factory export (contains one incorrect claim noted in review). |
| docs/.generated/config-baseline.sha256 | Updates generated config baseline hashes. |
| docs/.generated/config-baseline.counts.json | Updates generated config baseline counts. |
| | `plugin-sdk/channel-setup` | `defineChannelSetupContract`, channel-owned setup field/input types, `createOptionalChannelSetupSurface`, `createOptionalChannelSetupAdapter`, `createOptionalChannelSetupWizard`, plus `DEFAULT_ACCOUNT_ID`, `createTopLevelChannelDmPolicy`, `setSetupChannelEnabled`, `splitSetupEntries` | | ||
| | `plugin-sdk/setup` | Shared setup wizard helpers, setup translator, allowlist prompts, setup status builders | | ||
| | `plugin-sdk/setup-runtime` | `createSetupTranslator`, `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` | | ||
| | `plugin-sdk/setup-runtime` | `defineChannelSetupContract`, `createSetupTranslator`, `createPatchedAccountSetupAdapter`, `createEnvPatchedAccountSetupAdapter`, `createSetupInputPresenceValidator`, `noteChannelLookupFailure`, `noteChannelLookupSummary`, `promptResolvedAllowFrom`, `splitSetupEntries`, `createAllowlistSetupWizardProxy`, `createDelegatedSetupWizardProxy` | |
Contributor
|
Merged via squash.
|
github-actions Bot
pushed a commit
to Desicool/openclaw
that referenced
this pull request
Jul 23, 2026
* feat(channels): add channel-owned setup contracts * test(channels): align legacy setup fixtures * chore(channels): regenerate config and SDK baselines after rebase * fix(update): run fresh doctor after current-process core changes * fix(channels): align add pre-scan with execution precedence * style(cli): format channels-cli test additions * fix(channels): restore option-before-positional channel resolution via metadata arity scan * fix(channels): keep help flags out of metadata arity escalation * test(update): mock fresh post-update doctor in current-process suites * style: format review fixes and correct entrypoint mock type * fix(channels): register only modern contract options for dual-publishing plugins * test(update): align downgrade suites with fresh-doctor child invocation * docs(channels): record empty-contract and input-forwarding invariants * fix(line): keep the shipped --token switch as a channel access token alias * fix(signal): stop treating exact cross-family loopback endpoints as bind-aligned * chore(config): regenerate docs config baselines after second rebase * style: format rebased channels add tests * fix(channels): enforce field-key and flag-name agreement in setup contracts * fix(signal): detect container endpoints for bare --http-url setup * fix(signal): ignore unconfigured accounts in transport collision checks * fix(channels): validate negated setup flags in contract and normalizer * fix(signal): preserve existing transport kind when setup detection is unreachable * style(signal): use direct boolean check in collision guard * style(signal): type test config literals * docs(update): record two-read design of fresh-doctor validation gate * fix(channels): satisfy post-rebase architecture gates * docs: refresh channel setup map --------- Co-authored-by: Peter Steinberger <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New behavior
openclaw channels addnow uses one channel-owned setup contract for guided and non-interactive setup. Core selects a channel first, then registers only that channel's setup options.openclaw channels add --helpshows only the shared--channel,--account, and--nameenvelope.openclaw channels add telegram --helpandopenclaw channels add --channel telegram --helpshow Telegram fields only.This supersedes #108345 with the repository-wide architecture fix rather than landing the Signal-only compromise.
CLI proof
openclaw channels add --help--channel,--account, and--nameonlyopenclaw channels add telegram --help--token,--token-file, and--use-env; no Signal optionsopenclaw channels add --channel signal --helpcliAddOptionsfirst; released core compatibility options fill only missing switchesThe final row is intentionally selection-scoped. An external plugin's own option shape and default win collisions, while unrelated legacy options never leak into modern channels.
What a channel implements
A modern channel defines its runtime fields and config behavior once:
It publishes the matching serializable projection for lazy CLI discovery:
{ "openclaw": { "channel": { "id": "example", "setup": { "fields": [ { "key": "endpoint", "kind": "string", "cli": { "flags": "--endpoint <url>", "description": "Service endpoint" } }, { "key": "transport", "kind": "choice", "choices": ["native", "container"], "cli": { "flags": "--transport <kind>", "description": "Transport owner" } } ] } } } }Supported field kinds are
string,boolean,integer,string-list, andchoice. Runtime/package parity tests keep bundled projections aligned. A channel may also providesetupWizard; wizard values still pass through the same contract.Third-party compatibility
The released
setup/ChannelSetupInputadapter and package-levelcliAddOptionsremain accepted for existing external plugins. When such a plugin is selected:cliAddOptions.New plugins should use
setupContract. If a plugin exposes both contracts, OpenClaw prefers the modern contract. This gives existing plugins a migration path without keeping global, cross-channel help forever.Behavioral details
--channel <id>use the same selected-channel resolver; exact ids win before aliases.No screenshots are included because this is a CLI/plugin contract and config-flow change. The command/output matrix is the reviewer-checkable UI proof.
How to verify
Checks
pnpm check:changed— passedpnpm build— passedpnpm tsgo:coreandpnpm tsgo:extensions— passedpnpm check:test-types— passedpnpm plugin-sdk:api:check— passedpnpm docs:check-mdx— 749 files passedpnpm docs:check-links— 6,150 links checked, 0 brokenRebase and takeover notes (maintainer)
This branch was rebased onto current
mainand reconciled with #112319 (ChannelSetupInput envelope + deprecated compatibility tier):ChannelSetupInputenvelope.transportobject.CHANGELOG.mdedits were dropped (changelog is release-generation-owned); release-note context lives in this body.Flag ordering
channels addselection now matches execution precedence (--channelwins over positional, last--channelwins):channels add telegram --token Xchannels add --channel telegram --token Xchannels add --token X --channel telegram--channelanywhere)channels add --account work telegram --token Xchannels add telegram --channel signalchannels add --token X telegramChannels whose setup stores no flag values (twitch, feishu, msteams, reef, zalouser) publish empty contracts: previously-silently-ignored channel flags now fail loudly as unknown options. This is the intended selection-scoped contract — those flags never stored anything on shipped CLI (adapters are enable-only/wizard-driven).
Update flow hardening
freshDoctorRequiredin the current-process post-update path now also triggers when the core install itself changed (didCoreUpdateChangeInstall, shared with the fresh-process resume gate). Previously a downgrade or resume-fallback update with no plugin sync/npm changes skipped the fresh doctor, leaving retired config unmigrated for the restarted gateway's stricter schema.Red main repair
Current
mainis red inextensions/signal/src/monitor/event-handler.inbound-context.test.ts: 4 tests still asserted themessages.statusReactions.emojisandmessages.removeAckAfterReplyconfig surfaces that #111527 retired (verified failing on cleanorigin/mainon a Blacksmith Testbox). This PR aligns those tests with the retained-ack / curated-defaults behavior (3 rewritten, 1 deleted — its subject, reaction removal, is unreachable withremoveAckAfterReplyhardcoded false). Dead-branch cleanup in the event handler is deferred as a follow-up.