From d7f75ee08749d961277e2275a1880f84b352df92 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 5 Apr 2026 08:22:27 +0100 Subject: [PATCH] refactor: hide qa channels with exposure metadata --- docs/plugins/architecture.md | 5 +- docs/plugins/sdk-setup.md | 16 +++++- src/channels/chat-meta-shared.ts | 6 +-- src/channels/plugins/catalog.ts | 6 +-- src/channels/plugins/exposure.ts | 29 +++++++++++ src/channels/plugins/types.core.ts | 8 +++ src/commands/agents.providers.ts | 3 +- src/commands/channel-setup/discovery.test.ts | 52 +++++++++++++++++++- src/commands/channel-setup/discovery.ts | 17 +++++-- src/commands/channels/list.ts | 3 +- src/commands/configure.channels.ts | 2 + src/commands/onboard-channels.e2e.test.ts | 45 +++++++++++++++++ src/flows/channel-setup.status.ts | 40 ++++++++++----- src/flows/channel-setup.ts | 25 +++++++--- src/plugin-sdk/core.ts | 6 +-- src/plugins/manifest.ts | 6 +++ 16 files changed, 230 insertions(+), 39 deletions(-) create mode 100644 src/channels/plugins/exposure.ts diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md index 402e64db12d..6429caa7864 100644 --- a/docs/plugins/architecture.md +++ b/docs/plugins/architecture.md @@ -1454,7 +1454,10 @@ Useful `openclaw.channel` fields beyond the minimal example: - `preferOver`: lower-priority plugin/channel ids this catalog entry should outrank - `selectionDocsPrefix`, `selectionDocsOmitLabel`, `selectionExtras`: selection-surface copy controls - `markdownCapable`: marks the channel as markdown-capable for outbound formatting decisions -- `showConfigured`: hide the channel from configured-channel listing surfaces when set to `false` +- `exposure.configured`: hide the channel from configured-channel listing surfaces when set to `false` +- `exposure.setup`: hide the channel from interactive setup/configure pickers when set to `false` +- `exposure.docs`: mark the channel as internal/private for docs navigation surfaces +- `showConfigured` / `showInSetup`: legacy aliases still accepted for compatibility; prefer `exposure` - `quickstartAllowFrom`: opt the channel into the standard quickstart `allowFrom` flow - `forceAccountBinding`: require explicit account binding even when only one account exists - `preferSessionLookupForAnnounceTarget`: prefer session lookup when resolving announce targets diff --git a/docs/plugins/sdk-setup.md b/docs/plugins/sdk-setup.md index c38b66cd56c..33cfbf6f5b9 100644 --- a/docs/plugins/sdk-setup.md +++ b/docs/plugins/sdk-setup.md @@ -101,7 +101,7 @@ surfaces before runtime loads. | `selectionDocsOmitLabel` | `boolean` | Show the docs path directly instead of a labeled docs link in selection copy. | | `selectionExtras` | `string[]` | Extra short strings appended in selection copy. | | `markdownCapable` | `boolean` | Marks the channel as markdown-capable for outbound formatting decisions. | -| `showConfigured` | `boolean` | Controls whether configured-channel listing surfaces show this channel. | +| `exposure` | `object` | Channel visibility controls for setup, configured lists, and docs surfaces. | | `quickstartAllowFrom` | `boolean` | Opt this channel into the standard quickstart `allowFrom` setup flow. | | `forceAccountBinding` | `boolean` | Require explicit account binding even when only one account exists. | | `preferSessionLookupForAnnounceTarget` | `boolean` | Prefer session lookup when resolving announce targets for this channel. | @@ -125,12 +125,26 @@ Example: "selectionDocsPrefix": "Guide:", "selectionExtras": ["Markdown"], "markdownCapable": true, + "exposure": { + "configured": true, + "setup": true, + "docs": true + }, "quickstartAllowFrom": true } } } ``` +`exposure` supports: + +- `configured`: include the channel in configured/status-style listing surfaces +- `setup`: include the channel in interactive setup/configure pickers +- `docs`: mark the channel as public-facing in docs/navigation surfaces + +`showConfigured` and `showInSetup` remain supported as legacy aliases. Prefer +`exposure`. + ### `openclaw.install` `openclaw.install` is package metadata, not manifest metadata. diff --git a/src/channels/chat-meta-shared.ts b/src/channels/chat-meta-shared.ts index 22502a0f688..2ef5e6b9510 100644 --- a/src/channels/chat-meta-shared.ts +++ b/src/channels/chat-meta-shared.ts @@ -1,6 +1,7 @@ import { listChannelCatalogEntries } from "../plugins/channel-catalog-registry.js"; import type { PluginPackageChannel } from "../plugins/manifest.js"; import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; +import { resolveChannelExposure } from "./plugins/exposure.js"; import type { ChannelMeta } from "./plugins/types.js"; export type ChatChannelMeta = ChannelMeta; @@ -15,6 +16,7 @@ function toChatChannelMeta(params: { if (!label) { throw new Error(`Missing label for bundled chat channel "${params.id}"`); } + const exposure = resolveChannelExposure(params.channel); return { id: params.id, @@ -43,9 +45,7 @@ function toChatChannelMeta(params: { ...(params.channel.markdownCapable !== undefined ? { markdownCapable: params.channel.markdownCapable } : {}), - ...(params.channel.showConfigured !== undefined - ? { showConfigured: params.channel.showConfigured } - : {}), + exposure, ...(params.channel.quickstartAllowFrom !== undefined ? { quickstartAllowFrom: params.channel.quickstartAllowFrom } : {}), diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index 009efb6c3ea..1ec08d73ea0 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -7,6 +7,7 @@ import type { OpenClawPackageManifest } from "../../plugins/manifest.js"; import type { PluginPackageChannel, PluginPackageInstall } from "../../plugins/manifest.js"; import type { PluginOrigin } from "../../plugins/types.js"; import { isRecord, resolveConfigDir, resolveUserPath } from "../../utils.js"; +import { resolveChannelExposure } from "./exposure.js"; import type { ChannelMeta } from "./types.js"; export type ChannelUiMetaEntry = { @@ -180,6 +181,7 @@ function toChannelMeta(params: { const docsPath = params.channel.docsPath?.trim() || `/channels/${params.id}`; const blurb = params.channel.blurb?.trim() || ""; const systemImage = params.channel.systemImage?.trim(); + const exposure = resolveChannelExposure(params.channel); return { id: params.id, @@ -203,9 +205,7 @@ function toChannelMeta(params: { ...(params.channel.markdownCapable !== undefined ? { markdownCapable: params.channel.markdownCapable } : {}), - ...(params.channel.showConfigured !== undefined - ? { showConfigured: params.channel.showConfigured } - : {}), + exposure, ...(params.channel.quickstartAllowFrom !== undefined ? { quickstartAllowFrom: params.channel.quickstartAllowFrom } : {}), diff --git a/src/channels/plugins/exposure.ts b/src/channels/plugins/exposure.ts new file mode 100644 index 00000000000..30374b644e4 --- /dev/null +++ b/src/channels/plugins/exposure.ts @@ -0,0 +1,29 @@ +import type { ChannelMeta } from "./types.js"; + +export function resolveChannelExposure( + meta: Pick, +) { + return { + configured: meta.exposure?.configured ?? meta.showConfigured ?? true, + setup: meta.exposure?.setup ?? meta.showInSetup ?? true, + docs: meta.exposure?.docs ?? true, + }; +} + +export function isChannelVisibleInConfiguredLists( + meta: Pick, +): boolean { + return resolveChannelExposure(meta).configured; +} + +export function isChannelVisibleInSetup( + meta: Pick, +): boolean { + return resolveChannelExposure(meta).setup; +} + +export function isChannelVisibleInDocs( + meta: Pick, +): boolean { + return resolveChannelExposure(meta).docs; +} diff --git a/src/channels/plugins/types.core.ts b/src/channels/plugins/types.core.ts index 3dd77dbd3a1..f2fa2160f05 100644 --- a/src/channels/plugins/types.core.ts +++ b/src/channels/plugins/types.core.ts @@ -14,6 +14,12 @@ import type { ChannelMessageCapability } from "./message-capabilities.js"; export type ChannelId = ChatChannelId | (string & {}); +export type ChannelExposure = { + configured?: boolean; + setup?: boolean; + docs?: boolean; +}; + export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat"; /** Agent tool registered by a channel plugin. */ @@ -147,7 +153,9 @@ export type ChannelMeta = { detailLabel?: string; systemImage?: string; markdownCapable?: boolean; + exposure?: ChannelExposure; showConfigured?: boolean; + showInSetup?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; preferSessionLookupForAnnounceTarget?: boolean; diff --git a/src/commands/agents.providers.ts b/src/commands/agents.providers.ts index 0d53a62051e..17167a2363f 100644 --- a/src/commands/agents.providers.ts +++ b/src/commands/agents.providers.ts @@ -1,3 +1,4 @@ +import { isChannelVisibleInConfiguredLists } from "../channels/plugins/exposure.js"; import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js"; import { getChannelPlugin, @@ -104,7 +105,7 @@ function shouldShowProviderEntry(entry: ProviderAccountStatus, cfg: OpenClawConf if (!plugin) { return Boolean(entry.configured); } - if (plugin.meta.showConfigured === false) { + if (!isChannelVisibleInConfiguredLists(plugin.meta)) { const providerConfig = (cfg as Record)[plugin.id]; return Boolean(entry.configured) || Boolean(providerConfig); } diff --git a/src/commands/channel-setup/discovery.test.ts b/src/commands/channel-setup/discovery.test.ts index f8f6014cf3e..1550b6d2ef5 100644 --- a/src/commands/channel-setup/discovery.test.ts +++ b/src/commands/channel-setup/discovery.test.ts @@ -2,6 +2,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PluginAutoEnableResult } from "../../config/plugin-auto-enable.js"; const loadPluginManifestRegistry = vi.hoisted(() => vi.fn()); +const listChannelPluginCatalogEntries = vi.hoisted(() => vi.fn((): unknown[] => [])); +const listChatChannels = vi.hoisted(() => vi.fn((): Array> => [])); const applyPluginAutoEnable = vi.hoisted(() => vi.fn<(args: { config: unknown; env?: NodeJS.ProcessEnv }) => PluginAutoEnableResult>( ({ config }) => ({ @@ -21,11 +23,24 @@ vi.mock("../../config/plugin-auto-enable.js", () => ({ applyPluginAutoEnable(args as { config: unknown; env?: NodeJS.ProcessEnv }), })); -import { listManifestInstalledChannelIds } from "./discovery.js"; +vi.mock("../../channels/plugins/catalog.js", () => ({ + listChannelPluginCatalogEntries: (_args?: unknown) => listChannelPluginCatalogEntries(), +})); + +vi.mock("../../channels/registry.js", () => ({ + listChatChannels: () => listChatChannels(), +})); + +import { listManifestInstalledChannelIds, resolveChannelSetupEntries } from "./discovery.js"; describe("listManifestInstalledChannelIds", () => { beforeEach(() => { - loadPluginManifestRegistry.mockReset(); + loadPluginManifestRegistry.mockReset().mockReturnValue({ + plugins: [], + diagnostics: [], + }); + listChannelPluginCatalogEntries.mockReset().mockReturnValue([]); + listChatChannels.mockReset().mockReturnValue([]); applyPluginAutoEnable.mockReset().mockImplementation(({ config }) => ({ config: config as never, changes: [] as string[], @@ -68,4 +83,37 @@ describe("listManifestInstalledChannelIds", () => { }); expect(installedIds).toEqual(new Set(["slack"])); }); + + it("filters channels hidden from setup out of interactive entries", () => { + listChatChannels.mockReturnValue([ + { + id: "telegram", + label: "Telegram", + selectionLabel: "Telegram", + docsPath: "/channels/telegram", + blurb: "bot token", + }, + ]); + + const resolved = resolveChannelSetupEntries({ + cfg: {} as never, + installedPlugins: [ + { + id: "qa-channel", + meta: { + id: "qa-channel", + label: "QA Channel", + selectionLabel: "QA Channel", + docsPath: "/channels/qa-channel", + blurb: "synthetic", + exposure: { setup: false }, + }, + } as never, + ], + workspaceDir: "/tmp/workspace", + env: { OPENCLAW_HOME: "/tmp/home" } as NodeJS.ProcessEnv, + }); + + expect(resolved.entries.map((entry) => entry.id)).toEqual(["telegram"]); + }); }); diff --git a/src/commands/channel-setup/discovery.ts b/src/commands/channel-setup/discovery.ts index ffbb137ebd4..1973f7d8178 100644 --- a/src/commands/channel-setup/discovery.ts +++ b/src/commands/channel-setup/discovery.ts @@ -3,6 +3,7 @@ import { listChannelPluginCatalogEntries, type ChannelPluginCatalogEntry, } from "../../channels/plugins/catalog.js"; +import { isChannelVisibleInSetup } from "../../channels/plugins/exposure.js"; import type { ChannelMeta, ChannelPlugin } from "../../channels/plugins/types.js"; import { listChatChannels } from "../../channels/registry.js"; import type { OpenClawConfig } from "../../config/config.js"; @@ -15,6 +16,12 @@ type ChannelCatalogEntry = { meta: ChannelMeta; }; +export function shouldShowChannelInSetup( + meta: Pick, +): boolean { + return isChannelVisibleInSetup(meta); +} + export type ResolvedChannelSetupEntries = { entries: ChannelCatalogEntry[]; installedCatalogEntries: ChannelPluginCatalogEntry[]; @@ -71,11 +78,15 @@ export function resolveChannelSetupEntries(params: { const catalogEntries = listChannelPluginCatalogEntries({ workspaceDir }); const installedCatalogEntries = catalogEntries.filter( (entry) => - !installedPluginIds.has(entry.id) && manifestInstalledIds.has(entry.id as ChannelChoice), + !installedPluginIds.has(entry.id) && + manifestInstalledIds.has(entry.id as ChannelChoice) && + shouldShowChannelInSetup(entry.meta), ); const installableCatalogEntries = catalogEntries.filter( (entry) => - !installedPluginIds.has(entry.id) && !manifestInstalledIds.has(entry.id as ChannelChoice), + !installedPluginIds.has(entry.id) && + !manifestInstalledIds.has(entry.id as ChannelChoice) && + shouldShowChannelInSetup(entry.meta), ); const metaById = new Map(); @@ -100,7 +111,7 @@ export function resolveChannelSetupEntries(params: { entries: Array.from(metaById, ([id, meta]) => ({ id: id as ChannelChoice, meta, - })), + })).filter((entry) => shouldShowChannelInSetup(entry.meta)), installedCatalogEntries, installableCatalogEntries, installedCatalogById: new Map( diff --git a/src/commands/channels/list.ts b/src/commands/channels/list.ts index e0c69809799..95227c18942 100644 --- a/src/commands/channels/list.ts +++ b/src/commands/channels/list.ts @@ -1,4 +1,5 @@ import { loadAuthProfileStore } from "../../agents/auth-profiles.js"; +import { isChannelVisibleInConfiguredLists } from "../../channels/plugins/exposure.js"; import { listChannelPlugins } from "../../channels/plugins/index.js"; import { buildChannelAccountSnapshot } from "../../channels/plugins/status.js"; import type { ChannelAccountSnapshot, ChannelPlugin } from "../../channels/plugins/types.js"; @@ -47,7 +48,7 @@ function formatLinked(value: boolean): string { } function shouldShowConfigured(channel: ChannelPlugin): boolean { - return channel.meta.showConfigured !== false; + return isChannelVisibleInConfiguredLists(channel.meta); } function formatAccountLine(params: { diff --git a/src/commands/configure.channels.ts b/src/commands/configure.channels.ts index 7170fa88eb0..664b7cd30e2 100644 --- a/src/commands/configure.channels.ts +++ b/src/commands/configure.channels.ts @@ -5,6 +5,7 @@ import { CONFIG_PATH } from "../config/config.js"; import type { RuntimeEnv } from "../runtime.js"; import { note } from "../terminal/note.js"; import { shortenHomePath } from "../utils.js"; +import { shouldShowChannelInSetup } from "./channel-setup/discovery.js"; import { confirm, select } from "./configure.shared.js"; import { guardCancel } from "./onboard-helpers.js"; @@ -17,6 +18,7 @@ export async function removeChannelConfigWizard( const listConfiguredChannels = () => listChannelPlugins() .map((plugin) => plugin.meta) + .filter((meta) => shouldShowChannelInSetup(meta)) .filter((meta) => next.channels?.[meta.id] !== undefined); while (true) { diff --git a/src/commands/onboard-channels.e2e.test.ts b/src/commands/onboard-channels.e2e.test.ts index 38c2ae030c2..058e3655eb4 100644 --- a/src/commands/onboard-channels.e2e.test.ts +++ b/src/commands/onboard-channels.e2e.test.ts @@ -818,6 +818,51 @@ describe("setupChannels", () => { expect(multiselect).not.toHaveBeenCalled(); }); + it("hides channels marked hidden from setup in the picker", async () => { + const qaChannelBase = createChannelTestPluginBase({ + id: "qa-channel", + label: "QA Channel", + docsPath: "/channels/qa-channel", + }); + setActivePluginRegistry( + createTestRegistry([ + { + pluginId: "qa-channel", + source: "test", + plugin: { + ...qaChannelBase, + meta: { + ...qaChannelBase.meta, + showInSetup: false, + }, + }, + }, + ]), + ); + + const select = vi.fn(async ({ message, options }: { message: string; options: unknown[] }) => { + if (message === "Select a channel") { + expect( + (options as Array<{ label?: string }>).some((option) => + option.label?.includes("QA Channel"), + ), + ).toBe(false); + } + return "__done__"; + }); + const { multiselect, text } = createUnexpectedPromptGuards(); + const prompter = createPrompter({ + select: select as unknown as WizardPrompter["select"], + multiselect, + text, + }); + + await runSetupChannels({} as OpenClawConfig, prompter); + + expect(select).toHaveBeenCalledWith(expect.objectContaining({ message: "Select a channel" })); + expect(multiselect).not.toHaveBeenCalled(); + }); + it("treats installed external plugin channels as installed without reinstall prompts", async () => { setActivePluginRegistry(createEmptyPluginRegistry()); catalogMocks.listChannelPluginCatalogEntries.mockReturnValue([createMSTeamsCatalogEntry()]); diff --git a/src/flows/channel-setup.status.ts b/src/flows/channel-setup.status.ts index 2680de3fe6d..0ee5abc5630 100644 --- a/src/flows/channel-setup.status.ts +++ b/src/flows/channel-setup.status.ts @@ -9,6 +9,7 @@ import { } from "../channels/registry.js"; import { formatCliCommand } from "../cli/command-format.js"; import { resolveChannelSetupEntries } from "../commands/channel-setup/discovery.js"; +import { shouldShowChannelInSetup } from "../commands/channel-setup/discovery.js"; import { resolveChannelSetupWizardAdapterForPlugin } from "../commands/channel-setup/registry.js"; import type { ChannelSetupWizardAdapter, @@ -79,6 +80,9 @@ export async function collectChannelStatus(params: { )); const statusEntries = await Promise.all( installedPlugins.flatMap((plugin) => { + if (!shouldShowChannelInSetup(plugin.meta)) { + return []; + } const adapter = resolveAdapter(plugin.id); if (!adapter) { return []; @@ -92,6 +96,7 @@ export async function collectChannelStatus(params: { ); const statusByChannel = new Map(statusEntries.map((entry) => [entry.channel, entry])); const fallbackStatuses = listChatChannels() + .filter((meta) => shouldShowChannelInSetup(meta)) .filter((meta) => !statusByChannel.has(meta.id)) .map((meta) => { const configured = isChannelConfigured(params.cfg, meta.id); @@ -235,22 +240,31 @@ export function resolveChannelSelectionNoteLines(params: { export function resolveChannelSetupSelectionContributions(params: { entries: Array<{ id: ChannelChoice; - meta: { id: string; label: string; selectionLabel?: string }; + meta: { + id: string; + label: string; + selectionLabel?: string; + exposure?: { setup?: boolean }; + showConfigured?: boolean; + showInSetup?: boolean; + }; }>; statusByChannel: Map; resolveDisabledHint: (channel: ChannelChoice) => string | undefined; }): ChannelSetupSelectionContribution[] { - return params.entries.map((entry) => { - const disabledHint = params.resolveDisabledHint(entry.id); - const hint = - [params.statusByChannel.get(entry.id)?.selectionHint, disabledHint] - .filter(Boolean) - .join(" · ") || undefined; - return buildChannelSetupSelectionContribution({ - channel: entry.id, - label: entry.meta.selectionLabel ?? entry.meta.label, - hint, - source: listChatChannels().some((channel) => channel.id === entry.id) ? "core" : "plugin", + return params.entries + .filter((entry) => shouldShowChannelInSetup(entry.meta)) + .map((entry) => { + const disabledHint = params.resolveDisabledHint(entry.id); + const hint = + [params.statusByChannel.get(entry.id)?.selectionHint, disabledHint] + .filter(Boolean) + .join(" · ") || undefined; + return buildChannelSetupSelectionContribution({ + channel: entry.id, + label: entry.meta.selectionLabel ?? entry.meta.label, + hint, + source: listChatChannels().some((channel) => channel.id === entry.id) ? "core" : "plugin", + }); }); - }); } diff --git a/src/flows/channel-setup.ts b/src/flows/channel-setup.ts index 73bffea8c1f..9937418251a 100644 --- a/src/flows/channel-setup.ts +++ b/src/flows/channel-setup.ts @@ -8,7 +8,10 @@ import { import type { ChannelSetupPlugin } from "../channels/plugins/setup-wizard-types.js"; import { listChatChannels } from "../channels/registry.js"; import { formatCliCommand } from "../cli/command-format.js"; -import { resolveChannelSetupEntries } from "../commands/channel-setup/discovery.js"; +import { + resolveChannelSetupEntries, + shouldShowChannelInSetup, +} from "../commands/channel-setup/discovery.js"; import { ensureChannelSetupPluginInstalled, loadChannelSetupPluginRegistrySnapshotForChannel, @@ -98,10 +101,14 @@ export async function setupChannels( const listVisibleInstalledPlugins = (): ChannelSetupPlugin[] => { const merged = new Map(); for (const plugin of listChannelSetupPlugins()) { - merged.set(plugin.id, plugin); + if (shouldShowChannelInSetup(plugin.meta)) { + merged.set(plugin.id, plugin); + } } for (const plugin of scopedPluginsById.values()) { - merged.set(plugin.id, plugin); + if (shouldShowChannelInSetup(plugin.meta)) { + merged.set(plugin.id, plugin); + } } return Array.from(merged.values()); }; @@ -181,11 +188,13 @@ export async function setupChannels( return cfg; } - const corePrimer = listChatChannels().map((meta) => ({ - id: meta.id, - label: meta.label, - blurb: meta.blurb, - })); + const corePrimer = listChatChannels() + .filter((meta) => shouldShowChannelInSetup(meta)) + .map((meta) => ({ + id: meta.id, + label: meta.label, + blurb: meta.blurb, + })); const coreIds = new Set(corePrimer.map((entry) => entry.id)); const primerChannels = [ ...corePrimer, diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index e5cc089c053..a81a11ddf46 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -1,5 +1,6 @@ import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "../channels/ids.js"; import { emptyChannelConfigSchema } from "../channels/plugins/config-schema.js"; +import { resolveChannelExposure } from "../channels/plugins/exposure.js"; import { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js"; import { createScopedAccountReplyToModeResolver, @@ -231,6 +232,7 @@ function toSdkChatChannelMeta(params: { if (!label) { throw new Error(`Missing label for bundled chat channel "${params.id}"`); } + const exposure = resolveChannelExposure(params.channel); return { id: params.id, label, @@ -258,9 +260,7 @@ function toSdkChatChannelMeta(params: { ...(params.channel.markdownCapable !== undefined ? { markdownCapable: params.channel.markdownCapable } : {}), - ...(params.channel.showConfigured !== undefined - ? { showConfigured: params.channel.showConfigured } - : {}), + exposure, ...(params.channel.quickstartAllowFrom !== undefined ? { quickstartAllowFrom: params.channel.quickstartAllowFrom } : {}), diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 6ee25b98278..086fa3f7373 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -428,7 +428,13 @@ export type PluginPackageChannel = { selectionDocsOmitLabel?: boolean; selectionExtras?: readonly string[]; markdownCapable?: boolean; + exposure?: { + configured?: boolean; + setup?: boolean; + docs?: boolean; + }; showConfigured?: boolean; + showInSetup?: boolean; quickstartAllowFrom?: boolean; forceAccountBinding?: boolean; preferSessionLookupForAnnounceTarget?: boolean;