Step 5 + Review

This commit is contained in:
Tyler Yust
2026-01-19 20:16:14 -08:00
committed by Peter Steinberger
parent 2cf444be02
commit 1eab8fa9b0
10 changed files with 219 additions and 15 deletions

View File

@@ -9,6 +9,7 @@ import { resolveWhatsAppAccount } from "../web/accounts.js";
import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
import { requireActivePluginRegistry } from "../plugins/runtime.js";
import {
resolveBlueBubblesGroupRequireMention,
resolveDiscordGroupRequireMention,
resolveIMessageGroupRequireMention,
resolveSlackGroupRequireMention,
@@ -67,6 +68,27 @@ const formatLower = (allowFrom: Array<string | number>) =>
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Helper to delegate config operations to a plugin at runtime.
// Used for BlueBubbles which is in CHAT_CHANNEL_ORDER but implemented as a plugin.
function getPluginConfigAdapter(channelId: string) {
return {
resolveAllowFrom: (params: { cfg: ClawdbotConfig; accountId?: string | null }) => {
const registry = requireActivePluginRegistry();
const entry = registry.channels.find((e) => e.plugin.id === channelId);
return entry?.plugin.config?.resolveAllowFrom?.(params) ?? [];
},
formatAllowFrom: (params: {
cfg: ClawdbotConfig;
accountId?: string | null;
allowFrom: Array<string | number>;
}) => {
const registry = requireActivePluginRegistry();
const entry = registry.channels.find((e) => e.plugin.id === channelId);
return entry?.plugin.config?.formatAllowFrom?.(params) ?? params.allowFrom.map(String);
},
};
}
// Channel docks: lightweight channel metadata/behavior for shared code paths.
//
// Rules:
@@ -266,6 +288,30 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
}),
},
},
// BlueBubbles is in CHAT_CHANNEL_ORDER (Gate A: core registry) but implemented as a plugin.
// Config operations are delegated to the plugin at runtime.
// Note: Additional capabilities (edit, unsend, reply, effects, groupManagement) are exposed
// via the plugin's capabilities, not the dock's ChannelCapabilities type.
bluebubbles: {
id: "bluebubbles",
capabilities: {
chatTypes: ["direct", "group"],
reactions: true,
media: true,
},
outbound: { textChunkLimit: 4000 },
config: getPluginConfigAdapter("bluebubbles"),
groups: {
resolveRequireMention: resolveBlueBubblesGroupRequireMention,
},
threading: {
buildToolContext: ({ context, hasRepliedRef }) => ({
currentChannelId: context.To?.trim() || undefined,
currentThreadTs: context.ReplyToId,
hasRepliedRef,
}),
},
},
imessage: {
id: "imessage",
capabilities: {

View File

@@ -6,6 +6,14 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [
"reactions",
"read",
"edit",
"unsend",
"reply",
"sendWithEffect",
"renameGroup",
"addParticipant",
"removeParticipant",
"leaveGroup",
"sendAttachment",
"delete",
"pin",
"unpin",

View File

@@ -4,12 +4,15 @@ import { requireActivePluginRegistry } from "../plugins/runtime.js";
// Channel docking: add new core channels here (order + meta + aliases), then
// register the plugin in its extension entrypoint and keep protocol IDs in sync.
// BlueBubbles placed before imessage per Gate C decision: prefer BlueBubbles
// for iMessage use cases when both are available.
export const CHAT_CHANNEL_ORDER = [
"telegram",
"whatsapp",
"discord",
"slack",
"signal",
"bluebubbles",
"imessage",
] as const;
@@ -67,6 +70,14 @@ const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
docsLabel: "signal",
blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").',
},
bluebubbles: {
id: "bluebubbles",
label: "BlueBubbles",
selectionLabel: "BlueBubbles (macOS app)",
docsPath: "/channels/bluebubbles",
docsLabel: "bluebubbles",
blurb: "recommended for iMessage — uses the BlueBubbles mac app + REST API.",
},
imessage: {
id: "imessage",
label: "iMessage",
@@ -79,6 +90,7 @@ const CHAT_CHANNEL_META: Record<ChatChannelId, ChannelMeta> = {
export const CHAT_CHANNEL_ALIASES: Record<string, ChatChannelId> = {
imsg: "imessage",
bb: "bluebubbles",
};
const normalizeChannelKey = (raw?: string | null): string | undefined => {