refactor(src): split oversized modules

This commit is contained in:
Peter Steinberger
2026-01-14 01:08:15 +00:00
parent b2179de839
commit bcbfb357be
675 changed files with 91476 additions and 73453 deletions

View File

@@ -1,25 +1,10 @@
import {
createActionGate,
readNumberParam,
readStringArrayParam,
readStringParam,
} from "../../../agents/tools/common.js";
import { handleDiscordAction } from "../../../agents/tools/discord-actions.js";
import { createActionGate } from "../../../agents/tools/common.js";
import { listEnabledDiscordAccounts } from "../../../discord/accounts.js";
import type {
ChannelMessageActionAdapter,
ChannelMessageActionName,
} from "../types.js";
const providerId = "discord";
function readParentIdParam(
params: Record<string, unknown>,
): string | null | undefined {
if (params.clearParent === true) return null;
if (params.parentId === null) return null;
return readStringParam(params, "parentId");
}
import { handleDiscordMessageAction } from "./discord/handle-action.js";
export const discordMessageActions: ChannelMessageActionAdapter = {
listActions: ({ cfg }) => {
@@ -100,570 +85,6 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
return null;
},
handleAction: async ({ action, params, cfg }) => {
const resolveChannelId = () =>
readStringParam(params, "channelId") ??
readStringParam(params, "to", { required: true });
if (action === "send") {
const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", {
required: true,
allowEmpty: true,
});
const mediaUrl = readStringParam(params, "media", { trim: false });
const replyTo = readStringParam(params, "replyTo");
return await handleDiscordAction(
{
action: "sendMessage",
to,
content,
mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined,
},
cfg,
);
}
if (action === "poll") {
const to = readStringParam(params, "to", { required: true });
const question = readStringParam(params, "pollQuestion", {
required: true,
});
const answers =
readStringArrayParam(params, "pollOption", { required: true }) ?? [];
const allowMultiselect =
typeof params.pollMulti === "boolean" ? params.pollMulti : undefined;
const durationHours = readNumberParam(params, "pollDurationHours", {
integer: true,
});
return await handleDiscordAction(
{
action: "poll",
to,
question,
answers,
allowMultiselect,
durationHours: durationHours ?? undefined,
content: readStringParam(params, "message"),
},
cfg,
);
}
if (action === "react") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
const remove =
typeof params.remove === "boolean" ? params.remove : undefined;
return await handleDiscordAction(
{
action: "react",
channelId: resolveChannelId(),
messageId,
emoji,
remove,
},
cfg,
);
}
if (action === "reactions") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{
action: "reactions",
channelId: resolveChannelId(),
messageId,
limit,
},
cfg,
);
}
if (action === "read") {
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{
action: "readMessages",
channelId: resolveChannelId(),
limit,
before: readStringParam(params, "before"),
after: readStringParam(params, "after"),
around: readStringParam(params, "around"),
},
cfg,
);
}
if (action === "edit") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const content = readStringParam(params, "message", { required: true });
return await handleDiscordAction(
{
action: "editMessage",
channelId: resolveChannelId(),
messageId,
content,
},
cfg,
);
}
if (action === "delete") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
return await handleDiscordAction(
{
action: "deleteMessage",
channelId: resolveChannelId(),
messageId,
},
cfg,
);
}
if (action === "pin" || action === "unpin" || action === "list-pins") {
const messageId =
action === "list-pins"
? undefined
: readStringParam(params, "messageId", { required: true });
return await handleDiscordAction(
{
action:
action === "pin"
? "pinMessage"
: action === "unpin"
? "unpinMessage"
: "listPins",
channelId: resolveChannelId(),
messageId,
},
cfg,
);
}
if (action === "permissions") {
return await handleDiscordAction(
{
action: "permissions",
channelId: resolveChannelId(),
},
cfg,
);
}
if (action === "thread-create") {
const name = readStringParam(params, "threadName", { required: true });
const messageId = readStringParam(params, "messageId");
const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", {
integer: true,
});
return await handleDiscordAction(
{
action: "threadCreate",
channelId: resolveChannelId(),
name,
messageId,
autoArchiveMinutes,
},
cfg,
);
}
if (action === "thread-list") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const channelId = readStringParam(params, "channelId");
const includeArchived =
typeof params.includeArchived === "boolean"
? params.includeArchived
: undefined;
const before = readStringParam(params, "before");
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{
action: "threadList",
guildId,
channelId,
includeArchived,
before,
limit,
},
cfg,
);
}
if (action === "thread-reply") {
const content = readStringParam(params, "message", { required: true });
const mediaUrl = readStringParam(params, "media", { trim: false });
const replyTo = readStringParam(params, "replyTo");
return await handleDiscordAction(
{
action: "threadReply",
channelId: resolveChannelId(),
content,
mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined,
},
cfg,
);
}
if (action === "search") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const query = readStringParam(params, "query", { required: true });
return await handleDiscordAction(
{
action: "searchMessages",
guildId,
content: query,
channelId: readStringParam(params, "channelId"),
channelIds: readStringArrayParam(params, "channelIds"),
authorId: readStringParam(params, "authorId"),
authorIds: readStringArrayParam(params, "authorIds"),
limit: readNumberParam(params, "limit", { integer: true }),
},
cfg,
);
}
if (action === "sticker") {
const stickerIds =
readStringArrayParam(params, "stickerId", {
required: true,
label: "sticker-id",
}) ?? [];
return await handleDiscordAction(
{
action: "sticker",
to: readStringParam(params, "to", { required: true }),
stickerIds,
content: readStringParam(params, "message"),
},
cfg,
);
}
if (action === "member-info") {
const userId = readStringParam(params, "userId", { required: true });
const guildId = readStringParam(params, "guildId", {
required: true,
});
return await handleDiscordAction(
{ action: "memberInfo", guildId, userId },
cfg,
);
}
if (action === "role-info") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "roleInfo", guildId }, cfg);
}
if (action === "emoji-list") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "emojiList", guildId }, cfg);
}
if (action === "emoji-upload") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const name = readStringParam(params, "emojiName", { required: true });
const mediaUrl = readStringParam(params, "media", {
required: true,
trim: false,
});
const roleIds = readStringArrayParam(params, "roleIds");
return await handleDiscordAction(
{
action: "emojiUpload",
guildId,
name,
mediaUrl,
roleIds,
},
cfg,
);
}
if (action === "sticker-upload") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const name = readStringParam(params, "stickerName", {
required: true,
});
const description = readStringParam(params, "stickerDesc", {
required: true,
});
const tags = readStringParam(params, "stickerTags", {
required: true,
});
const mediaUrl = readStringParam(params, "media", {
required: true,
trim: false,
});
return await handleDiscordAction(
{
action: "stickerUpload",
guildId,
name,
description,
tags,
mediaUrl,
},
cfg,
);
}
if (action === "role-add" || action === "role-remove") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const userId = readStringParam(params, "userId", { required: true });
const roleId = readStringParam(params, "roleId", { required: true });
return await handleDiscordAction(
{
action: action === "role-add" ? "roleAdd" : "roleRemove",
guildId,
userId,
roleId,
},
cfg,
);
}
if (action === "channel-info") {
const channelId = readStringParam(params, "channelId", {
required: true,
});
return await handleDiscordAction(
{ action: "channelInfo", channelId },
cfg,
);
}
if (action === "channel-list") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "channelList", guildId }, cfg);
}
if (action === "channel-create") {
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const type = readNumberParam(params, "type", { integer: true });
const parentId = readParentIdParam(params);
const topic = readStringParam(params, "topic");
const position = readNumberParam(params, "position", { integer: true });
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
return await handleDiscordAction(
{
action: "channelCreate",
guildId,
name,
type: type ?? undefined,
parentId: parentId ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
nsfw,
},
cfg,
);
}
if (action === "channel-edit") {
const channelId = readStringParam(params, "channelId", {
required: true,
});
const name = readStringParam(params, "name");
const topic = readStringParam(params, "topic");
const position = readNumberParam(params, "position", { integer: true });
const parentId = readParentIdParam(params);
const nsfw = typeof params.nsfw === "boolean" ? params.nsfw : undefined;
const rateLimitPerUser = readNumberParam(params, "rateLimitPerUser", {
integer: true,
});
return await handleDiscordAction(
{
action: "channelEdit",
channelId,
name: name ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
parentId: parentId === undefined ? undefined : parentId,
nsfw,
rateLimitPerUser: rateLimitPerUser ?? undefined,
},
cfg,
);
}
if (action === "channel-delete") {
const channelId = readStringParam(params, "channelId", {
required: true,
});
return await handleDiscordAction(
{ action: "channelDelete", channelId },
cfg,
);
}
if (action === "channel-move") {
const guildId = readStringParam(params, "guildId", { required: true });
const channelId = readStringParam(params, "channelId", {
required: true,
});
const parentId = readParentIdParam(params);
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "channelMove",
guildId,
channelId,
parentId: parentId === undefined ? undefined : parentId,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-create") {
const guildId = readStringParam(params, "guildId", { required: true });
const name = readStringParam(params, "name", { required: true });
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "categoryCreate",
guildId,
name,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-edit") {
const categoryId = readStringParam(params, "categoryId", {
required: true,
});
const name = readStringParam(params, "name");
const position = readNumberParam(params, "position", { integer: true });
return await handleDiscordAction(
{
action: "categoryEdit",
categoryId,
name: name ?? undefined,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-delete") {
const categoryId = readStringParam(params, "categoryId", {
required: true,
});
return await handleDiscordAction(
{ action: "categoryDelete", categoryId },
cfg,
);
}
if (action === "voice-status") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const userId = readStringParam(params, "userId", { required: true });
return await handleDiscordAction(
{ action: "voiceStatus", guildId, userId },
cfg,
);
}
if (action === "event-list") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "eventList", guildId }, cfg);
}
if (action === "event-create") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const name = readStringParam(params, "eventName", { required: true });
const startTime = readStringParam(params, "startTime", {
required: true,
});
const endTime = readStringParam(params, "endTime");
const description = readStringParam(params, "desc");
const channelId = readStringParam(params, "channelId");
const location = readStringParam(params, "location");
const entityType = readStringParam(params, "eventType");
return await handleDiscordAction(
{
action: "eventCreate",
guildId,
name,
startTime,
endTime,
description,
channelId,
location,
entityType,
},
cfg,
);
}
if (action === "timeout" || action === "kick" || action === "ban") {
const guildId = readStringParam(params, "guildId", {
required: true,
});
const userId = readStringParam(params, "userId", { required: true });
const durationMinutes = readNumberParam(params, "durationMin", {
integer: true,
});
const until = readStringParam(params, "until");
const reason = readStringParam(params, "reason");
const deleteMessageDays = readNumberParam(params, "deleteDays", {
integer: true,
});
const discordAction = action as "timeout" | "kick" | "ban";
return await handleDiscordAction(
{
action: discordAction,
guildId,
userId,
durationMinutes,
until,
reason,
deleteMessageDays,
},
cfg,
);
}
throw new Error(
`Action ${String(action)} is not supported for provider ${providerId}.`,
);
return await handleDiscordMessageAction({ action, params, cfg });
},
};

View File

@@ -0,0 +1,395 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import {
readNumberParam,
readStringArrayParam,
readStringParam,
} from "../../../../agents/tools/common.js";
import { handleDiscordAction } from "../../../../agents/tools/discord-actions.js";
import type { ChannelMessageActionContext } from "../../types.js";
type Ctx = Pick<ChannelMessageActionContext, "action" | "params" | "cfg">;
export async function tryHandleDiscordMessageActionGuildAdmin(params: {
ctx: Ctx;
resolveChannelId: () => string;
readParentIdParam: (
params: Record<string, unknown>,
) => string | null | undefined;
}): Promise<AgentToolResult<unknown> | undefined> {
const { ctx, resolveChannelId, readParentIdParam } = params;
const { action, params: actionParams, cfg } = ctx;
if (action === "member-info") {
const userId = readStringParam(actionParams, "userId", { required: true });
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction(
{ action: "memberInfo", guildId, userId },
cfg,
);
}
if (action === "role-info") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "roleInfo", guildId }, cfg);
}
if (action === "emoji-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "emojiList", guildId }, cfg);
}
if (action === "emoji-upload") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const name = readStringParam(actionParams, "emojiName", { required: true });
const mediaUrl = readStringParam(actionParams, "media", {
required: true,
trim: false,
});
const roleIds = readStringArrayParam(actionParams, "roleIds");
return await handleDiscordAction(
{ action: "emojiUpload", guildId, name, mediaUrl, roleIds },
cfg,
);
}
if (action === "sticker-upload") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const name = readStringParam(actionParams, "stickerName", {
required: true,
});
const description = readStringParam(actionParams, "stickerDesc", {
required: true,
});
const tags = readStringParam(actionParams, "stickerTags", {
required: true,
});
const mediaUrl = readStringParam(actionParams, "media", {
required: true,
trim: false,
});
return await handleDiscordAction(
{ action: "stickerUpload", guildId, name, description, tags, mediaUrl },
cfg,
);
}
if (action === "role-add" || action === "role-remove") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const userId = readStringParam(actionParams, "userId", { required: true });
const roleId = readStringParam(actionParams, "roleId", { required: true });
return await handleDiscordAction(
{
action: action === "role-add" ? "roleAdd" : "roleRemove",
guildId,
userId,
roleId,
},
cfg,
);
}
if (action === "channel-info") {
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
return await handleDiscordAction({ action: "channelInfo", channelId }, cfg);
}
if (action === "channel-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "channelList", guildId }, cfg);
}
if (action === "channel-create") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const name = readStringParam(actionParams, "name", { required: true });
const type = readNumberParam(actionParams, "type", { integer: true });
const parentId = readParentIdParam(actionParams);
const topic = readStringParam(actionParams, "topic");
const position = readNumberParam(actionParams, "position", {
integer: true,
});
const nsfw =
typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
return await handleDiscordAction(
{
action: "channelCreate",
guildId,
name,
type: type ?? undefined,
parentId: parentId ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
nsfw,
},
cfg,
);
}
if (action === "channel-edit") {
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
const name = readStringParam(actionParams, "name");
const topic = readStringParam(actionParams, "topic");
const position = readNumberParam(actionParams, "position", {
integer: true,
});
const parentId = readParentIdParam(actionParams);
const nsfw =
typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
const rateLimitPerUser = readNumberParam(actionParams, "rateLimitPerUser", {
integer: true,
});
return await handleDiscordAction(
{
action: "channelEdit",
channelId,
name: name ?? undefined,
topic: topic ?? undefined,
position: position ?? undefined,
parentId: parentId === undefined ? undefined : parentId,
nsfw,
rateLimitPerUser: rateLimitPerUser ?? undefined,
},
cfg,
);
}
if (action === "channel-delete") {
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
return await handleDiscordAction(
{ action: "channelDelete", channelId },
cfg,
);
}
if (action === "channel-move") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
const parentId = readParentIdParam(actionParams);
const position = readNumberParam(actionParams, "position", {
integer: true,
});
return await handleDiscordAction(
{
action: "channelMove",
guildId,
channelId,
parentId: parentId === undefined ? undefined : parentId,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-create") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const name = readStringParam(actionParams, "name", { required: true });
const position = readNumberParam(actionParams, "position", {
integer: true,
});
return await handleDiscordAction(
{
action: "categoryCreate",
guildId,
name,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-edit") {
const categoryId = readStringParam(actionParams, "categoryId", {
required: true,
});
const name = readStringParam(actionParams, "name");
const position = readNumberParam(actionParams, "position", {
integer: true,
});
return await handleDiscordAction(
{
action: "categoryEdit",
categoryId,
name: name ?? undefined,
position: position ?? undefined,
},
cfg,
);
}
if (action === "category-delete") {
const categoryId = readStringParam(actionParams, "categoryId", {
required: true,
});
return await handleDiscordAction(
{ action: "categoryDelete", categoryId },
cfg,
);
}
if (action === "voice-status") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const userId = readStringParam(actionParams, "userId", { required: true });
return await handleDiscordAction(
{ action: "voiceStatus", guildId, userId },
cfg,
);
}
if (action === "event-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "eventList", guildId }, cfg);
}
if (action === "event-create") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const name = readStringParam(actionParams, "eventName", { required: true });
const startTime = readStringParam(actionParams, "startTime", {
required: true,
});
const endTime = readStringParam(actionParams, "endTime");
const description = readStringParam(actionParams, "desc");
const channelId = readStringParam(actionParams, "channelId");
const location = readStringParam(actionParams, "location");
const entityType = readStringParam(actionParams, "eventType");
return await handleDiscordAction(
{
action: "eventCreate",
guildId,
name,
startTime,
endTime,
description,
channelId,
location,
entityType,
},
cfg,
);
}
if (action === "timeout" || action === "kick" || action === "ban") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const userId = readStringParam(actionParams, "userId", { required: true });
const durationMinutes = readNumberParam(actionParams, "durationMin", {
integer: true,
});
const until = readStringParam(actionParams, "until");
const reason = readStringParam(actionParams, "reason");
const deleteMessageDays = readNumberParam(actionParams, "deleteDays", {
integer: true,
});
const discordAction = action as "timeout" | "kick" | "ban";
return await handleDiscordAction(
{
action: discordAction,
guildId,
userId,
durationMinutes,
until,
reason,
deleteMessageDays,
},
cfg,
);
}
// Some actions are conceptually "admin", but still act on a resolved channel.
if (action === "thread-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const channelId = readStringParam(actionParams, "channelId");
const includeArchived =
typeof actionParams.includeArchived === "boolean"
? actionParams.includeArchived
: undefined;
const before = readStringParam(actionParams, "before");
const limit = readNumberParam(actionParams, "limit", { integer: true });
return await handleDiscordAction(
{
action: "threadList",
guildId,
channelId,
includeArchived,
before,
limit,
},
cfg,
);
}
if (action === "thread-reply") {
const content = readStringParam(actionParams, "message", {
required: true,
});
const mediaUrl = readStringParam(actionParams, "media", { trim: false });
const replyTo = readStringParam(actionParams, "replyTo");
return await handleDiscordAction(
{
action: "threadReply",
channelId: resolveChannelId(),
content,
mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined,
},
cfg,
);
}
if (action === "search") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
const query = readStringParam(actionParams, "query", { required: true });
return await handleDiscordAction(
{
action: "searchMessages",
guildId,
content: query,
channelId: readStringParam(actionParams, "channelId"),
channelIds: readStringArrayParam(actionParams, "channelIds"),
authorId: readStringParam(actionParams, "authorId"),
authorIds: readStringArrayParam(actionParams, "authorIds"),
limit: readNumberParam(actionParams, "limit", { integer: true }),
},
cfg,
);
}
return undefined;
}

View File

@@ -0,0 +1,211 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import {
readNumberParam,
readStringArrayParam,
readStringParam,
} from "../../../../agents/tools/common.js";
import { handleDiscordAction } from "../../../../agents/tools/discord-actions.js";
import type { ChannelMessageActionContext } from "../../types.js";
import { tryHandleDiscordMessageActionGuildAdmin } from "./handle-action.guild-admin.js";
const providerId = "discord";
function readParentIdParam(
params: Record<string, unknown>,
): string | null | undefined {
if (params.clearParent === true) return null;
if (params.parentId === null) return null;
return readStringParam(params, "parentId");
}
export async function handleDiscordMessageAction(
ctx: Pick<ChannelMessageActionContext, "action" | "params" | "cfg">,
): Promise<AgentToolResult<unknown>> {
const { action, params, cfg } = ctx;
const resolveChannelId = () =>
readStringParam(params, "channelId") ??
readStringParam(params, "to", { required: true });
if (action === "send") {
const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", {
required: true,
allowEmpty: true,
});
const mediaUrl = readStringParam(params, "media", { trim: false });
const replyTo = readStringParam(params, "replyTo");
return await handleDiscordAction(
{
action: "sendMessage",
to,
content,
mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined,
},
cfg,
);
}
if (action === "poll") {
const to = readStringParam(params, "to", { required: true });
const question = readStringParam(params, "pollQuestion", {
required: true,
});
const answers =
readStringArrayParam(params, "pollOption", { required: true }) ?? [];
const allowMultiselect =
typeof params.pollMulti === "boolean" ? params.pollMulti : undefined;
const durationHours = readNumberParam(params, "pollDurationHours", {
integer: true,
});
return await handleDiscordAction(
{
action: "poll",
to,
question,
answers,
allowMultiselect,
durationHours: durationHours ?? undefined,
content: readStringParam(params, "message"),
},
cfg,
);
}
if (action === "react") {
const messageId = readStringParam(params, "messageId", { required: true });
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
const remove =
typeof params.remove === "boolean" ? params.remove : undefined;
return await handleDiscordAction(
{
action: "react",
channelId: resolveChannelId(),
messageId,
emoji,
remove,
},
cfg,
);
}
if (action === "reactions") {
const messageId = readStringParam(params, "messageId", { required: true });
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{ action: "reactions", channelId: resolveChannelId(), messageId, limit },
cfg,
);
}
if (action === "read") {
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{
action: "readMessages",
channelId: resolveChannelId(),
limit,
before: readStringParam(params, "before"),
after: readStringParam(params, "after"),
around: readStringParam(params, "around"),
},
cfg,
);
}
if (action === "edit") {
const messageId = readStringParam(params, "messageId", { required: true });
const content = readStringParam(params, "message", { required: true });
return await handleDiscordAction(
{
action: "editMessage",
channelId: resolveChannelId(),
messageId,
content,
},
cfg,
);
}
if (action === "delete") {
const messageId = readStringParam(params, "messageId", { required: true });
return await handleDiscordAction(
{ action: "deleteMessage", channelId: resolveChannelId(), messageId },
cfg,
);
}
if (action === "pin" || action === "unpin" || action === "list-pins") {
const messageId =
action === "list-pins"
? undefined
: readStringParam(params, "messageId", { required: true });
return await handleDiscordAction(
{
action:
action === "pin"
? "pinMessage"
: action === "unpin"
? "unpinMessage"
: "listPins",
channelId: resolveChannelId(),
messageId,
},
cfg,
);
}
if (action === "permissions") {
return await handleDiscordAction(
{ action: "permissions", channelId: resolveChannelId() },
cfg,
);
}
if (action === "thread-create") {
const name = readStringParam(params, "threadName", { required: true });
const messageId = readStringParam(params, "messageId");
const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", {
integer: true,
});
return await handleDiscordAction(
{
action: "threadCreate",
channelId: resolveChannelId(),
name,
messageId,
autoArchiveMinutes,
},
cfg,
);
}
if (action === "sticker") {
const stickerIds =
readStringArrayParam(params, "stickerId", {
required: true,
label: "sticker-id",
}) ?? [];
return await handleDiscordAction(
{
action: "sticker",
to: readStringParam(params, "to", { required: true }),
stickerIds,
content: readStringParam(params, "message"),
},
cfg,
);
}
const adminResult = await tryHandleDiscordMessageActionGuildAdmin({
ctx,
resolveChannelId,
readParentIdParam,
});
if (adminResult !== undefined) return adminResult;
throw new Error(
`Action ${String(action)} is not supported for provider ${providerId}.`,
);
}

View File

@@ -0,0 +1,224 @@
import {
createActionGate,
readNumberParam,
readStringParam,
} from "../../agents/tools/common.js";
import {
handleSlackAction,
type SlackActionContext,
} from "../../agents/tools/slack-actions.js";
import { listEnabledSlackAccounts } from "../../slack/accounts.js";
import type {
ChannelMessageActionAdapter,
ChannelMessageActionContext,
ChannelMessageActionName,
ChannelToolSend,
} from "./types.js";
export function createSlackActions(
providerId: string,
): ChannelMessageActionAdapter {
return {
listActions: ({ cfg }) => {
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) return [];
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
(account.actions ?? cfg.channels?.slack?.actions) as Record<
string,
boolean | undefined
>,
);
if (gate(key, defaultValue)) return true;
}
return false;
};
const actions = new Set<ChannelMessageActionName>(["send"]);
if (isActionEnabled("reactions")) {
actions.add("react");
actions.add("reactions");
}
if (isActionEnabled("messages")) {
actions.add("read");
actions.add("edit");
actions.add("delete");
}
if (isActionEnabled("pins")) {
actions.add("pin");
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) actions.add("member-info");
if (isActionEnabled("emojiList")) actions.add("emoji-list");
return Array.from(actions);
},
extractToolSend: ({ args }): ChannelToolSend | null => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") return null;
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) return null;
const accountId =
typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},
handleAction: async (ctx: ChannelMessageActionContext) => {
const { action, params, cfg } = ctx;
const accountId = ctx.accountId ?? undefined;
const toolContext = ctx.toolContext as SlackActionContext | undefined;
const resolveChannelId = () =>
readStringParam(params, "channelId") ??
readStringParam(params, "to", { required: true });
if (action === "send") {
const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", {
required: true,
allowEmpty: true,
});
const mediaUrl = readStringParam(params, "media", { trim: false });
const threadId = readStringParam(params, "threadId");
const replyTo = readStringParam(params, "replyTo");
return await handleSlackAction(
{
action: "sendMessage",
to,
content,
mediaUrl: mediaUrl ?? undefined,
accountId: accountId ?? undefined,
threadTs: threadId ?? replyTo ?? undefined,
},
cfg,
toolContext,
);
}
if (action === "react") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
const remove =
typeof params.remove === "boolean" ? params.remove : undefined;
return await handleSlackAction(
{
action: "react",
channelId: resolveChannelId(),
messageId,
emoji,
remove,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "reactions") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
{
action: "reactions",
channelId: resolveChannelId(),
messageId,
limit,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "read") {
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
{
action: "readMessages",
channelId: resolveChannelId(),
limit,
before: readStringParam(params, "before"),
after: readStringParam(params, "after"),
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "edit") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const content = readStringParam(params, "message", { required: true });
return await handleSlackAction(
{
action: "editMessage",
channelId: resolveChannelId(),
messageId,
content,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "delete") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
return await handleSlackAction(
{
action: "deleteMessage",
channelId: resolveChannelId(),
messageId,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "pin" || action === "unpin" || action === "list-pins") {
const messageId =
action === "list-pins"
? undefined
: readStringParam(params, "messageId", { required: true });
return await handleSlackAction(
{
action:
action === "pin"
? "pinMessage"
: action === "unpin"
? "unpinMessage"
: "listPins",
channelId: resolveChannelId(),
messageId,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "member-info") {
const userId = readStringParam(params, "userId", { required: true });
return await handleSlackAction(
{ action: "memberInfo", userId, accountId: accountId ?? undefined },
cfg,
);
}
if (action === "emoji-list") {
return await handleSlackAction(
{ action: "emojiList", accountId: accountId ?? undefined },
cfg,
);
}
throw new Error(
`Action ${action} is not supported for provider ${providerId}.`,
);
},
};
}

View File

@@ -1,15 +1,8 @@
import {
createActionGate,
readNumberParam,
readStringParam,
} from "../../agents/tools/common.js";
import { handleSlackAction } from "../../agents/tools/slack-actions.js";
import {
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
} from "../../routing/session-key.js";
import {
listEnabledSlackAccounts,
listSlackAccountIds,
type ResolvedSlackAccount,
resolveDefaultSlackAccountId,
@@ -31,7 +24,8 @@ import {
applyAccountNameToChannelSection,
migrateBaseNameToDefaultAccount,
} from "./setup-helpers.js";
import type { ChannelMessageActionName, ChannelPlugin } from "./types.js";
import { createSlackActions } from "./slack.actions.js";
import type { ChannelPlugin } from "./types.js";
const meta = getChatChannelMeta("slack");
@@ -157,206 +151,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
messaging: {
normalizeTarget: normalizeSlackMessagingTarget,
},
actions: {
listActions: ({ cfg }) => {
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) return [];
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
(account.actions ?? cfg.channels?.slack?.actions) as Record<
string,
boolean | undefined
>,
);
if (gate(key, defaultValue)) return true;
}
return false;
};
const actions = new Set<ChannelMessageActionName>(["send"]);
if (isActionEnabled("reactions")) {
actions.add("react");
actions.add("reactions");
}
if (isActionEnabled("messages")) {
actions.add("read");
actions.add("edit");
actions.add("delete");
}
if (isActionEnabled("pins")) {
actions.add("pin");
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) actions.add("member-info");
if (isActionEnabled("emojiList")) actions.add("emoji-list");
return Array.from(actions);
},
extractToolSend: ({ args }) => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") return null;
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) return null;
const accountId =
typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},
handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
const resolveChannelId = () =>
readStringParam(params, "channelId") ??
readStringParam(params, "to", { required: true });
if (action === "send") {
const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "message", {
required: true,
allowEmpty: true,
});
const mediaUrl = readStringParam(params, "media", { trim: false });
const threadId = readStringParam(params, "threadId");
const replyTo = readStringParam(params, "replyTo");
return await handleSlackAction(
{
action: "sendMessage",
to,
content,
mediaUrl: mediaUrl ?? undefined,
accountId: accountId ?? undefined,
threadTs: threadId ?? replyTo ?? undefined,
},
cfg,
toolContext,
);
}
if (action === "react") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
const remove =
typeof params.remove === "boolean" ? params.remove : undefined;
return await handleSlackAction(
{
action: "react",
channelId: resolveChannelId(),
messageId,
emoji,
remove,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "reactions") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
{
action: "reactions",
channelId: resolveChannelId(),
messageId,
limit,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "read") {
const limit = readNumberParam(params, "limit", { integer: true });
return await handleSlackAction(
{
action: "readMessages",
channelId: resolveChannelId(),
limit,
before: readStringParam(params, "before"),
after: readStringParam(params, "after"),
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "edit") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
const content = readStringParam(params, "message", { required: true });
return await handleSlackAction(
{
action: "editMessage",
channelId: resolveChannelId(),
messageId,
content,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "delete") {
const messageId = readStringParam(params, "messageId", {
required: true,
});
return await handleSlackAction(
{
action: "deleteMessage",
channelId: resolveChannelId(),
messageId,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "pin" || action === "unpin" || action === "list-pins") {
const messageId =
action === "list-pins"
? undefined
: readStringParam(params, "messageId", { required: true });
return await handleSlackAction(
{
action:
action === "pin"
? "pinMessage"
: action === "unpin"
? "unpinMessage"
: "listPins",
channelId: resolveChannelId(),
messageId,
accountId: accountId ?? undefined,
},
cfg,
);
}
if (action === "member-info") {
const userId = readStringParam(params, "userId", { required: true });
return await handleSlackAction(
{ action: "memberInfo", userId, accountId: accountId ?? undefined },
cfg,
);
}
if (action === "emoji-list") {
return await handleSlackAction(
{ action: "emojiList", accountId: accountId ?? undefined },
cfg,
);
}
throw new Error(
`Action ${action} is not supported for provider ${meta.id}.`,
);
},
},
actions: createSlackActions(meta.id),
setup: {
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
applyAccountName: ({ cfg, accountId, name }) =>

View File

@@ -0,0 +1,268 @@
import type { ClawdbotConfig } from "../../config/config.js";
import type {
OutboundDeliveryResult,
OutboundSendDeps,
} from "../../infra/outbound/deliver.js";
import type { RuntimeEnv } from "../../runtime.js";
import type {
ChannelAccountSnapshot,
ChannelAccountState,
ChannelGroupContext,
ChannelHeartbeatDeps,
ChannelLogSink,
ChannelOutboundTargetMode,
ChannelPollContext,
ChannelPollResult,
ChannelSecurityContext,
ChannelSecurityDmPolicy,
ChannelSetupInput,
ChannelStatusIssue,
} from "./types.core.js";
export type ChannelSetupAdapter = {
resolveAccountId?: (params: {
cfg: ClawdbotConfig;
accountId?: string;
}) => string;
applyAccountName?: (params: {
cfg: ClawdbotConfig;
accountId: string;
name?: string;
}) => ClawdbotConfig;
applyAccountConfig: (params: {
cfg: ClawdbotConfig;
accountId: string;
input: ChannelSetupInput;
}) => ClawdbotConfig;
validateInput?: (params: {
cfg: ClawdbotConfig;
accountId: string;
input: ChannelSetupInput;
}) => string | null;
};
export type ChannelConfigAdapter<ResolvedAccount> = {
listAccountIds: (cfg: ClawdbotConfig) => string[];
resolveAccount: (
cfg: ClawdbotConfig,
accountId?: string | null,
) => ResolvedAccount;
defaultAccountId?: (cfg: ClawdbotConfig) => string;
setAccountEnabled?: (params: {
cfg: ClawdbotConfig;
accountId: string;
enabled: boolean;
}) => ClawdbotConfig;
deleteAccount?: (params: {
cfg: ClawdbotConfig;
accountId: string;
}) => ClawdbotConfig;
isEnabled?: (account: ResolvedAccount, cfg: ClawdbotConfig) => boolean;
disabledReason?: (account: ResolvedAccount, cfg: ClawdbotConfig) => string;
isConfigured?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => boolean | Promise<boolean>;
unconfiguredReason?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => string;
describeAccount?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => ChannelAccountSnapshot;
resolveAllowFrom?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => string[] | undefined;
formatAllowFrom?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
allowFrom: Array<string | number>;
}) => string[];
};
export type ChannelGroupAdapter = {
resolveRequireMention?: (params: ChannelGroupContext) => boolean | undefined;
resolveGroupIntroHint?: (params: ChannelGroupContext) => string | undefined;
};
export type ChannelOutboundContext = {
cfg: ClawdbotConfig;
to: string;
text: string;
mediaUrl?: string;
gifPlayback?: boolean;
replyToId?: string | null;
threadId?: number | null;
accountId?: string | null;
deps?: OutboundSendDeps;
};
export type ChannelOutboundAdapter = {
deliveryMode: "direct" | "gateway" | "hybrid";
chunker?: ((text: string, limit: number) => string[]) | null;
textChunkLimit?: number;
pollMaxOptions?: number;
resolveTarget?: (params: {
cfg?: ClawdbotConfig;
to?: string;
allowFrom?: string[];
accountId?: string | null;
mode?: ChannelOutboundTargetMode;
}) => { ok: true; to: string } | { ok: false; error: Error };
sendText?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendMedia?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendPoll?: (ctx: ChannelPollContext) => Promise<ChannelPollResult>;
};
export type ChannelStatusAdapter<ResolvedAccount> = {
defaultRuntime?: ChannelAccountSnapshot;
buildChannelSummary?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
defaultAccountId: string;
snapshot: ChannelAccountSnapshot;
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
probeAccount?: (params: {
account: ResolvedAccount;
timeoutMs: number;
cfg: ClawdbotConfig;
}) => Promise<unknown>;
auditAccount?: (params: {
account: ResolvedAccount;
timeoutMs: number;
cfg: ClawdbotConfig;
probe?: unknown;
}) => Promise<unknown>;
buildAccountSnapshot?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
runtime?: ChannelAccountSnapshot;
probe?: unknown;
audit?: unknown;
}) => ChannelAccountSnapshot | Promise<ChannelAccountSnapshot>;
logSelfId?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
runtime: RuntimeEnv;
includeChannelPrefix?: boolean;
}) => void;
resolveAccountState?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
configured: boolean;
enabled: boolean;
}) => ChannelAccountState;
collectStatusIssues?: (
accounts: ChannelAccountSnapshot[],
) => ChannelStatusIssue[];
};
export type ChannelGatewayContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId: string;
account: ResolvedAccount;
runtime: RuntimeEnv;
abortSignal: AbortSignal;
log?: ChannelLogSink;
getStatus: () => ChannelAccountSnapshot;
setStatus: (next: ChannelAccountSnapshot) => void;
};
export type ChannelLogoutResult = {
cleared: boolean;
loggedOut?: boolean;
[key: string]: unknown;
};
export type ChannelLoginWithQrStartResult = {
qrDataUrl?: string;
message: string;
};
export type ChannelLoginWithQrWaitResult = {
connected: boolean;
message: string;
};
export type ChannelLogoutContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId: string;
account: ResolvedAccount;
runtime: RuntimeEnv;
log?: ChannelLogSink;
};
export type ChannelPairingAdapter = {
idLabel: string;
normalizeAllowEntry?: (entry: string) => string;
notifyApproval?: (params: {
cfg: ClawdbotConfig;
id: string;
runtime?: RuntimeEnv;
}) => Promise<void>;
};
export type ChannelGatewayAdapter<ResolvedAccount = unknown> = {
startAccount?: (
ctx: ChannelGatewayContext<ResolvedAccount>,
) => Promise<unknown>;
stopAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<void>;
loginWithQrStart?: (params: {
accountId?: string;
force?: boolean;
timeoutMs?: number;
verbose?: boolean;
}) => Promise<ChannelLoginWithQrStartResult>;
loginWithQrWait?: (params: {
accountId?: string;
timeoutMs?: number;
}) => Promise<ChannelLoginWithQrWaitResult>;
logoutAccount?: (
ctx: ChannelLogoutContext<ResolvedAccount>,
) => Promise<ChannelLogoutResult>;
};
export type ChannelAuthAdapter = {
login?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
runtime: RuntimeEnv;
verbose?: boolean;
channelInput?: string | null;
}) => Promise<void>;
};
export type ChannelHeartbeatAdapter = {
checkReady?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
deps?: ChannelHeartbeatDeps;
}) => Promise<{ ok: boolean; reason: string }>;
resolveRecipients?: (params: {
cfg: ClawdbotConfig;
opts?: { to?: string; all?: boolean };
}) => { recipients: string[]; source: string };
};
export type ChannelElevatedAdapter = {
allowFromFallback?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => Array<string | number> | undefined;
};
export type ChannelCommandAdapter = {
enforceOwnerForCommands?: boolean;
skipWhenConfigEmpty?: boolean;
};
export type ChannelSecurityAdapter<ResolvedAccount = unknown> = {
resolveDmPolicy?: (
ctx: ChannelSecurityContext<ResolvedAccount>,
) => ChannelSecurityDmPolicy | null;
collectWarnings?: (
ctx: ChannelSecurityContext<ResolvedAccount>,
) => Promise<string[]> | string[];
};

View File

@@ -0,0 +1,263 @@
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
import type { TSchema } from "@sinclair/typebox";
import type { MsgContext } from "../../auto-reply/templating.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { PollInput } from "../../polls.js";
import type {
GatewayClientMode,
GatewayClientName,
} from "../../utils/message-channel.js";
import type { ChatChannelId } from "../registry.js";
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
export type ChannelId = ChatChannelId;
export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat";
export type ChannelAgentTool = AgentTool<TSchema, unknown>;
export type ChannelAgentToolFactory = (params: {
cfg?: ClawdbotConfig;
}) => ChannelAgentTool[];
export type ChannelSetupInput = {
name?: string;
token?: string;
tokenFile?: string;
botToken?: string;
appToken?: string;
signalNumber?: string;
cliPath?: string;
dbPath?: string;
service?: "imessage" | "sms" | "auto";
region?: string;
authDir?: string;
httpUrl?: string;
httpHost?: string;
httpPort?: string;
useEnv?: boolean;
};
export type ChannelStatusIssue = {
channel: ChannelId;
accountId: string;
kind: "intent" | "permissions" | "config" | "auth" | "runtime";
message: string;
fix?: string;
};
export type ChannelAccountState =
| "linked"
| "not linked"
| "configured"
| "not configured"
| "enabled"
| "disabled";
export type ChannelHeartbeatDeps = {
webAuthExists?: () => Promise<boolean>;
hasActiveWebListener?: () => boolean;
};
export type ChannelMeta = {
id: ChannelId;
label: string;
selectionLabel: string;
docsPath: string;
docsLabel?: string;
blurb: string;
order?: number;
showConfigured?: boolean;
quickstartAllowFrom?: boolean;
forceAccountBinding?: boolean;
preferSessionLookupForAnnounceTarget?: boolean;
};
export type ChannelAccountSnapshot = {
accountId: string;
name?: string;
enabled?: boolean;
configured?: boolean;
linked?: boolean;
running?: boolean;
connected?: boolean;
reconnectAttempts?: number;
lastConnectedAt?: number | null;
lastDisconnect?:
| string
| {
at: number;
status?: number;
error?: string;
loggedOut?: boolean;
}
| null;
lastMessageAt?: number | null;
lastEventAt?: number | null;
lastError?: string | null;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
mode?: string;
dmPolicy?: string;
allowFrom?: string[];
tokenSource?: string;
botTokenSource?: string;
appTokenSource?: string;
baseUrl?: string;
allowUnmentionedGroups?: boolean;
cliPath?: string | null;
dbPath?: string | null;
port?: number | null;
probe?: unknown;
lastProbeAt?: number | null;
audit?: unknown;
application?: unknown;
bot?: unknown;
};
export type ChannelLogSink = {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
debug?: (msg: string) => void;
};
export type ChannelGroupContext = {
cfg: ClawdbotConfig;
groupId?: string | null;
groupRoom?: string | null;
groupSpace?: string | null;
accountId?: string | null;
};
export type ChannelCapabilities = {
chatTypes: Array<"direct" | "group" | "channel" | "thread">;
polls?: boolean;
reactions?: boolean;
threads?: boolean;
media?: boolean;
nativeCommands?: boolean;
blockStreaming?: boolean;
};
export type ChannelSecurityDmPolicy = {
policy: string;
allowFrom?: Array<string | number> | null;
policyPath?: string;
allowFromPath: string;
approveHint: string;
normalizeEntry?: (raw: string) => string;
};
export type ChannelSecurityContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId?: string | null;
account: ResolvedAccount;
};
export type ChannelMentionAdapter = {
stripPatterns?: (params: {
ctx: MsgContext;
cfg: ClawdbotConfig | undefined;
agentId?: string;
}) => string[];
stripMentions?: (params: {
text: string;
ctx: MsgContext;
cfg: ClawdbotConfig | undefined;
agentId?: string;
}) => string;
};
export type ChannelStreamingAdapter = {
blockStreamingCoalesceDefaults?: {
minChars: number;
idleMs: number;
};
};
export type ChannelThreadingAdapter = {
resolveReplyToMode?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => "off" | "first" | "all";
allowTagsWhenOff?: boolean;
buildToolContext?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
context: ChannelThreadingContext;
hasRepliedRef?: { value: boolean };
}) => ChannelThreadingToolContext | undefined;
};
export type ChannelThreadingContext = {
Channel?: string;
To?: string;
ReplyToId?: string;
ThreadLabel?: string;
};
export type ChannelThreadingToolContext = {
currentChannelId?: string;
currentThreadTs?: string;
replyToMode?: "off" | "first" | "all";
hasRepliedRef?: { value: boolean };
};
export type ChannelMessagingAdapter = {
normalizeTarget?: (raw: string) => string | undefined;
};
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
export type ChannelMessageActionContext = {
channel: ChannelId;
action: ChannelMessageActionName;
cfg: ClawdbotConfig;
params: Record<string, unknown>;
accountId?: string | null;
gateway?: {
url?: string;
token?: string;
timeoutMs?: number;
clientName: GatewayClientName;
clientDisplayName?: string;
mode: GatewayClientMode;
};
toolContext?: ChannelThreadingToolContext;
dryRun?: boolean;
};
export type ChannelToolSend = {
to: string;
accountId?: string | null;
};
export type ChannelMessageActionAdapter = {
listActions?: (params: { cfg: ClawdbotConfig }) => ChannelMessageActionName[];
supportsAction?: (params: { action: ChannelMessageActionName }) => boolean;
supportsButtons?: (params: { cfg: ClawdbotConfig }) => boolean;
extractToolSend?: (params: {
args: Record<string, unknown>;
}) => ChannelToolSend | null;
handleAction?: (
ctx: ChannelMessageActionContext,
) => Promise<AgentToolResult<unknown>>;
};
export type ChannelPollResult = {
messageId: string;
toJid?: string;
channelId?: string;
conversationId?: string;
pollId?: string;
};
export type ChannelPollContext = {
cfg: ClawdbotConfig;
to: string;
poll: PollInput;
accountId?: string | null;
};

View File

@@ -0,0 +1,58 @@
import type { ChannelOnboardingAdapter } from "./onboarding-types.js";
import type {
ChannelAuthAdapter,
ChannelCommandAdapter,
ChannelConfigAdapter,
ChannelElevatedAdapter,
ChannelGatewayAdapter,
ChannelGroupAdapter,
ChannelHeartbeatAdapter,
ChannelOutboundAdapter,
ChannelPairingAdapter,
ChannelSecurityAdapter,
ChannelSetupAdapter,
ChannelStatusAdapter,
} from "./types.adapters.js";
import type {
ChannelAgentTool,
ChannelAgentToolFactory,
ChannelCapabilities,
ChannelId,
ChannelMentionAdapter,
ChannelMessageActionAdapter,
ChannelMessagingAdapter,
ChannelMeta,
ChannelStreamingAdapter,
ChannelThreadingAdapter,
} from "./types.core.js";
// Channel docking: implement this contract in src/channels/plugins/<id>.ts.
// biome-ignore lint/suspicious/noExplicitAny: registry aggregates heterogeneous account types.
export type ChannelPlugin<ResolvedAccount = any> = {
id: ChannelId;
meta: ChannelMeta;
capabilities: ChannelCapabilities;
reload?: { configPrefixes: string[]; noopPrefixes?: string[] };
// CLI onboarding wizard hooks for this channel.
onboarding?: ChannelOnboardingAdapter;
config: ChannelConfigAdapter<ResolvedAccount>;
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
security?: ChannelSecurityAdapter<ResolvedAccount>;
groups?: ChannelGroupAdapter;
mentions?: ChannelMentionAdapter;
outbound?: ChannelOutboundAdapter;
status?: ChannelStatusAdapter<ResolvedAccount>;
gatewayMethods?: string[];
gateway?: ChannelGatewayAdapter<ResolvedAccount>;
auth?: ChannelAuthAdapter;
elevated?: ChannelElevatedAdapter;
commands?: ChannelCommandAdapter;
streaming?: ChannelStreamingAdapter;
threading?: ChannelThreadingAdapter;
messaging?: ChannelMessagingAdapter;
actions?: ChannelMessageActionAdapter;
heartbeat?: ChannelHeartbeatAdapter;
// Channel-owned agent tools (login flows, etc.).
agentTools?: ChannelAgentToolFactory | ChannelAgentTool[];
};

View File

@@ -1,550 +1,56 @@
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
import type { TSchema } from "@sinclair/typebox";
import type { MsgContext } from "../../auto-reply/templating.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type {
OutboundDeliveryResult,
OutboundSendDeps,
} from "../../infra/outbound/deliver.js";
import type { PollInput } from "../../polls.js";
import type { RuntimeEnv } from "../../runtime.js";
import type {
GatewayClientMode,
GatewayClientName,
} from "../../utils/message-channel.js";
import type { ChatChannelId } from "../registry.js";
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
import type { ChannelOnboardingAdapter } from "./onboarding-types.js";
export { CHANNEL_MESSAGE_ACTION_NAMES } from "./message-action-names.js";
export type ChannelId = ChatChannelId;
export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat";
export type ChannelAgentTool = AgentTool<TSchema, unknown>;
export type ChannelAgentToolFactory = (params: {
cfg?: ClawdbotConfig;
}) => ChannelAgentTool[];
export type ChannelSetupInput = {
name?: string;
token?: string;
tokenFile?: string;
botToken?: string;
appToken?: string;
signalNumber?: string;
cliPath?: string;
dbPath?: string;
service?: "imessage" | "sms" | "auto";
region?: string;
authDir?: string;
httpUrl?: string;
httpHost?: string;
httpPort?: string;
useEnv?: boolean;
};
export type ChannelStatusIssue = {
channel: ChannelId;
accountId: string;
kind: "intent" | "permissions" | "config" | "auth" | "runtime";
message: string;
fix?: string;
};
export type ChannelAccountState =
| "linked"
| "not linked"
| "configured"
| "not configured"
| "enabled"
| "disabled";
export type ChannelSetupAdapter = {
resolveAccountId?: (params: {
cfg: ClawdbotConfig;
accountId?: string;
}) => string;
applyAccountName?: (params: {
cfg: ClawdbotConfig;
accountId: string;
name?: string;
}) => ClawdbotConfig;
applyAccountConfig: (params: {
cfg: ClawdbotConfig;
accountId: string;
input: ChannelSetupInput;
}) => ClawdbotConfig;
validateInput?: (params: {
cfg: ClawdbotConfig;
accountId: string;
input: ChannelSetupInput;
}) => string | null;
};
export type ChannelHeartbeatDeps = {
webAuthExists?: () => Promise<boolean>;
hasActiveWebListener?: () => boolean;
};
export type ChannelMeta = {
id: ChannelId;
label: string;
selectionLabel: string;
docsPath: string;
docsLabel?: string;
blurb: string;
order?: number;
showConfigured?: boolean;
quickstartAllowFrom?: boolean;
forceAccountBinding?: boolean;
preferSessionLookupForAnnounceTarget?: boolean;
};
export type ChannelAccountSnapshot = {
accountId: string;
name?: string;
enabled?: boolean;
configured?: boolean;
linked?: boolean;
running?: boolean;
connected?: boolean;
reconnectAttempts?: number;
lastConnectedAt?: number | null;
lastDisconnect?:
| string
| {
at: number;
status?: number;
error?: string;
loggedOut?: boolean;
}
| null;
lastMessageAt?: number | null;
lastEventAt?: number | null;
lastError?: string | null;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
mode?: string;
dmPolicy?: string;
allowFrom?: string[];
tokenSource?: string;
botTokenSource?: string;
appTokenSource?: string;
baseUrl?: string;
allowUnmentionedGroups?: boolean;
cliPath?: string | null;
dbPath?: string | null;
port?: number | null;
probe?: unknown;
lastProbeAt?: number | null;
audit?: unknown;
application?: unknown;
bot?: unknown;
};
export type ChannelLogSink = {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
debug?: (msg: string) => void;
};
export type ChannelConfigAdapter<ResolvedAccount> = {
listAccountIds: (cfg: ClawdbotConfig) => string[];
resolveAccount: (
cfg: ClawdbotConfig,
accountId?: string | null,
) => ResolvedAccount;
defaultAccountId?: (cfg: ClawdbotConfig) => string;
setAccountEnabled?: (params: {
cfg: ClawdbotConfig;
accountId: string;
enabled: boolean;
}) => ClawdbotConfig;
deleteAccount?: (params: {
cfg: ClawdbotConfig;
accountId: string;
}) => ClawdbotConfig;
isEnabled?: (account: ResolvedAccount, cfg: ClawdbotConfig) => boolean;
disabledReason?: (account: ResolvedAccount, cfg: ClawdbotConfig) => string;
isConfigured?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => boolean | Promise<boolean>;
unconfiguredReason?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => string;
describeAccount?: (
account: ResolvedAccount,
cfg: ClawdbotConfig,
) => ChannelAccountSnapshot;
resolveAllowFrom?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => string[] | undefined;
formatAllowFrom?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
allowFrom: Array<string | number>;
}) => string[];
};
export type ChannelGroupContext = {
cfg: ClawdbotConfig;
groupId?: string | null;
groupRoom?: string | null;
groupSpace?: string | null;
accountId?: string | null;
};
export type ChannelGroupAdapter = {
resolveRequireMention?: (params: ChannelGroupContext) => boolean | undefined;
resolveGroupIntroHint?: (params: ChannelGroupContext) => string | undefined;
};
export type ChannelOutboundContext = {
cfg: ClawdbotConfig;
to: string;
text: string;
mediaUrl?: string;
gifPlayback?: boolean;
replyToId?: string | null;
threadId?: number | null;
accountId?: string | null;
deps?: OutboundSendDeps;
};
export type ChannelPollResult = {
messageId: string;
toJid?: string;
channelId?: string;
conversationId?: string;
pollId?: string;
};
export type ChannelPollContext = {
cfg: ClawdbotConfig;
to: string;
poll: PollInput;
accountId?: string | null;
};
export type ChannelOutboundAdapter = {
deliveryMode: "direct" | "gateway" | "hybrid";
chunker?: ((text: string, limit: number) => string[]) | null;
textChunkLimit?: number;
pollMaxOptions?: number;
resolveTarget?: (params: {
cfg?: ClawdbotConfig;
to?: string;
allowFrom?: string[];
accountId?: string | null;
mode?: ChannelOutboundTargetMode;
}) => { ok: true; to: string } | { ok: false; error: Error };
sendText?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendMedia?: (ctx: ChannelOutboundContext) => Promise<OutboundDeliveryResult>;
sendPoll?: (ctx: ChannelPollContext) => Promise<ChannelPollResult>;
};
export type ChannelStatusAdapter<ResolvedAccount> = {
defaultRuntime?: ChannelAccountSnapshot;
buildChannelSummary?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
defaultAccountId: string;
snapshot: ChannelAccountSnapshot;
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
probeAccount?: (params: {
account: ResolvedAccount;
timeoutMs: number;
cfg: ClawdbotConfig;
}) => Promise<unknown>;
auditAccount?: (params: {
account: ResolvedAccount;
timeoutMs: number;
cfg: ClawdbotConfig;
probe?: unknown;
}) => Promise<unknown>;
buildAccountSnapshot?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
runtime?: ChannelAccountSnapshot;
probe?: unknown;
audit?: unknown;
}) => ChannelAccountSnapshot | Promise<ChannelAccountSnapshot>;
logSelfId?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
runtime: RuntimeEnv;
includeChannelPrefix?: boolean;
}) => void;
resolveAccountState?: (params: {
account: ResolvedAccount;
cfg: ClawdbotConfig;
configured: boolean;
enabled: boolean;
}) => ChannelAccountState;
collectStatusIssues?: (
accounts: ChannelAccountSnapshot[],
) => ChannelStatusIssue[];
};
export type ChannelGatewayContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId: string;
account: ResolvedAccount;
runtime: RuntimeEnv;
abortSignal: AbortSignal;
log?: ChannelLogSink;
getStatus: () => ChannelAccountSnapshot;
setStatus: (next: ChannelAccountSnapshot) => void;
};
export type ChannelLogoutResult = {
cleared: boolean;
loggedOut?: boolean;
[key: string]: unknown;
};
export type ChannelLoginWithQrStartResult = {
qrDataUrl?: string;
message: string;
};
export type ChannelLoginWithQrWaitResult = {
connected: boolean;
message: string;
};
export type ChannelLogoutContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId: string;
account: ResolvedAccount;
runtime: RuntimeEnv;
log?: ChannelLogSink;
};
export type ChannelPairingAdapter = {
idLabel: string;
normalizeAllowEntry?: (entry: string) => string;
notifyApproval?: (params: {
cfg: ClawdbotConfig;
id: string;
runtime?: RuntimeEnv;
}) => Promise<void>;
};
export type ChannelGatewayAdapter<ResolvedAccount = unknown> = {
startAccount?: (
ctx: ChannelGatewayContext<ResolvedAccount>,
) => Promise<unknown>;
stopAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<void>;
loginWithQrStart?: (params: {
accountId?: string;
force?: boolean;
timeoutMs?: number;
verbose?: boolean;
}) => Promise<ChannelLoginWithQrStartResult>;
loginWithQrWait?: (params: {
accountId?: string;
timeoutMs?: number;
}) => Promise<ChannelLoginWithQrWaitResult>;
logoutAccount?: (
ctx: ChannelLogoutContext<ResolvedAccount>,
) => Promise<ChannelLogoutResult>;
};
export type ChannelAuthAdapter = {
login?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
runtime: RuntimeEnv;
verbose?: boolean;
channelInput?: string | null;
}) => Promise<void>;
};
export type ChannelHeartbeatAdapter = {
checkReady?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
deps?: ChannelHeartbeatDeps;
}) => Promise<{ ok: boolean; reason: string }>;
resolveRecipients?: (params: {
cfg: ClawdbotConfig;
opts?: { to?: string; all?: boolean };
}) => { recipients: string[]; source: string };
};
export type ChannelCapabilities = {
chatTypes: Array<"direct" | "group" | "channel" | "thread">;
polls?: boolean;
reactions?: boolean;
threads?: boolean;
media?: boolean;
nativeCommands?: boolean;
blockStreaming?: boolean;
};
export type ChannelElevatedAdapter = {
allowFromFallback?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => Array<string | number> | undefined;
};
export type ChannelCommandAdapter = {
enforceOwnerForCommands?: boolean;
skipWhenConfigEmpty?: boolean;
};
export type ChannelSecurityDmPolicy = {
policy: string;
allowFrom?: Array<string | number> | null;
policyPath?: string;
allowFromPath: string;
approveHint: string;
normalizeEntry?: (raw: string) => string;
};
export type ChannelSecurityContext<ResolvedAccount = unknown> = {
cfg: ClawdbotConfig;
accountId?: string | null;
account: ResolvedAccount;
};
export type ChannelSecurityAdapter<ResolvedAccount = unknown> = {
resolveDmPolicy?: (
ctx: ChannelSecurityContext<ResolvedAccount>,
) => ChannelSecurityDmPolicy | null;
collectWarnings?: (
ctx: ChannelSecurityContext<ResolvedAccount>,
) => Promise<string[]> | string[];
};
export type ChannelMentionAdapter = {
stripPatterns?: (params: {
ctx: MsgContext;
cfg: ClawdbotConfig | undefined;
agentId?: string;
}) => string[];
stripMentions?: (params: {
text: string;
ctx: MsgContext;
cfg: ClawdbotConfig | undefined;
agentId?: string;
}) => string;
};
export type ChannelStreamingAdapter = {
blockStreamingCoalesceDefaults?: {
minChars: number;
idleMs: number;
};
};
export type ChannelThreadingAdapter = {
resolveReplyToMode?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
}) => "off" | "first" | "all";
allowTagsWhenOff?: boolean;
buildToolContext?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
context: ChannelThreadingContext;
hasRepliedRef?: { value: boolean };
}) => ChannelThreadingToolContext | undefined;
};
export type ChannelThreadingContext = {
Channel?: string;
To?: string;
ReplyToId?: string;
ThreadLabel?: string;
};
export type ChannelThreadingToolContext = {
currentChannelId?: string;
currentThreadTs?: string;
replyToMode?: "off" | "first" | "all";
hasRepliedRef?: { value: boolean };
};
export type ChannelMessagingAdapter = {
normalizeTarget?: (raw: string) => string | undefined;
};
export type ChannelMessageActionName = ChannelMessageActionNameFromList;
export type ChannelMessageActionContext = {
channel: ChannelId;
action: ChannelMessageActionName;
cfg: ClawdbotConfig;
params: Record<string, unknown>;
accountId?: string | null;
gateway?: {
url?: string;
token?: string;
timeoutMs?: number;
clientName: GatewayClientName;
clientDisplayName?: string;
mode: GatewayClientMode;
};
toolContext?: ChannelThreadingToolContext;
dryRun?: boolean;
};
export type {
ChannelAuthAdapter,
ChannelCommandAdapter,
ChannelConfigAdapter,
ChannelElevatedAdapter,
ChannelGatewayAdapter,
ChannelGatewayContext,
ChannelGroupAdapter,
ChannelHeartbeatAdapter,
ChannelLoginWithQrStartResult,
ChannelLoginWithQrWaitResult,
ChannelLogoutContext,
ChannelLogoutResult,
ChannelOutboundAdapter,
ChannelOutboundContext,
ChannelPairingAdapter,
ChannelSecurityAdapter,
ChannelSetupAdapter,
ChannelStatusAdapter,
} from "./types.adapters.js";
export type {
ChannelAccountSnapshot,
ChannelAccountState,
ChannelAgentTool,
ChannelAgentToolFactory,
ChannelCapabilities,
ChannelGroupContext,
ChannelHeartbeatDeps,
ChannelId,
ChannelLogSink,
ChannelMentionAdapter,
ChannelMessageActionAdapter,
ChannelMessageActionContext,
ChannelMessagingAdapter,
ChannelMeta,
ChannelOutboundTargetMode,
ChannelPollContext,
ChannelPollResult,
ChannelSecurityContext,
ChannelSecurityDmPolicy,
ChannelSetupInput,
ChannelStatusIssue,
ChannelStreamingAdapter,
ChannelThreadingAdapter,
ChannelThreadingContext,
ChannelThreadingToolContext,
ChannelToolSend,
} from "./types.core.js";
export type ChannelToolSend = {
to: string;
accountId?: string | null;
};
export type ChannelMessageActionAdapter = {
listActions?: (params: { cfg: ClawdbotConfig }) => ChannelMessageActionName[];
supportsAction?: (params: { action: ChannelMessageActionName }) => boolean;
supportsButtons?: (params: { cfg: ClawdbotConfig }) => boolean;
extractToolSend?: (params: {
args: Record<string, unknown>;
}) => ChannelToolSend | null;
handleAction?: (
ctx: ChannelMessageActionContext,
) => Promise<AgentToolResult<unknown>>;
};
// Channel docking: implement this contract in src/channels/plugins/<id>.ts.
// biome-ignore lint/suspicious/noExplicitAny: registry aggregates heterogeneous account types.
export type ChannelPlugin<ResolvedAccount = any> = {
id: ChannelId;
meta: ChannelMeta;
capabilities: ChannelCapabilities;
reload?: { configPrefixes: string[]; noopPrefixes?: string[] };
// CLI onboarding wizard hooks for this channel.
onboarding?: ChannelOnboardingAdapter;
config: ChannelConfigAdapter<ResolvedAccount>;
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
security?: ChannelSecurityAdapter<ResolvedAccount>;
groups?: ChannelGroupAdapter;
mentions?: ChannelMentionAdapter;
outbound?: ChannelOutboundAdapter;
status?: ChannelStatusAdapter<ResolvedAccount>;
gatewayMethods?: string[];
gateway?: ChannelGatewayAdapter<ResolvedAccount>;
auth?: ChannelAuthAdapter;
elevated?: ChannelElevatedAdapter;
commands?: ChannelCommandAdapter;
streaming?: ChannelStreamingAdapter;
threading?: ChannelThreadingAdapter;
messaging?: ChannelMessagingAdapter;
actions?: ChannelMessageActionAdapter;
heartbeat?: ChannelHeartbeatAdapter;
// Channel-owned agent tools (login flows, etc.).
agentTools?: ChannelAgentToolFactory | ChannelAgentTool[];
};
export type { ChannelPlugin } from "./types.plugin.js";