feat: add cross-context messaging resolver

Co-authored-by: Thinh Dinh <tobalsan@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-17 03:17:08 +00:00
parent 1481a3d90f
commit 46015a3dd8
23 changed files with 859 additions and 60 deletions

View File

@@ -32,6 +32,7 @@ export async function handleDiscordMessageAction(
});
const mediaUrl = readStringParam(params, "media", { trim: false });
const replyTo = readStringParam(params, "replyTo");
const embeds = Array.isArray(params.embeds) ? params.embeds : undefined;
return await handleDiscordAction(
{
action: "sendMessage",
@@ -39,6 +40,7 @@ export async function handleDiscordMessageAction(
content,
mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined,
embeds,
},
cfg,
);

View File

@@ -9,7 +9,11 @@ import {
collectDiscordAuditChannelIds,
} from "../../discord/audit.js";
import { probeDiscord } from "../../discord/probe.js";
import { sendMessageDiscord, sendPollDiscord } from "../../discord/send.js";
import {
listGuildChannelsDiscord,
sendMessageDiscord,
sendPollDiscord,
} from "../../discord/send.js";
import { shouldLogVerbose } from "../../globals.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { getChatChannelMeta } from "../registry.js";
@@ -209,6 +213,31 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
.map((id) => ({ kind: "group", id }) as const);
return groups;
},
listGroupsLive: async ({ cfg, accountId, query, limit }) => {
const account = resolveDiscordAccount({ cfg, accountId });
const q = query?.trim().toLowerCase() || "";
const guildIds = Object.keys(account.config.guilds ?? {}).filter((id) => /^\d+$/.test(id));
const rows: Array<{ kind: "group"; id: string; name?: string; raw?: unknown }> = [];
for (const guildId of guildIds) {
const channels = await listGuildChannelsDiscord(guildId, {
accountId: account.accountId,
});
for (const channel of channels) {
const name = typeof channel.name === "string" ? channel.name : undefined;
if (q && name && !name.toLowerCase().includes(q)) continue;
rows.push({
kind: "group",
id: `channel:${channel.id}`,
name: name ?? undefined,
raw: channel,
});
}
}
const filtered = q ? rows.filter((row) => row.name?.toLowerCase().includes(q)) : rows;
const limited =
typeof limit === "number" && limit > 0 ? filtered.slice(0, limit) : filtered;
return limited;
},
},
actions: discordMessageActions,
setup: {

View File

@@ -1,5 +1,6 @@
export const CHANNEL_MESSAGE_ACTION_NAMES = [
"send",
"broadcast",
"poll",
"react",
"reactions",

View File

@@ -5,7 +5,7 @@ import { getChannelPlugin, listChannelPlugins } from "./index.js";
import type { ChannelMessageActionContext, ChannelMessageActionName } from "./types.js";
export function listChannelMessageActions(cfg: ClawdbotConfig): ChannelMessageActionName[] {
const actions = new Set<ChannelMessageActionName>(["send"]);
const actions = new Set<ChannelMessageActionName>(["send", "broadcast"]);
for (const plugin of listChannelPlugins()) {
const list = plugin.actions?.listActions?.({ cfg });
if (!list) continue;

View File

@@ -233,6 +233,13 @@ export type ChannelDirectoryAdapter = {
limit?: number | null;
runtime: RuntimeEnv;
}) => Promise<ChannelDirectoryEntry[]>;
listPeersLive?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
query?: string | null;
limit?: number | null;
runtime: RuntimeEnv;
}) => Promise<ChannelDirectoryEntry[]>;
listGroups?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
@@ -240,6 +247,13 @@ export type ChannelDirectoryAdapter = {
limit?: number | null;
runtime: RuntimeEnv;
}) => Promise<ChannelDirectoryEntry[]>;
listGroupsLive?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
query?: string | null;
limit?: number | null;
runtime: RuntimeEnv;
}) => Promise<ChannelDirectoryEntry[]>;
listGroupMembers?: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;

View File

@@ -207,6 +207,7 @@ export type ChannelThreadingContext = {
export type ChannelThreadingToolContext = {
currentChannelId?: string;
currentChannelProvider?: ChannelId;
currentThreadTs?: string;
replyToMode?: "off" | "first" | "all";
hasRepliedRef?: { value: boolean };