跳转到内容

插件 SDK 重构

目标:每个消息连接器都是一个插件(内置或外部),使用统一稳定的 API。 插件不直接从 src/** 导入任何内容。所有依赖项均通过 SDK 或运行时获取。

  • 当前连接器混用多种模式:直接导入核心模块、仅 dist 的桥接方式以及自定义辅助函数。
  • 这使得升级变得脆弱,并阻碍了干净的外部插件接口。

1)插件 SDK(编译时,稳定,可发布)

Section titled “1)插件 SDK(编译时,稳定,可发布)”

范围:类型、辅助函数和配置工具。无运行时状态,无副作用。

内容(示例):

  • 类型:ChannelPlugin、适配器、ChannelMetaChannelCapabilitiesChannelDirectoryEntry
  • 配置辅助函数:buildChannelConfigSchemasetAccountEnabledInConfigSectiondeleteAccountFromConfigSectionapplyAccountNameToChannelSection
  • 配对辅助函数:PAIRING_APPROVED_MESSAGEformatPairingApproveHint
  • 新手引导辅助函数:promptChannelAccessConfigaddWildcardAllowFrom、新手引导类型。
  • 工具参数辅助函数:createActionGatereadStringParamreadNumberParamreadReactionParamsjsonResult
  • 文档链接辅助函数:formatDocsLink

交付方式:

  • coderclaw/plugin-sdk 发布(或从核心以 coderclaw/plugin-sdk 导出)。
  • 使用语义化版本控制,提供明确的稳定性保证。

2)插件运行时(执行层,注入式)

Section titled “2)插件运行时(执行层,注入式)”

范围:所有涉及核心运行时行为的内容。 通过 CoderClawPluginApi.runtime 访问,确保插件永远不会导入 src/**

建议的接口(最小但完整):

export type PluginRuntime = {
channel: {
text: {
chunkMarkdownText(text: string, limit: number): string[];
resolveTextChunkLimit(cfg: CoderClawConfig, channel: string, accountId?: string): number;
hasControlCommand(text: string, cfg: CoderClawConfig): boolean;
};
reply: {
dispatchReplyWithBufferedBlockDispatcher(params: {
ctx: unknown;
cfg: unknown;
dispatcherOptions: {
deliver: (payload: {
text?: string;
mediaUrls?: string[];
mediaUrl?: string;
}) => void | Promise<void>;
onError?: (err: unknown, info: { kind: string }) => void;
};
}): Promise<void>;
createReplyDispatcherWithTyping?: unknown; // adapter for Teams-style flows
};
routing: {
resolveAgentRoute(params: {
cfg: unknown;
channel: string;
accountId: string;
peer: { kind: RoutePeerKind; id: string };
}): { sessionKey: string; accountId: string };
};
pairing: {
buildPairingReply(params: { channel: string; idLine: string; code: string }): string;
readAllowFromStore(channel: string): Promise<string[]>;
upsertPairingRequest(params: {
channel: string;
id: string;
meta?: { name?: string };
}): Promise<{ code: string; created: boolean }>;
};
media: {
fetchRemoteMedia(params: { url: string }): Promise<{ buffer: Buffer; contentType?: string }>;
saveMediaBuffer(
buffer: Uint8Array,
contentType: string | undefined,
direction: "inbound" | "outbound",
maxBytes: number,
): Promise<{ path: string; contentType?: string }>;
};
mentions: {
buildMentionRegexes(cfg: CoderClawConfig, agentId?: string): RegExp[];
matchesMentionPatterns(text: string, regexes: RegExp[]): boolean;
};
groups: {
resolveGroupPolicy(
cfg: CoderClawConfig,
channel: string,
accountId: string,
groupId: string,
): {
allowlistEnabled: boolean;
allowed: boolean;
groupConfig?: unknown;
defaultConfig?: unknown;
};
resolveRequireMention(
cfg: CoderClawConfig,
channel: string,
accountId: string,
groupId: string,
override?: boolean,
): boolean;
};
debounce: {
createInboundDebouncer<T>(opts: {
debounceMs: number;
buildKey: (v: T) => string | null;
shouldDebounce: (v: T) => boolean;
onFlush: (entries: T[]) => Promise<void>;
onError?: (err: unknown) => void;
}): { push: (v: T) => void; flush: () => Promise<void> };
resolveInboundDebounceMs(cfg: CoderClawConfig, channel: string): number;
};
commands: {
resolveCommandAuthorizedFromAuthorizers(params: {
useAccessGroups: boolean;
authorizers: Array<{ configured: boolean; allowed: boolean }>;
}): boolean;
};
};
logging: {
shouldLogVerbose(): boolean;
getChildLogger(name: string): PluginLogger;
};
state: {
resolveStateDir(cfg: CoderClawConfig): string;
};
};

备注:

  • 运行时是访问核心行为的唯一方式。
  • SDK 故意保持小巧和稳定。
  • 每个运行时方法都映射到现有的核心实现(无重复代码)。
  • 引入 coderclaw/plugin-sdk
  • CoderClawPluginApi 中添加带有上述接口的 api.runtime
  • 在过渡期内保留现有导入方式(添加弃用警告)。
  • api.runtime 替换每个扩展中的 core-bridge.ts
  • 优先迁移 BlueBubbles、Zalo、Zalo Personal(已经接近完成)。
  • 移除重复的桥接代码。
  • 将 Matrix 迁移到 SDK + 运行时。
  • 验证新手引导、目录、群组提及逻辑。
  • 迁移 Microsoft Teams(使用运行时辅助函数最多的插件)。
  • 确保回复/正在输入的语义与当前行为一致。
  • 将 iMessage 移入 extensions/imessage
  • api.runtime 替换直接的核心调用。
  • 保持配置键、CLI 行为和文档不变。
  • 添加 lint 规则 / CI 检查:禁止 extensions/**src/** 导入。
  • 添加插件 SDK/版本兼容性检查(运行时 + SDK 语义化版本)。
  • SDK:语义化版本控制,已发布,变更有文档记录。
  • 运行时:按核心版本进行版本控制。添加 api.runtime.version
  • 插件声明所需的运行时版本范围(例如 coderclawRuntime: ">=2026.2.0")。
  • 适配器级单元测试(使用真实核心实现验证运行时函数)。
  • 每个插件的黄金测试:确保行为无偏差(路由、配对、允许列表、提及过滤)。
  • CI 中使用单个端到端插件示例(安装 + 运行 + 冒烟测试)。
  • SDK 类型托管在哪里:独立包还是核心导出?
  • 运行时类型分发:在 SDK 中(仅类型)还是在核心中?
  • 如何为内置插件与外部插件暴露文档链接?
  • 过渡期间是否允许仓库内插件有限地直接导入核心模块?
  • 所有渠道连接器都是使用 SDK + 运行时的插件。
  • extensions/** 不再从 src/** 导入。
  • 新连接器模板仅依赖 SDK + 运行时。
  • 外部插件可以在无需访问核心源码的情况下进行开发和更新。

相关文档:插件渠道配置