Plugin SDK와 Runtime 리팩토링: 더 안정적인 확장 생태계 만들기
프로젝트가 커지면서 여기저기 직접 import를 하다 보면 나중에 코드를 수정하기가 정말 힘들어지죠. 특히 플러그인을 만들 때 코어 내부 구조를 너무 많이 알게 되면, 코어가 업데이트될 때마다 플러그인이 고장 나기 일쑤입니다.
지금까지는 커넥터마다 코어를 직접 가져다 쓰거나 별도의 브릿지를 만드는 등 방식이 제각각이었어요. 이런 복잡함을 해결하고 누구나 안정적으로 외부 플러그인을 만들 수 있도록, OpenClaw의 아키텍처를 두 개의 레이어로 깔끔하게 정리하려고 합니다.
필요한 것
섹션 제목: “필요한 것”openclaw/plugin-sdk패키지- OpenClaw 코어 환경
빠른 시작
섹션 제목: “빠른 시작”모든 메시징 커넥터를 하나의 안정적인 API를 사용하는 플러그인으로 전환하는 것이 목표입니다. 이제 src/**에서 직접 코드를 가져오는 대신, SDK와 Runtime을 거치게 됩니다.
1. Plugin SDK 사용하기
섹션 제목: “1. Plugin SDK 사용하기”SDK는 컴파일 타임에 사용하며, 타입 정의와 설정 헬퍼를 포함합니다. openclaw/plugin-sdk에서 필요한 기능을 가져오세요.
- Types:
ChannelPlugin,ChannelMeta,ChannelCapabilities등 - Config Helpers:
buildChannelConfigSchema,deleteAccountFromConfigSection등 - Tool Param Helpers:
createActionGate,readStringParam,jsonResult등
2. Plugin Runtime 접근하기
섹션 제목: “2. Plugin Runtime 접근하기”실행 시점에 코어의 동작이 필요한 기능은 OpenClawPluginApi.runtime을 통해 접근합니다.
export type PluginRuntime = { channel: { text: { chunkMarkdownText(text: string, limit: number): string[]; resolveTextChunkLimit(cfg: OpenClawConfig, channel: string, accountId?: string): number; hasControlCommand(text: string, cfg: OpenClawConfig): 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; }; 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: OpenClawConfig, agentId?: string): RegExp[]; matchesMentionPatterns(text: string, regexes: RegExp[]): boolean; }; groups: { resolveGroupPolicy( cfg: OpenClawConfig, channel: string, accountId: string, groupId: string, ): { allowlistEnabled: boolean; allowed: boolean; groupConfig?: unknown; defaultConfig?: unknown; }; resolveRequireMention( cfg: OpenClawConfig, 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: OpenClawConfig, 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: OpenClawConfig): string; };};3. 단계별 마이그레이션
섹션 제목: “3. 단계별 마이그레이션”- Phase 0 & 1:
api.runtime을 도입하고 기존의 개별core-bridge.ts를 교체합니다. - Phase 2 & 3: Matrix, MS Teams 같은 플러그인들을 새로운 SDK와 Runtime으로 옮깁니다.
- Phase 4 & 5: iMessage를 플러그인화하고,
src/**임포트를 금지하는 CI 규칙을 적용합니다.
문제 해결
섹션 제목: “문제 해결”- 업데이트 시 플러그인 작동 중지: 기존 커넥터들이 코어 내부 코드를 직접 참조(direct core import)하고 있기 때문입니다.
api.runtime을 사용하도록 전환하면 코어 내부가 바뀌어도 플러그인은 안전하게 유지됩니다. - 중복된 브릿지 코드: 각 확장 프로그램마다 별도의 브릿지를 만들면 유지보수가 어렵습니다. Phase 1 단계에서
api.runtime으로 통합하여 중복을 제거하세요.
궁금한 점이 있다면 AI Setup Assistant에게 물어보세요!
다음 단계
섹션 제목: “다음 단계”- Plugins: 플러그인 시스템 기초 알아보기
- Channels: 지원되는 채널 목록 확인하기
- Configuration: 게이트웨이 설정 가이드
OpenClaw Expert
아직 막혀 있나요?
이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.