mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-20 14:30:57 +00:00
refactor: deduplicate channel config adapters
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
|
||||
@@ -47,8 +46,12 @@ const loadBlueBubblesChannelRuntime = createLazyRuntimeNamedExport(
|
||||
"blueBubblesChannelRuntime",
|
||||
);
|
||||
|
||||
const bluebubblesConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveBlueBubblesAccount({ cfg, accountId }),
|
||||
const bluebubblesConfigAdapter = createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
|
||||
sectionKey: "bluebubbles",
|
||||
listAccountIds: listBlueBubblesAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveBlueBubblesAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultBlueBubblesAccountId,
|
||||
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
|
||||
resolveAllowFrom: (account: ResolvedBlueBubblesAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
@@ -57,14 +60,6 @@ const bluebubblesConfigAccessors = createScopedAccountConfigAccessors({
|
||||
}),
|
||||
});
|
||||
|
||||
const bluebubblesConfigBase = createScopedChannelConfigBase<ResolvedBlueBubblesAccount>({
|
||||
sectionKey: "bluebubbles",
|
||||
listAccountIds: listBlueBubblesAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveBlueBubblesAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultBlueBubblesAccountId,
|
||||
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
|
||||
});
|
||||
|
||||
const resolveBlueBubblesDmPolicy = createScopedDmSecurityResolver<ResolvedBlueBubblesAccount>({
|
||||
channelKey: "bluebubbles",
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
@@ -115,7 +110,7 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
||||
configSchema: buildChannelConfigSchema(BlueBubblesConfigSchema),
|
||||
setupWizard: blueBubblesSetupWizard,
|
||||
config: {
|
||||
...bluebubblesConfigBase,
|
||||
...bluebubblesConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account): ChannelAccountSnapshot => ({
|
||||
accountId: account.accountId,
|
||||
@@ -124,7 +119,6 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
||||
configured: account.configured,
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
...bluebubblesConfigAccessors,
|
||||
},
|
||||
actions: bluebubblesMessageActions,
|
||||
security: {
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
import { getDiscordRuntime } from "./runtime.js";
|
||||
import { fetchChannelPermissionsDiscord } from "./send.js";
|
||||
import { discordSetupAdapter } from "./setup-core.js";
|
||||
import { createDiscordPluginBase, discordConfigAccessors } from "./shared.js";
|
||||
import { createDiscordPluginBase, discordConfigAdapter } from "./shared.js";
|
||||
import { collectDiscordStatusIssues } from "./status-issues.js";
|
||||
import { parseDiscordTarget } from "./targets.js";
|
||||
import { DiscordUiContainer } from "./ui.js";
|
||||
@@ -307,7 +307,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
applyConfigEdit: buildAccountScopedAllowlistConfigEditor({
|
||||
channelId: "discord",
|
||||
normalize: ({ cfg, accountId, values }) =>
|
||||
discordConfigAccessors.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
discordConfigAdapter.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
resolvePaths: resolveLegacyDmAllowlistConfigPaths,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -23,8 +23,11 @@ export {
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
export {
|
||||
createHybridChannelConfigAdapter,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createTopLevelChannelConfigAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
export {
|
||||
createAccountActionGate,
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
type ResolvedDiscordAccount,
|
||||
} from "./accounts.js";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
buildChannelConfigSchema,
|
||||
DiscordConfigSchema,
|
||||
getChatChannelMeta,
|
||||
@@ -27,20 +26,16 @@ export const discordSetupWizard = createDiscordSetupWizardProxy(
|
||||
async () => (await loadDiscordChannelRuntime()).discordSetupWizard,
|
||||
);
|
||||
|
||||
export const discordConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveDiscordAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedDiscordAccount) => account.config.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
resolveDefaultTo: (account: ResolvedDiscordAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const discordConfigBase = createScopedChannelConfigBase<ResolvedDiscordAccount>({
|
||||
export const discordConfigAdapter = createScopedChannelConfigAdapter<ResolvedDiscordAccount>({
|
||||
sectionKey: DISCORD_CHANNEL,
|
||||
listAccountIds: listDiscordAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }),
|
||||
inspectAccount: (cfg, accountId) => inspectDiscordAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultDiscordAccountId,
|
||||
clearBaseFields: ["token", "name"],
|
||||
resolveAllowFrom: (account: ResolvedDiscordAccount) => account.config.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
resolveDefaultTo: (account: ResolvedDiscordAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export function createDiscordPluginBase(params: {
|
||||
@@ -75,7 +70,7 @@ export function createDiscordPluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.discord"] },
|
||||
configSchema: buildChannelConfigSchema(DiscordConfigSchema),
|
||||
config: {
|
||||
...discordConfigBase,
|
||||
...discordConfigAdapter,
|
||||
isConfigured: (account) => Boolean(account.token?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -84,7 +79,6 @@ export function createDiscordPluginBase(params: {
|
||||
configured: Boolean(account.token?.trim()),
|
||||
tokenSource: account.tokenSource,
|
||||
}),
|
||||
...discordConfigAccessors,
|
||||
},
|
||||
setup: params.setup,
|
||||
}) as Pick<
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createHybridChannelConfigBase,
|
||||
createScopedAccountConfigAccessors,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createHybridChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type {
|
||||
@@ -130,17 +127,16 @@ function setFeishuNamedAccountEnabled(
|
||||
};
|
||||
}
|
||||
|
||||
const feishuConfigBase = createHybridChannelConfigBase<ResolvedFeishuAccount, ClawdbotConfig>({
|
||||
const feishuConfigAdapter = createHybridChannelConfigAdapter<
|
||||
ResolvedFeishuAccount,
|
||||
ResolvedFeishuAccount,
|
||||
ClawdbotConfig
|
||||
>({
|
||||
sectionKey: "feishu",
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveFeishuAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultFeishuAccountId,
|
||||
clearBaseFields: [],
|
||||
});
|
||||
|
||||
const feishuConfigAccessors = createScopedAccountConfigAccessors<ResolvedFeishuAccount>({
|
||||
resolveAccount: ({ cfg, accountId }) =>
|
||||
resolveFeishuAccount({ cfg: cfg as ClawdbotConfig, accountId }),
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
});
|
||||
@@ -396,7 +392,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
reload: { configPrefixes: ["channels.feishu"] },
|
||||
configSchema: buildChannelConfigSchema(FeishuConfigSchema),
|
||||
config: {
|
||||
...feishuConfigBase,
|
||||
...feishuConfigAdapter,
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
||||
const isDefault = accountId === DEFAULT_ACCOUNT_ID;
|
||||
if (isDefault) {
|
||||
@@ -454,7 +450,6 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
appId: account.appId,
|
||||
domain: account.domain,
|
||||
}),
|
||||
...feishuConfigAccessors,
|
||||
},
|
||||
actions: {
|
||||
describeMessageTool: describeFeishuMessageTool,
|
||||
|
||||
@@ -9,8 +9,11 @@ export {
|
||||
readStringParam,
|
||||
} from "../../src/agents/tools/common.js";
|
||||
export {
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createTopLevelChannelConfigAdapter,
|
||||
createHybridChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "../../src/plugin-sdk/channel-config-helpers.js";
|
||||
export {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
@@ -61,18 +60,7 @@ const formatAllowFromEntry = (entry: string) =>
|
||||
.replace(/^users\//i, "")
|
||||
.toLowerCase();
|
||||
|
||||
const googleChatConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveGoogleChatAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedGoogleChatAccount) => account.config.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: formatAllowFromEntry,
|
||||
}),
|
||||
resolveDefaultTo: (account: ResolvedGoogleChatAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
const googleChatConfigBase = createScopedChannelConfigBase<ResolvedGoogleChatAccount>({
|
||||
const googleChatConfigAdapter = createScopedChannelConfigAdapter<ResolvedGoogleChatAccount>({
|
||||
sectionKey: "googlechat",
|
||||
listAccountIds: listGoogleChatAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveGoogleChatAccount({ cfg, accountId }),
|
||||
@@ -87,6 +75,13 @@ const googleChatConfigBase = createScopedChannelConfigBase<ResolvedGoogleChatAcc
|
||||
"botUser",
|
||||
"name",
|
||||
],
|
||||
resolveAllowFrom: (account: ResolvedGoogleChatAccount) => account.config.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: formatAllowFromEntry,
|
||||
}),
|
||||
resolveDefaultTo: (account: ResolvedGoogleChatAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
const resolveGoogleChatDmPolicy = createScopedDmSecurityResolver<ResolvedGoogleChatAccount>({
|
||||
@@ -146,7 +141,7 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
reload: { configPrefixes: ["channels.googlechat"] },
|
||||
configSchema: buildChannelConfigSchema(GoogleChatConfigSchema),
|
||||
config: {
|
||||
...googleChatConfigBase,
|
||||
...googleChatConfigAdapter,
|
||||
isConfigured: (account) => account.credentialSource !== "none",
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -155,7 +150,6 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
configured: account.credentialSource !== "none",
|
||||
credentialSource: account.credentialSource,
|
||||
}),
|
||||
...googleChatConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveGoogleChatDmPolicy,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
collectAllowlistProviderRestrictSendersWarnings,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
formatTrimmedAllowFromEntries,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
@@ -29,19 +29,15 @@ export const imessageSetupWizard = createIMessageSetupWizardProxy(
|
||||
async () => (await loadIMessageChannelRuntime()).imessageSetupWizard,
|
||||
);
|
||||
|
||||
export const imessageConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveIMessageAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedIMessageAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
|
||||
resolveDefaultTo: (account: ResolvedIMessageAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const imessageConfigBase = createScopedChannelConfigBase<ResolvedIMessageAccount>({
|
||||
export const imessageConfigAdapter = createScopedChannelConfigAdapter<ResolvedIMessageAccount>({
|
||||
sectionKey: IMESSAGE_CHANNEL,
|
||||
listAccountIds: listIMessageAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveIMessageAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultIMessageAccountId,
|
||||
clearBaseFields: ["cliPath", "dbPath", "service", "region", "name"],
|
||||
resolveAllowFrom: (account: ResolvedIMessageAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatTrimmedAllowFromEntries(allowFrom),
|
||||
resolveDefaultTo: (account: ResolvedIMessageAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const imessageResolveDmPolicy = createScopedDmSecurityResolver<ResolvedIMessageAccount>({
|
||||
@@ -97,7 +93,7 @@ export function createIMessagePluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.imessage"] },
|
||||
configSchema: buildChannelConfigSchema(IMessageConfigSchema),
|
||||
config: {
|
||||
...imessageConfigBase,
|
||||
...imessageConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -105,7 +101,6 @@ export function createIMessagePluginBase(params: {
|
||||
enabled: account.enabled,
|
||||
configured: account.configured,
|
||||
}),
|
||||
...imessageConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: imessageResolveDmPolicy,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
@@ -51,18 +50,11 @@ function normalizePairingTarget(raw: string): string {
|
||||
return normalized.split(/[!@]/, 1)[0]?.trim() ?? "";
|
||||
}
|
||||
|
||||
const ircConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedIrcAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: normalizeIrcAllowEntry,
|
||||
}),
|
||||
resolveDefaultTo: (account: ResolvedIrcAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
const ircConfigBase = createScopedChannelConfigBase<ResolvedIrcAccount, CoreConfig>({
|
||||
const ircConfigAdapter = createScopedChannelConfigAdapter<
|
||||
ResolvedIrcAccount,
|
||||
ResolvedIrcAccount,
|
||||
CoreConfig
|
||||
>({
|
||||
sectionKey: "irc",
|
||||
listAccountIds: listIrcAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveIrcAccount({ cfg, accountId }),
|
||||
@@ -79,6 +71,13 @@ const ircConfigBase = createScopedChannelConfigBase<ResolvedIrcAccount, CoreConf
|
||||
"passwordFile",
|
||||
"channels",
|
||||
],
|
||||
resolveAllowFrom: (account: ResolvedIrcAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: normalizeIrcAllowEntry,
|
||||
}),
|
||||
resolveDefaultTo: (account: ResolvedIrcAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
const resolveIrcDmPolicy = createScopedDmSecurityResolver<ResolvedIrcAccount>({
|
||||
@@ -116,7 +115,7 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = {
|
||||
reload: { configPrefixes: ["channels.irc"] },
|
||||
configSchema: buildChannelConfigSchema(IrcConfigSchema),
|
||||
config: {
|
||||
...ircConfigBase,
|
||||
...ircConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -129,7 +128,6 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = {
|
||||
nick: account.nick,
|
||||
passwordSource: account.passwordSource,
|
||||
}),
|
||||
...ircConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveIrcDmPolicy,
|
||||
|
||||
@@ -2,10 +2,9 @@ import {
|
||||
buildChannelConfigSchema,
|
||||
LineConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedLineAccount,
|
||||
} from "../api.js";
|
||||
import { listLineAccountIds, resolveDefaultLineAccountId, resolveLineAccount } from "../api.js";
|
||||
import { lineConfigAdapter } from "./config-adapter.js";
|
||||
import { lineSetupAdapter } from "./setup-core.js";
|
||||
import { lineSetupWizard } from "./setup-surface.js";
|
||||
|
||||
@@ -20,8 +19,6 @@ const meta = {
|
||||
systemImage: "message.fill",
|
||||
} as const;
|
||||
|
||||
const normalizeLineAllowFrom = (entry: string) => entry.replace(/^line:(?:user:)?/i, "");
|
||||
|
||||
export const lineSetupPlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
id: "line",
|
||||
meta: {
|
||||
@@ -39,10 +36,7 @@ export const lineSetupPlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
reload: { configPrefixes: ["channels.line"] },
|
||||
configSchema: buildChannelConfigSchema(LineConfigSchema),
|
||||
config: {
|
||||
listAccountIds: (cfg: OpenClawConfig) => listLineAccountIds(cfg),
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>
|
||||
resolveLineAccount({ cfg, accountId: accountId ?? undefined }),
|
||||
defaultAccountId: (cfg: OpenClawConfig) => resolveDefaultLineAccountId(cfg),
|
||||
...lineConfigAdapter,
|
||||
isConfigured: (account) =>
|
||||
Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
@@ -52,13 +46,6 @@ export const lineSetupPlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
configured: Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
tokenSource: account.tokenSource ?? undefined,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
resolveLineAccount({ cfg, accountId: accountId ?? undefined }).config.allowFrom,
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => normalizeLineAllowFrom(entry)),
|
||||
},
|
||||
setupWizard: lineSetupWizard,
|
||||
setup: lineSetupAdapter,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
@@ -14,11 +10,11 @@ import {
|
||||
processLineMessage,
|
||||
type ChannelPlugin,
|
||||
type ChannelStatusIssue,
|
||||
type OpenClawConfig,
|
||||
type LineConfig,
|
||||
type LineChannelData,
|
||||
type ResolvedLineAccount,
|
||||
} from "../api.js";
|
||||
import { lineConfigAdapter } from "./config-adapter.js";
|
||||
import { resolveLineGroupRequireMention } from "./group-policy.js";
|
||||
import { getLineRuntime } from "./runtime.js";
|
||||
import { lineSetupAdapter } from "./setup-core.js";
|
||||
@@ -36,26 +32,6 @@ const meta = {
|
||||
systemImage: "message.fill",
|
||||
};
|
||||
|
||||
const lineConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) =>
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId: accountId ?? undefined }),
|
||||
resolveAllowFrom: (account: ResolvedLineAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.replace(/^line:(?:user:)?/i, "")),
|
||||
});
|
||||
|
||||
const lineConfigBase = createScopedChannelConfigBase<ResolvedLineAccount, OpenClawConfig>({
|
||||
sectionKey: "line",
|
||||
listAccountIds: (cfg) => getLineRuntime().channel.line.listLineAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId: accountId ?? undefined }),
|
||||
defaultAccountId: (cfg) => getLineRuntime().channel.line.resolveDefaultLineAccountId(cfg),
|
||||
clearBaseFields: ["channelSecret", "tokenFile", "secretFile"],
|
||||
});
|
||||
|
||||
const resolveLineDmPolicy = createScopedDmSecurityResolver<ResolvedLineAccount>({
|
||||
channelKey: "line",
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
@@ -100,7 +76,7 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
configSchema: buildChannelConfigSchema(LineConfigSchema),
|
||||
setupWizard: lineSetupWizard,
|
||||
config: {
|
||||
...lineConfigBase,
|
||||
...lineConfigAdapter,
|
||||
isConfigured: (account) =>
|
||||
Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
@@ -110,7 +86,6 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
configured: Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
|
||||
tokenSource: account.tokenSource ?? undefined,
|
||||
}),
|
||||
...lineConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveLineDmPolicy,
|
||||
|
||||
32
extensions/line/src/config-adapter.ts
Normal file
32
extensions/line/src/config-adapter.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { OpenClawConfig, ResolvedLineAccount } from "../api.js";
|
||||
import { getLineRuntime } from "./runtime.js";
|
||||
|
||||
function resolveLineRuntimeAccount(cfg: OpenClawConfig, accountId?: string | null) {
|
||||
return getLineRuntime().channel.line.resolveLineAccount({
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeLineAllowFrom(entry: string): string {
|
||||
return entry.replace(/^line:(?:user:)?/i, "");
|
||||
}
|
||||
|
||||
export const lineConfigAdapter = createScopedChannelConfigAdapter<
|
||||
ResolvedLineAccount,
|
||||
ResolvedLineAccount,
|
||||
OpenClawConfig
|
||||
>({
|
||||
sectionKey: "line",
|
||||
listAccountIds: (cfg) => getLineRuntime().channel.line.listLineAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) => resolveLineRuntimeAccount(cfg, accountId),
|
||||
defaultAccountId: (cfg) => getLineRuntime().channel.line.resolveDefaultLineAccountId(cfg),
|
||||
clearBaseFields: ["channelSecret", "tokenFile", "secretFile"],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map(normalizeLineAllowFrom),
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
@@ -69,17 +68,16 @@ function normalizeMatrixMessagingTarget(raw: string): string | undefined {
|
||||
return stripped || undefined;
|
||||
}
|
||||
|
||||
const matrixConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) =>
|
||||
resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId }),
|
||||
resolveAllowFrom: (account) => account.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => normalizeMatrixAllowList(allowFrom),
|
||||
});
|
||||
|
||||
const matrixConfigBase = createScopedChannelConfigBase<ResolvedMatrixAccount, CoreConfig>({
|
||||
const matrixConfigAdapter = createScopedChannelConfigAdapter<
|
||||
ResolvedMatrixAccount,
|
||||
ReturnType<typeof resolveMatrixAccountConfig>,
|
||||
CoreConfig
|
||||
>({
|
||||
sectionKey: "matrix",
|
||||
listAccountIds: listMatrixAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveMatrixAccount({ cfg, accountId }),
|
||||
resolveAccessorAccount: ({ cfg, accountId }) =>
|
||||
resolveMatrixAccountConfig({ cfg: cfg as CoreConfig, accountId }),
|
||||
defaultAccountId: resolveDefaultMatrixAccountId,
|
||||
clearBaseFields: [
|
||||
"name",
|
||||
@@ -90,6 +88,8 @@ const matrixConfigBase = createScopedChannelConfigBase<ResolvedMatrixAccount, Co
|
||||
"deviceName",
|
||||
"initialSyncLimit",
|
||||
],
|
||||
resolveAllowFrom: (account) => account.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => normalizeMatrixAllowList(allowFrom),
|
||||
});
|
||||
|
||||
const resolveMatrixDmPolicy = createScopedDmSecurityResolver<ResolvedMatrixAccount>({
|
||||
@@ -122,7 +122,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
|
||||
reload: { configPrefixes: ["channels.matrix"] },
|
||||
configSchema: buildChannelConfigSchema(MatrixConfigSchema),
|
||||
config: {
|
||||
...matrixConfigBase,
|
||||
...matrixConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -131,7 +131,6 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
|
||||
configured: account.configured,
|
||||
baseUrl: account.homeserver,
|
||||
}),
|
||||
...matrixConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveMatrixDmPolicy,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
|
||||
@@ -248,8 +247,12 @@ function formatAllowEntry(entry: string): string {
|
||||
return trimmed.replace(/^(mattermost|user):/i, "").toLowerCase();
|
||||
}
|
||||
|
||||
const mattermostConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveMattermostAccount({ cfg, accountId }),
|
||||
const mattermostConfigAdapter = createScopedChannelConfigAdapter<ResolvedMattermostAccount>({
|
||||
sectionKey: "mattermost",
|
||||
listAccountIds: listMattermostAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveMattermostAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultMattermostAccountId,
|
||||
clearBaseFields: ["botToken", "baseUrl", "name"],
|
||||
resolveAllowFrom: (account: ResolvedMattermostAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
@@ -258,14 +261,6 @@ const mattermostConfigAccessors = createScopedAccountConfigAccessors({
|
||||
}),
|
||||
});
|
||||
|
||||
const mattermostConfigBase = createScopedChannelConfigBase<ResolvedMattermostAccount>({
|
||||
sectionKey: "mattermost",
|
||||
listAccountIds: listMattermostAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveMattermostAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultMattermostAccountId,
|
||||
clearBaseFields: ["botToken", "baseUrl", "name"],
|
||||
});
|
||||
|
||||
const resolveMattermostDmPolicy = createScopedDmSecurityResolver<ResolvedMattermostAccount>({
|
||||
channelKey: "mattermost",
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
@@ -311,7 +306,7 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
reload: { configPrefixes: ["channels.mattermost"] },
|
||||
configSchema: buildChannelConfigSchema(MattermostConfigSchema),
|
||||
config: {
|
||||
...mattermostConfigBase,
|
||||
...mattermostConfigAdapter,
|
||||
isConfigured: (account) => Boolean(account.botToken && account.baseUrl),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -321,7 +316,6 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
botTokenSource: account.botTokenSource,
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
...mattermostConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveMattermostDmPolicy,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createTopLevelChannelConfigBase,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { collectAllowlistProviderRestrictSendersWarnings } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createMessageToolCardSchema } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type {
|
||||
@@ -73,20 +70,20 @@ const resolveMSTeamsChannelConfig = (cfg: OpenClawConfig) => ({
|
||||
defaultTo: cfg.channels?.msteams?.defaultTo,
|
||||
});
|
||||
|
||||
const msteamsConfigBase = createTopLevelChannelConfigBase<ResolvedMSTeamsAccount>({
|
||||
const msteamsConfigAdapter = createTopLevelChannelConfigAdapter<
|
||||
ResolvedMSTeamsAccount,
|
||||
{
|
||||
allowFrom?: Array<string | number>;
|
||||
defaultTo?: string;
|
||||
}
|
||||
>({
|
||||
sectionKey: "msteams",
|
||||
resolveAccount: (cfg) => ({
|
||||
accountId: DEFAULT_ACCOUNT_ID,
|
||||
enabled: cfg.channels?.msteams?.enabled !== false,
|
||||
configured: Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
|
||||
}),
|
||||
});
|
||||
|
||||
const msteamsConfigAccessors = createScopedAccountConfigAccessors<{
|
||||
allowFrom?: Array<string | number>;
|
||||
defaultTo?: string;
|
||||
}>({
|
||||
resolveAccount: ({ cfg }) => resolveMSTeamsChannelConfig(cfg),
|
||||
resolveAccessorAccount: ({ cfg }) => resolveMSTeamsChannelConfig(cfg),
|
||||
resolveAllowFrom: (account) => account.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
resolveDefaultTo: (account) => account.defaultTo,
|
||||
@@ -157,14 +154,13 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
reload: { configPrefixes: ["channels.msteams"] },
|
||||
configSchema: buildChannelConfigSchema(MSTeamsConfigSchema),
|
||||
config: {
|
||||
...msteamsConfigBase,
|
||||
...msteamsConfigAdapter,
|
||||
isConfigured: (_account, cfg) => Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
enabled: account.enabled,
|
||||
configured: account.configured,
|
||||
}),
|
||||
...msteamsConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
collectWarnings: ({ cfg }) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
|
||||
@@ -51,19 +50,8 @@ const meta = {
|
||||
quickstartAllowFrom: true,
|
||||
};
|
||||
|
||||
const nextcloudTalkConfigAccessors =
|
||||
createScopedAccountConfigAccessors<ResolvedNextcloudTalkAccount>({
|
||||
resolveAccount: ({ cfg, accountId }) =>
|
||||
resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }),
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({
|
||||
allowFrom,
|
||||
stripPrefixRe: /^(nextcloud-talk|nc-talk|nc):/i,
|
||||
}),
|
||||
});
|
||||
|
||||
const nextcloudTalkConfigBase = createScopedChannelConfigBase<
|
||||
const nextcloudTalkConfigAdapter = createScopedChannelConfigAdapter<
|
||||
ResolvedNextcloudTalkAccount,
|
||||
ResolvedNextcloudTalkAccount,
|
||||
CoreConfig
|
||||
>({
|
||||
@@ -72,6 +60,12 @@ const nextcloudTalkConfigBase = createScopedChannelConfigBase<
|
||||
resolveAccount: (cfg, accountId) => resolveNextcloudTalkAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultNextcloudTalkAccountId,
|
||||
clearBaseFields: ["botSecret", "botSecretFile", "baseUrl", "name"],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({
|
||||
allowFrom,
|
||||
stripPrefixRe: /^(nextcloud-talk|nc-talk|nc):/i,
|
||||
}),
|
||||
});
|
||||
|
||||
const resolveNextcloudTalkDmPolicy = createScopedDmSecurityResolver<ResolvedNextcloudTalkAccount>({
|
||||
@@ -105,7 +99,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
||||
reload: { configPrefixes: ["channels.nextcloud-talk"] },
|
||||
configSchema: buildChannelConfigSchema(NextcloudTalkConfigSchema),
|
||||
config: {
|
||||
...nextcloudTalkConfigBase,
|
||||
...nextcloudTalkConfigAdapter,
|
||||
isConfigured: (account) => Boolean(account.secret?.trim() && account.baseUrl?.trim()),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -115,7 +109,6 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
||||
secretSource: account.secretSource,
|
||||
baseUrl: account.baseUrl ? "[set]" : "[missing]",
|
||||
}),
|
||||
...nextcloudTalkConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveNextcloudTalkDmPolicy,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
createScopedDmSecurityResolver,
|
||||
createTopLevelChannelConfigAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
collectStatusIssuesFromLastError,
|
||||
createDefaultChannelRuntimeState,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatPairingApproveHint,
|
||||
mapAllowFromEntries,
|
||||
type ChannelPlugin,
|
||||
} from "openclaw/plugin-sdk/nostr";
|
||||
import {
|
||||
@@ -49,6 +51,39 @@ const resolveNostrDmPolicy = createScopedDmSecurityResolver<ResolvedNostrAccount
|
||||
},
|
||||
});
|
||||
|
||||
const nostrConfigAdapter = createTopLevelChannelConfigAdapter<ResolvedNostrAccount>({
|
||||
sectionKey: "nostr",
|
||||
resolveAccount: (cfg) => resolveNostrAccount({ cfg }),
|
||||
listAccountIds: listNostrAccountIds,
|
||||
defaultAccountId: resolveDefaultNostrAccountId,
|
||||
deleteMode: "clear-fields",
|
||||
clearBaseFields: [
|
||||
"name",
|
||||
"defaultAccount",
|
||||
"privateKey",
|
||||
"relays",
|
||||
"dmPolicy",
|
||||
"allowFrom",
|
||||
"profile",
|
||||
],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => {
|
||||
if (entry === "*") {
|
||||
return "*";
|
||||
}
|
||||
try {
|
||||
return normalizePubkey(entry);
|
||||
} catch {
|
||||
return entry;
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
});
|
||||
|
||||
export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
id: "nostr",
|
||||
meta: {
|
||||
@@ -70,9 +105,7 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
setupWizard: nostrSetupWizard,
|
||||
|
||||
config: {
|
||||
listAccountIds: (cfg) => listNostrAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) => resolveNostrAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultNostrAccountId(cfg),
|
||||
...nostrConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -81,23 +114,6 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
configured: account.configured,
|
||||
publicKey: account.publicKey,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
mapAllowFromEntries(resolveNostrAccount({ cfg, accountId }).config.allowFrom),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => {
|
||||
if (entry === "*") {
|
||||
return "*";
|
||||
}
|
||||
try {
|
||||
return normalizePubkey(entry);
|
||||
} catch {
|
||||
return entry; // Keep as-is if normalization fails
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
},
|
||||
|
||||
pairing: {
|
||||
|
||||
@@ -31,8 +31,8 @@ import { getSignalRuntime } from "./runtime.js";
|
||||
import { signalSetupAdapter } from "./setup-core.js";
|
||||
import {
|
||||
collectSignalSecurityWarnings,
|
||||
signalConfigAdapter,
|
||||
createSignalPluginBase,
|
||||
signalConfigAccessors,
|
||||
signalResolveDmPolicy,
|
||||
signalSetupWizard,
|
||||
} from "./shared.js";
|
||||
@@ -290,7 +290,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
applyConfigEdit: buildAccountScopedAllowlistConfigEditor({
|
||||
channelId: "signal",
|
||||
normalize: ({ cfg, accountId, values }) =>
|
||||
signalConfigAccessors.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
signalConfigAdapter.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
resolvePaths: (scope) => ({
|
||||
readPaths: [[scope === "dm" ? "allowFrom" : "groupAllowFrom"]],
|
||||
writePath: [scope === "dm" ? "allowFrom" : "groupAllowFrom"],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
collectAllowlistProviderRestrictSendersWarnings,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
@@ -30,8 +29,12 @@ export const signalSetupWizard = createSignalSetupWizardProxy(
|
||||
async () => (await loadSignalChannelRuntime()).signalSetupWizard,
|
||||
);
|
||||
|
||||
export const signalConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveSignalAccount({ cfg, accountId }),
|
||||
export const signalConfigAdapter = createScopedChannelConfigAdapter<ResolvedSignalAccount>({
|
||||
sectionKey: SIGNAL_CHANNEL,
|
||||
listAccountIds: listSignalAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveSignalAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultSignalAccountId,
|
||||
clearBaseFields: ["account", "httpUrl", "httpHost", "httpPort", "cliPath", "name"],
|
||||
resolveAllowFrom: (account: ResolvedSignalAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom
|
||||
@@ -42,14 +45,6 @@ export const signalConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveDefaultTo: (account: ResolvedSignalAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const signalConfigBase = createScopedChannelConfigBase<ResolvedSignalAccount>({
|
||||
sectionKey: SIGNAL_CHANNEL,
|
||||
listAccountIds: listSignalAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveSignalAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultSignalAccountId,
|
||||
clearBaseFields: ["account", "httpUrl", "httpHost", "httpPort", "cliPath", "name"],
|
||||
});
|
||||
|
||||
export const signalResolveDmPolicy = createScopedDmSecurityResolver<ResolvedSignalAccount>({
|
||||
channelKey: SIGNAL_CHANNEL,
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
@@ -107,7 +102,7 @@ export function createSignalPluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.signal"] },
|
||||
configSchema: buildChannelConfigSchema(SignalConfigSchema),
|
||||
config: {
|
||||
...signalConfigBase,
|
||||
...signalConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -116,7 +111,6 @@ export function createSignalPluginBase(params: {
|
||||
configured: account.configured,
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
...signalConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: signalResolveDmPolicy,
|
||||
|
||||
@@ -44,7 +44,7 @@ import { slackSetupWizard } from "./setup-surface.js";
|
||||
import {
|
||||
createSlackPluginBase,
|
||||
isSlackPluginAccountConfigured,
|
||||
slackConfigAccessors,
|
||||
slackConfigAdapter,
|
||||
SLACK_CHANNEL,
|
||||
} from "./shared.js";
|
||||
import { parseSlackTarget } from "./targets.js";
|
||||
@@ -352,7 +352,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
applyConfigEdit: buildAccountScopedAllowlistConfigEditor({
|
||||
channelId: "slack",
|
||||
normalize: ({ cfg, accountId, values }) =>
|
||||
slackConfigAccessors.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
slackConfigAdapter.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
resolvePaths: resolveLegacyDmAllowlistConfigPaths,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
formatDocsLink,
|
||||
@@ -145,20 +142,16 @@ export function isSlackSetupAccountConfigured(account: ResolvedSlackAccount): bo
|
||||
return hasConfiguredBotToken && hasConfiguredAppToken;
|
||||
}
|
||||
|
||||
export const slackConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveSlackAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedSlackAccount) => account.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
resolveDefaultTo: (account: ResolvedSlackAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const slackConfigBase = createScopedChannelConfigBase({
|
||||
export const slackConfigAdapter = createScopedChannelConfigAdapter<ResolvedSlackAccount>({
|
||||
sectionKey: SLACK_CHANNEL,
|
||||
listAccountIds: listSlackAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveSlackAccount({ cfg, accountId }),
|
||||
inspectAccount: (cfg, accountId) => inspectSlackAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultSlackAccountId,
|
||||
clearBaseFields: ["botToken", "appToken", "name"],
|
||||
resolveAllowFrom: (account: ResolvedSlackAccount) => account.dm?.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
||||
resolveDefaultTo: (account: ResolvedSlackAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export function createSlackPluginBase(params: {
|
||||
@@ -208,7 +201,7 @@ export function createSlackPluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.slack"] },
|
||||
configSchema: buildChannelConfigSchema(SlackConfigSchema),
|
||||
config: {
|
||||
...slackConfigBase,
|
||||
...slackConfigAdapter,
|
||||
isConfigured: (account) => isSlackPluginAccountConfigured(account),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -218,7 +211,6 @@ export function createSlackPluginBase(params: {
|
||||
botTokenSource: account.botTokenSource,
|
||||
appTokenSource: account.appTokenSource,
|
||||
}),
|
||||
...slackConfigAccessors,
|
||||
},
|
||||
setup: params.setup,
|
||||
}) as Pick<
|
||||
|
||||
@@ -57,6 +57,16 @@ describe("createSynologyChatPlugin", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(plugin.config.defaultAccountId?.({})).toBe("default");
|
||||
});
|
||||
|
||||
it("formats allowFrom entries through the shared adapter", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(
|
||||
plugin.config.formatAllowFrom?.({
|
||||
cfg: {},
|
||||
allowFrom: [" USER1 ", 42],
|
||||
}),
|
||||
).toEqual(["user1", "42"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("security", () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
createHybridChannelConfigBase,
|
||||
createHybridChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { z } from "zod";
|
||||
@@ -32,7 +32,7 @@ const resolveSynologyChatDmPolicy = createScopedDmSecurityResolver<ResolvedSynol
|
||||
normalizeEntry: (raw) => raw.toLowerCase().trim(),
|
||||
});
|
||||
|
||||
const synologyChatConfigBase = createHybridChannelConfigBase<ResolvedSynologyChatAccount>({
|
||||
const synologyChatConfigAdapter = createHybridChannelConfigAdapter<ResolvedSynologyChatAccount>({
|
||||
sectionKey: CHANNEL_ID,
|
||||
listAccountIds: (cfg: any) => listAccountIds(cfg),
|
||||
resolveAccount: (cfg: any, accountId?: string | null) => resolveAccount(cfg, accountId),
|
||||
@@ -48,6 +48,9 @@ const synologyChatConfigBase = createHybridChannelConfigBase<ResolvedSynologyCha
|
||||
"botName",
|
||||
"allowInsecureSsl",
|
||||
],
|
||||
resolveAllowFrom: (account) => account.allowedUserIds,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean),
|
||||
});
|
||||
|
||||
function waitUntilAbort(signal?: AbortSignal, onAbort?: () => void): Promise<void> {
|
||||
@@ -100,7 +103,7 @@ export function createSynologyChatPlugin() {
|
||||
setupWizard: synologyChatSetupWizard,
|
||||
|
||||
config: {
|
||||
...synologyChatConfigBase,
|
||||
...synologyChatConfigAdapter,
|
||||
},
|
||||
|
||||
pairing: {
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
createTelegramPluginBase,
|
||||
findTelegramTokenOwnerAccountId,
|
||||
formatDuplicateTelegramTokenReason,
|
||||
telegramConfigAccessors,
|
||||
telegramConfigAdapter,
|
||||
} from "./shared.js";
|
||||
import { collectTelegramStatusIssues } from "./status-issues.js";
|
||||
import { parseTelegramTarget } from "./targets.js";
|
||||
@@ -325,7 +325,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
|
||||
applyConfigEdit: buildAccountScopedAllowlistConfigEditor({
|
||||
channelId: "telegram",
|
||||
normalize: ({ cfg, accountId, values }) =>
|
||||
telegramConfigAccessors.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
telegramConfigAdapter.formatAllowFrom!({ cfg, accountId, allowFrom: values }),
|
||||
resolvePaths: (scope) => ({
|
||||
readPaths: [[scope === "dm" ? "allowFrom" : "groupAllowFrom"]],
|
||||
writePath: [scope === "dm" ? "allowFrom" : "groupAllowFrom"],
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
@@ -56,21 +53,17 @@ export function formatDuplicateTelegramTokenReason(params: {
|
||||
);
|
||||
}
|
||||
|
||||
export const telegramConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveTelegramAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedTelegramAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }),
|
||||
resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export const telegramConfigBase = createScopedChannelConfigBase<ResolvedTelegramAccount>({
|
||||
export const telegramConfigAdapter = createScopedChannelConfigAdapter<ResolvedTelegramAccount>({
|
||||
sectionKey: TELEGRAM_CHANNEL,
|
||||
listAccountIds: listTelegramAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }),
|
||||
inspectAccount: (cfg, accountId) => inspectTelegramAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultTelegramAccountId,
|
||||
clearBaseFields: ["botToken", "tokenFile", "name"],
|
||||
resolveAllowFrom: (account: ResolvedTelegramAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(telegram|tg):/i }),
|
||||
resolveDefaultTo: (account: ResolvedTelegramAccount) => account.config.defaultTo,
|
||||
});
|
||||
|
||||
export function createTelegramPluginBase(params: {
|
||||
@@ -99,7 +92,7 @@ export function createTelegramPluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.telegram"] },
|
||||
configSchema: buildChannelConfigSchema(TelegramConfigSchema),
|
||||
config: {
|
||||
...telegramConfigBase,
|
||||
...telegramConfigAdapter,
|
||||
isConfigured: (account, cfg) => {
|
||||
if (!account.token?.trim()) {
|
||||
return false;
|
||||
@@ -131,7 +124,6 @@ export function createTelegramPluginBase(params: {
|
||||
!findTelegramTokenOwnerAccountId({ cfg, accountId: account.accountId }),
|
||||
tokenSource: account.tokenSource,
|
||||
}),
|
||||
...telegramConfigAccessors,
|
||||
},
|
||||
setup: params.setup,
|
||||
}) as Pick<
|
||||
|
||||
32
extensions/tlon/src/channel.test.ts
Normal file
32
extensions/tlon/src/channel.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/tlon";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { tlonPlugin } from "./channel.js";
|
||||
|
||||
describe("tlonPlugin config", () => {
|
||||
it("formats dm allowlist entries through the shared hybrid adapter", () => {
|
||||
expect(
|
||||
tlonPlugin.config.formatAllowFrom?.({
|
||||
cfg: {} as OpenClawConfig,
|
||||
allowFrom: ["zod", " ~nec "],
|
||||
}),
|
||||
).toEqual(["~zod", "~nec"]);
|
||||
});
|
||||
|
||||
it("resolves dm allowlist from the default account", () => {
|
||||
expect(
|
||||
tlonPlugin.config.resolveAllowFrom?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
tlon: {
|
||||
ship: "~sampel-palnet",
|
||||
url: "https://urbit.example.com",
|
||||
code: "lidlut-tabwed-pillex-ridrup",
|
||||
dmAllowlist: ["~zod"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
accountId: "default",
|
||||
}),
|
||||
).toEqual(["~zod"]);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createHybridChannelConfigBase } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createHybridChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { ChannelAccountSnapshot, ChannelPlugin } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
@@ -39,7 +39,7 @@ const tlonSetupWizardProxy = createTlonSetupWizardBase({
|
||||
).tlonSetupWizard.finalize!(params),
|
||||
}) satisfies NonNullable<ChannelPlugin["setupWizard"]>;
|
||||
|
||||
const tlonConfigBase = createHybridChannelConfigBase({
|
||||
const tlonConfigAdapter = createHybridChannelConfigAdapter({
|
||||
sectionKey: TLON_CHANNEL_ID,
|
||||
listAccountIds: (cfg: OpenClawConfig) => listTlonAccountIds(cfg),
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>
|
||||
@@ -47,6 +47,9 @@ const tlonConfigBase = createHybridChannelConfigBase({
|
||||
defaultAccountId: () => "default",
|
||||
clearBaseFields: ["ship", "code", "url", "name"],
|
||||
preserveSectionOnDefaultDelete: true,
|
||||
resolveAllowFrom: (account) => account.dmAllowlist,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
allowFrom.map((entry) => normalizeShip(String(entry))).filter(Boolean),
|
||||
});
|
||||
|
||||
export const tlonPlugin: ChannelPlugin = {
|
||||
@@ -72,7 +75,7 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
reload: { configPrefixes: ["channels.tlon"] },
|
||||
configSchema: tlonChannelConfigSchema,
|
||||
config: {
|
||||
...tlonConfigBase,
|
||||
...tlonConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {
|
||||
collectAllowlistProviderGroupPolicyWarnings,
|
||||
collectOpenGroupPolicyRouteAllowlistWarnings,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
@@ -37,17 +36,13 @@ export const whatsappSetupWizardProxy = createWhatsAppSetupWizardProxy(
|
||||
async () => (await loadWhatsAppChannelRuntime()).whatsappSetupWizard,
|
||||
);
|
||||
|
||||
const whatsappConfigBase = createScopedChannelConfigBase<ResolvedWhatsAppAccount>({
|
||||
const whatsappConfigAdapter = createScopedChannelConfigAdapter<ResolvedWhatsAppAccount>({
|
||||
sectionKey: WHATSAPP_CHANNEL,
|
||||
listAccountIds: listWhatsAppAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultWhatsAppAccountId,
|
||||
clearBaseFields: [],
|
||||
allowTopLevel: false,
|
||||
});
|
||||
|
||||
const whatsappConfigAccessors = createScopedAccountConfigAccessors<ResolvedWhatsAppAccount>({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveWhatsAppAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account) => account.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => formatWhatsAppConfigAllowFromEntries(allowFrom),
|
||||
resolveDefaultTo: (account) => account.defaultTo,
|
||||
@@ -133,7 +128,7 @@ export function createWhatsAppPluginBase(params: {
|
||||
gatewayMethods: ["web.login.start", "web.login.wait"],
|
||||
configSchema: buildChannelConfigSchema(WhatsAppConfigSchema),
|
||||
config: {
|
||||
...whatsappConfigBase,
|
||||
...whatsappConfigAdapter,
|
||||
isEnabled: (account, cfg) => account.enabled && cfg.web?.enabled !== false,
|
||||
disabledReason: () => "disabled",
|
||||
isConfigured: params.isConfigured,
|
||||
@@ -147,7 +142,6 @@ export function createWhatsAppPluginBase(params: {
|
||||
dmPolicy: account.dmPolicy,
|
||||
allowFrom: account.allowFrom,
|
||||
}),
|
||||
...whatsappConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: whatsappResolveDmPolicy,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedDmSecurityResolver,
|
||||
mapAllowFromEntries,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
@@ -62,19 +61,15 @@ function normalizeZaloMessagingTarget(raw: string): string | undefined {
|
||||
|
||||
const loadZaloChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime.js"));
|
||||
|
||||
const zaloConfigAccessors = createScopedAccountConfigAccessors({
|
||||
resolveAccount: ({ cfg, accountId }) => resolveZaloAccount({ cfg, accountId }),
|
||||
resolveAllowFrom: (account: ResolvedZaloAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }),
|
||||
});
|
||||
|
||||
const zaloConfigBase = createScopedChannelConfigBase<ResolvedZaloAccount>({
|
||||
const zaloConfigAdapter = createScopedChannelConfigAdapter<ResolvedZaloAccount>({
|
||||
sectionKey: "zalo",
|
||||
listAccountIds: listZaloAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveZaloAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultZaloAccountId,
|
||||
clearBaseFields: ["botToken", "tokenFile", "name"],
|
||||
resolveAllowFrom: (account: ResolvedZaloAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalo|zl):/i }),
|
||||
});
|
||||
|
||||
const resolveZaloDmPolicy = createScopedDmSecurityResolver<ResolvedZaloAccount>({
|
||||
@@ -102,7 +97,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
|
||||
reload: { configPrefixes: ["channels.zalo"] },
|
||||
configSchema: buildChannelConfigSchema(ZaloConfigSchema),
|
||||
config: {
|
||||
...zaloConfigBase,
|
||||
...zaloConfigAdapter,
|
||||
isConfigured: (account) => Boolean(account.token?.trim()),
|
||||
describeAccount: (account): ChannelAccountSnapshot => ({
|
||||
accountId: account.accountId,
|
||||
@@ -111,7 +106,6 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount> = {
|
||||
configured: Boolean(account.token?.trim()),
|
||||
tokenSource: account.tokenSource,
|
||||
}),
|
||||
...zaloConfigAccessors,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: resolveZaloDmPolicy,
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/zalouser";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
deleteAccountFromConfigSection,
|
||||
formatAllowFromLowercase,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "openclaw/plugin-sdk/zalouser";
|
||||
import { buildChannelConfigSchema, formatAllowFromLowercase } from "openclaw/plugin-sdk/zalouser";
|
||||
import {
|
||||
listZalouserAccountIds,
|
||||
resolveDefaultZalouserAccountId,
|
||||
@@ -27,6 +22,27 @@ export const zalouserMeta = {
|
||||
quickstartAllowFrom: false,
|
||||
} satisfies ChannelPlugin<ResolvedZalouserAccount>["meta"];
|
||||
|
||||
const zalouserConfigAdapter = createScopedChannelConfigAdapter<ResolvedZalouserAccount>({
|
||||
sectionKey: "zalouser",
|
||||
listAccountIds: listZalouserAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveZalouserAccountSync({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultZalouserAccountId,
|
||||
clearBaseFields: [
|
||||
"profile",
|
||||
"name",
|
||||
"dmPolicy",
|
||||
"allowFrom",
|
||||
"historyLimit",
|
||||
"groupAllowFrom",
|
||||
"groupPolicy",
|
||||
"groups",
|
||||
"messagePrefix",
|
||||
],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
||||
});
|
||||
|
||||
export function createZalouserPluginBase(params: {
|
||||
setupWizard: NonNullable<ChannelPlugin<ResolvedZalouserAccount>["setupWizard"]>;
|
||||
setup: NonNullable<ChannelPlugin<ResolvedZalouserAccount>["setup"]>;
|
||||
@@ -50,34 +66,7 @@ export function createZalouserPluginBase(params: {
|
||||
reload: { configPrefixes: ["channels.zalouser"] },
|
||||
configSchema: buildChannelConfigSchema(ZalouserConfigSchema),
|
||||
config: {
|
||||
listAccountIds: (cfg) => listZalouserAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) => resolveZalouserAccountSync({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultZalouserAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
cfg,
|
||||
sectionKey: "zalouser",
|
||||
accountId,
|
||||
enabled,
|
||||
allowTopLevel: true,
|
||||
}),
|
||||
deleteAccount: ({ cfg, accountId }) =>
|
||||
deleteAccountFromConfigSection({
|
||||
cfg,
|
||||
sectionKey: "zalouser",
|
||||
accountId,
|
||||
clearBaseFields: [
|
||||
"profile",
|
||||
"name",
|
||||
"dmPolicy",
|
||||
"allowFrom",
|
||||
"historyLimit",
|
||||
"groupAllowFrom",
|
||||
"groupPolicy",
|
||||
"groups",
|
||||
"messagePrefix",
|
||||
],
|
||||
}),
|
||||
...zalouserConfigAdapter,
|
||||
isConfigured: async (account) => await checkZcaAuthenticated(account.profile),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
@@ -85,10 +74,6 @@ export function createZalouserPluginBase(params: {
|
||||
enabled: account.enabled,
|
||||
configured: undefined,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
mapAllowFromEntries(resolveZalouserAccountSync({ cfg, accountId }).config.allowFrom),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
formatAllowFromLowercase({ allowFrom, stripPrefixRe: /^(zalouser|zlu):/i }),
|
||||
},
|
||||
setup: params.setup,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedDmSecurityResolver,
|
||||
createHybridChannelConfigAdapter,
|
||||
createTopLevelChannelConfigAdapter,
|
||||
createTopLevelChannelConfigBase,
|
||||
createHybridChannelConfigBase,
|
||||
mapAllowFromEntries,
|
||||
@@ -160,6 +163,41 @@ describe("createScopedChannelConfigBase", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createScopedChannelConfigAdapter", () => {
|
||||
it("combines scoped CRUD and allowFrom accessors", () => {
|
||||
const adapter = createScopedChannelConfigAdapter({
|
||||
sectionKey: "demo",
|
||||
listAccountIds: () => ["default", "alt"],
|
||||
resolveAccount: (_cfg, accountId) => ({
|
||||
accountId: accountId ?? "default",
|
||||
allowFrom: accountId ? [accountId] : ["fallback"],
|
||||
defaultTo: " room:123 ",
|
||||
}),
|
||||
defaultAccountId: () => "default",
|
||||
clearBaseFields: ["token"],
|
||||
resolveAllowFrom: (account) => account.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry).toUpperCase()),
|
||||
resolveDefaultTo: (account) => account.defaultTo,
|
||||
});
|
||||
|
||||
expect(adapter.listAccountIds({})).toEqual(["default", "alt"]);
|
||||
expect(adapter.resolveAccount({}, "alt")).toEqual({
|
||||
accountId: "alt",
|
||||
allowFrom: ["alt"],
|
||||
defaultTo: " room:123 ",
|
||||
});
|
||||
expect(adapter.resolveAllowFrom?.({ cfg: {}, accountId: "alt" })).toEqual(["alt"]);
|
||||
expect(adapter.resolveDefaultTo?.({ cfg: {}, accountId: "alt" })).toBe("room:123");
|
||||
expect(
|
||||
adapter.setAccountEnabled!({
|
||||
cfg: {},
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
}).channels?.demo,
|
||||
).toEqual({ enabled: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createScopedDmSecurityResolver", () => {
|
||||
it("builds account-aware DM policy payloads", () => {
|
||||
const resolveDmPolicy = createScopedDmSecurityResolver<{
|
||||
@@ -232,6 +270,69 @@ describe("createTopLevelChannelConfigBase", () => {
|
||||
}).channels,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("can clear only account-scoped fields while preserving channel settings", () => {
|
||||
const base = createTopLevelChannelConfigBase({
|
||||
sectionKey: "demo",
|
||||
resolveAccount: () => ({ accountId: "default" }),
|
||||
deleteMode: "clear-fields",
|
||||
clearBaseFields: ["token", "allowFrom"],
|
||||
});
|
||||
|
||||
expect(
|
||||
base.deleteAccount!({
|
||||
cfg: {
|
||||
channels: {
|
||||
demo: {
|
||||
token: "secret",
|
||||
allowFrom: ["owner"],
|
||||
markdown: { tables: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "default",
|
||||
}).channels?.demo,
|
||||
).toEqual({
|
||||
markdown: { tables: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createTopLevelChannelConfigAdapter", () => {
|
||||
it("combines top-level CRUD with separate accessor account resolution", () => {
|
||||
const adapter = createTopLevelChannelConfigAdapter<
|
||||
{ accountId: string; enabled: boolean },
|
||||
{ allowFrom: string[]; defaultTo: string }
|
||||
>({
|
||||
sectionKey: "demo",
|
||||
resolveAccount: () => ({ accountId: "default", enabled: true }),
|
||||
resolveAccessorAccount: () => ({ allowFrom: ["owner"], defaultTo: " chat:123 " }),
|
||||
deleteMode: "clear-fields",
|
||||
clearBaseFields: ["token"],
|
||||
resolveAllowFrom: (account) => account.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry)),
|
||||
resolveDefaultTo: (account) => account.defaultTo,
|
||||
});
|
||||
|
||||
expect(adapter.resolveAccount({})).toEqual({ accountId: "default", enabled: true });
|
||||
expect(adapter.resolveAllowFrom?.({ cfg: {} })).toEqual(["owner"]);
|
||||
expect(adapter.resolveDefaultTo?.({ cfg: {} })).toBe("chat:123");
|
||||
expect(
|
||||
adapter.deleteAccount!({
|
||||
cfg: {
|
||||
channels: {
|
||||
demo: {
|
||||
token: "secret",
|
||||
markdown: { tables: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "default",
|
||||
}).channels?.demo,
|
||||
).toEqual({
|
||||
markdown: { tables: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createHybridChannelConfigBase", () => {
|
||||
@@ -309,3 +410,54 @@ describe("createHybridChannelConfigBase", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createHybridChannelConfigAdapter", () => {
|
||||
it("combines hybrid CRUD with allowFrom/defaultTo accessors", () => {
|
||||
const adapter = createHybridChannelConfigAdapter<
|
||||
{ accountId: string; enabled: boolean },
|
||||
{ allowFrom: string[]; defaultTo: string }
|
||||
>({
|
||||
sectionKey: "demo",
|
||||
listAccountIds: () => ["default", "alt"],
|
||||
resolveAccount: (_cfg, accountId) => ({
|
||||
accountId: accountId ?? "default",
|
||||
enabled: true,
|
||||
}),
|
||||
resolveAccessorAccount: ({ accountId }) => ({
|
||||
allowFrom: [accountId ?? "default"],
|
||||
defaultTo: " room:123 ",
|
||||
}),
|
||||
defaultAccountId: () => "default",
|
||||
clearBaseFields: ["token"],
|
||||
preserveSectionOnDefaultDelete: true,
|
||||
resolveAllowFrom: (account) => account.allowFrom,
|
||||
formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry).toUpperCase()),
|
||||
resolveDefaultTo: (account) => account.defaultTo,
|
||||
});
|
||||
|
||||
expect(adapter.resolveAllowFrom?.({ cfg: {}, accountId: "alt" })).toEqual(["alt"]);
|
||||
expect(adapter.resolveDefaultTo?.({ cfg: {}, accountId: "alt" })).toBe("room:123");
|
||||
expect(
|
||||
adapter.setAccountEnabled!({
|
||||
cfg: {},
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
}).channels?.demo,
|
||||
).toEqual({ enabled: true });
|
||||
expect(
|
||||
adapter.deleteAccount!({
|
||||
cfg: {
|
||||
channels: {
|
||||
demo: {
|
||||
token: "secret",
|
||||
markdown: { tables: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
accountId: "default",
|
||||
}).channels?.demo,
|
||||
).toEqual({
|
||||
markdown: { tables: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,6 +116,59 @@ export function createScopedChannelConfigBase<
|
||||
};
|
||||
}
|
||||
|
||||
/** Build the full shared config adapter for account-scoped channels with allowlist/default target accessors. */
|
||||
export function createScopedChannelConfigAdapter<
|
||||
ResolvedAccount,
|
||||
AccessorAccount = ResolvedAccount,
|
||||
Config extends OpenClawConfig = OpenClawConfig,
|
||||
>(params: {
|
||||
sectionKey: string;
|
||||
listAccountIds: (cfg: Config) => string[];
|
||||
resolveAccount: (cfg: Config, accountId?: string | null) => ResolvedAccount;
|
||||
resolveAccessorAccount?: (params: { cfg: Config; accountId?: string | null }) => AccessorAccount;
|
||||
defaultAccountId: (cfg: Config) => string;
|
||||
inspectAccount?: (cfg: Config, accountId?: string | null) => unknown;
|
||||
clearBaseFields: string[];
|
||||
allowTopLevel?: boolean;
|
||||
resolveAllowFrom: (account: AccessorAccount) => Array<string | number> | null | undefined;
|
||||
formatAllowFrom: (allowFrom: Array<string | number>) => string[];
|
||||
resolveDefaultTo?: (account: AccessorAccount) => string | number | null | undefined;
|
||||
}): Pick<
|
||||
ChannelConfigAdapter<ResolvedAccount>,
|
||||
| "listAccountIds"
|
||||
| "resolveAccount"
|
||||
| "inspectAccount"
|
||||
| "defaultAccountId"
|
||||
| "setAccountEnabled"
|
||||
| "deleteAccount"
|
||||
| "resolveAllowFrom"
|
||||
| "formatAllowFrom"
|
||||
| "resolveDefaultTo"
|
||||
> {
|
||||
const resolveAccessorAccount =
|
||||
params.resolveAccessorAccount ??
|
||||
(({ cfg, accountId }: { cfg: Config; accountId?: string | null }) =>
|
||||
params.resolveAccount(cfg, accountId) as unknown as AccessorAccount);
|
||||
|
||||
return {
|
||||
...createScopedChannelConfigBase<ResolvedAccount, Config>({
|
||||
sectionKey: params.sectionKey,
|
||||
listAccountIds: params.listAccountIds,
|
||||
resolveAccount: params.resolveAccount,
|
||||
inspectAccount: params.inspectAccount,
|
||||
defaultAccountId: params.defaultAccountId,
|
||||
clearBaseFields: params.clearBaseFields,
|
||||
allowTopLevel: params.allowTopLevel,
|
||||
}),
|
||||
...createScopedAccountConfigAccessors<AccessorAccount>({
|
||||
resolveAccount: resolveAccessorAccount,
|
||||
resolveAllowFrom: params.resolveAllowFrom,
|
||||
formatAllowFrom: params.formatAllowFrom,
|
||||
resolveDefaultTo: params.resolveDefaultTo,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function setTopLevelChannelEnabledInConfigSection<Config extends OpenClawConfig>(params: {
|
||||
cfg: Config;
|
||||
sectionKey: string;
|
||||
@@ -219,6 +272,59 @@ export function createTopLevelChannelConfigBase<
|
||||
};
|
||||
}
|
||||
|
||||
/** Build the full shared config adapter for top-level single-account channels with allowlist/default target accessors. */
|
||||
export function createTopLevelChannelConfigAdapter<
|
||||
ResolvedAccount,
|
||||
AccessorAccount = ResolvedAccount,
|
||||
Config extends OpenClawConfig = OpenClawConfig,
|
||||
>(params: {
|
||||
sectionKey: string;
|
||||
resolveAccount: (cfg: Config) => ResolvedAccount;
|
||||
resolveAccessorAccount?: (params: { cfg: Config; accountId?: string | null }) => AccessorAccount;
|
||||
listAccountIds?: (cfg: Config) => string[];
|
||||
defaultAccountId?: (cfg: Config) => string;
|
||||
inspectAccount?: (cfg: Config) => unknown;
|
||||
deleteMode?: "remove-section" | "clear-fields";
|
||||
clearBaseFields?: string[];
|
||||
resolveAllowFrom: (account: AccessorAccount) => Array<string | number> | null | undefined;
|
||||
formatAllowFrom: (allowFrom: Array<string | number>) => string[];
|
||||
resolveDefaultTo?: (account: AccessorAccount) => string | number | null | undefined;
|
||||
}): Pick<
|
||||
ChannelConfigAdapter<ResolvedAccount>,
|
||||
| "listAccountIds"
|
||||
| "resolveAccount"
|
||||
| "inspectAccount"
|
||||
| "defaultAccountId"
|
||||
| "setAccountEnabled"
|
||||
| "deleteAccount"
|
||||
| "resolveAllowFrom"
|
||||
| "formatAllowFrom"
|
||||
| "resolveDefaultTo"
|
||||
> {
|
||||
const resolveAccessorAccount =
|
||||
params.resolveAccessorAccount ??
|
||||
(({ cfg }: { cfg: Config; accountId?: string | null }) =>
|
||||
params.resolveAccount(cfg) as unknown as AccessorAccount);
|
||||
|
||||
return {
|
||||
...createTopLevelChannelConfigBase<ResolvedAccount, Config>({
|
||||
sectionKey: params.sectionKey,
|
||||
resolveAccount: params.resolveAccount,
|
||||
listAccountIds: params.listAccountIds,
|
||||
defaultAccountId: params.defaultAccountId,
|
||||
inspectAccount: params.inspectAccount,
|
||||
deleteMode: params.deleteMode,
|
||||
clearBaseFields: params.clearBaseFields,
|
||||
}),
|
||||
...createScopedAccountConfigAccessors<AccessorAccount>({
|
||||
resolveAccount: resolveAccessorAccount,
|
||||
resolveAllowFrom: params.resolveAllowFrom,
|
||||
formatAllowFrom: params.formatAllowFrom,
|
||||
resolveDefaultTo: params.resolveDefaultTo,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Build CRUD/config helpers for channels where the default account lives at channel root and named accounts live under `accounts`. */
|
||||
export function createHybridChannelConfigBase<
|
||||
ResolvedAccount,
|
||||
@@ -288,6 +394,59 @@ export function createHybridChannelConfigBase<
|
||||
};
|
||||
}
|
||||
|
||||
/** Build the full shared config adapter for hybrid channels with allowlist/default target accessors. */
|
||||
export function createHybridChannelConfigAdapter<
|
||||
ResolvedAccount,
|
||||
AccessorAccount = ResolvedAccount,
|
||||
Config extends OpenClawConfig = OpenClawConfig,
|
||||
>(params: {
|
||||
sectionKey: string;
|
||||
listAccountIds: (cfg: Config) => string[];
|
||||
resolveAccount: (cfg: Config, accountId?: string | null) => ResolvedAccount;
|
||||
resolveAccessorAccount?: (params: { cfg: Config; accountId?: string | null }) => AccessorAccount;
|
||||
defaultAccountId: (cfg: Config) => string;
|
||||
inspectAccount?: (cfg: Config, accountId?: string | null) => unknown;
|
||||
clearBaseFields: string[];
|
||||
preserveSectionOnDefaultDelete?: boolean;
|
||||
resolveAllowFrom: (account: AccessorAccount) => Array<string | number> | null | undefined;
|
||||
formatAllowFrom: (allowFrom: Array<string | number>) => string[];
|
||||
resolveDefaultTo?: (account: AccessorAccount) => string | number | null | undefined;
|
||||
}): Pick<
|
||||
ChannelConfigAdapter<ResolvedAccount>,
|
||||
| "listAccountIds"
|
||||
| "resolveAccount"
|
||||
| "inspectAccount"
|
||||
| "defaultAccountId"
|
||||
| "setAccountEnabled"
|
||||
| "deleteAccount"
|
||||
| "resolveAllowFrom"
|
||||
| "formatAllowFrom"
|
||||
| "resolveDefaultTo"
|
||||
> {
|
||||
const resolveAccessorAccount =
|
||||
params.resolveAccessorAccount ??
|
||||
(({ cfg, accountId }: { cfg: Config; accountId?: string | null }) =>
|
||||
params.resolveAccount(cfg, accountId) as unknown as AccessorAccount);
|
||||
|
||||
return {
|
||||
...createHybridChannelConfigBase<ResolvedAccount, Config>({
|
||||
sectionKey: params.sectionKey,
|
||||
listAccountIds: params.listAccountIds,
|
||||
resolveAccount: params.resolveAccount,
|
||||
inspectAccount: params.inspectAccount,
|
||||
defaultAccountId: params.defaultAccountId,
|
||||
clearBaseFields: params.clearBaseFields,
|
||||
preserveSectionOnDefaultDelete: params.preserveSectionOnDefaultDelete,
|
||||
}),
|
||||
...createScopedAccountConfigAccessors<AccessorAccount>({
|
||||
resolveAccount: resolveAccessorAccount,
|
||||
resolveAllowFrom: params.resolveAllowFrom,
|
||||
formatAllowFrom: params.formatAllowFrom,
|
||||
resolveDefaultTo: params.resolveDefaultTo,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Convert account-specific DM security fields into the shared runtime policy resolver shape. */
|
||||
export function createScopedDmSecurityResolver<
|
||||
ResolvedAccount extends { accountId?: string | null },
|
||||
|
||||
@@ -25,10 +25,13 @@ export { createPluginRuntimeStore } from "./runtime-store.js";
|
||||
export { KeyedAsyncQueue } from "./keyed-async-queue.js";
|
||||
|
||||
export {
|
||||
createHybridChannelConfigAdapter,
|
||||
createHybridChannelConfigBase,
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigAdapter,
|
||||
createScopedChannelConfigBase,
|
||||
createScopedDmSecurityResolver,
|
||||
createTopLevelChannelConfigAdapter,
|
||||
createTopLevelChannelConfigBase,
|
||||
mapAllowFromEntries,
|
||||
} from "./channel-config-helpers.js";
|
||||
|
||||
@@ -62,6 +62,9 @@ describe("plugin-sdk subpath exports", () => {
|
||||
it("exports compat helpers", () => {
|
||||
expect(typeof compatSdk.emptyPluginConfigSchema).toBe("function");
|
||||
expect(typeof compatSdk.resolveControlCommandGate).toBe("function");
|
||||
expect(typeof compatSdk.createScopedChannelConfigAdapter).toBe("function");
|
||||
expect(typeof compatSdk.createTopLevelChannelConfigAdapter).toBe("function");
|
||||
expect(typeof compatSdk.createHybridChannelConfigAdapter).toBe("function");
|
||||
});
|
||||
|
||||
it("keeps core focused on generic shared exports", () => {
|
||||
|
||||
Reference in New Issue
Block a user