From 583bea001cb21148501a068a52b9b859023f044b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Mar 2026 01:45:58 +0000 Subject: [PATCH] refactor: share parsed channel allowlist prompts --- docs/plugins/sdk-setup.md | 6 + extensions/bluebubbles/src/setup-surface.ts | 68 +++++------ extensions/feishu/src/setup-surface.ts | 42 +++---- extensions/googlechat/src/setup-surface.ts | 37 ++---- extensions/irc/src/setup-surface.ts | 53 ++++----- extensions/nostr/src/setup-surface.ts | 35 ++---- .../plugins/setup-wizard-helpers.test.ts | 88 ++++++++++++++ src/channels/plugins/setup-wizard-helpers.ts | 112 +++++++++++++++++- src/plugin-sdk/setup.ts | 3 + 9 files changed, 297 insertions(+), 147 deletions(-) diff --git a/docs/plugins/sdk-setup.md b/docs/plugins/sdk-setup.md index acfe44afc51..32524cd21e6 100644 --- a/docs/plugins/sdk-setup.md +++ b/docs/plugins/sdk-setup.md @@ -277,6 +277,12 @@ The `ChannelSetupWizard` type supports `credentials`, `textInputs`, See bundled plugins (e.g. `extensions/discord/src/channel.setup.ts`) for full examples. +For DM allowlist prompts that only need the standard +`note -> prompt -> parse -> merge -> patch` flow, prefer the shared setup +helpers from `openclaw/plugin-sdk/setup`: `createPromptParsedAllowFromForAccount(...)`, +`createTopLevelChannelParsedAllowFromPrompt(...)`, and +`createNestedChannelParsedAllowFromPrompt(...)`. + 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 6b98de3acb9..1b2491d4c5d 100644 --- a/extensions/bluebubbles/src/setup-surface.ts +++ b/extensions/bluebubbles/src/setup-surface.ts @@ -1,12 +1,11 @@ import { createAllowFromSection, + createPromptParsedAllowFromForAccount, DEFAULT_ACCOUNT_ID, formatDocsLink, - promptParsedAllowFromForAccount, type ChannelSetupDmPolicy, type ChannelSetupWizard, type OpenClawConfig, - type WizardPrompter, } from "openclaw/plugin-sdk/setup"; import { listBlueBubblesAccountIds, @@ -49,44 +48,35 @@ function validateBlueBubblesAllowFromEntry(value: string): string | null { } } -async function promptBlueBubblesAllowFrom(params: { - cfg: OpenClawConfig; - prompter: WizardPrompter; - accountId?: string; -}): Promise { - return await promptParsedAllowFromForAccount({ - cfg: params.cfg, - accountId: params.accountId, - defaultAccountId: resolveDefaultBlueBubblesAccountId(params.cfg), - prompter: params.prompter, - noteTitle: "BlueBubbles allowlist", - noteLines: [ - "Allowlist BlueBubbles DMs by handle or chat target.", - "Examples:", - "- +15555550123", - "- user@example.com", - "- chat_id:123", - "- chat_guid:iMessage;-;+15555550123", - "Multiple entries: comma- or newline-separated.", - `Docs: ${formatDocsLink("/channels/bluebubbles", "bluebubbles")}`, - ], - message: "BlueBubbles allowFrom (handle or chat_id)", - placeholder: "+15555550123, user@example.com, chat_id:123", - parseEntries: (raw) => { - const entries = parseBlueBubblesAllowFromInput(raw); - for (const entry of entries) { - if (!validateBlueBubblesAllowFromEntry(entry)) { - return { entries: [], error: `Invalid entry: ${entry}` }; - } +const promptBlueBubblesAllowFrom = createPromptParsedAllowFromForAccount({ + defaultAccountId: (cfg) => resolveDefaultBlueBubblesAccountId(cfg), + noteTitle: "BlueBubbles allowlist", + noteLines: [ + "Allowlist BlueBubbles DMs by handle or chat target.", + "Examples:", + "- +15555550123", + "- user@example.com", + "- chat_id:123", + "- chat_guid:iMessage;-;+15555550123", + "Multiple entries: comma- or newline-separated.", + `Docs: ${formatDocsLink("/channels/bluebubbles", "bluebubbles")}`, + ], + message: "BlueBubbles allowFrom (handle or chat_id)", + placeholder: "+15555550123, user@example.com, chat_id:123", + parseEntries: (raw) => { + const entries = parseBlueBubblesAllowFromInput(raw); + for (const entry of entries) { + if (!validateBlueBubblesAllowFromEntry(entry)) { + return { entries: [], error: `Invalid entry: ${entry}` }; } - return { entries }; - }, - getExistingAllowFrom: ({ cfg, accountId }) => - resolveBlueBubblesAccount({ cfg, accountId }).config.allowFrom ?? [], - applyAllowFrom: ({ cfg, accountId, allowFrom }) => - setBlueBubblesAllowFrom(cfg, accountId, allowFrom), - }); -} + } + return { entries }; + }, + getExistingAllowFrom: ({ cfg, accountId }) => + resolveBlueBubblesAccount({ cfg, accountId }).config.allowFrom ?? [], + applyAllowFrom: ({ cfg, accountId, allowFrom }) => + setBlueBubblesAllowFrom(cfg, accountId, allowFrom), +}); function validateBlueBubblesServerUrlInput(value: unknown): string | undefined { const trimmed = String(value ?? "").trim(); diff --git a/extensions/feishu/src/setup-surface.ts b/extensions/feishu/src/setup-surface.ts index 6dcb770bd0c..1a7d51a7d9d 100644 --- a/extensions/feishu/src/setup-surface.ts +++ b/extensions/feishu/src/setup-surface.ts @@ -3,12 +3,12 @@ import { createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicy, createTopLevelChannelGroupPolicySetter, + createTopLevelChannelParsedAllowFromPrompt, DEFAULT_ACCOUNT_ID, formatDocsLink, hasConfiguredSecretInput, mergeAllowFromEntries, patchTopLevelChannelConfigSection, - promptParsedAllowFromForAccount, promptSingleChannelSecretInput, splitSetupEntries, type ChannelSetupDmPolicy, @@ -93,30 +93,22 @@ function isFeishuConfigured(cfg: OpenClawConfig): boolean { return topLevelConfigured || accountConfigured; } -async function promptFeishuAllowFrom(params: { - cfg: OpenClawConfig; - prompter: Parameters>[0]["prompter"]; -}): Promise { - return await promptParsedAllowFromForAccount({ - cfg: params.cfg, - defaultAccountId: DEFAULT_ACCOUNT_ID, - prompter: params.prompter, - noteTitle: "Feishu allowlist", - noteLines: [ - "Allowlist Feishu DMs by open_id or user_id.", - "You can find user open_id in Feishu admin console or via API.", - "Examples:", - "- ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "- on_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - ], - message: "Feishu allowFrom (user open_ids)", - placeholder: "ou_xxxxx, ou_yyyyy", - parseEntries: (raw) => ({ entries: splitSetupEntries(raw) }), - getExistingAllowFrom: ({ cfg }) => cfg.channels?.feishu?.allowFrom ?? [], - mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed), - applyAllowFrom: ({ cfg, allowFrom }) => setFeishuAllowFrom(cfg, allowFrom), - }); -} +const promptFeishuAllowFrom = createTopLevelChannelParsedAllowFromPrompt({ + channel, + defaultAccountId: DEFAULT_ACCOUNT_ID, + noteTitle: "Feishu allowlist", + noteLines: [ + "Allowlist Feishu DMs by open_id or user_id.", + "You can find user open_id in Feishu admin console or via API.", + "Examples:", + "- ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "- on_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + ], + message: "Feishu allowFrom (user open_ids)", + placeholder: "ou_xxxxx, ou_yyyyy", + parseEntries: (raw) => ({ entries: splitSetupEntries(raw) }), + mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed), +}); async function noteFeishuCredentialHelp( prompter: Parameters>[0]["prompter"], diff --git a/extensions/googlechat/src/setup-surface.ts b/extensions/googlechat/src/setup-surface.ts index 93f6d37d82e..f9247c96529 100644 --- a/extensions/googlechat/src/setup-surface.ts +++ b/extensions/googlechat/src/setup-surface.ts @@ -1,11 +1,11 @@ import { applySetupAccountConfigPatch, + createNestedChannelParsedAllowFromPrompt, createNestedChannelDmPolicy, DEFAULT_ACCOUNT_ID, formatDocsLink, mergeAllowFromEntries, migrateBaseNameToDefaultAccount, - patchNestedChannelConfigSection, splitSetupEntries, type ChannelSetupDmPolicy, type ChannelSetupWizard, @@ -24,30 +24,17 @@ const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE"; const USE_ENV_FLAG = "__googlechatUseEnv"; const AUTH_METHOD_FLAG = "__googlechatAuthMethod"; -async function promptAllowFrom(params: { - cfg: OpenClawConfig; - prompter: Parameters>[0]["prompter"]; -}): Promise { - const current = params.cfg.channels?.googlechat?.dm?.allowFrom ?? []; - const entry = await params.prompter.text({ - message: "Google Chat allowFrom (users/ or raw email; avoid users/)", - placeholder: "users/123456789, name@example.com", - initialValue: current[0] ? String(current[0]) : undefined, - validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), - }); - const parts = splitSetupEntries(String(entry)); - const unique = mergeAllowFromEntries(undefined, parts); - return patchNestedChannelConfigSection({ - cfg: params.cfg, - channel, - section: "dm", - enabled: true, - patch: { - policy: "allowlist", - allowFrom: unique, - }, - }); -} +const promptAllowFrom = createNestedChannelParsedAllowFromPrompt({ + channel, + section: "dm", + defaultAccountId: DEFAULT_ACCOUNT_ID, + enabled: true, + message: "Google Chat allowFrom (users/ or raw email; avoid users/)", + placeholder: "users/123456789, name@example.com", + parseEntries: (raw) => ({ + entries: mergeAllowFromEntries(undefined, splitSetupEntries(raw)), + }), +}); const googlechatDmPolicy: ChannelSetupDmPolicy = createNestedChannelDmPolicy({ label: "Google Chat", diff --git a/extensions/irc/src/setup-surface.ts b/extensions/irc/src/setup-surface.ts index ef86fbf7052..213b7c37a47 100644 --- a/extensions/irc/src/setup-surface.ts +++ b/extensions/irc/src/setup-surface.ts @@ -2,7 +2,7 @@ import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime"; import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing"; import { createAllowFromSection, - promptParsedAllowFromForAccount, + createPromptParsedAllowFromForAccount, setSetupChannelEnabled, } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup"; @@ -52,36 +52,27 @@ function normalizeGroupEntry(raw: string): string | null { return `#${normalized.replace(/^#+/, "")}`; } -async function promptIrcAllowFrom(params: { - cfg: CoreConfig; - prompter: WizardPrompter; - accountId?: string; -}): Promise { - return await promptParsedAllowFromForAccount({ - cfg: params.cfg, - accountId: params.accountId, - defaultAccountId: resolveDefaultIrcAccountId(params.cfg), - prompter: params.prompter, - noteTitle: "IRC allowlist", - noteLines: [ - "Allowlist IRC DMs by sender.", - "Examples:", - "- alice", - "- alice!ident@example.org", - "Multiple entries: comma-separated.", - ], - message: "IRC allowFrom (nick or nick!user@host)", - placeholder: "alice, bob!ident@example.org", - parseEntries: (raw) => ({ - entries: parseListInput(raw) - .map((entry) => normalizeIrcAllowEntry(entry)) - .map((entry) => entry.trim()) - .filter(Boolean), - }), - getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [], - applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom), - }); -} +const promptIrcAllowFrom = createPromptParsedAllowFromForAccount({ + defaultAccountId: (cfg) => resolveDefaultIrcAccountId(cfg), + noteTitle: "IRC allowlist", + noteLines: [ + "Allowlist IRC DMs by sender.", + "Examples:", + "- alice", + "- alice!ident@example.org", + "Multiple entries: comma-separated.", + ], + message: "IRC allowFrom (nick or nick!user@host)", + placeholder: "alice, bob!ident@example.org", + parseEntries: (raw) => ({ + entries: parseListInput(raw) + .map((entry) => normalizeIrcAllowEntry(entry)) + .map((entry) => entry.trim()) + .filter(Boolean), + }), + getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [], + applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom), +}); async function promptIrcNickServConfig(params: { cfg: CoreConfig; diff --git a/extensions/nostr/src/setup-surface.ts b/extensions/nostr/src/setup-surface.ts index bdcb2ca31bf..71c22faeac2 100644 --- a/extensions/nostr/src/setup-surface.ts +++ b/extensions/nostr/src/setup-surface.ts @@ -2,26 +2,21 @@ import type { ChannelSetupAdapter } 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 { - createTopLevelChannelAllowFromSetter, + createTopLevelChannelParsedAllowFromPrompt, createTopLevelChannelDmPolicy, mergeAllowFromEntries, parseSetupEntriesWithParser, patchTopLevelChannelConfigSection, - promptParsedAllowFromForAccount, splitSetupEntries, } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; import { formatDocsLink } from "openclaw/plugin-sdk/setup"; -import type { WizardPrompter } from "openclaw/plugin-sdk/setup"; import { DEFAULT_RELAYS } from "./default-relays.js"; import { getPublicKeyFromPrivate, normalizePubkey } from "./nostr-bus.js"; import { resolveNostrAccount } from "./types.js"; const channel = "nostr" as const; -const setNostrAllowFrom = createTopLevelChannelAllowFromSetter({ - channel, -}); const NOSTR_SETUP_HELP_LINES = [ "Use a Nostr private key in nsec or 64-character hex format.", @@ -68,24 +63,16 @@ function parseNostrAllowFrom(raw: string): { entries: string[]; error?: string } }); } -async function promptNostrAllowFrom(params: { - cfg: OpenClawConfig; - prompter: WizardPrompter; -}): Promise { - return await promptParsedAllowFromForAccount({ - cfg: params.cfg, - defaultAccountId: DEFAULT_ACCOUNT_ID, - prompter: params.prompter, - noteTitle: "Nostr allowlist", - noteLines: NOSTR_ALLOW_FROM_HELP_LINES, - message: "Nostr allowFrom", - placeholder: "npub1..., 0123abcd...", - parseEntries: parseNostrAllowFrom, - getExistingAllowFrom: ({ cfg }) => cfg.channels?.nostr?.allowFrom ?? [], - mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed), - applyAllowFrom: ({ cfg, allowFrom }) => setNostrAllowFrom(cfg, allowFrom), - }); -} +const promptNostrAllowFrom = createTopLevelChannelParsedAllowFromPrompt({ + channel, + defaultAccountId: DEFAULT_ACCOUNT_ID, + noteTitle: "Nostr allowlist", + noteLines: NOSTR_ALLOW_FROM_HELP_LINES, + message: "Nostr allowFrom", + placeholder: "npub1..., 0123abcd...", + parseEntries: parseNostrAllowFrom, + mergeEntries: ({ existing, parsed }) => mergeAllowFromEntries(existing, parsed), +}); const nostrDmPolicy: ChannelSetupDmPolicy = createTopLevelChannelDmPolicy({ label: "Nostr", diff --git a/src/channels/plugins/setup-wizard-helpers.test.ts b/src/channels/plugins/setup-wizard-helpers.test.ts index 6084c5a0f44..cbf177544ba 100644 --- a/src/channels/plugins/setup-wizard-helpers.test.ts +++ b/src/channels/plugins/setup-wizard-helpers.test.ts @@ -12,6 +12,8 @@ import { createAccountScopedGroupAccessSection, createAllowFromSection, createLegacyCompatChannelDmPolicy, + createNestedChannelParsedAllowFromPrompt, + createPromptParsedAllowFromForAccount, createNestedChannelAllowFromSetter, createNestedChannelDmPolicy, createNestedChannelDmPolicySetter, @@ -19,6 +21,7 @@ import { createTopLevelChannelDmPolicy, createTopLevelChannelDmPolicySetter, createTopLevelChannelGroupPolicySetter, + createTopLevelChannelParsedAllowFromPrompt, normalizeAllowFromEntries, noteChannelLookupFailure, noteChannelLookupSummary, @@ -659,6 +662,91 @@ describe("promptParsedAllowFromForAccount", () => { }); }); +describe("createPromptParsedAllowFromForAccount", () => { + it("supports computed default account ids and optional notes", async () => { + const promptAllowFrom = createPromptParsedAllowFromForAccount({ + defaultAccountId: () => "work", + message: "msg", + placeholder: "placeholder", + parseEntries: (raw) => ({ entries: [raw.trim().toLowerCase()] }), + getExistingAllowFrom: ({ cfg, accountId }) => + cfg.channels?.bluebubbles?.accounts?.[accountId]?.allowFrom ?? [], + applyAllowFrom: ({ cfg, accountId, allowFrom }) => + patchChannelConfigForAccount({ + cfg, + channel: "bluebubbles", + accountId, + patch: { allowFrom }, + }), + }); + + const prompter = createPrompter(["Alice"]); + const next = await promptAllowFrom({ + cfg: { + channels: { + bluebubbles: { + accounts: { + work: { + allowFrom: ["old"], + }, + }, + }, + }, + }, + // oxlint-disable-next-line typescript/no-explicit-any + prompter: prompter as any, + }); + + expect(next.channels?.bluebubbles?.accounts?.work?.allowFrom).toEqual(["alice"]); + expect(prompter.note).not.toHaveBeenCalled(); + }); +}); + +describe("parsed allowFrom prompt builders", () => { + it("builds a top-level parsed allowFrom prompt", async () => { + const promptAllowFrom = createTopLevelChannelParsedAllowFromPrompt({ + channel: "nostr", + defaultAccountId: DEFAULT_ACCOUNT_ID, + noteTitle: "Nostr allowlist", + noteLines: ["line"], + message: "msg", + placeholder: "placeholder", + parseEntries: (raw) => ({ entries: [raw.trim().toLowerCase()] }), + }); + + const prompter = createPrompter(["npub1"]); + const next = await promptAllowFrom({ + cfg: {}, + // oxlint-disable-next-line typescript/no-explicit-any + prompter: prompter as any, + }); + + expect(next.channels?.nostr?.allowFrom).toEqual(["npub1"]); + expect(prompter.note).toHaveBeenCalledWith("line", "Nostr allowlist"); + }); + + it("builds a nested parsed allowFrom prompt", async () => { + const promptAllowFrom = createNestedChannelParsedAllowFromPrompt({ + channel: "googlechat", + section: "dm", + defaultAccountId: DEFAULT_ACCOUNT_ID, + enabled: true, + message: "msg", + placeholder: "placeholder", + parseEntries: (raw) => ({ entries: [raw.trim()] }), + }); + + const next = await promptAllowFrom({ + cfg: {}, + // oxlint-disable-next-line typescript/no-explicit-any + prompter: createPrompter(["users/123"]) as any, + }); + + expect(next.channels?.googlechat?.enabled).toBe(true); + expect(next.channels?.googlechat?.dm?.allowFrom).toEqual(["users/123"]); + }); +}); + describe("channel lookup note helpers", () => { it("emits summary lines for resolved and unresolved entries", async () => { const prompter = { note: vi.fn(async () => undefined) }; diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 00a389a79fd..915ea9c4c3c 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -1070,8 +1070,8 @@ export async function promptParsedAllowFromForAccount; - noteTitle: string; - noteLines: string[]; + noteTitle?: string; + noteLines?: string[]; message: string; placeholder: string; parseEntries: (raw: string) => ParsedAllowFromResult; @@ -1091,7 +1091,9 @@ export async function promptParsedAllowFromForAccount 0) { + await params.prompter.note(params.noteLines.join("\n"), params.noteTitle); + } const entry = await params.prompter.text({ message: params.message, placeholder: params.placeholder, @@ -1117,6 +1119,41 @@ export async function promptParsedAllowFromForAccount(params: { + defaultAccountId: string | ((cfg: TConfig) => string); + noteTitle?: string; + noteLines?: string[]; + message: string; + placeholder: string; + parseEntries: (raw: string) => ParsedAllowFromResult; + getExistingAllowFrom: (params: { cfg: TConfig; accountId: string }) => Array; + mergeEntries?: (params: { existing: Array; parsed: string[] }) => string[]; + applyAllowFrom: (params: { + cfg: TConfig; + accountId: string; + allowFrom: string[]; + }) => TConfig | Promise; +}): NonNullable { + return async ({ cfg, prompter, accountId }) => + await promptParsedAllowFromForAccount({ + cfg: cfg as TConfig, + accountId, + defaultAccountId: + typeof params.defaultAccountId === "function" + ? params.defaultAccountId(cfg as TConfig) + : params.defaultAccountId, + prompter, + ...(params.noteTitle ? { noteTitle: params.noteTitle } : {}), + ...(params.noteLines ? { noteLines: params.noteLines } : {}), + message: params.message, + placeholder: params.placeholder, + parseEntries: params.parseEntries, + getExistingAllowFrom: params.getExistingAllowFrom, + ...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}), + applyAllowFrom: params.applyAllowFrom, + }); +} + export async function promptParsedAllowFromForScopedChannel(params: { cfg: OpenClawConfig; channel: "imessage" | "signal"; @@ -1154,6 +1191,75 @@ export async function promptParsedAllowFromForScopedChannel(params: { }); } +export function createTopLevelChannelParsedAllowFromPrompt(params: { + channel: string; + defaultAccountId: string; + enabled?: boolean; + noteTitle?: string; + noteLines?: string[]; + message: string; + placeholder: string; + parseEntries: (raw: string) => ParsedAllowFromResult; + getExistingAllowFrom?: (cfg: OpenClawConfig) => Array; + mergeEntries?: (params: { existing: Array; parsed: string[] }) => string[]; +}): NonNullable { + const setAllowFrom = createTopLevelChannelAllowFromSetter({ + channel: params.channel, + ...(params.enabled ? { enabled: true } : {}), + }); + return createPromptParsedAllowFromForAccount({ + defaultAccountId: params.defaultAccountId, + ...(params.noteTitle ? { noteTitle: params.noteTitle } : {}), + ...(params.noteLines ? { noteLines: params.noteLines } : {}), + message: params.message, + placeholder: params.placeholder, + parseEntries: params.parseEntries, + getExistingAllowFrom: ({ cfg }) => + params.getExistingAllowFrom?.(cfg) ?? + (((cfg.channels?.[params.channel] as { allowFrom?: Array } | undefined) + ?.allowFrom ?? + []) as Array), + ...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}), + applyAllowFrom: ({ cfg, allowFrom }) => setAllowFrom(cfg, allowFrom), + }); +} + +export function createNestedChannelParsedAllowFromPrompt(params: { + channel: string; + section: string; + defaultAccountId: string; + enabled?: boolean; + noteTitle?: string; + noteLines?: string[]; + message: string; + placeholder: string; + parseEntries: (raw: string) => ParsedAllowFromResult; + getExistingAllowFrom?: (cfg: OpenClawConfig) => Array; + mergeEntries?: (params: { existing: Array; parsed: string[] }) => string[]; +}): NonNullable { + const setAllowFrom = createNestedChannelAllowFromSetter({ + channel: params.channel, + section: params.section, + ...(params.enabled ? { enabled: true } : {}), + }); + return createPromptParsedAllowFromForAccount({ + defaultAccountId: params.defaultAccountId, + ...(params.noteTitle ? { noteTitle: params.noteTitle } : {}), + ...(params.noteLines ? { noteLines: params.noteLines } : {}), + message: params.message, + placeholder: params.placeholder, + parseEntries: params.parseEntries, + getExistingAllowFrom: ({ cfg }) => + params.getExistingAllowFrom?.(cfg) ?? + (((cfg.channels?.[params.channel] as Record | undefined)?.[ + params.section + ] as { allowFrom?: Array } | undefined)?.allowFrom ?? + []), + ...(params.mergeEntries ? { mergeEntries: params.mergeEntries } : {}), + applyAllowFrom: ({ cfg, allowFrom }) => setAllowFrom(cfg, allowFrom), + }); +} + export function resolveParsedAllowFromEntries(params: { entries: string[]; parseId: (raw: string) => string | null; diff --git a/src/plugin-sdk/setup.ts b/src/plugin-sdk/setup.ts index f6f1f54f22d..89d88783547 100644 --- a/src/plugin-sdk/setup.ts +++ b/src/plugin-sdk/setup.ts @@ -40,6 +40,8 @@ export { createAccountScopedGroupAccessSection, createAllowFromSection, createLegacyCompatChannelDmPolicy, + createNestedChannelParsedAllowFromPrompt, + createPromptParsedAllowFromForAccount, createNestedChannelAllowFromSetter, createNestedChannelDmPolicy, createNestedChannelDmPolicySetter, @@ -47,6 +49,7 @@ export { createTopLevelChannelDmPolicy, createTopLevelChannelDmPolicySetter, createTopLevelChannelGroupPolicySetter, + createTopLevelChannelParsedAllowFromPrompt, mergeAllowFromEntries, normalizeAllowFromEntries, noteChannelLookupFailure,