diff --git a/extensions/feishu/src/directory.test.ts b/extensions/feishu/src/directory.test.ts new file mode 100644 index 00000000000..c06b2fb6c80 --- /dev/null +++ b/extensions/feishu/src/directory.test.ts @@ -0,0 +1,40 @@ +import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; +import { describe, expect, it, vi } from "vitest"; + +vi.mock("./accounts.js", () => ({ + resolveFeishuAccount: vi.fn(() => ({ + configured: false, + config: { + allowFrom: ["user:alice", "user:bob"], + dms: { + "user:carla": {}, + }, + groups: { + "chat-1": {}, + }, + groupAllowFrom: ["chat-2"], + }, + })), +})); + +import { listFeishuDirectoryGroups, listFeishuDirectoryPeers } from "./directory.js"; + +describe("feishu directory (config-backed)", () => { + const cfg = {} as ClawdbotConfig; + + it("merges allowFrom + dms into peer entries", async () => { + const peers = await listFeishuDirectoryPeers({ cfg, query: "a" }); + expect(peers).toEqual([ + { kind: "user", id: "alice" }, + { kind: "user", id: "carla" }, + ]); + }); + + it("merges groups map + groupAllowFrom into group entries", async () => { + const groups = await listFeishuDirectoryGroups({ cfg }); + expect(groups).toEqual([ + { kind: "group", id: "chat-1" }, + { kind: "group", id: "chat-2" }, + ]); + }); +}); diff --git a/extensions/feishu/src/directory.ts b/extensions/feishu/src/directory.ts index e88b94b229c..94c011f6bc0 100644 --- a/extensions/feishu/src/directory.ts +++ b/extensions/feishu/src/directory.ts @@ -1,3 +1,7 @@ +import { + listDirectoryGroupEntriesFromMapKeysAndAllowFrom, + listDirectoryUserEntriesFromAllowFromAndMapKeys, +} from "openclaw/plugin-sdk"; import type { ClawdbotConfig } from "openclaw/plugin-sdk/feishu"; import { resolveFeishuAccount } from "./accounts.js"; import { createFeishuClient } from "./client.js"; @@ -22,31 +26,14 @@ export async function listFeishuDirectoryPeers(params: { accountId?: string; }): Promise { const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); - const feishuCfg = account.config; - const q = params.query?.trim().toLowerCase() || ""; - const ids = new Set(); - - for (const entry of feishuCfg?.allowFrom ?? []) { - const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") { - ids.add(trimmed); - } - } - - for (const userId of Object.keys(feishuCfg?.dms ?? {})) { - const trimmed = userId.trim(); - if (trimmed) { - ids.add(trimmed); - } - } - - return Array.from(ids) - .map((raw) => raw.trim()) - .filter(Boolean) - .map((raw) => normalizeFeishuTarget(raw) ?? raw) - .filter((id) => (q ? id.toLowerCase().includes(q) : true)) - .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) - .map((id) => ({ kind: "user" as const, id })); + return listDirectoryUserEntriesFromAllowFromAndMapKeys({ + allowFrom: account.config.allowFrom, + map: account.config.dms, + query: params.query, + limit: params.limit, + normalizeAllowFromId: (entry) => normalizeFeishuTarget(entry) ?? entry, + normalizeMapKeyId: (entry) => normalizeFeishuTarget(entry) ?? entry, + }); } export async function listFeishuDirectoryGroups(params: { @@ -56,30 +43,12 @@ export async function listFeishuDirectoryGroups(params: { accountId?: string; }): Promise { const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId }); - const feishuCfg = account.config; - const q = params.query?.trim().toLowerCase() || ""; - const ids = new Set(); - - for (const groupId of Object.keys(feishuCfg?.groups ?? {})) { - const trimmed = groupId.trim(); - if (trimmed && trimmed !== "*") { - ids.add(trimmed); - } - } - - for (const entry of feishuCfg?.groupAllowFrom ?? []) { - const trimmed = String(entry).trim(); - if (trimmed && trimmed !== "*") { - ids.add(trimmed); - } - } - - return Array.from(ids) - .map((raw) => raw.trim()) - .filter(Boolean) - .filter((id) => (q ? id.toLowerCase().includes(q) : true)) - .slice(0, params.limit && params.limit > 0 ? params.limit : undefined) - .map((id) => ({ kind: "group" as const, id })); + return listDirectoryGroupEntriesFromMapKeysAndAllowFrom({ + groups: account.config.groups, + allowFrom: account.config.groupAllowFrom, + query: params.query, + limit: params.limit, + }); } export async function listFeishuDirectoryPeersLive(params: { diff --git a/extensions/matrix/src/resolve-targets.ts b/extensions/matrix/src/resolve-targets.ts index 23f0e33727e..272579bc533 100644 --- a/extensions/matrix/src/resolve-targets.ts +++ b/extensions/matrix/src/resolve-targets.ts @@ -1,3 +1,4 @@ +import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk"; import type { ChannelDirectoryEntry, ChannelResolveKind, @@ -71,56 +72,54 @@ export async function resolveMatrixTargets(params: { kind: ChannelResolveKind; runtime?: RuntimeEnv; }): Promise { - const results: ChannelResolveResult[] = []; - for (const input of params.inputs) { - const trimmed = input.trim(); - if (!trimmed) { - results.push({ input, resolved: false, note: "empty input" }); - continue; - } - if (params.kind === "user") { - if (trimmed.startsWith("@") && trimmed.includes(":")) { - results.push({ input, resolved: true, id: trimmed }); - continue; + return await mapAllowlistResolutionInputs({ + inputs: params.inputs, + mapInput: async (input): Promise => { + const trimmed = input.trim(); + if (!trimmed) { + return { input, resolved: false, note: "empty input" }; + } + if (params.kind === "user") { + if (trimmed.startsWith("@") && trimmed.includes(":")) { + return { input, resolved: true, id: trimmed }; + } + try { + const matches = await listMatrixDirectoryPeersLive({ + cfg: params.cfg, + query: trimmed, + limit: 5, + }); + const best = pickBestUserMatch(matches, trimmed); + return { + input, + resolved: Boolean(best?.id), + id: best?.id, + name: best?.name, + note: best ? undefined : describeUserMatchFailure(matches, trimmed), + }; + } catch (err) { + params.runtime?.error?.(`matrix resolve failed: ${String(err)}`); + return { input, resolved: false, note: "lookup failed" }; + } } try { - const matches = await listMatrixDirectoryPeersLive({ + const matches = await listMatrixDirectoryGroupsLive({ cfg: params.cfg, query: trimmed, limit: 5, }); - const best = pickBestUserMatch(matches, trimmed); - results.push({ + const best = pickBestGroupMatch(matches, trimmed); + return { input, resolved: Boolean(best?.id), id: best?.id, name: best?.name, - note: best ? undefined : describeUserMatchFailure(matches, trimmed), - }); + note: matches.length > 1 ? "multiple matches; chose first" : undefined, + }; } catch (err) { params.runtime?.error?.(`matrix resolve failed: ${String(err)}`); - results.push({ input, resolved: false, note: "lookup failed" }); + return { input, resolved: false, note: "lookup failed" }; } - continue; - } - try { - const matches = await listMatrixDirectoryGroupsLive({ - cfg: params.cfg, - query: trimmed, - limit: 5, - }); - const best = pickBestGroupMatch(matches, trimmed); - results.push({ - input, - resolved: Boolean(best?.id), - id: best?.id, - name: best?.name, - note: matches.length > 1 ? "multiple matches; chose first" : undefined, - }); - } catch (err) { - params.runtime?.error?.(`matrix resolve failed: ${String(err)}`); - results.push({ input, resolved: false, note: "lookup failed" }); - } - } - return results; + }, + }); } diff --git a/extensions/msteams/src/resolve-allowlist.test.ts b/extensions/msteams/src/resolve-allowlist.test.ts new file mode 100644 index 00000000000..03d97c15b01 --- /dev/null +++ b/extensions/msteams/src/resolve-allowlist.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it, vi } from "vitest"; + +const { + listTeamsByName, + listChannelsForTeam, + normalizeQuery, + resolveGraphToken, + searchGraphUsers, +} = vi.hoisted(() => ({ + listTeamsByName: vi.fn(), + listChannelsForTeam: vi.fn(), + normalizeQuery: vi.fn((value: string) => value.trim().toLowerCase()), + resolveGraphToken: vi.fn(async () => "graph-token"), + searchGraphUsers: vi.fn(), +})); + +vi.mock("./graph.js", () => ({ + listTeamsByName, + listChannelsForTeam, + normalizeQuery, + resolveGraphToken, +})); + +vi.mock("./graph-users.js", () => ({ + searchGraphUsers, +})); + +import { + resolveMSTeamsChannelAllowlist, + resolveMSTeamsUserAllowlist, +} from "./resolve-allowlist.js"; + +describe("resolveMSTeamsUserAllowlist", () => { + it("marks empty input unresolved", async () => { + const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: [" "] }); + expect(result).toEqual({ input: " ", resolved: false }); + }); + + it("resolves first Graph user match", async () => { + searchGraphUsers.mockResolvedValueOnce([ + { id: "user-1", displayName: "Alice One" }, + { id: "user-2", displayName: "Alice Two" }, + ]); + const [result] = await resolveMSTeamsUserAllowlist({ cfg: {}, entries: ["alice"] }); + expect(result).toEqual({ + input: "alice", + resolved: true, + id: "user-1", + name: "Alice One", + note: "multiple matches; chose first", + }); + }); +}); + +describe("resolveMSTeamsChannelAllowlist", () => { + it("resolves team/channel by team name + channel display name", async () => { + listTeamsByName.mockResolvedValueOnce([{ id: "team-1", displayName: "Product Team" }]); + listChannelsForTeam.mockResolvedValueOnce([ + { id: "channel-1", displayName: "General" }, + { id: "channel-2", displayName: "Roadmap" }, + ]); + + const [result] = await resolveMSTeamsChannelAllowlist({ + cfg: {}, + entries: ["Product Team/Roadmap"], + }); + + expect(result).toEqual({ + input: "Product Team/Roadmap", + resolved: true, + teamId: "team-1", + teamName: "Product Team", + channelId: "channel-2", + channelName: "Roadmap", + note: "multiple channels; chose first", + }); + }); +}); diff --git a/extensions/msteams/src/resolve-allowlist.ts b/extensions/msteams/src/resolve-allowlist.ts index 1e66c4972df..ae9aad7c67a 100644 --- a/extensions/msteams/src/resolve-allowlist.ts +++ b/extensions/msteams/src/resolve-allowlist.ts @@ -1,3 +1,4 @@ +import { mapAllowlistResolutionInputs } from "openclaw/plugin-sdk"; import { searchGraphUsers } from "./graph-users.js"; import { listChannelsForTeam, @@ -105,61 +106,55 @@ export async function resolveMSTeamsChannelAllowlist(params: { entries: string[]; }): Promise { const token = await resolveGraphToken(params.cfg); - const results: MSTeamsChannelResolution[] = []; - - for (const input of params.entries) { - const { team, channel } = parseMSTeamsTeamChannelInput(input); - if (!team) { - results.push({ input, resolved: false }); - continue; - } - const teams = /^[0-9a-fA-F-]{16,}$/.test(team) - ? [{ id: team, displayName: team }] - : await listTeamsByName(token, team); - if (teams.length === 0) { - results.push({ input, resolved: false, note: "team not found" }); - continue; - } - const teamMatch = teams[0]; - const teamId = teamMatch.id?.trim(); - const teamName = teamMatch.displayName?.trim() || team; - if (!teamId) { - results.push({ input, resolved: false, note: "team id missing" }); - continue; - } - if (!channel) { - results.push({ + return await mapAllowlistResolutionInputs({ + inputs: params.entries, + mapInput: async (input): Promise => { + const { team, channel } = parseMSTeamsTeamChannelInput(input); + if (!team) { + return { input, resolved: false }; + } + const teams = /^[0-9a-fA-F-]{16,}$/.test(team) + ? [{ id: team, displayName: team }] + : await listTeamsByName(token, team); + if (teams.length === 0) { + return { input, resolved: false, note: "team not found" }; + } + const teamMatch = teams[0]; + const teamId = teamMatch.id?.trim(); + const teamName = teamMatch.displayName?.trim() || team; + if (!teamId) { + return { input, resolved: false, note: "team id missing" }; + } + if (!channel) { + return { + input, + resolved: true, + teamId, + teamName, + note: teams.length > 1 ? "multiple teams; chose first" : undefined, + }; + } + const channels = await listChannelsForTeam(token, teamId); + const channelMatch = + channels.find((item) => item.id === channel) ?? + channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ?? + channels.find((item) => + item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""), + ); + if (!channelMatch?.id) { + return { input, resolved: false, note: "channel not found" }; + } + return { input, resolved: true, teamId, teamName, - note: teams.length > 1 ? "multiple teams; chose first" : undefined, - }); - continue; - } - const channels = await listChannelsForTeam(token, teamId); - const channelMatch = - channels.find((item) => item.id === channel) ?? - channels.find((item) => item.displayName?.toLowerCase() === channel.toLowerCase()) ?? - channels.find((item) => - item.displayName?.toLowerCase().includes(channel.toLowerCase() ?? ""), - ); - if (!channelMatch?.id) { - results.push({ input, resolved: false, note: "channel not found" }); - continue; - } - results.push({ - input, - resolved: true, - teamId, - teamName, - channelId: channelMatch.id, - channelName: channelMatch.displayName ?? channel, - note: channels.length > 1 ? "multiple channels; chose first" : undefined, - }); - } - - return results; + channelId: channelMatch.id, + channelName: channelMatch.displayName ?? channel, + note: channels.length > 1 ? "multiple channels; chose first" : undefined, + }; + }, + }); } export async function resolveMSTeamsUserAllowlist(params: { @@ -167,32 +162,28 @@ export async function resolveMSTeamsUserAllowlist(params: { entries: string[]; }): Promise { const token = await resolveGraphToken(params.cfg); - const results: MSTeamsUserResolution[] = []; - - for (const input of params.entries) { - const query = normalizeQuery(normalizeMSTeamsUserInput(input)); - if (!query) { - results.push({ input, resolved: false }); - continue; - } - if (/^[0-9a-fA-F-]{16,}$/.test(query)) { - results.push({ input, resolved: true, id: query }); - continue; - } - const users = await searchGraphUsers({ token, query, top: 10 }); - const match = users[0]; - if (!match?.id) { - results.push({ input, resolved: false }); - continue; - } - results.push({ - input, - resolved: true, - id: match.id, - name: match.displayName ?? undefined, - note: users.length > 1 ? "multiple matches; chose first" : undefined, - }); - } - - return results; + return await mapAllowlistResolutionInputs({ + inputs: params.entries, + mapInput: async (input): Promise => { + const query = normalizeQuery(normalizeMSTeamsUserInput(input)); + if (!query) { + return { input, resolved: false }; + } + if (/^[0-9a-fA-F-]{16,}$/.test(query)) { + return { input, resolved: true, id: query }; + } + const users = await searchGraphUsers({ token, query, top: 10 }); + const match = users[0]; + if (!match?.id) { + return { input, resolved: false }; + } + return { + input, + resolved: true, + id: match.id, + name: match.displayName ?? undefined, + note: users.length > 1 ? "multiple matches; chose first" : undefined, + }; + }, + }); } diff --git a/src/channels/plugins/directory-config-helpers.test.ts b/src/channels/plugins/directory-config-helpers.test.ts index c4f5370cc4a..c9ba1429791 100644 --- a/src/channels/plugins/directory-config-helpers.test.ts +++ b/src/channels/plugins/directory-config-helpers.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "vitest"; import { + listDirectoryGroupEntriesFromMapKeysAndAllowFrom, listDirectoryGroupEntriesFromMapKeys, + listDirectoryUserEntriesFromAllowFromAndMapKeys, listDirectoryUserEntriesFromAllowFrom, } from "./directory-config-helpers.js"; @@ -37,3 +39,41 @@ describe("listDirectoryGroupEntriesFromMapKeys", () => { ]); }); }); + +describe("listDirectoryUserEntriesFromAllowFromAndMapKeys", () => { + it("merges allowFrom and map keys with dedupe/query/limit", () => { + const entries = listDirectoryUserEntriesFromAllowFromAndMapKeys({ + allowFrom: ["user:alice", "user:bob"], + map: { + "user:carla": {}, + "user:alice": {}, + }, + normalizeAllowFromId: (entry) => entry.replace(/^user:/i, ""), + normalizeMapKeyId: (entry) => entry.replace(/^user:/i, ""), + query: "a", + limit: 2, + }); + + expect(entries).toEqual([ + { kind: "user", id: "alice" }, + { kind: "user", id: "carla" }, + ]); + }); +}); + +describe("listDirectoryGroupEntriesFromMapKeysAndAllowFrom", () => { + it("merges groups keys and group allowFrom entries", () => { + const entries = listDirectoryGroupEntriesFromMapKeysAndAllowFrom({ + groups: { + "team/a": {}, + }, + allowFrom: ["team/b", "team/a"], + query: "team/", + }); + + expect(entries).toEqual([ + { kind: "group", id: "team/a" }, + { kind: "group", id: "team/b" }, + ]); + }); +}); diff --git a/src/channels/plugins/directory-config-helpers.ts b/src/channels/plugins/directory-config-helpers.ts index b431bc14a8b..13cd05d65c3 100644 --- a/src/channels/plugins/directory-config-helpers.ts +++ b/src/channels/plugins/directory-config-helpers.ts @@ -22,44 +22,106 @@ function toDirectoryEntries(kind: "user" | "group", ids: string[]): ChannelDirec return ids.map((id) => ({ kind, id }) as const); } +function collectDirectoryIdsFromEntries(params: { + entries?: readonly unknown[]; + normalizeId?: (entry: string) => string | null | undefined; +}): string[] { + return (params.entries ?? []) + .map((entry) => String(entry).trim()) + .filter((entry) => Boolean(entry) && entry !== "*") + .map((entry) => { + const normalized = params.normalizeId ? params.normalizeId(entry) : entry; + return typeof normalized === "string" ? normalized.trim() : ""; + }) + .filter(Boolean); +} + +function collectDirectoryIdsFromMapKeys(params: { + groups?: Record; + normalizeId?: (entry: string) => string | null | undefined; +}): string[] { + return Object.keys(params.groups ?? {}) + .map((entry) => entry.trim()) + .filter((entry) => Boolean(entry) && entry !== "*") + .map((entry) => { + const normalized = params.normalizeId ? params.normalizeId(entry) : entry; + return typeof normalized === "string" ? normalized.trim() : ""; + }) + .filter(Boolean); +} + +function dedupeDirectoryIds(ids: string[]): string[] { + return Array.from(new Set(ids)); +} + export function listDirectoryUserEntriesFromAllowFrom(params: { allowFrom?: readonly unknown[]; query?: string | null; limit?: number | null; normalizeId?: (entry: string) => string | null | undefined; }): ChannelDirectoryEntry[] { - const ids = Array.from( - new Set( - (params.allowFrom ?? []) - .map((entry) => String(entry).trim()) - .filter((entry) => Boolean(entry) && entry !== "*") - .map((entry) => { - const normalized = params.normalizeId ? params.normalizeId(entry) : entry; - return typeof normalized === "string" ? normalized.trim() : ""; - }) - .filter(Boolean), - ), + const ids = dedupeDirectoryIds( + collectDirectoryIdsFromEntries({ + entries: params.allowFrom, + normalizeId: params.normalizeId, + }), ); return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params)); } +export function listDirectoryUserEntriesFromAllowFromAndMapKeys(params: { + allowFrom?: readonly unknown[]; + map?: Record; + query?: string | null; + limit?: number | null; + normalizeAllowFromId?: (entry: string) => string | null | undefined; + normalizeMapKeyId?: (entry: string) => string | null | undefined; +}): ChannelDirectoryEntry[] { + const ids = dedupeDirectoryIds([ + ...collectDirectoryIdsFromEntries({ + entries: params.allowFrom, + normalizeId: params.normalizeAllowFromId, + }), + ...collectDirectoryIdsFromMapKeys({ + groups: params.map, + normalizeId: params.normalizeMapKeyId, + }), + ]); + return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params)); +} + export function listDirectoryGroupEntriesFromMapKeys(params: { groups?: Record; query?: string | null; limit?: number | null; normalizeId?: (entry: string) => string | null | undefined; }): ChannelDirectoryEntry[] { - const ids = Array.from( - new Set( - Object.keys(params.groups ?? {}) - .map((entry) => entry.trim()) - .filter((entry) => Boolean(entry) && entry !== "*") - .map((entry) => { - const normalized = params.normalizeId ? params.normalizeId(entry) : entry; - return typeof normalized === "string" ? normalized.trim() : ""; - }) - .filter(Boolean), - ), + const ids = dedupeDirectoryIds( + collectDirectoryIdsFromMapKeys({ + groups: params.groups, + normalizeId: params.normalizeId, + }), ); return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params)); } + +export function listDirectoryGroupEntriesFromMapKeysAndAllowFrom(params: { + groups?: Record; + allowFrom?: readonly unknown[]; + query?: string | null; + limit?: number | null; + normalizeMapKeyId?: (entry: string) => string | null | undefined; + normalizeAllowFromId?: (entry: string) => string | null | undefined; +}): ChannelDirectoryEntry[] { + const ids = dedupeDirectoryIds([ + ...collectDirectoryIdsFromMapKeys({ + groups: params.groups, + normalizeId: params.normalizeMapKeyId, + }), + ...collectDirectoryIdsFromEntries({ + entries: params.allowFrom, + normalizeId: params.normalizeAllowFromId, + }), + ]); + return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params)); +} diff --git a/src/plugin-sdk/allowlist-resolution.test.ts b/src/plugin-sdk/allowlist-resolution.test.ts index 84b51101c33..5b606cfbe9f 100644 --- a/src/plugin-sdk/allowlist-resolution.test.ts +++ b/src/plugin-sdk/allowlist-resolution.test.ts @@ -1,40 +1,18 @@ import { describe, expect, it } from "vitest"; -import { - mapBasicAllowlistResolutionEntries, - type BasicAllowlistResolutionEntry, -} from "./allowlist-resolution.js"; +import { mapAllowlistResolutionInputs } from "./allowlist-resolution.js"; -describe("mapBasicAllowlistResolutionEntries", () => { - it("maps entries to normalized allowlist resolver output", () => { - const entries: BasicAllowlistResolutionEntry[] = [ - { - input: "alice", - resolved: true, - id: "U123", - name: "Alice", - note: "ok", +describe("mapAllowlistResolutionInputs", () => { + it("maps inputs sequentially and preserves order", async () => { + const visited: string[] = []; + const result = await mapAllowlistResolutionInputs({ + inputs: ["one", "two", "three"], + mapInput: async (input) => { + visited.push(input); + return input.toUpperCase(); }, - { - input: "bob", - resolved: false, - }, - ]; + }); - expect(mapBasicAllowlistResolutionEntries(entries)).toEqual([ - { - input: "alice", - resolved: true, - id: "U123", - name: "Alice", - note: "ok", - }, - { - input: "bob", - resolved: false, - id: undefined, - name: undefined, - note: undefined, - }, - ]); + expect(visited).toEqual(["one", "two", "three"]); + expect(result).toEqual(["ONE", "TWO", "THREE"]); }); }); diff --git a/src/plugin-sdk/allowlist-resolution.ts b/src/plugin-sdk/allowlist-resolution.ts index edfb27d9ef8..8e955e422b3 100644 --- a/src/plugin-sdk/allowlist-resolution.ts +++ b/src/plugin-sdk/allowlist-resolution.ts @@ -17,3 +17,14 @@ export function mapBasicAllowlistResolutionEntries( note: entry.note, })); } + +export async function mapAllowlistResolutionInputs(params: { + inputs: string[]; + mapInput: (input: string) => Promise | T; +}): Promise { + const results: T[] = []; + for (const input of params.inputs) { + results.push(await params.mapInput(input)); + } + return results; +} diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 156d183a27c..684a4dbc4a6 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -133,6 +133,7 @@ export { isDangerousNameMatchingEnabled } from "../config/dangerous-name-matchin export type { FileLockHandle, FileLockOptions } from "./file-lock.js"; export { acquireFileLock, withFileLock } from "./file-lock.js"; export { + mapAllowlistResolutionInputs, mapBasicAllowlistResolutionEntries, type BasicAllowlistResolutionEntry, } from "./allowlist-resolution.js"; @@ -515,6 +516,12 @@ export { optionalStringEnum, stringEnum } from "../agents/schema/typebox.js"; export type { PollInput } from "../polls.js"; export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { + listDirectoryGroupEntriesFromMapKeys, + listDirectoryGroupEntriesFromMapKeysAndAllowFrom, + listDirectoryUserEntriesFromAllowFrom, + listDirectoryUserEntriesFromAllowFromAndMapKeys, +} from "../channels/plugins/directory-config-helpers.js"; export { clearAccountEntryFields, deleteAccountFromConfigSection,