From 5c8ea0a1750e12fed18ccb788d07ff1d00b5aa87 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Mar 2026 01:51:38 +0000 Subject: [PATCH] refactor: share channel setup status helpers --- docs/plugins/sdk-setup.md | 5 ++ extensions/bluebubbles/src/setup-surface.ts | 30 +++++------ extensions/discord/src/setup-core.ts | 6 ++- extensions/googlechat/src/setup-surface.ts | 13 ++--- extensions/irc/src/setup-surface.ts | 10 ++-- extensions/line/src/setup-surface.ts | 17 ++++--- extensions/mattermost/src/setup-surface.ts | 12 +++-- extensions/msteams/src/setup-surface.ts | 22 +++----- .../nextcloud-talk/src/setup-surface.ts | 14 ++++-- extensions/nostr/src/setup-surface.ts | 14 +++--- extensions/slack/src/setup-core.ts | 6 ++- extensions/synology-chat/src/setup-surface.ts | 12 ++--- extensions/telegram/src/setup-surface.ts | 6 ++- extensions/zalo/src/setup-surface.ts | 11 ++-- .../plugins/setup-wizard-helpers.test.ts | 41 +++++++++++++++ src/channels/plugins/setup-wizard-helpers.ts | 50 ++++++++++++++++++- src/plugin-sdk/setup-runtime.ts | 1 + src/plugin-sdk/setup.ts | 1 + 18 files changed, 188 insertions(+), 83 deletions(-) diff --git a/docs/plugins/sdk-setup.md b/docs/plugins/sdk-setup.md index 32524cd21e6..1df97530730 100644 --- a/docs/plugins/sdk-setup.md +++ b/docs/plugins/sdk-setup.md @@ -283,6 +283,11 @@ helpers from `openclaw/plugin-sdk/setup`: `createPromptParsedAllowFromForAccount `createTopLevelChannelParsedAllowFromPrompt(...)`, and `createNestedChannelParsedAllowFromPrompt(...)`. +For channel setup status blocks that only vary by labels, scores, and optional +extra lines, prefer `createStandardChannelSetupStatus(...)` from +`openclaw/plugin-sdk/setup` instead of hand-rolling the same `status` object in +each plugin. + For optional setup surfaces that should only appear in certain contexts, use `createOptionalChannelSetupSurface` from `openclaw/plugin-sdk/channel-setup`: diff --git a/extensions/bluebubbles/src/setup-surface.ts b/extensions/bluebubbles/src/setup-surface.ts index 1b2491d4c5d..f086331c284 100644 --- a/extensions/bluebubbles/src/setup-surface.ts +++ b/extensions/bluebubbles/src/setup-surface.ts @@ -1,6 +1,7 @@ import { createAllowFromSection, createPromptParsedAllowFromForAccount, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, type ChannelSetupDmPolicy, @@ -143,20 +144,21 @@ export const blueBubblesSetupWizard: ChannelSetupWizard = { channel, stepOrder: "text-first", status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - configuredHint: "configured", - unconfiguredHint: "iMessage via BlueBubbles app", - configuredScore: 1, - unconfiguredScore: 0, - resolveConfigured: ({ cfg }) => - listBlueBubblesAccountIds(cfg).some((accountId) => { - const account = resolveBlueBubblesAccount({ cfg, accountId }); - return account.configured; - }), - resolveStatusLines: ({ configured }) => [ - `BlueBubbles: ${configured ? "configured" : "needs setup"}`, - ], + ...createStandardChannelSetupStatus({ + channelLabel: "BlueBubbles", + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + configuredHint: "configured", + unconfiguredHint: "iMessage via BlueBubbles app", + configuredScore: 1, + unconfiguredScore: 0, + includeStatusLine: true, + resolveConfigured: ({ cfg }) => + listBlueBubblesAccountIds(cfg).some((accountId) => { + const account = resolveBlueBubblesAccount({ cfg, accountId }); + return account.configured; + }), + }), resolveSelectionHint: ({ configured }) => configured ? "configured" : "iMessage via BlueBubbles app", }, diff --git a/extensions/discord/src/setup-core.ts b/extensions/discord/src/setup-core.ts index e8bf6bb424d..30b682f7992 100644 --- a/extensions/discord/src/setup-core.ts +++ b/extensions/discord/src/setup-core.ts @@ -7,6 +7,7 @@ import type { ChannelSetupDmPolicy, ChannelSetupWizard, } from "openclaw/plugin-sdk/setup-runtime"; +import { createStandardChannelSetupStatus } from "openclaw/plugin-sdk/setup-runtime"; import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools"; import { inspectDiscordSetupAccount, @@ -99,7 +100,8 @@ export function createDiscordSetupWizardBase(handlers: { return { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Discord", configuredLabel: "configured", unconfiguredLabel: "needs token", configuredHint: "configured", @@ -111,7 +113,7 @@ export function createDiscordSetupWizardBase(handlers: { const account = inspectDiscordSetupAccount({ cfg, accountId }); return account.configured; }), - }, + }), credentials: [ { inputKey: "token", diff --git a/extensions/googlechat/src/setup-surface.ts b/extensions/googlechat/src/setup-surface.ts index f9247c96529..5d552eed2d7 100644 --- a/extensions/googlechat/src/setup-surface.ts +++ b/extensions/googlechat/src/setup-surface.ts @@ -2,6 +2,7 @@ import { applySetupAccountConfigPatch, createNestedChannelParsedAllowFromPrompt, createNestedChannelDmPolicy, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, mergeAllowFromEntries, @@ -51,22 +52,18 @@ export { googlechatSetupAdapter } from "./setup-core.js"; export const googlechatSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Google Chat", configuredLabel: "configured", unconfiguredLabel: "needs service account", configuredHint: "configured", unconfiguredHint: "needs auth", + includeStatusLine: true, resolveConfigured: ({ cfg }) => listGoogleChatAccountIds(cfg).some( (accountId) => resolveGoogleChatAccount({ cfg, accountId }).credentialSource !== "none", ), - resolveStatusLines: ({ cfg }) => { - const configured = listGoogleChatAccountIds(cfg).some( - (accountId) => resolveGoogleChatAccount({ cfg, accountId }).credentialSource !== "none", - ); - return [`Google Chat: ${configured ? "configured" : "needs service account"}`]; - }, - }, + }), introNote: { title: "Google Chat setup", lines: [ diff --git a/extensions/irc/src/setup-surface.ts b/extensions/irc/src/setup-surface.ts index 213b7c37a47..d1c1958da3c 100644 --- a/extensions/irc/src/setup-surface.ts +++ b/extensions/irc/src/setup-surface.ts @@ -3,6 +3,7 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing"; import { createAllowFromSection, createPromptParsedAllowFromForAccount, + createStandardChannelSetupStatus, setSetupChannelEnabled, } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup"; @@ -168,21 +169,20 @@ const ircDmPolicy: ChannelSetupDmPolicy = { export const ircSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "IRC", configuredLabel: "configured", unconfiguredLabel: "needs host + nick", configuredHint: "configured", unconfiguredHint: "needs host + nick", configuredScore: 1, unconfiguredScore: 0, + includeStatusLine: true, resolveConfigured: ({ cfg }) => listIrcAccountIds(cfg as CoreConfig).some( (accountId) => resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).configured, ), - resolveStatusLines: ({ configured }) => [ - `IRC: ${configured ? "configured" : "needs host + nick"}`, - ], - }, + }), introNote: { title: "IRC setup", lines: [ diff --git a/extensions/line/src/setup-surface.ts b/extensions/line/src/setup-surface.ts index 6f46cc92217..aa53dc7f69e 100644 --- a/extensions/line/src/setup-surface.ts +++ b/extensions/line/src/setup-surface.ts @@ -1,4 +1,8 @@ -import { createAllowFromSection, createTopLevelChannelDmPolicy } from "openclaw/plugin-sdk/setup"; +import { + createAllowFromSection, + createStandardChannelSetupStatus, + createTopLevelChannelDmPolicy, +} from "openclaw/plugin-sdk/setup"; import { DEFAULT_ACCOUNT_ID, formatDocsLink, @@ -47,20 +51,19 @@ export { lineSetupAdapter } from "./setup-core.js"; export const lineSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "LINE", configuredLabel: "configured", unconfiguredLabel: "needs token + secret", configuredHint: "configured", unconfiguredHint: "needs token + secret", configuredScore: 1, unconfiguredScore: 0, + includeStatusLine: true, resolveConfigured: ({ cfg }) => listLineAccountIds(cfg).some((accountId) => isLineConfigured(cfg, accountId)), - resolveStatusLines: ({ cfg, configured }) => [ - `LINE: ${configured ? "configured" : "needs token + secret"}`, - `Accounts: ${listLineAccountIds(cfg).length || 0}`, - ], - }, + resolveExtraStatusLines: ({ cfg }) => [`Accounts: ${listLineAccountIds(cfg).length || 0}`], + }), introNote: { title: "LINE Messaging API", lines: LINE_SETUP_HELP_LINES, diff --git a/extensions/mattermost/src/setup-surface.ts b/extensions/mattermost/src/setup-surface.ts index dd09e3a1492..bf5abca79f0 100644 --- a/extensions/mattermost/src/setup-surface.ts +++ b/extensions/mattermost/src/setup-surface.ts @@ -1,5 +1,8 @@ -import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "openclaw/plugin-sdk/setup"; +import { + createStandardChannelSetupStatus, + formatDocsLink, + type ChannelSetupWizard, +} from "openclaw/plugin-sdk/setup"; import { listMattermostAccountIds } from "./mattermost/accounts.js"; import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; import { @@ -19,7 +22,8 @@ export { mattermostSetupAdapter } from "./setup-core.js"; export const mattermostSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Mattermost", configuredLabel: "configured", unconfiguredLabel: "needs token + url", configuredHint: "configured", @@ -30,7 +34,7 @@ export const mattermostSetupWizard: ChannelSetupWizard = { listMattermostAccountIds(cfg).some((accountId) => isMattermostConfigured(resolveMattermostAccountWithSecrets(cfg, accountId)), ), - }, + }), introNote: { title: "Mattermost bot token", lines: [ diff --git a/extensions/msteams/src/setup-surface.ts b/extensions/msteams/src/setup-surface.ts index 3407a25187f..44c2390dcd8 100644 --- a/extensions/msteams/src/setup-surface.ts +++ b/extensions/msteams/src/setup-surface.ts @@ -2,6 +2,7 @@ import { createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, mergeAllowFromEntries, @@ -274,26 +275,19 @@ export const msteamsSetupWizard: ChannelSetupWizard = { channel, resolveAccountIdForConfigure: () => DEFAULT_ACCOUNT_ID, resolveShouldPromptAccountIds: () => false, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "MS Teams", configuredLabel: "configured", unconfiguredLabel: "needs app credentials", configuredHint: "configured", unconfiguredHint: "needs app creds", configuredScore: 2, unconfiguredScore: 0, - resolveConfigured: ({ cfg }) => { - return ( - Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) || - hasConfiguredMSTeamsCredentials(cfg.channels?.msteams) - ); - }, - resolveStatusLines: ({ cfg }) => { - const configured = - Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) || - hasConfiguredMSTeamsCredentials(cfg.channels?.msteams); - return [`MS Teams: ${configured ? "configured" : "needs app credentials"}`]; - }, - }, + includeStatusLine: true, + resolveConfigured: ({ cfg }) => + Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)) || + hasConfiguredMSTeamsCredentials(cfg.channels?.msteams), + }), credentials: [], finalize: async ({ cfg, prompter }) => { const resolved = resolveMSTeamsCredentials(cfg.channels?.msteams); diff --git a/extensions/nextcloud-talk/src/setup-surface.ts b/extensions/nextcloud-talk/src/setup-surface.ts index 4aa27c91009..f1e18961cfb 100644 --- a/extensions/nextcloud-talk/src/setup-surface.ts +++ b/extensions/nextcloud-talk/src/setup-surface.ts @@ -2,9 +2,12 @@ import type { ChannelSetupInput } from "openclaw/plugin-sdk/channel-setup"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing"; import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input"; -import { setSetupChannelEnabled } from "openclaw/plugin-sdk/setup"; -import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "openclaw/plugin-sdk/setup"; +import { + createStandardChannelSetupStatus, + formatDocsLink, + setSetupChannelEnabled, + type ChannelSetupWizard, +} from "openclaw/plugin-sdk/setup"; import { listNextcloudTalkAccountIds, resolveNextcloudTalkAccount } from "./accounts.js"; import { clearNextcloudTalkAccountFields, @@ -22,7 +25,8 @@ const CONFIGURE_API_FLAG = "__nextcloudTalkConfigureApiCredentials"; export const nextcloudTalkSetupWizard: ChannelSetupWizard = { channel, stepOrder: "text-first", - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Nextcloud Talk", configuredLabel: "configured", unconfiguredLabel: "needs setup", configuredHint: "configured", @@ -34,7 +38,7 @@ export const nextcloudTalkSetupWizard: ChannelSetupWizard = { const account = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }); return Boolean(account.secret && account.baseUrl); }), - }, + }), introNote: { title: "Nextcloud Talk bot setup", lines: [ diff --git a/extensions/nostr/src/setup-surface.ts b/extensions/nostr/src/setup-surface.ts index 71c22faeac2..0feb6a1853c 100644 --- a/extensions/nostr/src/setup-surface.ts +++ b/extensions/nostr/src/setup-surface.ts @@ -4,6 +4,7 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing"; import { createTopLevelChannelParsedAllowFromPrompt, createTopLevelChannelDmPolicy, + createStandardChannelSetupStatus, mergeAllowFromEntries, parseSetupEntriesWithParser, patchTopLevelChannelConfigSection, @@ -139,22 +140,21 @@ export const nostrSetupWizard: ChannelSetupWizard = { channel, resolveAccountIdForConfigure: () => DEFAULT_ACCOUNT_ID, resolveShouldPromptAccountIds: () => false, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Nostr", configuredLabel: "configured", unconfiguredLabel: "needs private key", configuredHint: "configured", unconfiguredHint: "needs private key", configuredScore: 1, unconfiguredScore: 0, + includeStatusLine: true, resolveConfigured: ({ cfg }) => resolveNostrAccount({ cfg }).configured, - resolveStatusLines: ({ cfg, configured }) => { + resolveExtraStatusLines: ({ cfg }) => { const account = resolveNostrAccount({ cfg }); - return [ - `Nostr: ${configured ? "configured" : "needs private key"}`, - `Relays: ${account.relays.length || DEFAULT_RELAYS.length}`, - ]; + return [`Relays: ${account.relays.length || DEFAULT_RELAYS.length}`]; }, - }, + }), introNote: { title: "Nostr setup", lines: NOSTR_SETUP_HELP_LINES, diff --git a/extensions/slack/src/setup-core.ts b/extensions/slack/src/setup-core.ts index bb9495767b0..e8c6498613a 100644 --- a/extensions/slack/src/setup-core.ts +++ b/extensions/slack/src/setup-core.ts @@ -3,6 +3,7 @@ import { createAccountScopedAllowFromSection, createAccountScopedGroupAccessSection, createLegacyCompatChannelDmPolicy, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, createEnvPatchedAccountSetupAdapter, hasConfiguredSecretInput, @@ -119,7 +120,8 @@ export function createSlackSetupWizardBase(handlers: { return { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Slack", configuredLabel: "configured", unconfiguredLabel: "needs tokens", configuredHint: "configured", @@ -131,7 +133,7 @@ export function createSlackSetupWizardBase(handlers: { const account = inspectSlackAccount({ cfg, accountId }); return account.configured; }), - }, + }), introNote: { title: "Slack socket mode tokens", lines: buildSlackSetupLines(), diff --git a/extensions/synology-chat/src/setup-surface.ts b/extensions/synology-chat/src/setup-surface.ts index 854a0cc8bdd..79bf8ca2137 100644 --- a/extensions/synology-chat/src/setup-surface.ts +++ b/extensions/synology-chat/src/setup-surface.ts @@ -1,5 +1,6 @@ import { createAllowFromSection, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, mergeAllowFromEntries, @@ -176,20 +177,19 @@ export const synologyChatSetupAdapter: ChannelSetupAdapter = { export const synologyChatSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Synology Chat", configuredLabel: "configured", unconfiguredLabel: "needs token + incoming webhook", configuredHint: "configured", unconfiguredHint: "needs token + incoming webhook", configuredScore: 1, unconfiguredScore: 0, + includeStatusLine: true, resolveConfigured: ({ cfg }) => listAccountIds(cfg).some((accountId) => isSynologyChatConfigured(cfg, accountId)), - resolveStatusLines: ({ cfg, configured }) => [ - `Synology Chat: ${configured ? "configured" : "needs token + incoming webhook"}`, - `Accounts: ${listAccountIds(cfg).length || 0}`, - ], - }, + resolveExtraStatusLines: ({ cfg }) => [`Accounts: ${listAccountIds(cfg).length || 0}`], + }), introNote: { title: "Synology Chat webhook setup", lines: SYNOLOGY_SETUP_HELP_LINES, diff --git a/extensions/telegram/src/setup-surface.ts b/extensions/telegram/src/setup-surface.ts index 0857d3bcf33..873295c2709 100644 --- a/extensions/telegram/src/setup-surface.ts +++ b/extensions/telegram/src/setup-surface.ts @@ -1,5 +1,6 @@ import { createAllowFromSection, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, hasConfiguredSecretInput, type OpenClawConfig, @@ -92,7 +93,8 @@ const dmPolicy: ChannelSetupDmPolicy = { export const telegramSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Telegram", configuredLabel: "configured", unconfiguredLabel: "needs token", configuredHint: "recommended · configured", @@ -104,7 +106,7 @@ export const telegramSetupWizard: ChannelSetupWizard = { const account = inspectTelegramAccount({ cfg, accountId }); return account.configured; }), - }, + }), prepare: async ({ cfg, accountId, credentialValues }) => ({ cfg: ensureTelegramDefaultGroupMentionGate(cfg, accountId), credentialValues, diff --git a/extensions/zalo/src/setup-surface.ts b/extensions/zalo/src/setup-surface.ts index c97e189ba4a..c292a15f9bd 100644 --- a/extensions/zalo/src/setup-surface.ts +++ b/extensions/zalo/src/setup-surface.ts @@ -1,6 +1,7 @@ import { buildSingleChannelSecretPromptState, createTopLevelChannelDmPolicy, + createStandardChannelSetupStatus, DEFAULT_ACCOUNT_ID, formatDocsLink, hasConfiguredSecretInput, @@ -195,13 +196,15 @@ export { zaloSetupAdapter } from "./setup-core.js"; export const zaloSetupWizard: ChannelSetupWizard = { channel, - status: { + status: createStandardChannelSetupStatus({ + channelLabel: "Zalo", configuredLabel: "configured", unconfiguredLabel: "needs token", configuredHint: "recommended · configured", unconfiguredHint: "recommended · newcomer-friendly", configuredScore: 1, unconfiguredScore: 10, + includeStatusLine: true, resolveConfigured: ({ cfg }) => listZaloAccountIds(cfg).some((accountId) => { const account = resolveZaloAccount({ @@ -215,11 +218,7 @@ export const zaloSetupWizard: ChannelSetupWizard = { Boolean(account.config.tokenFile?.trim()) ); }), - resolveStatusLines: ({ cfg, configured }) => { - void cfg; - return [`Zalo: ${configured ? "configured" : "needs token"}`]; - }, - }, + }), credentials: [], finalize: async ({ cfg, accountId, forceAllowFrom, options, prompter }) => { let next = cfg; diff --git a/src/channels/plugins/setup-wizard-helpers.test.ts b/src/channels/plugins/setup-wizard-helpers.test.ts index cbf177544ba..1bfb5fe6250 100644 --- a/src/channels/plugins/setup-wizard-helpers.test.ts +++ b/src/channels/plugins/setup-wizard-helpers.test.ts @@ -14,6 +14,7 @@ import { createLegacyCompatChannelDmPolicy, createNestedChannelParsedAllowFromPrompt, createPromptParsedAllowFromForAccount, + createStandardChannelSetupStatus, createNestedChannelAllowFromSetter, createNestedChannelDmPolicy, createNestedChannelDmPolicySetter, @@ -1816,6 +1817,46 @@ describe("normalizeAllowFromEntries", () => { }); }); +describe("createStandardChannelSetupStatus", () => { + it("returns the shared status fields without status lines by default", async () => { + const status = createStandardChannelSetupStatus({ + channelLabel: "Demo", + configuredLabel: "configured", + unconfiguredLabel: "needs token", + configuredHint: "ready", + unconfiguredHint: "missing token", + configuredScore: 2, + unconfiguredScore: 0, + resolveConfigured: ({ cfg }) => Boolean(cfg.channels?.demo), + }); + + expect(status.configuredHint).toBe("ready"); + expect(status.unconfiguredHint).toBe("missing token"); + expect(status.configuredScore).toBe(2); + expect(status.unconfiguredScore).toBe(0); + expect(await status.resolveConfigured({ cfg: { channels: { demo: {} } } })).toBe(true); + expect(status.resolveStatusLines).toBeUndefined(); + }); + + it("builds the default status line plus extra lines when requested", async () => { + const status = createStandardChannelSetupStatus({ + channelLabel: "Demo", + configuredLabel: "configured", + unconfiguredLabel: "needs token", + includeStatusLine: true, + resolveConfigured: ({ cfg }) => Boolean(cfg.channels?.demo), + resolveExtraStatusLines: ({ configured }) => [`Configured: ${configured ? "yes" : "no"}`], + }); + + expect( + await status.resolveStatusLines?.({ + cfg: { channels: { demo: {} } }, + configured: true, + }), + ).toEqual(["Demo: configured", "Configured: yes"]); + }); +}); + describe("resolveSetupAccountId", () => { it("normalizes provided account ids", () => { expect( diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 915ea9c4c3c..5f330d262ba 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -13,7 +13,11 @@ import type { PromptAccountId, PromptAccountIdParams, } from "./setup-wizard-types.js"; -import type { ChannelSetupWizard, ChannelSetupWizardAllowFromEntry } from "./setup-wizard.js"; +import type { + ChannelSetupWizard, + ChannelSetupWizardAllowFromEntry, + ChannelSetupWizardStatus, +} from "./setup-wizard.js"; let providerAuthInputPromise: | Promise> @@ -156,6 +160,50 @@ export function normalizeAllowFromEntries( return [...new Set(normalized)]; } +export function createStandardChannelSetupStatus(params: { + channelLabel: string; + configuredLabel: string; + unconfiguredLabel: string; + configuredHint?: string; + unconfiguredHint?: string; + configuredScore?: number; + unconfiguredScore?: number; + includeStatusLine?: boolean; + resolveConfigured: ChannelSetupWizardStatus["resolveConfigured"]; + resolveExtraStatusLines?: (params: { + cfg: OpenClawConfig; + configured: boolean; + }) => string[] | Promise; +}): ChannelSetupWizardStatus { + const status: ChannelSetupWizardStatus = { + configuredLabel: params.configuredLabel, + unconfiguredLabel: params.unconfiguredLabel, + resolveConfigured: params.resolveConfigured, + ...(params.configuredHint ? { configuredHint: params.configuredHint } : {}), + ...(params.unconfiguredHint ? { unconfiguredHint: params.unconfiguredHint } : {}), + ...(typeof params.configuredScore === "number" + ? { configuredScore: params.configuredScore } + : {}), + ...(typeof params.unconfiguredScore === "number" + ? { unconfiguredScore: params.unconfiguredScore } + : {}), + }; + + if (params.includeStatusLine || params.resolveExtraStatusLines) { + status.resolveStatusLines = async ({ cfg, configured }) => { + const lines = params.includeStatusLine + ? [ + `${params.channelLabel}: ${configured ? params.configuredLabel : params.unconfiguredLabel}`, + ] + : []; + const extraLines = (await params.resolveExtraStatusLines?.({ cfg, configured })) ?? []; + return [...lines, ...extraLines]; + }; + } + + return status; +} + export function resolveSetupAccountId(params: { accountId?: string; defaultAccountId: string; diff --git a/src/plugin-sdk/setup-runtime.ts b/src/plugin-sdk/setup-runtime.ts index fee9ca10ef0..b0d82655dea 100644 --- a/src/plugin-sdk/setup-runtime.ts +++ b/src/plugin-sdk/setup-runtime.ts @@ -15,6 +15,7 @@ export { createAccountScopedAllowFromSection, createAccountScopedGroupAccessSection, createLegacyCompatChannelDmPolicy, + createStandardChannelSetupStatus, parseMentionOrPrefixedId, patchChannelConfigForAccount, promptLegacyChannelAllowFromForAccount, diff --git a/src/plugin-sdk/setup.ts b/src/plugin-sdk/setup.ts index 89d88783547..e83c3d264a4 100644 --- a/src/plugin-sdk/setup.ts +++ b/src/plugin-sdk/setup.ts @@ -42,6 +42,7 @@ export { createLegacyCompatChannelDmPolicy, createNestedChannelParsedAllowFromPrompt, createPromptParsedAllowFromForAccount, + createStandardChannelSetupStatus, createNestedChannelAllowFromSetter, createNestedChannelDmPolicy, createNestedChannelDmPolicySetter,