mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
test: slim channel directory contracts
This commit is contained in:
4
extensions/discord/directory-contract-api.ts
Normal file
4
extensions/discord/directory-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "./src/directory-config.js";
|
||||
4
extensions/slack/directory-contract-api.ts
Normal file
4
extensions/slack/directory-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
} from "./src/directory-config.js";
|
||||
4
extensions/telegram/directory-contract-api.ts
Normal file
4
extensions/telegram/directory-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "./src/directory-config.js";
|
||||
37
extensions/telegram/src/account-config.ts
Normal file
37
extensions/telegram/src/account-config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
normalizeAccountId,
|
||||
resolveAccountEntry,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-core";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
|
||||
export function resolveTelegramAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): TelegramAccountConfig | undefined {
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
return resolveAccountEntry(cfg.channels?.telegram?.accounts, normalized);
|
||||
}
|
||||
|
||||
export function mergeTelegramAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): TelegramAccountConfig {
|
||||
const {
|
||||
accounts: _ignored,
|
||||
defaultAccount: _ignoredDefaultAccount,
|
||||
groups: channelGroups,
|
||||
...base
|
||||
} = (cfg.channels?.telegram ?? {}) as TelegramAccountConfig & {
|
||||
accounts?: unknown;
|
||||
defaultAccount?: unknown;
|
||||
};
|
||||
const account = resolveTelegramAccountConfig(cfg, accountId) ?? {};
|
||||
|
||||
// Multi-account bots must not inherit channel-level groups unless explicitly set.
|
||||
const configuredAccountIds = Object.keys(cfg.channels?.telegram?.accounts ?? {});
|
||||
const isMultiAccount = configuredAccountIds.length > 1;
|
||||
const groups = account.groups ?? (isMultiAccount ? undefined : channelGroups);
|
||||
|
||||
return { ...base, ...account, groups };
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
createAccountActionGate,
|
||||
normalizeAccountId,
|
||||
normalizeOptionalAccountId,
|
||||
resolveAccountEntry,
|
||||
resolveAccountWithDefaultFallback,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-core";
|
||||
@@ -14,6 +13,7 @@ import type {
|
||||
import { formatSetExplicitDefaultInstruction } from "openclaw/plugin-sdk/routing";
|
||||
import { createSubsystemLogger, isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { mergeTelegramAccountConfig, resolveTelegramAccountConfig } from "./account-config.js";
|
||||
import {
|
||||
listTelegramAccountIds as listSelectedTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountSelection,
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import type { TelegramTransport } from "./fetch.js";
|
||||
import { resolveTelegramToken } from "./token.js";
|
||||
|
||||
export { mergeTelegramAccountConfig, resolveTelegramAccountConfig } from "./account-config.js";
|
||||
|
||||
let log: ReturnType<typeof createSubsystemLogger> | null = null;
|
||||
|
||||
function getLog() {
|
||||
@@ -89,43 +91,6 @@ export function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string {
|
||||
return selection.accountId;
|
||||
}
|
||||
|
||||
export function resolveTelegramAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): TelegramAccountConfig | undefined {
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
return resolveAccountEntry(cfg.channels?.telegram?.accounts, normalized);
|
||||
}
|
||||
|
||||
export function mergeTelegramAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): TelegramAccountConfig {
|
||||
const {
|
||||
accounts: _ignored,
|
||||
defaultAccount: _ignoredDefaultAccount,
|
||||
groups: channelGroups,
|
||||
...base
|
||||
} = (cfg.channels?.telegram ?? {}) as TelegramAccountConfig & {
|
||||
accounts?: unknown;
|
||||
defaultAccount?: unknown;
|
||||
};
|
||||
const account = resolveTelegramAccountConfig(cfg, accountId) ?? {};
|
||||
|
||||
// In multi-account setups, channel-level `groups` must NOT be inherited by
|
||||
// accounts that don't have their own `groups` config. A bot that is not a
|
||||
// member of a configured group will fail when handling group messages, and
|
||||
// this failure disrupts message delivery for *all* accounts.
|
||||
// Single-account setups keep backward compat: channel-level groups still
|
||||
// applies when the account has no override.
|
||||
// See: https://github.com/openclaw/openclaw/issues/30673
|
||||
const configuredAccountIds = Object.keys(cfg.channels?.telegram?.accounts ?? {});
|
||||
const isMultiAccount = configuredAccountIds.length > 1;
|
||||
const groups = account.groups ?? (isMultiAccount ? undefined : channelGroups);
|
||||
|
||||
return { ...base, ...account, groups };
|
||||
}
|
||||
|
||||
export function createTelegramActionGate(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/account-core";
|
||||
import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createInspectedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { inspectTelegramAccount, type InspectedTelegramAccount } from "./account-inspect.js";
|
||||
import type { OpenClawConfig, TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { mergeTelegramAccountConfig } from "./account-config.js";
|
||||
import { resolveDefaultTelegramAccountSelection } from "./account-selection.js";
|
||||
|
||||
type TelegramDirectoryAccount = {
|
||||
config: TelegramAccountConfig;
|
||||
};
|
||||
|
||||
function resolveTelegramDirectoryAccount(
|
||||
cfg: OpenClawConfig,
|
||||
accountId?: string | null,
|
||||
): TelegramDirectoryAccount {
|
||||
const resolvedAccountId = accountId?.trim()
|
||||
? normalizeAccountId(accountId)
|
||||
: resolveDefaultTelegramAccountSelection(cfg).accountId;
|
||||
return {
|
||||
config: mergeTelegramAccountConfig(cfg, resolvedAccountId),
|
||||
};
|
||||
}
|
||||
|
||||
export const listTelegramDirectoryPeersFromConfig =
|
||||
createInspectedDirectoryEntriesLister<InspectedTelegramAccount>({
|
||||
createResolvedDirectoryEntriesLister<TelegramDirectoryAccount>({
|
||||
kind: "user",
|
||||
inspectAccount: (cfg, accountId) =>
|
||||
inspectTelegramAccount({ cfg, accountId }) as InspectedTelegramAccount | null,
|
||||
resolveAccount: (cfg, accountId) => resolveTelegramDirectoryAccount(cfg, accountId),
|
||||
resolveSources: (account) => [
|
||||
mapAllowFromEntries(account.config.allowFrom),
|
||||
Object.keys(account.config.dms ?? {}),
|
||||
@@ -24,10 +42,9 @@ export const listTelegramDirectoryPeersFromConfig =
|
||||
});
|
||||
|
||||
export const listTelegramDirectoryGroupsFromConfig =
|
||||
createInspectedDirectoryEntriesLister<InspectedTelegramAccount>({
|
||||
createResolvedDirectoryEntriesLister<TelegramDirectoryAccount>({
|
||||
kind: "group",
|
||||
inspectAccount: (cfg, accountId) =>
|
||||
inspectTelegramAccount({ cfg, accountId }) as InspectedTelegramAccount | null,
|
||||
resolveAccount: (cfg, accountId) => resolveTelegramDirectoryAccount(cfg, accountId),
|
||||
resolveSources: (account) => [Object.keys(account.config.groups ?? {})],
|
||||
normalizeId: (entry) => entry.trim() || null,
|
||||
});
|
||||
|
||||
4
extensions/whatsapp/directory-contract-api.ts
Normal file
4
extensions/whatsapp/directory-contract-api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "./src/directory-config.js";
|
||||
@@ -6,56 +6,66 @@ import type {
|
||||
} from "../../../src/channels/plugins/types.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { LineProbeResult } from "../../../src/plugin-sdk/line.js";
|
||||
import { loadBundledPluginContractApiSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import { loadBundledPluginPublicSurfaceSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
import { withEnvAsync } from "../../../src/test-utils/env.js";
|
||||
|
||||
type DiscordContractApiSurface = Pick<
|
||||
typeof import("@openclaw/discord/contract-api.js"),
|
||||
type DiscordDirectoryContractApiSurface = Pick<
|
||||
typeof import("@openclaw/discord/directory-contract-api.js"),
|
||||
"listDiscordDirectoryPeersFromConfig" | "listDiscordDirectoryGroupsFromConfig"
|
||||
>;
|
||||
type DiscordProbe = import("@openclaw/discord/api.js").DiscordProbe;
|
||||
type DiscordTokenResolution = import("@openclaw/discord/api.js").DiscordTokenResolution;
|
||||
type IMessageProbe = import("@openclaw/imessage/runtime-api.js").IMessageProbe;
|
||||
type SignalProbe = import("@openclaw/signal/api.js").SignalProbe;
|
||||
type SlackContractApiSurface = Pick<
|
||||
typeof import("@openclaw/slack/contract-api.js"),
|
||||
type SlackDirectoryContractApiSurface = Pick<
|
||||
typeof import("@openclaw/slack/directory-contract-api.js"),
|
||||
"listSlackDirectoryPeersFromConfig" | "listSlackDirectoryGroupsFromConfig"
|
||||
>;
|
||||
type SlackProbe = import("@openclaw/slack/api.js").SlackProbe;
|
||||
type TelegramContractApiSurface = Pick<
|
||||
typeof import("@openclaw/telegram/contract-api.js"),
|
||||
type TelegramDirectoryContractApiSurface = Pick<
|
||||
typeof import("@openclaw/telegram/directory-contract-api.js"),
|
||||
"listTelegramDirectoryPeersFromConfig" | "listTelegramDirectoryGroupsFromConfig"
|
||||
>;
|
||||
type TelegramProbe = import("@openclaw/telegram/api.js").TelegramProbe;
|
||||
type TelegramTokenResolution = import("@openclaw/telegram/api.js").TelegramTokenResolution;
|
||||
type WhatsAppContractApiSurface = Pick<
|
||||
typeof import("@openclaw/whatsapp/contract-api.js"),
|
||||
type WhatsAppDirectoryContractApiSurface = Pick<
|
||||
typeof import("@openclaw/whatsapp/directory-contract-api.js"),
|
||||
"listWhatsAppDirectoryPeersFromConfig" | "listWhatsAppDirectoryGroupsFromConfig"
|
||||
>;
|
||||
|
||||
let discordContractApi: DiscordContractApiSurface | undefined;
|
||||
let slackContractApi: SlackContractApiSurface | undefined;
|
||||
let telegramContractApi: TelegramContractApiSurface | undefined;
|
||||
let whatsappContractApi: WhatsAppContractApiSurface | undefined;
|
||||
let discordDirectoryContractApi: DiscordDirectoryContractApiSurface | undefined;
|
||||
let slackDirectoryContractApi: SlackDirectoryContractApiSurface | undefined;
|
||||
let telegramDirectoryContractApi: TelegramDirectoryContractApiSurface | undefined;
|
||||
let whatsappDirectoryContractApi: WhatsAppDirectoryContractApiSurface | undefined;
|
||||
|
||||
function getDiscordContractApi(): DiscordContractApiSurface {
|
||||
discordContractApi ??= loadBundledPluginContractApiSync<DiscordContractApiSurface>("discord");
|
||||
return discordContractApi;
|
||||
function loadDirectoryContractApi<T extends object>(pluginId: string): T {
|
||||
return loadBundledPluginPublicSurfaceSync<T>({
|
||||
pluginId,
|
||||
artifactBasename: "directory-contract-api.js",
|
||||
});
|
||||
}
|
||||
|
||||
function getSlackContractApi(): SlackContractApiSurface {
|
||||
slackContractApi ??= loadBundledPluginContractApiSync<SlackContractApiSurface>("slack");
|
||||
return slackContractApi;
|
||||
function getDiscordDirectoryContractApi(): DiscordDirectoryContractApiSurface {
|
||||
discordDirectoryContractApi ??=
|
||||
loadDirectoryContractApi<DiscordDirectoryContractApiSurface>("discord");
|
||||
return discordDirectoryContractApi;
|
||||
}
|
||||
|
||||
function getTelegramContractApi(): TelegramContractApiSurface {
|
||||
telegramContractApi ??= loadBundledPluginContractApiSync<TelegramContractApiSurface>("telegram");
|
||||
return telegramContractApi;
|
||||
function getSlackDirectoryContractApi(): SlackDirectoryContractApiSurface {
|
||||
slackDirectoryContractApi ??= loadDirectoryContractApi<SlackDirectoryContractApiSurface>("slack");
|
||||
return slackDirectoryContractApi;
|
||||
}
|
||||
|
||||
function getWhatsAppContractApi(): WhatsAppContractApiSurface {
|
||||
whatsappContractApi ??= loadBundledPluginContractApiSync<WhatsAppContractApiSurface>("whatsapp");
|
||||
return whatsappContractApi;
|
||||
function getTelegramDirectoryContractApi(): TelegramDirectoryContractApiSurface {
|
||||
telegramDirectoryContractApi ??=
|
||||
loadDirectoryContractApi<TelegramDirectoryContractApiSurface>("telegram");
|
||||
return telegramDirectoryContractApi;
|
||||
}
|
||||
|
||||
function getWhatsAppDirectoryContractApi(): WhatsAppDirectoryContractApiSurface {
|
||||
whatsappDirectoryContractApi ??=
|
||||
loadDirectoryContractApi<WhatsAppDirectoryContractApiSurface>("whatsapp");
|
||||
return whatsappDirectoryContractApi;
|
||||
}
|
||||
|
||||
type DirectoryListFn = (params: {
|
||||
@@ -87,8 +97,8 @@ async function expectDirectoryIds(
|
||||
|
||||
export function describeDiscordPluginsCoreExtensionContract() {
|
||||
describe("discord plugins-core extension contract", () => {
|
||||
const listPeers = () => getDiscordContractApi().listDiscordDirectoryPeersFromConfig;
|
||||
const listGroups = () => getDiscordContractApi().listDiscordDirectoryGroupsFromConfig;
|
||||
const listPeers = () => getDiscordDirectoryContractApi().listDiscordDirectoryPeersFromConfig;
|
||||
const listGroups = () => getDiscordDirectoryContractApi().listDiscordDirectoryGroupsFromConfig;
|
||||
|
||||
it("DiscordProbe satisfies BaseProbeResult", () => {
|
||||
expectTypeOf<DiscordProbe>().toMatchTypeOf<BaseProbeResult>();
|
||||
@@ -188,8 +198,8 @@ export function describeDiscordPluginsCoreExtensionContract() {
|
||||
|
||||
export function describeSlackPluginsCoreExtensionContract() {
|
||||
describe("slack plugins-core extension contract", () => {
|
||||
const listPeers = () => getSlackContractApi().listSlackDirectoryPeersFromConfig;
|
||||
const listGroups = () => getSlackContractApi().listSlackDirectoryGroupsFromConfig;
|
||||
const listPeers = () => getSlackDirectoryContractApi().listSlackDirectoryPeersFromConfig;
|
||||
const listGroups = () => getSlackDirectoryContractApi().listSlackDirectoryGroupsFromConfig;
|
||||
|
||||
it("SlackProbe satisfies BaseProbeResult", () => {
|
||||
expectTypeOf<SlackProbe>().toMatchTypeOf<BaseProbeResult>();
|
||||
@@ -264,8 +274,9 @@ export function describeSlackPluginsCoreExtensionContract() {
|
||||
|
||||
export function describeTelegramPluginsCoreExtensionContract() {
|
||||
describe("telegram plugins-core extension contract", () => {
|
||||
const listPeers = () => getTelegramContractApi().listTelegramDirectoryPeersFromConfig;
|
||||
const listGroups = () => getTelegramContractApi().listTelegramDirectoryGroupsFromConfig;
|
||||
const listPeers = () => getTelegramDirectoryContractApi().listTelegramDirectoryPeersFromConfig;
|
||||
const listGroups = () =>
|
||||
getTelegramDirectoryContractApi().listTelegramDirectoryGroupsFromConfig;
|
||||
|
||||
it("TelegramProbe satisfies BaseProbeResult", () => {
|
||||
expectTypeOf<TelegramProbe>().toMatchTypeOf<BaseProbeResult>();
|
||||
@@ -359,8 +370,9 @@ export function describeTelegramPluginsCoreExtensionContract() {
|
||||
|
||||
export function describeWhatsAppPluginsCoreExtensionContract() {
|
||||
describe("whatsapp plugins-core extension contract", () => {
|
||||
const listPeers = () => getWhatsAppContractApi().listWhatsAppDirectoryPeersFromConfig;
|
||||
const listGroups = () => getWhatsAppContractApi().listWhatsAppDirectoryGroupsFromConfig;
|
||||
const listPeers = () => getWhatsAppDirectoryContractApi().listWhatsAppDirectoryPeersFromConfig;
|
||||
const listGroups = () =>
|
||||
getWhatsAppDirectoryContractApi().listWhatsAppDirectoryGroupsFromConfig;
|
||||
|
||||
it("lists peers/groups from config", async () => {
|
||||
const cfg = {
|
||||
|
||||
Reference in New Issue
Block a user