refactor: share parsed channel allowlist prompts

This commit is contained in:
Peter Steinberger
2026-03-23 01:45:58 +00:00
parent 7d032ed38c
commit 583bea001c
9 changed files with 297 additions and 147 deletions

View File

@@ -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<OpenClawConfig> {
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();

View File

@@ -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<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
}): Promise<OpenClawConfig> {
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<NonNullable<ChannelSetupWizard["finalize"]>>[0]["prompter"],

View File

@@ -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<NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>>[0]["prompter"];
}): Promise<OpenClawConfig> {
const current = params.cfg.channels?.googlechat?.dm?.allowFrom ?? [];
const entry = await params.prompter.text({
message: "Google Chat allowFrom (users/<id> or raw email; avoid users/<email>)",
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/<id> or raw email; avoid users/<email>)",
placeholder: "users/123456789, name@example.com",
parseEntries: (raw) => ({
entries: mergeAllowFromEntries(undefined, splitSetupEntries(raw)),
}),
});
const googlechatDmPolicy: ChannelSetupDmPolicy = createNestedChannelDmPolicy({
label: "Google Chat",

View File

@@ -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<CoreConfig> {
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<CoreConfig>({
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;

View File

@@ -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<OpenClawConfig> {
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",