Feat/channels list show all and drop auth#78456
Conversation
|
Codex review: needs maintainer review before merge. Summary Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path. Real behavior proof Next step before merge Review detailsBest possible solution: Retry the Codex review after fixing the execution failure. Do we have a high-confidence way to reproduce the issue? Unclear. The review failed before ClawSweeper could establish a reproduction path. Is this the best way to solve the issue? Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 1831e124b221. |
f5edc4e to
059fe3a
Compare
20e219e to
09be1b4
Compare
…d/configured/enabled `openclaw channels list` used to conflate two very different surfaces: chat channels and OAuth/API-key auth providers for model routing. The auth section was the first and most visible block in the output even for operators who only cared about chat channels, and its JSON `auth` key leaked model-provider identities into a command whose top-level help describes it as channel management. Worse, the command silently hid every channel that had no configured account, so users could not tell from `channels list` which bundled or catalog channels were even available to configure. Split the surface cleanly around channels only: 1. Remove the `Auth providers (OAuth + API keys)` text section and the `auth` field from the JSON payload. Model-provider auth profiles remain reachable via `openclaw models auth list`, which is where they conceptually belong. 2. Add a `--all` flag to surface every channel an operator could configure: bundled channel plugins that have no account yet and catalog-listed external channels whose plugin package is not even installed on disk. Without `--all` the output still shows only channels with at least one configured account, matching the previous default behavior so existing scripts keep working. The "empty" default path now prints a hint pointing at `--all`. 3. Render three explicit status tags per row — `installed` / `not installed`, `configured` / `not configured`, `enabled` / `disabled` — so bundled-but-unconfigured plugins and installable catalog channels both render with accurate state instead of being invisible. Installed state comes from the same `isCatalogChannelInstalled` probe the setup flow uses, so it stays consistent with `openclaw onboard` and `channels add`. 4. JSON payload now carries an `origin` per channel (`configured`, `available`, `installable`) alongside `installed: boolean`, which lets tooling distinguish "user has set this up" from "user could set this up" without second-guessing. Register `--all` on both the Commander CLI and the fast-path route-arg parser so the flag works in both code paths, update the one routes test that asserted the parsed args shape, and rewrite the old auth profiles surface test as a broader `channels list` behavior spec covering default output, `--all` output, JSON shape (no `auth`), and the bundled-unconfigured + catalog-not-installed cases. Docs: call out that `channels list` is chat-channel only now, mention `--all`, and point at `openclaw models auth list` for what used to be the auth providers block.
…sk but not yet configured The previous `--all` path filtered catalog entries with `!installedByChannelId.get(entry.id)` before rendering them as catalog-only rows. That assumed "catalog entry not already rendered as a plugin row" implied "not installed", which is wrong: an external channel plugin package can be installed on disk (`isCatalogChannelInstalled` returns true) while the read-only channel loader still declines to surface a plugin object for it — the loader only activates channels that appear in user config, so a plugin that is installed but never configured ended up in neither bucket and silently dropped out of `channels list --all`. Operator-facing symptom: `pnpm openclaw channels list --all` omitted WeCom (and any other catalog channel in the same state) even though its npm package was present on disk and its catalog entry existed, while rendering every other uninstalled catalog channel as expected. Fix: drop the `installed` filter from `catalogOnlyLines` so every catalog entry that is not already represented by a plugin row is rendered, and let the row itself carry the real installed/not-installed tag. Two renderings now land in the catalog-only bucket: - Not installed — rendered as `not installed, not configured, disabled` (installable row). - Installed but unconfigured — rendered as `installed, not configured, disabled` (ready-to-configure row). The JSON `origin` for this case becomes `available`, matching the existing origin for bundled plugins that are installed but unconfigured, so downstream tooling sees a consistent "you could configure this now" signal regardless of whether the plugin came from bundled sources or from the catalog. Regression test added under the WeCom scenario.
…command channel-only
`openclaw channels list` used to append a model-provider usage/quota
snapshot (Anthropic, OpenRouter, OpenAI Codex, Gemini, Zai, Minimax,
etc.) under every invocation. That was a leftover from the days when
`channels list` was the only "operator overview" command; the same
data is now owned by `openclaw status` (overview) and
`openclaw models list` (per-provider), which handle timeouts, probe
errors, and output shape consistently for that class of data. Keeping
the snapshot wired into `channels list` meant:
- Every default invocation made one blocking `loadProviderUsageSummary`
call that fanned out to every configured provider billing/auth
endpoint, adding seconds of latency to a command that otherwise
just reads local config.
- `channels list --no-usage` was the escape hatch, but the flag was
itself a self-sustaining bug: it only existed because the command
did work that did not belong to it.
- JSON consumers had an optional `usage` key whose shape was owned by
the provider-usage module, not by the channels module, so any
change upstream silently reshaped `channels list --json` output.
- Failed provider fetches printed provider-side errors on a command
that never advertised itself as a provider-health surface.
Scope this PR tightens, in one move:
1. Remove `loadProviderUsageSummary` / `formatUsageReportLines` usage
from `src/commands/channels/list.ts`. The command now only reads
config, the read-only channel plugin registry, and the trusted
catalog — matching its name.
2. Drop `--no-usage` from the Commander CLI registration, from the
fast-path route-arg parser (`parseChannelsListRouteArgs`), and
from `ChannelsListOptions`. The flag is gone, not silently
ignored, so anyone depending on it will get a clear
"unknown option" from Commander and from the fast-path router.
3. Drop the `usage` key from `channels list --json` payloads. Shape
of the `chat` record and the new `origin` / `installed` tags
introduced earlier in this branch are unchanged.
4. Print a single-line migration pointer at the bottom of the text
output so operators who expected usage know where it went
(`openclaw status` / `openclaw models list`). This replaces what
used to be a block of fetched provider data with one static line,
so it cannot fail or add latency.
5. Update `docs/cli/channels.md` troubleshooting to remove the
`--no-usage` mention and point at the two new entry points.
6. Update tests: drop the `loadProviderUsageSummary` mock and the
`"keeps JSON output valid when usage loading fails"` case,
replace it with a positive assertion that `payload.usage` is
undefined (locking in the narrower contract), and remove `usage`
from every `channelsListCommand(...)` call to match the narrowed
`ChannelsListOptions` type. The route-args test is updated to
expect `{ json, all }` without `usage`.
No other command changes. `openclaw status` and `openclaw models list`
already render usage; they are the documented replacements.
Breaking-ish surface:
- CLI: `channels list --no-usage` now fails with "unknown option".
Tooling should drop the flag — there is nothing left to opt out of.
- JSON: `channels list --json` no longer carries a top-level `usage`
key. Tooling that read it must migrate to
`openclaw status --json` or `openclaw models list --json`.
…re to accept entry param
CI typecheck failed because the mock was declared with a zero-arg signature while one test called mockImplementation(({ entry }) => …). Tighten the generic so vitest's mock accepts the same params the real helper does.
09be1b4 to
5d7a70a
Compare
* feat(channels list): drop auth providers, add --all, surface installed/configured/enabled
`openclaw channels list` used to conflate two very different surfaces: chat
channels and OAuth/API-key auth providers for model routing. The auth
section was the first and most visible block in the output even for
operators who only cared about chat channels, and its JSON `auth` key
leaked model-provider identities into a command whose top-level help
describes it as channel management. Worse, the command silently hid
every channel that had no configured account, so users could not tell
from `channels list` which bundled or catalog channels were even
available to configure.
Split the surface cleanly around channels only:
1. Remove the `Auth providers (OAuth + API keys)` text section and the
`auth` field from the JSON payload. Model-provider auth profiles
remain reachable via `openclaw models auth list`, which is where
they conceptually belong.
2. Add a `--all` flag to surface every channel an operator could
configure: bundled channel plugins that have no account yet and
catalog-listed external channels whose plugin package is not even
installed on disk. Without `--all` the output still shows only
channels with at least one configured account, matching the
previous default behavior so existing scripts keep working. The
"empty" default path now prints a hint pointing at `--all`.
3. Render three explicit status tags per row — `installed` /
`not installed`, `configured` / `not configured`, `enabled` /
`disabled` — so bundled-but-unconfigured plugins and installable
catalog channels both render with accurate state instead of being
invisible. Installed state comes from the same
`isCatalogChannelInstalled` probe the setup flow uses, so it stays
consistent with `openclaw onboard` and `channels add`.
4. JSON payload now carries an `origin` per channel (`configured`,
`available`, `installable`) alongside `installed: boolean`, which
lets tooling distinguish "user has set this up" from "user could
set this up" without second-guessing.
Register `--all` on both the Commander CLI and the fast-path route-arg
parser so the flag works in both code paths, update the one routes
test that asserted the parsed args shape, and rewrite the old auth
profiles surface test as a broader `channels list` behavior spec
covering default output, `--all` output, JSON shape (no `auth`), and
the bundled-unconfigured + catalog-not-installed cases.
Docs: call out that `channels list` is chat-channel only now, mention
`--all`, and point at `openclaw models auth list` for what used to be
the auth providers block.
* fix(channels list): surface catalog channels that are installed on disk but not yet configured
The previous `--all` path filtered catalog entries with
`!installedByChannelId.get(entry.id)` before rendering them as
catalog-only rows. That assumed "catalog entry not already rendered
as a plugin row" implied "not installed", which is wrong: an external
channel plugin package can be installed on disk (`isCatalogChannelInstalled`
returns true) while the read-only channel loader still declines to
surface a plugin object for it — the loader only activates channels
that appear in user config, so a plugin that is installed but never
configured ended up in neither bucket and silently dropped out of
`channels list --all`.
Operator-facing symptom: `pnpm openclaw channels list --all` omitted
WeCom (and any other catalog channel in the same state) even though
its npm package was present on disk and its catalog entry existed,
while rendering every other uninstalled catalog channel as expected.
Fix: drop the `installed` filter from `catalogOnlyLines` so every
catalog entry that is not already represented by a plugin row is
rendered, and let the row itself carry the real installed/not-installed
tag. Two renderings now land in the catalog-only bucket:
- Not installed — rendered as `not installed, not configured, disabled`
(installable row).
- Installed but unconfigured — rendered as `installed, not configured,
disabled` (ready-to-configure row). The JSON `origin` for this case
becomes `available`, matching the existing origin for bundled
plugins that are installed but unconfigured, so downstream tooling
sees a consistent "you could configure this now" signal regardless
of whether the plugin came from bundled sources or from the catalog.
Regression test added under the WeCom scenario.
* refactor(channels list): drop model-provider usage surface, make the command channel-only
`openclaw channels list` used to append a model-provider usage/quota
snapshot (Anthropic, OpenRouter, OpenAI Codex, Gemini, Zai, Minimax,
etc.) under every invocation. That was a leftover from the days when
`channels list` was the only "operator overview" command; the same
data is now owned by `openclaw status` (overview) and
`openclaw models list` (per-provider), which handle timeouts, probe
errors, and output shape consistently for that class of data. Keeping
the snapshot wired into `channels list` meant:
- Every default invocation made one blocking `loadProviderUsageSummary`
call that fanned out to every configured provider billing/auth
endpoint, adding seconds of latency to a command that otherwise
just reads local config.
- `channels list --no-usage` was the escape hatch, but the flag was
itself a self-sustaining bug: it only existed because the command
did work that did not belong to it.
- JSON consumers had an optional `usage` key whose shape was owned by
the provider-usage module, not by the channels module, so any
change upstream silently reshaped `channels list --json` output.
- Failed provider fetches printed provider-side errors on a command
that never advertised itself as a provider-health surface.
Scope this PR tightens, in one move:
1. Remove `loadProviderUsageSummary` / `formatUsageReportLines` usage
from `src/commands/channels/list.ts`. The command now only reads
config, the read-only channel plugin registry, and the trusted
catalog — matching its name.
2. Drop `--no-usage` from the Commander CLI registration, from the
fast-path route-arg parser (`parseChannelsListRouteArgs`), and
from `ChannelsListOptions`. The flag is gone, not silently
ignored, so anyone depending on it will get a clear
"unknown option" from Commander and from the fast-path router.
3. Drop the `usage` key from `channels list --json` payloads. Shape
of the `chat` record and the new `origin` / `installed` tags
introduced earlier in this branch are unchanged.
4. Print a single-line migration pointer at the bottom of the text
output so operators who expected usage know where it went
(`openclaw status` / `openclaw models list`). This replaces what
used to be a block of fetched provider data with one static line,
so it cannot fail or add latency.
5. Update `docs/cli/channels.md` troubleshooting to remove the
`--no-usage` mention and point at the two new entry points.
6. Update tests: drop the `loadProviderUsageSummary` mock and the
`"keeps JSON output valid when usage loading fails"` case,
replace it with a positive assertion that `payload.usage` is
undefined (locking in the narrower contract), and remove `usage`
from every `channelsListCommand(...)` call to match the narrowed
`ChannelsListOptions` type. The route-args test is updated to
expect `{ json, all }` without `usage`.
No other command changes. `openclaw status` and `openclaw models list`
already render usage; they are the documented replacements.
Breaking-ish surface:
- CLI: `channels list --no-usage` now fails with "unknown option".
Tooling should drop the flag — there is nothing left to opt out of.
- JSON: `channels list --json` no longer carries a top-level `usage`
key. Tooling that read it must migrate to
`openclaw status --json` or `openclaw models list --json`.
* fix(channels.list.test): widen isCatalogChannelInstalled mock signature to accept entry param
CI typecheck failed because the mock was declared with a zero-arg signature while one test called mockImplementation(({ entry }) => …). Tighten the generic so vitest's mock accepts the same params the real helper does.
* changelog: record channels list channel-only rework (#78456)
* feat(channels list): drop auth providers, add --all, surface installed/configured/enabled
`openclaw channels list` used to conflate two very different surfaces: chat
channels and OAuth/API-key auth providers for model routing. The auth
section was the first and most visible block in the output even for
operators who only cared about chat channels, and its JSON `auth` key
leaked model-provider identities into a command whose top-level help
describes it as channel management. Worse, the command silently hid
every channel that had no configured account, so users could not tell
from `channels list` which bundled or catalog channels were even
available to configure.
Split the surface cleanly around channels only:
1. Remove the `Auth providers (OAuth + API keys)` text section and the
`auth` field from the JSON payload. Model-provider auth profiles
remain reachable via `openclaw models auth list`, which is where
they conceptually belong.
2. Add a `--all` flag to surface every channel an operator could
configure: bundled channel plugins that have no account yet and
catalog-listed external channels whose plugin package is not even
installed on disk. Without `--all` the output still shows only
channels with at least one configured account, matching the
previous default behavior so existing scripts keep working. The
"empty" default path now prints a hint pointing at `--all`.
3. Render three explicit status tags per row — `installed` /
`not installed`, `configured` / `not configured`, `enabled` /
`disabled` — so bundled-but-unconfigured plugins and installable
catalog channels both render with accurate state instead of being
invisible. Installed state comes from the same
`isCatalogChannelInstalled` probe the setup flow uses, so it stays
consistent with `openclaw onboard` and `channels add`.
4. JSON payload now carries an `origin` per channel (`configured`,
`available`, `installable`) alongside `installed: boolean`, which
lets tooling distinguish "user has set this up" from "user could
set this up" without second-guessing.
Register `--all` on both the Commander CLI and the fast-path route-arg
parser so the flag works in both code paths, update the one routes
test that asserted the parsed args shape, and rewrite the old auth
profiles surface test as a broader `channels list` behavior spec
covering default output, `--all` output, JSON shape (no `auth`), and
the bundled-unconfigured + catalog-not-installed cases.
Docs: call out that `channels list` is chat-channel only now, mention
`--all`, and point at `openclaw models auth list` for what used to be
the auth providers block.
* fix(channels list): surface catalog channels that are installed on disk but not yet configured
The previous `--all` path filtered catalog entries with
`!installedByChannelId.get(entry.id)` before rendering them as
catalog-only rows. That assumed "catalog entry not already rendered
as a plugin row" implied "not installed", which is wrong: an external
channel plugin package can be installed on disk (`isCatalogChannelInstalled`
returns true) while the read-only channel loader still declines to
surface a plugin object for it — the loader only activates channels
that appear in user config, so a plugin that is installed but never
configured ended up in neither bucket and silently dropped out of
`channels list --all`.
Operator-facing symptom: `pnpm openclaw channels list --all` omitted
WeCom (and any other catalog channel in the same state) even though
its npm package was present on disk and its catalog entry existed,
while rendering every other uninstalled catalog channel as expected.
Fix: drop the `installed` filter from `catalogOnlyLines` so every
catalog entry that is not already represented by a plugin row is
rendered, and let the row itself carry the real installed/not-installed
tag. Two renderings now land in the catalog-only bucket:
- Not installed — rendered as `not installed, not configured, disabled`
(installable row).
- Installed but unconfigured — rendered as `installed, not configured,
disabled` (ready-to-configure row). The JSON `origin` for this case
becomes `available`, matching the existing origin for bundled
plugins that are installed but unconfigured, so downstream tooling
sees a consistent "you could configure this now" signal regardless
of whether the plugin came from bundled sources or from the catalog.
Regression test added under the WeCom scenario.
* refactor(channels list): drop model-provider usage surface, make the command channel-only
`openclaw channels list` used to append a model-provider usage/quota
snapshot (Anthropic, OpenRouter, OpenAI Codex, Gemini, Zai, Minimax,
etc.) under every invocation. That was a leftover from the days when
`channels list` was the only "operator overview" command; the same
data is now owned by `openclaw status` (overview) and
`openclaw models list` (per-provider), which handle timeouts, probe
errors, and output shape consistently for that class of data. Keeping
the snapshot wired into `channels list` meant:
- Every default invocation made one blocking `loadProviderUsageSummary`
call that fanned out to every configured provider billing/auth
endpoint, adding seconds of latency to a command that otherwise
just reads local config.
- `channels list --no-usage` was the escape hatch, but the flag was
itself a self-sustaining bug: it only existed because the command
did work that did not belong to it.
- JSON consumers had an optional `usage` key whose shape was owned by
the provider-usage module, not by the channels module, so any
change upstream silently reshaped `channels list --json` output.
- Failed provider fetches printed provider-side errors on a command
that never advertised itself as a provider-health surface.
Scope this PR tightens, in one move:
1. Remove `loadProviderUsageSummary` / `formatUsageReportLines` usage
from `src/commands/channels/list.ts`. The command now only reads
config, the read-only channel plugin registry, and the trusted
catalog — matching its name.
2. Drop `--no-usage` from the Commander CLI registration, from the
fast-path route-arg parser (`parseChannelsListRouteArgs`), and
from `ChannelsListOptions`. The flag is gone, not silently
ignored, so anyone depending on it will get a clear
"unknown option" from Commander and from the fast-path router.
3. Drop the `usage` key from `channels list --json` payloads. Shape
of the `chat` record and the new `origin` / `installed` tags
introduced earlier in this branch are unchanged.
4. Print a single-line migration pointer at the bottom of the text
output so operators who expected usage know where it went
(`openclaw status` / `openclaw models list`). This replaces what
used to be a block of fetched provider data with one static line,
so it cannot fail or add latency.
5. Update `docs/cli/channels.md` troubleshooting to remove the
`--no-usage` mention and point at the two new entry points.
6. Update tests: drop the `loadProviderUsageSummary` mock and the
`"keeps JSON output valid when usage loading fails"` case,
replace it with a positive assertion that `payload.usage` is
undefined (locking in the narrower contract), and remove `usage`
from every `channelsListCommand(...)` call to match the narrowed
`ChannelsListOptions` type. The route-args test is updated to
expect `{ json, all }` without `usage`.
No other command changes. `openclaw status` and `openclaw models list`
already render usage; they are the documented replacements.
Breaking-ish surface:
- CLI: `channels list --no-usage` now fails with "unknown option".
Tooling should drop the flag — there is nothing left to opt out of.
- JSON: `channels list --json` no longer carries a top-level `usage`
key. Tooling that read it must migrate to
`openclaw status --json` or `openclaw models list --json`.
* fix(channels.list.test): widen isCatalogChannelInstalled mock signature to accept entry param
CI typecheck failed because the mock was declared with a zero-arg signature while one test called mockImplementation(({ entry }) => …). Tighten the generic so vitest's mock accepts the same params the real helper does.
* changelog: record channels list channel-only rework (openclaw#78456)
* feat(channels list): drop auth providers, add --all, surface installed/configured/enabled
`openclaw channels list` used to conflate two very different surfaces: chat
channels and OAuth/API-key auth providers for model routing. The auth
section was the first and most visible block in the output even for
operators who only cared about chat channels, and its JSON `auth` key
leaked model-provider identities into a command whose top-level help
describes it as channel management. Worse, the command silently hid
every channel that had no configured account, so users could not tell
from `channels list` which bundled or catalog channels were even
available to configure.
Split the surface cleanly around channels only:
1. Remove the `Auth providers (OAuth + API keys)` text section and the
`auth` field from the JSON payload. Model-provider auth profiles
remain reachable via `openclaw models auth list`, which is where
they conceptually belong.
2. Add a `--all` flag to surface every channel an operator could
configure: bundled channel plugins that have no account yet and
catalog-listed external channels whose plugin package is not even
installed on disk. Without `--all` the output still shows only
channels with at least one configured account, matching the
previous default behavior so existing scripts keep working. The
"empty" default path now prints a hint pointing at `--all`.
3. Render three explicit status tags per row — `installed` /
`not installed`, `configured` / `not configured`, `enabled` /
`disabled` — so bundled-but-unconfigured plugins and installable
catalog channels both render with accurate state instead of being
invisible. Installed state comes from the same
`isCatalogChannelInstalled` probe the setup flow uses, so it stays
consistent with `openclaw onboard` and `channels add`.
4. JSON payload now carries an `origin` per channel (`configured`,
`available`, `installable`) alongside `installed: boolean`, which
lets tooling distinguish "user has set this up" from "user could
set this up" without second-guessing.
Register `--all` on both the Commander CLI and the fast-path route-arg
parser so the flag works in both code paths, update the one routes
test that asserted the parsed args shape, and rewrite the old auth
profiles surface test as a broader `channels list` behavior spec
covering default output, `--all` output, JSON shape (no `auth`), and
the bundled-unconfigured + catalog-not-installed cases.
Docs: call out that `channels list` is chat-channel only now, mention
`--all`, and point at `openclaw models auth list` for what used to be
the auth providers block.
* fix(channels list): surface catalog channels that are installed on disk but not yet configured
The previous `--all` path filtered catalog entries with
`!installedByChannelId.get(entry.id)` before rendering them as
catalog-only rows. That assumed "catalog entry not already rendered
as a plugin row" implied "not installed", which is wrong: an external
channel plugin package can be installed on disk (`isCatalogChannelInstalled`
returns true) while the read-only channel loader still declines to
surface a plugin object for it — the loader only activates channels
that appear in user config, so a plugin that is installed but never
configured ended up in neither bucket and silently dropped out of
`channels list --all`.
Operator-facing symptom: `pnpm openclaw channels list --all` omitted
WeCom (and any other catalog channel in the same state) even though
its npm package was present on disk and its catalog entry existed,
while rendering every other uninstalled catalog channel as expected.
Fix: drop the `installed` filter from `catalogOnlyLines` so every
catalog entry that is not already represented by a plugin row is
rendered, and let the row itself carry the real installed/not-installed
tag. Two renderings now land in the catalog-only bucket:
- Not installed — rendered as `not installed, not configured, disabled`
(installable row).
- Installed but unconfigured — rendered as `installed, not configured,
disabled` (ready-to-configure row). The JSON `origin` for this case
becomes `available`, matching the existing origin for bundled
plugins that are installed but unconfigured, so downstream tooling
sees a consistent "you could configure this now" signal regardless
of whether the plugin came from bundled sources or from the catalog.
Regression test added under the WeCom scenario.
* refactor(channels list): drop model-provider usage surface, make the command channel-only
`openclaw channels list` used to append a model-provider usage/quota
snapshot (Anthropic, OpenRouter, OpenAI Codex, Gemini, Zai, Minimax,
etc.) under every invocation. That was a leftover from the days when
`channels list` was the only "operator overview" command; the same
data is now owned by `openclaw status` (overview) and
`openclaw models list` (per-provider), which handle timeouts, probe
errors, and output shape consistently for that class of data. Keeping
the snapshot wired into `channels list` meant:
- Every default invocation made one blocking `loadProviderUsageSummary`
call that fanned out to every configured provider billing/auth
endpoint, adding seconds of latency to a command that otherwise
just reads local config.
- `channels list --no-usage` was the escape hatch, but the flag was
itself a self-sustaining bug: it only existed because the command
did work that did not belong to it.
- JSON consumers had an optional `usage` key whose shape was owned by
the provider-usage module, not by the channels module, so any
change upstream silently reshaped `channels list --json` output.
- Failed provider fetches printed provider-side errors on a command
that never advertised itself as a provider-health surface.
Scope this PR tightens, in one move:
1. Remove `loadProviderUsageSummary` / `formatUsageReportLines` usage
from `src/commands/channels/list.ts`. The command now only reads
config, the read-only channel plugin registry, and the trusted
catalog — matching its name.
2. Drop `--no-usage` from the Commander CLI registration, from the
fast-path route-arg parser (`parseChannelsListRouteArgs`), and
from `ChannelsListOptions`. The flag is gone, not silently
ignored, so anyone depending on it will get a clear
"unknown option" from Commander and from the fast-path router.
3. Drop the `usage` key from `channels list --json` payloads. Shape
of the `chat` record and the new `origin` / `installed` tags
introduced earlier in this branch are unchanged.
4. Print a single-line migration pointer at the bottom of the text
output so operators who expected usage know where it went
(`openclaw status` / `openclaw models list`). This replaces what
used to be a block of fetched provider data with one static line,
so it cannot fail or add latency.
5. Update `docs/cli/channels.md` troubleshooting to remove the
`--no-usage` mention and point at the two new entry points.
6. Update tests: drop the `loadProviderUsageSummary` mock and the
`"keeps JSON output valid when usage loading fails"` case,
replace it with a positive assertion that `payload.usage` is
undefined (locking in the narrower contract), and remove `usage`
from every `channelsListCommand(...)` call to match the narrowed
`ChannelsListOptions` type. The route-args test is updated to
expect `{ json, all }` without `usage`.
No other command changes. `openclaw status` and `openclaw models list`
already render usage; they are the documented replacements.
Breaking-ish surface:
- CLI: `channels list --no-usage` now fails with "unknown option".
Tooling should drop the flag — there is nothing left to opt out of.
- JSON: `channels list --json` no longer carries a top-level `usage`
key. Tooling that read it must migrate to
`openclaw status --json` or `openclaw models list --json`.
* fix(channels.list.test): widen isCatalogChannelInstalled mock signature to accept entry param
CI typecheck failed because the mock was declared with a zero-arg signature while one test called mockImplementation(({ entry }) => …). Tighten the generic so vitest's mock accepts the same params the real helper does.
* changelog: record channels list channel-only rework (openclaw#78456)





Summary
openclaw channels listwas a catch-all: it mixed chat channels with model-provider auth profiles (OAuth + API keys) and a model-provider usage/quota snapshot, silently hid every channel without a configured account, and — even with a hypothetical--all— offered no way to discover the full set of channels an operator could configure. External catalog channels that happened to be installed on disk but not yet configured (e.g. WeCom after an experimental install) also disappeared from the listing because the read-only channel loader only activates channels that appear in user config.channelsis the CLI contract for discovering, configuring, and operating chat integrations. Operators kept askingchannels add/channels loginfor a channel they had never heard of becausechannels listrefused to advertise it until a config entry already existed; scripts were pulling a staleauthfield out of the JSON payload that actually belonged to model-provider auth (reachable fromopenclaw models auth list); and every defaultchannels listcall fanned out to every configured model provider's billing/auth endpoint before printing anything, so a command that should be pure-local routinely added seconds of network latency and printed provider fetch errors under a channel-management heading.Auth providers (OAuth + API keys)text section and theauthfield from the JSON payload. Model-provider auth now stays inopenclaw models auth list, where it conceptually belongs.loadProviderUsageSummaryfan-out), the--no-usageflag, and theusagefield from the JSON payload. Usage is now owned byopenclaw status(overview) andopenclaw models list(per-provider), which already render it consistently. The text output ends with a single static line pointing at those two commands so operators who expected usage know where it went.--allflag. Default output is unchanged (still only configured accounts).--alladditionally surfaces bundled channels without a configured account, catalog channels that are not yet installed on disk, and catalog channels that are installed on disk but not yet configured.installed/not installed,configured/not configured,enabled/disabled— sourced from the sameisCatalogChannelInstalledprobe used byopenclaw onboardandchannels add, so tags stay consistent across commands.origintag per channel (configured/available/installable) alongside aninstalled: booleanfield so tooling can distinguish "user has set this up" from "user could set this up" without inference.channels listrow layout for configured channels (same per-row label, same account-id formatting, sametoken/bot/app/base/linkedbits when present, same empty-state behavior except for a hint pointing at--alland the new one-line migration pointer replacing the former usage block).channels add/channels login/channels logout/channels status/channels capabilities/channels resolve/channels removebehavior.listReadOnlyChannelPluginsForConfig), the catalog loader (listTrustedChannelPluginCatalogEntries), the installed-channel probe (isCatalogChannelInstalled), and the provider usage module (loadProviderUsageSummary/formatUsageReportLines). Onlychannels liststops consuming the latter;openclaw status/openclaw models liststill do.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Regression / clean-up context:
channels list --alleven though@wecom/wecom-openclaw-pluginwas installed on disk.channels listwas the only operator overview surface, and both are already owned by dedicated commands (openclaw models auth list/openclaw status/openclaw models list).Real behavior proof (required for external PRs)
openclaw channels listconflated chat channels with model-provider auth profiles and a per-provider usage fetch, hid every channel without a configured account, and even with--allomitted catalog channels whose npm package was already on disk but had no config entry yet (WeCom-class regression).7a45871ef5, rebased on latestorigin/main) on Linux, against~/.openclaw/openclaw.jsoncontaining only a configuredqqbotchannel entry, nowecomconfig entry, and@wecom/wecom-openclaw-plugininstalled on disk from a previous experimental install.pnpm openclaw channels listpnpm openclaw channels list --allpnpm openclaw channels list --json/pnpm openclaw channels list --all --jsonpnpm openclaw channels list --no-usage— expect "unknown option" (flag removed).src/commands/channels.list.test.ts— 9 tests pass (WeCom catalog-installed-but-unconfigured regression is the 9th).--allnow lists bundled-unconfigured + catalog-not-installed + catalog-installed-but-unconfigured rows, each with correctinstalled/configured/enabledtags.channels add/channels login— out of scope forchannels list.Root Cause (if applicable)
channels listwas originally the only "operator overview" command and absorbed model-provider auth profiles beforeopenclaw models auth listexisted. It was never revisited aftermodels auth listlanded.--no-usageescape hatch whose only purpose was to turn off work that did not belong to the command.openclaw statusandopenclaw models listalready own usage; thechannels listcode path was carrying them for no reason.listReadOnlyChannelPluginsForConfig(cfg)and skipped any plugin whoselistAccountIds(cfg)was empty. There was no second pass over the catalog, so a channel that was installable-but-unconfigured or installed-but-unconfigured never appeared in the listing.--all: the--allcatalog-only pass filtered with!installedByChannelId.get(entry.id), assuming "not already rendered as a plugin row → not installed". That assumption is wrong for catalog plugins whose npm package is on disk but whose config entry is absent: the read-only channel loader activates channels only if they appear in user config, so the plugin object is never surfaced, yet the manifest-based install probe reportsinstalled: true. Both pieces were needed to drop the row.--all. The auth-profiles test actively asserted the auth section was present, which is why dropping it required a rewrite of that spec. There was also no test asserting thatchannels listdoes not fetch provider usage; the command's boundary was only enforced by convention.pluginId=wecom-openclaw-plugin,channelId=wecom), so any implementation that keys on pluginId rather than channelId is additionally prone to mismatch here. The fix keys everything on channelId, matching catalog entryid.Regression Test Plan (if applicable)
src/commands/channels.list.test.ts(new; replaces the narrowerchannels.list.auth-profiles.test.ts).payload.usageis undefined in JSON.--allsurfaces uninstalled catalog channels withnot installed, not configured, disabled.--allsurfaces bundled-unconfigured plugins withinstalled, not configured, disabled.--allsurfaces catalog channels installed on disk but unconfigured withinstalled, not configured, disabled(WeCom-class regression).origincorrectly mapsconfigured/available/installableacross all three states.channels listis a pure formatter over(cfg, read-only-plugins, catalog, installed-probe). Mocking those four seams produces deterministic output and directly covers the discovery logic the bug lived in, without requiring a live catalog fetch or a real npm install.channels.list.auth-profiles.test.tsasserted auth section presence, which was the opposite invariant.User-visible / Behavior Changes
openclaw channels listtext output no longer prints theAuth providers (OAuth + API keys):block or the model-provider usage/quota snapshot. Operators who previously scannedchannels listfor provider auth must useopenclaw models auth list; those who wanted usage must useopenclaw status(overview) oropenclaw models list(per-provider). The command prints one static migration line at the bottom pointing at the replacement entry points.openclaw channels list --jsonno longer includes theauthorusagefields. Tooling that read them must migrate toopenclaw models auth list --jsonandopenclaw status --json/openclaw models list --jsonrespectively.--no-usageis gone. Runningopenclaw channels list --no-usagenow errors with "unknown option"; there is nothing left to opt out of, and the provider usage fetch no longer happens at all on this path.--allflag onopenclaw channels list. Absent it, the default output is unchanged (modulo the auth-providers / usage /--no-usageremovals) and still shows only channels with a configured account.installed | not installed,configured | not configured,enabled | disabled. Existingtoken=/bot=/app=/base=/linkedbits are preserved when present.{ accounts: string[], installed: boolean, origin: "configured" | "available" | "installable" }. Previously the value was a barestring[]of account ids.openclaw channels list --all.Diagram (if applicable)
Security Impact (required)
NoNo(removing the auth-providers section only stops rendering provider ids that were already rendered; no secret handling changes, no new reads, no new writes)Yes — strictly fewer.channels listno longer fans out to model-provider billing/auth endpoints vialoadProviderUsageSummary. Every network call in the previous path is gone.openclaw statusandopenclaw models liststill make the same calls they already made.NoYes — strictly narrower.channels listno longer readsloadAuthProfileStoreWithoutExternalProfilesand no longer reads provider-usage state. Catalog entries and the installed-probe it now reads were already readable by otherchannelssubcommands.Repro + Verification
Environment
scripts/run-node.mjsdispatchqqbot(configured),@wecom/wecom-openclaw-plugin(installed but no config entry), plus the unmodified bundled channel set{ "channels": { "qqbot": { "accounts": { "default": { } } } }, "plugins": { "entries": { "openrouter": { }, "qqbot": { } } } }Steps
pnpm build(or install from the published build).pnpm openclaw channels list— expect only configuredQQ Bot default: installed, configured, enabled, noAuth providersblock, no model-provider usage block, one static "Model provider usage moved out ofchannels list— seeopenclaw statusoropenclaw models list." line at the bottom.pnpm openclaw channels list --all— expectQQ Bot,WeCom(installed, not configured, disabled), and every bundled / catalog entry with correct status tags.pnpm openclaw channels list --all --json— expectchat.qqbot.origin === "configured",chat.wecom.origin === "available",chat.telegram.origin === "installable"; expect noauthkey and nousagekey.pnpm openclaw channels list --no-usage— expect Commander error "unknown option '--no-usage'".Expected
--allincludes every catalog + bundled channel with accurateinstalled/configured/enabledtags, including WeCom.originandinstalledfor every rendered channel; noauthorusagefields.--no-usageis rejected; the command no longer accepts it.Actual
Evidence
channels.list.auth-profiles.test.ts→channels.list.test.ts; new WeCom regression test added; 9 passed / 9).Human Verification (required)
pnpm openclaw channels list,channels list --all, and their--jsoncounterparts against a local config that hasqqbotconfigured and@wecom/wecom-openclaw-plugininstalled but unconfigured; confirmed WeCom now appears withinstalled, not configured, disabledand both the auth and usage sections are gone.channels list --no-usagenow errors with "unknown option" instead of silently no-op'ing.--all) output's configured-row shape is unchanged; only the blocks under it changed.--allJSON mapsorigincorrectly acrossconfigured/available/installableand that noauth/usagekeys appear.9 passed / 9 (25 ms). Ran the route-args test impacted by the newallflag and the removed--no-usage:33 passed / 33 (40 ms).plugins.entries.<id>.enabled: false/channels.<id>.enabled: false) still render withdisabledtag under--allinstead of being silently dropped.--all, not a blank "Chat channels:" header.channels listat all; there is no fetch on this path to fail.--allon deployments with dozens of installed catalog plugins (tested set was ~25 entries).openclaw statusandopenclaw models listusage rendering — unchanged by this PR and covered by their own tests.Review Conversations
Compatibility / Migration
Mostly — default text row shape for configured channels is compatible; the dropped sections (auth, usage) and the removed --no-usage flag are not.NoYes, for CLI flag users and JSON consumersopenclaw channels list --no-usage: drop the flag. The provider-usage fetch is gone, so there is nothing to skip.openclaw channels list --json'sauthfield: switch toopenclaw models auth list --json. Same profile set, owned by the correct command.openclaw channels list --json'susagefield: switch toopenclaw status --json(overview) oropenclaw models list --json(per-provider). Both already expose usage in the same provider-usage format.openclaw channels list --json'schat.<id>as astring[]: update to consumechat.<id>.accounts(same data, new nesting). The new object also exposesinstalled: booleanandorigin: "configured" | "available" | "installable"which you can use to filter.Chat channels:block; the- <label> <accountId>:row prefix and docs-link footer are unchanged.Risks and Mitigations
openclaw channels list --no-usagein scripts will fail with "unknown option".authfield, theusagefield, or onchat.<id>being astring[]will break on upgrade.openclaw models auth list --json/openclaw status --json/openclaw models list --json). The shape change keeps field names stable where they overlap (accountsstill holds the same string list under the new object).--allperforms one extraisCatalogChannelInstalledcheck per catalog entry, which does a manifest scan. On deployments with very large catalogs this could add noticeable latency tochannels list --all.channels listwithout--all) path is unchanged and still runs zero extra catalog work.--allis explicit operator opt-in. The extra call reuses the existing manifest snapshot helper, so there is no additional filesystem traversal beyond whatopenclaw onboardandopenclaw channels addalready perform in their steady state. Net effect on the default path is strictly less work than before, because the usage fan-out is gone.channels listwill not immediately realize it moved.channels listprints a single static migration pointer at the bottom of every run ("Model provider usage moved out ofchannels list— seeopenclaw statusoropenclaw models list.").docs/cli/channels.mdwas updated to spell out both migrations in the common-commands section and the troubleshooting section. Both removed sections were header blocks, so their disappearance is obvious on first run; no silent drop.