mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 01:21:36 +00:00
refactor: finish plugin-owned channel runtime seams
This commit is contained in:
@@ -41,9 +41,17 @@ import {
|
||||
type OpenClawConfig,
|
||||
type ResolvedDiscordAccount,
|
||||
} from "openclaw/plugin-sdk/discord";
|
||||
import {
|
||||
buildAgentSessionKey,
|
||||
resolveThreadSessionKeys,
|
||||
type RoutePeer,
|
||||
} from "openclaw/plugin-sdk/routing";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import { normalizeMessageChannel } from "../../../src/utils/message-channel.js";
|
||||
import { isDiscordExecApprovalClientEnabled } from "./exec-approvals.js";
|
||||
import {
|
||||
isDiscordExecApprovalClientEnabled,
|
||||
shouldSuppressLocalDiscordExecApprovalPrompt,
|
||||
} from "./exec-approvals.js";
|
||||
import type { DiscordProbe } from "./probe.js";
|
||||
import { resolveDiscordUserAllowlist } from "./resolve-users.js";
|
||||
import { getDiscordRuntime } from "./runtime.js";
|
||||
@@ -453,6 +461,12 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
isDiscordExecApprovalClientEnabled({ cfg, accountId })
|
||||
? { kind: "enabled" }
|
||||
: { kind: "disabled" },
|
||||
shouldSuppressLocalPrompt: ({ cfg, accountId, payload }) =>
|
||||
shouldSuppressLocalDiscordExecApprovalPrompt({
|
||||
cfg,
|
||||
accountId,
|
||||
payload,
|
||||
}),
|
||||
hasConfiguredDmRoute: ({ cfg }) => hasDiscordExecApprovalDmRoute(cfg),
|
||||
shouldSuppressForwardingFallback: ({ cfg, target }) =>
|
||||
(normalizeMessageChannel(target.channel) ?? target.channel) === "discord" &&
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type ChannelPlugin,
|
||||
type ResolvedIMessageAccount,
|
||||
} from "openclaw/plugin-sdk/imessage";
|
||||
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js";
|
||||
import { getIMessageRuntime } from "./runtime.js";
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
createScopedAccountConfigAccessors,
|
||||
collectAllowlistProviderRestrictSendersWarnings,
|
||||
} from "openclaw/plugin-sdk/compat";
|
||||
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/core";
|
||||
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
buildBaseAccountStatusSnapshot,
|
||||
buildBaseChannelStatusSummary,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
buildAgentSessionKey,
|
||||
resolveThreadSessionKeys,
|
||||
type RoutePeer,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
} from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
buildComputedAccountStatusSnapshot,
|
||||
buildChannelConfigSchema,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
buildAgentSessionKey,
|
||||
resolveThreadSessionKeys,
|
||||
type RoutePeer,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
} from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
buildTokenChannelStatusSummary,
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
"types": "./dist/plugin-sdk/compat.d.ts",
|
||||
"default": "./dist/plugin-sdk/compat.js"
|
||||
},
|
||||
"./plugin-sdk/routing": {
|
||||
"types": "./dist/plugin-sdk/routing.d.ts",
|
||||
"default": "./dist/plugin-sdk/routing.js"
|
||||
},
|
||||
"./plugin-sdk/telegram": {
|
||||
"types": "./dist/plugin-sdk/telegram.d.ts",
|
||||
"default": "./dist/plugin-sdk/telegram.js"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"index",
|
||||
"core",
|
||||
"compat",
|
||||
"routing",
|
||||
"telegram",
|
||||
"discord",
|
||||
"slack",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { parseDiscordTarget } from "../../../../extensions/discord/src/targets.js";
|
||||
import { resolveStoredSubagentCapabilities } from "../../../agents/subagent-capabilities.js";
|
||||
import type { ResolvedSubagentController } from "../../../agents/subagent-control.js";
|
||||
import {
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
sanitizeTextContent,
|
||||
stripToolMessages,
|
||||
} from "../../../agents/tools/sessions-helpers.js";
|
||||
import { parseExplicitTargetForChannel } from "../../../channels/plugins/target-parsing.js";
|
||||
import type {
|
||||
SessionEntry,
|
||||
loadSessionStore as loadSessionStoreFn,
|
||||
@@ -335,13 +335,9 @@ export function resolveDiscordChannelIdForFocus(
|
||||
typeof params.ctx.To === "string" ? params.ctx.To.trim() : "",
|
||||
].filter(Boolean);
|
||||
for (const candidate of toCandidates) {
|
||||
try {
|
||||
const target = parseDiscordTarget(candidate, { defaultKind: "channel" });
|
||||
if (target?.kind === "channel" && target.id) {
|
||||
return target.id;
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse failures and try the next candidate.
|
||||
const target = parseExplicitTargetForChannel("discord", candidate);
|
||||
if (target?.chatType === "channel" && target.to) {
|
||||
return target.to;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { discordPlugin } from "../../../extensions/discord/src/channel.js";
|
||||
import { AcpRuntimeError } from "../../acp/runtime/errors.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
|
||||
import type { PluginTargetedInboundClaimOutcome } from "../../plugins/hooks.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { createInternalHookEventPayload } from "../../test-utils/internal-hook-event-payload.js";
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||
@@ -252,6 +255,9 @@ async function dispatchTwiceWithFreshDispatchers(params: Omit<DispatchReplyArgs,
|
||||
|
||||
describe("dispatchReplyFromConfig", () => {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "discord", source: "test", plugin: discordPlugin }]),
|
||||
);
|
||||
acpManagerTesting.resetAcpSessionManagerForTests();
|
||||
resetInboundDedupe();
|
||||
mocks.routeReply.mockReset();
|
||||
@@ -1295,6 +1301,11 @@ describe("dispatchReplyFromConfig", () => {
|
||||
commands: {
|
||||
text: false,
|
||||
},
|
||||
session: {
|
||||
sendPolicy: {
|
||||
default: "allow",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { shouldSuppressLocalDiscordExecApprovalPrompt } from "../../../extensions/discord/src/exec-approvals.js";
|
||||
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
||||
import { shouldSuppressLocalExecApprovalPrompt } from "../../channels/plugins/exec-approval-local.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
@@ -506,8 +506,8 @@ export async function dispatchReplyFromConfig(params: {
|
||||
|
||||
const resolveToolDeliveryPayload = (payload: ReplyPayload): ReplyPayload | null => {
|
||||
if (
|
||||
normalizeMessageChannel(ctx.Surface ?? ctx.Provider) === "discord" &&
|
||||
shouldSuppressLocalDiscordExecApprovalPrompt({
|
||||
shouldSuppressLocalExecApprovalPrompt({
|
||||
channel: normalizeMessageChannel(ctx.Surface ?? ctx.Provider),
|
||||
cfg,
|
||||
accountId: ctx.AccountId,
|
||||
payload,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parseTelegramTarget } from "../../../extensions/telegram/src/targets.js";
|
||||
import { isMessagingToolDuplicate } from "../../agents/pi-embedded-helpers.js";
|
||||
import type { MessagingToolSend } from "../../agents/pi-embedded-runner.js";
|
||||
import { normalizeChannelId } from "../../channels/plugins/index.js";
|
||||
import { parseExplicitTargetForChannel } from "../../channels/plugins/target-parsing.js";
|
||||
import type { ReplyToMode } from "../../config/types.js";
|
||||
import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js";
|
||||
import { hasReplyChannelData, hasReplyContent } from "../../interactive/payload.js";
|
||||
@@ -210,15 +210,16 @@ function targetsMatchForSuppression(params: {
|
||||
return params.targetKey === params.originTarget;
|
||||
}
|
||||
|
||||
const origin = parseTelegramTarget(params.originTarget);
|
||||
const target = parseTelegramTarget(params.targetKey);
|
||||
const origin = parseExplicitTargetForChannel("telegram", params.originTarget);
|
||||
const target = parseExplicitTargetForChannel("telegram", params.targetKey);
|
||||
if (!origin || !target) {
|
||||
return params.targetKey === params.originTarget;
|
||||
}
|
||||
const explicitTargetThreadId = normalizeThreadIdForComparison(params.targetThreadId);
|
||||
const targetThreadId =
|
||||
explicitTargetThreadId ??
|
||||
(target.messageThreadId != null ? String(target.messageThreadId) : undefined);
|
||||
const originThreadId =
|
||||
origin.messageThreadId != null ? String(origin.messageThreadId) : undefined;
|
||||
if (origin.chatId.trim().toLowerCase() !== target.chatId.trim().toLowerCase()) {
|
||||
explicitTargetThreadId ?? (target.threadId != null ? String(target.threadId) : undefined);
|
||||
const originThreadId = origin.threadId != null ? String(origin.threadId) : undefined;
|
||||
if (origin.to.trim().toLowerCase() !== target.to.trim().toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
if (originThreadId && targetThreadId != null) {
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { resolveTelegramConversationId } from "./telegram-context.js";
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "telegram", source: "test", plugin: telegramPlugin }]),
|
||||
);
|
||||
});
|
||||
|
||||
describe("resolveTelegramConversationId", () => {
|
||||
it("builds canonical topic ids from chat target and message thread id", () => {
|
||||
const conversationId = resolveTelegramConversationId({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseTelegramTarget } from "../../../extensions/telegram/src/targets.js";
|
||||
import { parseExplicitTargetForChannel } from "../../channels/plugins/target-parsing.js";
|
||||
|
||||
type TelegramConversationParams = {
|
||||
ctx: {
|
||||
@@ -25,7 +25,7 @@ export function resolveTelegramConversationId(
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
const chatId = toCandidates
|
||||
.map((candidate) => parseTelegramTarget(candidate).chatId.trim())
|
||||
.map((candidate) => parseExplicitTargetForChannel("telegram", candidate)?.to.trim() ?? "")
|
||||
.find((candidate) => candidate.length > 0);
|
||||
if (!chatId) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { inspectDiscordAccount } from "../../../extensions/discord/src/account-inspect.js";
|
||||
import { inspectSlackAccount } from "../../../extensions/slack/src/account-inspect.js";
|
||||
import { inspectTelegramAccount } from "../../../extensions/telegram/src/account-inspect.js";
|
||||
import { resolveWhatsAppAccount } from "../../../extensions/whatsapp/src/accounts.js";
|
||||
import type { OpenClawConfig } from "../../config/types.js";
|
||||
import { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
|
||||
import type { InspectedDiscordAccount } from "../read-only-account-inspect.discord.runtime.js";
|
||||
import { inspectReadOnlyChannelAccount } from "../read-only-account-inspect.js";
|
||||
import type { InspectedSlackAccount } from "../read-only-account-inspect.slack.runtime.js";
|
||||
import type { InspectedTelegramAccount } from "../read-only-account-inspect.telegram.runtime.js";
|
||||
import { applyDirectoryQueryAndLimit, toDirectoryEntries } from "./directory-config-helpers.js";
|
||||
import { normalizeSlackMessagingTarget } from "./normalize/slack.js";
|
||||
import { getChannelPlugin } from "./registry.js";
|
||||
import type { ChannelDirectoryEntry } from "./types.js";
|
||||
|
||||
export type DirectoryConfigParams = {
|
||||
@@ -58,7 +59,14 @@ function normalizeTrimmedSet(
|
||||
export async function listSlackDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectSlackAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
|
||||
addAllowFromAndDmsIds(ids, account.config.allowFrom ?? account.dm?.allowFrom, account.config.dms);
|
||||
@@ -81,7 +89,14 @@ export async function listSlackDirectoryPeersFromConfig(
|
||||
export async function listSlackDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectSlackAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.config.channels ?? {})
|
||||
.map((raw) => raw.trim())
|
||||
.filter(Boolean)
|
||||
@@ -93,7 +108,14 @@ export async function listSlackDirectoryGroupsFromConfig(
|
||||
export async function listDiscordDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
|
||||
addAllowFromAndDmsIds(
|
||||
@@ -122,7 +144,14 @@ export async function listDiscordDirectoryPeersFromConfig(
|
||||
export async function listDiscordDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
for (const guild of Object.values(account.config.guilds ?? {})) {
|
||||
addTrimmedEntries(ids, Object.keys(guild.channels ?? {}));
|
||||
@@ -142,7 +171,14 @@ export async function listDiscordDirectoryGroupsFromConfig(
|
||||
export async function listTelegramDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectTelegramAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const raw = [
|
||||
...mapAllowFromEntries(account.config.allowFrom),
|
||||
...Object.keys(account.config.dms ?? {}),
|
||||
@@ -173,7 +209,14 @@ export async function listTelegramDirectoryPeersFromConfig(
|
||||
export async function listTelegramDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = inspectTelegramAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.config.groups ?? {})
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => Boolean(id) && id !== "*");
|
||||
@@ -183,9 +226,15 @@ export async function listTelegramDirectoryGroupsFromConfig(
|
||||
export async function listWhatsAppDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = getChannelPlugin("whatsapp")?.config.resolveAccount(
|
||||
params.cfg,
|
||||
params.accountId,
|
||||
) as { allowFrom?: unknown[] } | null | undefined;
|
||||
if (!account || typeof account !== "object") {
|
||||
return [];
|
||||
}
|
||||
const ids = (account.allowFrom ?? [])
|
||||
.map((entry) => String(entry).trim())
|
||||
.map((entry: unknown) => String(entry).trim())
|
||||
.filter((entry) => Boolean(entry) && entry !== "*")
|
||||
.map((entry) => normalizeWhatsAppTarget(entry) ?? "")
|
||||
.filter(Boolean)
|
||||
@@ -196,7 +245,13 @@ export async function listWhatsAppDirectoryPeersFromConfig(
|
||||
export async function listWhatsAppDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const account = getChannelPlugin("whatsapp")?.config.resolveAccount(
|
||||
params.cfg,
|
||||
params.accountId,
|
||||
) as { groups?: Record<string, unknown> } | null | undefined;
|
||||
if (!account || typeof account !== "object") {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.groups ?? {})
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => Boolean(id) && id !== "*");
|
||||
|
||||
22
src/channels/plugins/exec-approval-local.ts
Normal file
22
src/channels/plugins/exec-approval-local.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { getChannelPlugin, normalizeChannelId } from "./registry.js";
|
||||
|
||||
export function shouldSuppressLocalExecApprovalPrompt(params: {
|
||||
channel?: string | null;
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
payload: ReplyPayload;
|
||||
}): boolean {
|
||||
const channel = params.channel ? normalizeChannelId(params.channel) : null;
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
getChannelPlugin(channel)?.execApprovals?.shouldSuppressLocalPrompt?.({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
payload: params.payload,
|
||||
}) ?? false
|
||||
);
|
||||
}
|
||||
@@ -1,93 +1,4 @@
|
||||
import {
|
||||
getActivePluginRegistryVersion,
|
||||
requireActivePluginRegistry,
|
||||
} from "../../plugins/runtime.js";
|
||||
import { CHAT_CHANNEL_ORDER, type ChatChannelId, normalizeAnyChannelId } from "../registry.js";
|
||||
import type { ChannelId, ChannelPlugin } from "./types.js";
|
||||
|
||||
// Channel plugins registry (runtime).
|
||||
//
|
||||
// This module is intentionally "heavy" (plugins may import channel monitors, web login, etc).
|
||||
// Shared code paths should prefer narrower adapters and helpers instead of reaching into
|
||||
// channel-specific runtime modules directly.
|
||||
//
|
||||
function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
|
||||
const seen = new Set<string>();
|
||||
const resolved: ChannelPlugin[] = [];
|
||||
for (const plugin of channels) {
|
||||
const id = String(plugin.id).trim();
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
resolved.push(plugin);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
type CachedChannelPlugins = {
|
||||
registryVersion: number;
|
||||
sorted: ChannelPlugin[];
|
||||
byId: Map<string, ChannelPlugin>;
|
||||
};
|
||||
|
||||
const EMPTY_CHANNEL_PLUGIN_CACHE: CachedChannelPlugins = {
|
||||
registryVersion: -1,
|
||||
sorted: [],
|
||||
byId: new Map(),
|
||||
};
|
||||
|
||||
let cachedChannelPlugins = EMPTY_CHANNEL_PLUGIN_CACHE;
|
||||
|
||||
function resolveCachedChannelPlugins(): CachedChannelPlugins {
|
||||
const registry = requireActivePluginRegistry();
|
||||
const registryVersion = getActivePluginRegistryVersion();
|
||||
const cached = cachedChannelPlugins;
|
||||
if (cached.registryVersion === registryVersion) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const sorted = dedupeChannels(registry.channels.map((entry) => entry.plugin)).toSorted((a, b) => {
|
||||
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
|
||||
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
|
||||
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
|
||||
const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB);
|
||||
if (orderA !== orderB) {
|
||||
return orderA - orderB;
|
||||
}
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
const byId = new Map<string, ChannelPlugin>();
|
||||
for (const plugin of sorted) {
|
||||
byId.set(plugin.id, plugin);
|
||||
}
|
||||
|
||||
const next: CachedChannelPlugins = {
|
||||
registryVersion,
|
||||
sorted,
|
||||
byId,
|
||||
};
|
||||
cachedChannelPlugins = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
export function listChannelPlugins(): ChannelPlugin[] {
|
||||
return resolveCachedChannelPlugins().sorted.slice();
|
||||
}
|
||||
|
||||
export function getChannelPlugin(id: ChannelId): ChannelPlugin | undefined {
|
||||
const resolvedId = String(id).trim();
|
||||
if (!resolvedId) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveCachedChannelPlugins().byId.get(resolvedId);
|
||||
}
|
||||
|
||||
export function normalizeChannelId(raw?: string | null): ChannelId | null {
|
||||
// Channel docking: keep input normalization centralized in src/channels/registry.ts.
|
||||
// Plugin registry must be initialized before calling.
|
||||
return normalizeAnyChannelId(raw);
|
||||
}
|
||||
export { getChannelPlugin, listChannelPlugins, normalizeChannelId } from "./registry.js";
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
|
||||
82
src/channels/plugins/registry.ts
Normal file
82
src/channels/plugins/registry.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
getActivePluginRegistryVersion,
|
||||
requireActivePluginRegistry,
|
||||
} from "../../plugins/runtime.js";
|
||||
import { CHAT_CHANNEL_ORDER, type ChatChannelId, normalizeAnyChannelId } from "../registry.js";
|
||||
import type { ChannelId, ChannelPlugin } from "./types.js";
|
||||
|
||||
function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
|
||||
const seen = new Set<string>();
|
||||
const resolved: ChannelPlugin[] = [];
|
||||
for (const plugin of channels) {
|
||||
const id = String(plugin.id).trim();
|
||||
if (!id || seen.has(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
resolved.push(plugin);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
type CachedChannelPlugins = {
|
||||
registryVersion: number;
|
||||
sorted: ChannelPlugin[];
|
||||
byId: Map<string, ChannelPlugin>;
|
||||
};
|
||||
|
||||
const EMPTY_CHANNEL_PLUGIN_CACHE: CachedChannelPlugins = {
|
||||
registryVersion: -1,
|
||||
sorted: [],
|
||||
byId: new Map(),
|
||||
};
|
||||
|
||||
let cachedChannelPlugins = EMPTY_CHANNEL_PLUGIN_CACHE;
|
||||
|
||||
function resolveCachedChannelPlugins(): CachedChannelPlugins {
|
||||
const registry = requireActivePluginRegistry();
|
||||
const registryVersion = getActivePluginRegistryVersion();
|
||||
const cached = cachedChannelPlugins;
|
||||
if (cached.registryVersion === registryVersion) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const sorted = dedupeChannels(registry.channels.map((entry) => entry.plugin)).toSorted((a, b) => {
|
||||
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
|
||||
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
|
||||
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
|
||||
const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB);
|
||||
if (orderA !== orderB) {
|
||||
return orderA - orderB;
|
||||
}
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
const byId = new Map<string, ChannelPlugin>();
|
||||
for (const plugin of sorted) {
|
||||
byId.set(plugin.id, plugin);
|
||||
}
|
||||
|
||||
const next: CachedChannelPlugins = {
|
||||
registryVersion,
|
||||
sorted,
|
||||
byId,
|
||||
};
|
||||
cachedChannelPlugins = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
export function listChannelPlugins(): ChannelPlugin[] {
|
||||
return resolveCachedChannelPlugins().sorted.slice();
|
||||
}
|
||||
|
||||
export function getChannelPlugin(id: ChannelId): ChannelPlugin | undefined {
|
||||
const resolvedId = String(id).trim();
|
||||
if (!resolvedId) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveCachedChannelPlugins().byId.get(resolvedId);
|
||||
}
|
||||
|
||||
export function normalizeChannelId(raw?: string | null): ChannelId | null {
|
||||
return normalizeAnyChannelId(raw);
|
||||
}
|
||||
26
src/channels/plugins/target-parsing.ts
Normal file
26
src/channels/plugins/target-parsing.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { ChatType } from "../chat-type.js";
|
||||
import { getChannelPlugin, normalizeChannelId } from "./registry.js";
|
||||
|
||||
export type ParsedChannelExplicitTarget = {
|
||||
to: string;
|
||||
threadId?: string | number;
|
||||
chatType?: ChatType;
|
||||
};
|
||||
|
||||
function parseWithPlugin(
|
||||
rawChannel: string,
|
||||
rawTarget: string,
|
||||
): ParsedChannelExplicitTarget | null {
|
||||
const channel = normalizeChannelId(rawChannel);
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
return getChannelPlugin(channel)?.messaging?.parseExplicitTarget?.({ raw: rawTarget }) ?? null;
|
||||
}
|
||||
|
||||
export function parseExplicitTargetForChannel(
|
||||
channel: string,
|
||||
rawTarget: string,
|
||||
): ParsedChannelExplicitTarget | null {
|
||||
return parseWithPlugin(channel, rawTarget);
|
||||
}
|
||||
@@ -456,6 +456,11 @@ export type ChannelExecApprovalAdapter = {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}) => ChannelExecApprovalInitiatingSurfaceState;
|
||||
shouldSuppressLocalPrompt?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
payload: ReplyPayload;
|
||||
}) => boolean;
|
||||
hasConfiguredDmRoute?: (params: { cfg: OpenClawConfig }) => boolean;
|
||||
shouldSuppressForwardingFallback?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { resolveIMessageAccount } from "../../extensions/imessage/src/accounts.js";
|
||||
import { resolveWhatsAppAccount } from "../../extensions/whatsapp/src/accounts.js";
|
||||
import {
|
||||
deleteAccountFromConfigSection,
|
||||
setAccountEnabledInConfigSection,
|
||||
} from "../channels/plugins/config-helpers.js";
|
||||
import { buildAccountScopedDmSecurityPolicy } from "../channels/plugins/helpers.js";
|
||||
import { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize/whatsapp.js";
|
||||
import { getChannelPlugin } from "../channels/plugins/registry.js";
|
||||
import type { ChannelConfigAdapter } from "../channels/plugins/types.adapters.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { normalizeAccountId } from "../routing/session-key.js";
|
||||
@@ -148,7 +147,10 @@ export function resolveWhatsAppConfigAllowFrom(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}): string[] {
|
||||
return resolveWhatsAppAccount(params).allowFrom ?? [];
|
||||
const account = getChannelPlugin("whatsapp")?.config.resolveAccount(params.cfg, params.accountId);
|
||||
return account && typeof account === "object" && Array.isArray(account.allowFrom)
|
||||
? account.allowFrom.map(String)
|
||||
: [];
|
||||
}
|
||||
|
||||
export function formatWhatsAppConfigAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
@@ -169,12 +171,20 @@ export function resolveIMessageConfigAllowFrom(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}): string[] {
|
||||
return mapAllowFromEntries(resolveIMessageAccount(params).config.allowFrom);
|
||||
const account = getChannelPlugin("imessage")?.config.resolveAccount(params.cfg, params.accountId);
|
||||
if (!account || typeof account !== "object" || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
return mapAllowFromEntries(account.config.allowFrom);
|
||||
}
|
||||
|
||||
export function resolveIMessageConfigDefaultTo(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}): string | undefined {
|
||||
return resolveOptionalConfigString(resolveIMessageAccount(params).config.defaultTo);
|
||||
const account = getChannelPlugin("imessage")?.config.resolveAccount(params.cfg, params.accountId);
|
||||
if (!account || typeof account !== "object" || !("config" in account)) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveOptionalConfigString(account.config.defaultTo);
|
||||
}
|
||||
|
||||
6
src/plugin-sdk/routing.ts
Normal file
6
src/plugin-sdk/routing.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export {
|
||||
buildAgentSessionKey,
|
||||
type RoutePeer,
|
||||
type RoutePeerKind,
|
||||
} from "../routing/resolve-route.js";
|
||||
export { resolveThreadSessionKeys } from "../routing/session-key.js";
|
||||
@@ -1,4 +1,6 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { discordPlugin } from "../../extensions/discord/src/channel.js";
|
||||
import { createTestRegistry } from "../test-utils/channel-plugins.js";
|
||||
import {
|
||||
__testing,
|
||||
clearPluginCommands,
|
||||
@@ -7,6 +9,13 @@ import {
|
||||
listPluginCommands,
|
||||
registerPluginCommand,
|
||||
} from "./commands.js";
|
||||
import { setActivePluginRegistry } from "./runtime.js";
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "discord", source: "test", plugin: discordPlugin }]),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearPluginCommands();
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
* These commands are processed before built-in commands and before agent invocation.
|
||||
*/
|
||||
|
||||
import { parseDiscordTarget } from "../../extensions/discord/src/targets.js";
|
||||
import { parseTelegramTarget } from "../../extensions/telegram/src/targets.js";
|
||||
import { parseExplicitTargetForChannel } from "../channels/plugins/target-parsing.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import {
|
||||
@@ -286,12 +285,15 @@ function resolveBindingConversationFromCommand(params: {
|
||||
if (!rawTarget) {
|
||||
return null;
|
||||
}
|
||||
const target = parseTelegramTarget(rawTarget);
|
||||
const target = parseExplicitTargetForChannel("telegram", rawTarget);
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
conversationId: target.chatId,
|
||||
threadId: params.messageThreadId ?? target.messageThreadId,
|
||||
conversationId: target.to,
|
||||
threadId: params.messageThreadId ?? target.threadId,
|
||||
};
|
||||
}
|
||||
if (params.channel === "discord") {
|
||||
@@ -304,14 +306,14 @@ function resolveBindingConversationFromCommand(params: {
|
||||
if (!rawTarget || rawTarget.startsWith("slash:")) {
|
||||
return null;
|
||||
}
|
||||
const target = parseDiscordTarget(rawTarget, { defaultKind: "channel" });
|
||||
const target = parseExplicitTargetForChannel("discord", rawTarget);
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channel: "discord",
|
||||
accountId,
|
||||
conversationId: `${target.kind}:${target.id}`,
|
||||
conversationId: `${target.chatType === "direct" ? "user" : "channel"}:${target.to}`,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,59 +1,4 @@
|
||||
import { auditDiscordChannelPermissions } from "../../../extensions/discord/src/audit.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsLive,
|
||||
listDiscordDirectoryPeersLive,
|
||||
} from "../../../extensions/discord/src/directory-live.js";
|
||||
import { monitorDiscordProvider } from "../../../extensions/discord/src/monitor.js";
|
||||
import { probeDiscord } from "../../../extensions/discord/src/probe.js";
|
||||
import { resolveDiscordChannelAllowlist } from "../../../extensions/discord/src/resolve-channels.js";
|
||||
import { resolveDiscordUserAllowlist } from "../../../extensions/discord/src/resolve-users.js";
|
||||
import {
|
||||
createThreadDiscord,
|
||||
deleteMessageDiscord,
|
||||
editChannelDiscord,
|
||||
editMessageDiscord,
|
||||
pinMessageDiscord,
|
||||
sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
sendTypingDiscord,
|
||||
unpinMessageDiscord,
|
||||
} from "../../../extensions/discord/src/send.js";
|
||||
import { monitorIMessageProvider } from "../../../extensions/imessage/src/monitor.js";
|
||||
import { probeIMessage } from "../../../extensions/imessage/src/probe.js";
|
||||
import { sendMessageIMessage } from "../../../extensions/imessage/src/send.js";
|
||||
import { monitorSignalProvider } from "../../../extensions/signal/src/index.js";
|
||||
import { probeSignal } from "../../../extensions/signal/src/probe.js";
|
||||
import { sendMessageSignal } from "../../../extensions/signal/src/send.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsLive,
|
||||
listSlackDirectoryPeersLive,
|
||||
} from "../../../extensions/slack/src/directory-live.js";
|
||||
import { monitorSlackProvider } from "../../../extensions/slack/src/index.js";
|
||||
import { probeSlack } from "../../../extensions/slack/src/probe.js";
|
||||
import { resolveSlackChannelAllowlist } from "../../../extensions/slack/src/resolve-channels.js";
|
||||
import { resolveSlackUserAllowlist } from "../../../extensions/slack/src/resolve-users.js";
|
||||
import { sendMessageSlack } from "../../../extensions/slack/src/send.js";
|
||||
import {
|
||||
auditTelegramGroupMembership,
|
||||
collectTelegramUnmentionedGroupIds,
|
||||
} from "../../../extensions/telegram/src/audit.js";
|
||||
import { monitorTelegramProvider } from "../../../extensions/telegram/src/monitor.js";
|
||||
import { probeTelegram } from "../../../extensions/telegram/src/probe.js";
|
||||
import {
|
||||
deleteMessageTelegram,
|
||||
editMessageReplyMarkupTelegram,
|
||||
editMessageTelegram,
|
||||
pinMessageTelegram,
|
||||
renameForumTopicTelegram,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
sendTypingTelegram,
|
||||
unpinMessageTelegram,
|
||||
} from "../../../extensions/telegram/src/send.js";
|
||||
import { resolveTelegramToken } from "../../../extensions/telegram/src/token.js";
|
||||
import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||
import { handleSlackAction } from "../../agents/tools/slack-actions.js";
|
||||
import {
|
||||
chunkByNewline,
|
||||
chunkMarkdownText,
|
||||
@@ -90,9 +35,6 @@ import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply
|
||||
import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { removeAckReactionAfterReply, shouldAckReaction } from "../../channels/ack-reactions.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
||||
import { discordMessageActions } from "../../channels/plugins/actions/discord.js";
|
||||
import { signalMessageActions } from "../../channels/plugins/actions/signal.js";
|
||||
import { telegramMessageActions } from "../../channels/plugins/actions/telegram.js";
|
||||
import { recordInboundSession } from "../../channels/session.js";
|
||||
import {
|
||||
resolveChannelGroupPolicy,
|
||||
@@ -134,8 +76,11 @@ import {
|
||||
upsertChannelPairingRequest,
|
||||
} from "../../pairing/pairing-store.js";
|
||||
import { buildAgentSessionKey, resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||
import { createDiscordTypingLease } from "./runtime-discord-typing.js";
|
||||
import { createTelegramTypingLease } from "./runtime-telegram-typing.js";
|
||||
import { createRuntimeDiscord } from "./runtime-discord.js";
|
||||
import { createRuntimeIMessage } from "./runtime-imessage.js";
|
||||
import { createRuntimeSignal } from "./runtime-signal.js";
|
||||
import { createRuntimeSlack } from "./runtime-slack.js";
|
||||
import { createRuntimeTelegram } from "./runtime-telegram.js";
|
||||
import { createRuntimeWhatsApp } from "./runtime-whatsapp.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
@@ -222,100 +167,11 @@ export function createRuntimeChannel(): PluginRuntime["channel"] {
|
||||
shouldComputeCommandAuthorized,
|
||||
shouldHandleTextCommands,
|
||||
},
|
||||
discord: {
|
||||
messageActions: discordMessageActions,
|
||||
auditChannelPermissions: auditDiscordChannelPermissions,
|
||||
listDirectoryGroupsLive: listDiscordDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listDiscordDirectoryPeersLive,
|
||||
probeDiscord,
|
||||
resolveChannelAllowlist: resolveDiscordChannelAllowlist,
|
||||
resolveUserAllowlist: resolveDiscordUserAllowlist,
|
||||
sendComponentMessage: sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
monitorDiscordProvider,
|
||||
typing: {
|
||||
pulse: sendTypingDiscord,
|
||||
start: async ({ channelId, accountId, cfg, intervalMs }) =>
|
||||
await createDiscordTypingLease({
|
||||
channelId,
|
||||
accountId,
|
||||
cfg,
|
||||
intervalMs,
|
||||
pulse: async ({ channelId, accountId, cfg }) =>
|
||||
void (await sendTypingDiscord(channelId, {
|
||||
accountId,
|
||||
cfg,
|
||||
})),
|
||||
}),
|
||||
},
|
||||
conversationActions: {
|
||||
editMessage: editMessageDiscord,
|
||||
deleteMessage: deleteMessageDiscord,
|
||||
pinMessage: pinMessageDiscord,
|
||||
unpinMessage: unpinMessageDiscord,
|
||||
createThread: createThreadDiscord,
|
||||
editChannel: editChannelDiscord,
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
listDirectoryGroupsLive: listSlackDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listSlackDirectoryPeersLive,
|
||||
probeSlack,
|
||||
resolveChannelAllowlist: resolveSlackChannelAllowlist,
|
||||
resolveUserAllowlist: resolveSlackUserAllowlist,
|
||||
sendMessageSlack,
|
||||
monitorSlackProvider,
|
||||
handleSlackAction,
|
||||
},
|
||||
telegram: {
|
||||
auditGroupMembership: auditTelegramGroupMembership,
|
||||
collectUnmentionedGroupIds: collectTelegramUnmentionedGroupIds,
|
||||
probeTelegram,
|
||||
resolveTelegramToken,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
monitorTelegramProvider,
|
||||
messageActions: telegramMessageActions,
|
||||
typing: {
|
||||
pulse: sendTypingTelegram,
|
||||
start: async ({ to, accountId, cfg, intervalMs, messageThreadId }) =>
|
||||
await createTelegramTypingLease({
|
||||
to,
|
||||
accountId,
|
||||
cfg,
|
||||
intervalMs,
|
||||
messageThreadId,
|
||||
pulse: async ({ to, accountId, cfg, messageThreadId }) =>
|
||||
await sendTypingTelegram(to, {
|
||||
accountId,
|
||||
cfg,
|
||||
messageThreadId,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
conversationActions: {
|
||||
editMessage: editMessageTelegram,
|
||||
editReplyMarkup: editMessageReplyMarkupTelegram,
|
||||
clearReplyMarkup: async (chatIdInput, messageIdInput, opts = {}) =>
|
||||
await editMessageReplyMarkupTelegram(chatIdInput, messageIdInput, [], opts),
|
||||
deleteMessage: deleteMessageTelegram,
|
||||
renameTopic: renameForumTopicTelegram,
|
||||
pinMessage: pinMessageTelegram,
|
||||
unpinMessage: unpinMessageTelegram,
|
||||
},
|
||||
},
|
||||
signal: {
|
||||
probeSignal,
|
||||
sendMessageSignal,
|
||||
monitorSignalProvider,
|
||||
messageActions: signalMessageActions,
|
||||
},
|
||||
imessage: {
|
||||
monitorIMessageProvider,
|
||||
probeIMessage,
|
||||
sendMessageIMessage,
|
||||
},
|
||||
discord: createRuntimeDiscord(),
|
||||
slack: createRuntimeSlack(),
|
||||
telegram: createRuntimeTelegram(),
|
||||
signal: createRuntimeSignal(),
|
||||
imessage: createRuntimeIMessage(),
|
||||
whatsapp: createRuntimeWhatsApp(),
|
||||
line: {
|
||||
listLineAccountIds,
|
||||
|
||||
60
src/plugins/runtime/runtime-discord.ts
Normal file
60
src/plugins/runtime/runtime-discord.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { auditDiscordChannelPermissions } from "../../../extensions/discord/src/audit.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsLive,
|
||||
listDiscordDirectoryPeersLive,
|
||||
} from "../../../extensions/discord/src/directory-live.js";
|
||||
import { monitorDiscordProvider } from "../../../extensions/discord/src/monitor.js";
|
||||
import { probeDiscord } from "../../../extensions/discord/src/probe.js";
|
||||
import { resolveDiscordChannelAllowlist } from "../../../extensions/discord/src/resolve-channels.js";
|
||||
import { resolveDiscordUserAllowlist } from "../../../extensions/discord/src/resolve-users.js";
|
||||
import {
|
||||
createThreadDiscord,
|
||||
deleteMessageDiscord,
|
||||
editChannelDiscord,
|
||||
editMessageDiscord,
|
||||
pinMessageDiscord,
|
||||
sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
sendTypingDiscord,
|
||||
unpinMessageDiscord,
|
||||
} from "../../../extensions/discord/src/send.js";
|
||||
import { discordMessageActions } from "../../channels/plugins/actions/discord.js";
|
||||
import { createDiscordTypingLease } from "./runtime-discord-typing.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeDiscord(): PluginRuntimeChannel["discord"] {
|
||||
return {
|
||||
messageActions: discordMessageActions,
|
||||
auditChannelPermissions: auditDiscordChannelPermissions,
|
||||
listDirectoryGroupsLive: listDiscordDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listDiscordDirectoryPeersLive,
|
||||
probeDiscord,
|
||||
resolveChannelAllowlist: resolveDiscordChannelAllowlist,
|
||||
resolveUserAllowlist: resolveDiscordUserAllowlist,
|
||||
sendComponentMessage: sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
monitorDiscordProvider,
|
||||
typing: {
|
||||
pulse: sendTypingDiscord,
|
||||
start: async ({ channelId, accountId, cfg, intervalMs }) =>
|
||||
await createDiscordTypingLease({
|
||||
channelId,
|
||||
accountId,
|
||||
cfg,
|
||||
intervalMs,
|
||||
pulse: async ({ channelId, accountId, cfg }) =>
|
||||
void (await sendTypingDiscord(channelId, { accountId, cfg })),
|
||||
}),
|
||||
},
|
||||
conversationActions: {
|
||||
editMessage: editMessageDiscord,
|
||||
deleteMessage: deleteMessageDiscord,
|
||||
pinMessage: pinMessageDiscord,
|
||||
unpinMessage: unpinMessageDiscord,
|
||||
createThread: createThreadDiscord,
|
||||
editChannel: editChannelDiscord,
|
||||
},
|
||||
};
|
||||
}
|
||||
12
src/plugins/runtime/runtime-imessage.ts
Normal file
12
src/plugins/runtime/runtime-imessage.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { monitorIMessageProvider } from "../../../extensions/imessage/src/monitor.js";
|
||||
import { probeIMessage } from "../../../extensions/imessage/src/probe.js";
|
||||
import { sendMessageIMessage } from "../../../extensions/imessage/src/send.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeIMessage(): PluginRuntimeChannel["imessage"] {
|
||||
return {
|
||||
monitorIMessageProvider,
|
||||
probeIMessage,
|
||||
sendMessageIMessage,
|
||||
};
|
||||
}
|
||||
14
src/plugins/runtime/runtime-signal.ts
Normal file
14
src/plugins/runtime/runtime-signal.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { monitorSignalProvider } from "../../../extensions/signal/src/index.js";
|
||||
import { probeSignal } from "../../../extensions/signal/src/probe.js";
|
||||
import { sendMessageSignal } from "../../../extensions/signal/src/send.js";
|
||||
import { signalMessageActions } from "../../channels/plugins/actions/signal.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeSignal(): PluginRuntimeChannel["signal"] {
|
||||
return {
|
||||
probeSignal,
|
||||
sendMessageSignal,
|
||||
monitorSignalProvider,
|
||||
messageActions: signalMessageActions,
|
||||
};
|
||||
}
|
||||
24
src/plugins/runtime/runtime-slack.ts
Normal file
24
src/plugins/runtime/runtime-slack.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
listSlackDirectoryGroupsLive,
|
||||
listSlackDirectoryPeersLive,
|
||||
} from "../../../extensions/slack/src/directory-live.js";
|
||||
import { monitorSlackProvider } from "../../../extensions/slack/src/index.js";
|
||||
import { probeSlack } from "../../../extensions/slack/src/probe.js";
|
||||
import { resolveSlackChannelAllowlist } from "../../../extensions/slack/src/resolve-channels.js";
|
||||
import { resolveSlackUserAllowlist } from "../../../extensions/slack/src/resolve-users.js";
|
||||
import { sendMessageSlack } from "../../../extensions/slack/src/send.js";
|
||||
import { handleSlackAction } from "../../agents/tools/slack-actions.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeSlack(): PluginRuntimeChannel["slack"] {
|
||||
return {
|
||||
listDirectoryGroupsLive: listSlackDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listSlackDirectoryPeersLive,
|
||||
probeSlack,
|
||||
resolveChannelAllowlist: resolveSlackChannelAllowlist,
|
||||
resolveUserAllowlist: resolveSlackUserAllowlist,
|
||||
sendMessageSlack,
|
||||
monitorSlackProvider,
|
||||
handleSlackAction,
|
||||
};
|
||||
}
|
||||
61
src/plugins/runtime/runtime-telegram.ts
Normal file
61
src/plugins/runtime/runtime-telegram.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
auditTelegramGroupMembership,
|
||||
collectTelegramUnmentionedGroupIds,
|
||||
} from "../../../extensions/telegram/src/audit.js";
|
||||
import { monitorTelegramProvider } from "../../../extensions/telegram/src/monitor.js";
|
||||
import { probeTelegram } from "../../../extensions/telegram/src/probe.js";
|
||||
import {
|
||||
deleteMessageTelegram,
|
||||
editMessageReplyMarkupTelegram,
|
||||
editMessageTelegram,
|
||||
pinMessageTelegram,
|
||||
renameForumTopicTelegram,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
sendTypingTelegram,
|
||||
unpinMessageTelegram,
|
||||
} from "../../../extensions/telegram/src/send.js";
|
||||
import { resolveTelegramToken } from "../../../extensions/telegram/src/token.js";
|
||||
import { telegramMessageActions } from "../../channels/plugins/actions/telegram.js";
|
||||
import { createTelegramTypingLease } from "./runtime-telegram-typing.js";
|
||||
import type { PluginRuntimeChannel } from "./types-channel.js";
|
||||
|
||||
export function createRuntimeTelegram(): PluginRuntimeChannel["telegram"] {
|
||||
return {
|
||||
auditGroupMembership: auditTelegramGroupMembership,
|
||||
collectUnmentionedGroupIds: collectTelegramUnmentionedGroupIds,
|
||||
probeTelegram,
|
||||
resolveTelegramToken,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
monitorTelegramProvider,
|
||||
messageActions: telegramMessageActions,
|
||||
typing: {
|
||||
pulse: sendTypingTelegram,
|
||||
start: async ({ to, accountId, cfg, intervalMs, messageThreadId }) =>
|
||||
await createTelegramTypingLease({
|
||||
to,
|
||||
accountId,
|
||||
cfg,
|
||||
intervalMs,
|
||||
messageThreadId,
|
||||
pulse: async ({ to, accountId, cfg, messageThreadId }) =>
|
||||
await sendTypingTelegram(to, {
|
||||
accountId,
|
||||
cfg,
|
||||
messageThreadId,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
conversationActions: {
|
||||
editMessage: editMessageTelegram,
|
||||
editReplyMarkup: editMessageReplyMarkupTelegram,
|
||||
clearReplyMarkup: async (chatIdInput, messageIdInput, opts = {}) =>
|
||||
await editMessageReplyMarkupTelegram(chatIdInput, messageIdInput, [], opts),
|
||||
deleteMessage: deleteMessageTelegram,
|
||||
renameTopic: renameForumTopicTelegram,
|
||||
pinMessage: pinMessageTelegram,
|
||||
unpinMessage: unpinMessageTelegram,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user