refactor(src): split oversized modules
This commit is contained in:
@@ -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 });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
211
src/channels/plugins/actions/discord/handle-action.ts
Normal file
211
src/channels/plugins/actions/discord/handle-action.ts
Normal 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}.`,
|
||||
);
|
||||
}
|
||||
224
src/channels/plugins/slack.actions.ts
Normal file
224
src/channels/plugins/slack.actions.ts
Normal 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}.`,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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 }) =>
|
||||
|
||||
268
src/channels/plugins/types.adapters.ts
Normal file
268
src/channels/plugins/types.adapters.ts
Normal 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[];
|
||||
};
|
||||
263
src/channels/plugins/types.core.ts
Normal file
263
src/channels/plugins/types.core.ts
Normal 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;
|
||||
};
|
||||
58
src/channels/plugins/types.plugin.ts
Normal file
58
src/channels/plugins/types.plugin.ts
Normal 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[];
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user