메인 콘텐츠로 건너뛰기

채널 Plugin 구축

이 가이드는 OpenClaw를 메시징 플랫폼에 연결하는 채널 plugin을 구축하는 과정을 안내합니다. 이 가이드를 마치면 DM 보안, 페어링, 답장 스레딩, 아웃바운드 메시징이 작동하는 채널을 갖추게 됩니다.
아직 OpenClaw plugin을 한 번도 만들어본 적이 없다면 먼저 기본 패키지 구조와 manifest 설정을 위해 시작하기를 읽으세요.

채널 plugin의 작동 방식

채널 plugin은 자체 send/edit/react 도구가 필요하지 않습니다. OpenClaw는 코어에 하나의 공유 message 도구를 유지합니다. plugin이 담당하는 영역은 다음과 같습니다:
  • 설정 — 계정 해석 및 설정 wizard
  • 보안 — DM 정책 및 허용 목록
  • 페어링 — DM 승인 흐름
  • 세션 문법 — provider별 대화 id가 기본 채팅, 스레드 id, 부모 대체 경로에 어떻게 매핑되는지
  • 아웃바운드 — 플랫폼으로 텍스트, 미디어, 투표를 보내기
  • 스레딩 — 답장을 어떻게 스레드로 연결할지
코어는 공유 메시지 도구, 프롬프트 연결, 바깥쪽 세션 키 형태, 일반적인 :thread: 기록 관리, 그리고 디스패치를 담당합니다. 플랫폼이 대화 id 안에 추가 범위를 저장한다면, 그 파싱은 plugin에서 messaging.resolveSessionConversation(...)으로 유지하세요. 이것이 rawId를 기본 대화 id, 선택적 스레드 id, 명시적 baseConversationId, 그리고 모든 parentConversationCandidates로 매핑하는 정식 훅입니다. parentConversationCandidates를 반환할 때는 가장 좁은 부모부터 가장 넓은/기본 대화 순으로 정렬해 두세요. 채널 레지스트리가 부팅되기 전에 같은 파싱이 필요한 번들 plugin은 일치하는 resolveSessionConversation(...) export를 가진 최상위 session-key-api.ts 파일도 노출할 수 있습니다. 코어는 런타임 plugin 레지스트리를 아직 사용할 수 없을 때만 이 부트스트랩 안전 표면을 사용합니다. messaging.resolveParentConversationCandidates(...)는 plugin이 일반/raw id 위에 부모 대체 경로만 필요로 할 때를 위한 레거시 호환성 대체 수단으로 여전히 사용할 수 있습니다. 두 훅이 모두 존재하면 코어는 먼저 resolveSessionConversation(...).parentConversationCandidates를 사용하고, 정식 훅이 이를 생략할 때만 resolveParentConversationCandidates(...)로 대체합니다.

승인 및 채널 기능

대부분의 채널 plugin은 승인 전용 코드가 필요하지 않습니다.
  • 코어는 동일 채팅의 /approve, 공유 승인 버튼 페이로드, 일반적인 대체 전달을 담당합니다.
  • 채널에 승인 전용 동작이 필요할 때는 채널 plugin에 하나의 approvalCapability 객체를 두는 방식을 선호하세요.
  • ChannelPlugin.approvals는 제거되었습니다. 승인 전달/네이티브/렌더링/인증 관련 정보는 approvalCapability에 두세요.
  • plugin.auth는 login/logout 전용입니다. 코어는 더 이상 그 객체에서 승인 인증 훅을 읽지 않습니다.
  • approvalCapability.authorizeActorActionapprovalCapability.getActionAvailabilityState가 정식 승인 인증 연결 지점입니다.
  • 동일 채팅 승인 인증 가용성에는 approvalCapability.getActionAvailabilityState를 사용하세요.
  • 채널이 네이티브 exec 승인을 노출한다면, 동일 채팅 승인 인증과 다를 때 시작 표면/네이티브 클라이언트 상태를 위해 approvalCapability.getExecInitiatingSurfaceState를 사용하세요. 코어는 이 exec 전용 훅으로 enableddisabled를 구분하고, 시작 채널이 네이티브 exec 승인을 지원하는지 판단하며, 네이티브 클라이언트 대체 안내에 해당 채널을 포함합니다. 일반적인 경우에는 createApproverRestrictedNativeApprovalCapability(...)가 이를 채워 줍니다.
  • 중복된 로컬 승인 프롬프트 숨기기나 전달 전 타이핑 표시 보내기 같은 채널 전용 페이로드 수명 주기 동작에는 outbound.shouldSuppressLocalPayloadPrompt 또는 outbound.beforeDeliverPayload를 사용하세요.
  • approvalCapability.delivery는 네이티브 승인 라우팅 또는 대체 억제에만 사용하세요.
  • 채널 소유 네이티브 승인 관련 정보에는 approvalCapability.nativeRuntime를 사용하세요. 핫 채널 엔트리포인트에서는 createLazyChannelApprovalNativeRuntimeAdapter(...)로 이를 지연 로드 상태로 유지하세요. 그러면 코어가 승인 수명 주기를 조립할 수 있으면서도 필요 시 런타임 모듈을 가져올 수 있습니다.
  • 채널이 공유 렌더러 대신 진짜로 커스텀 승인 페이로드가 필요할 때만 approvalCapability.render를 사용하세요.
  • 채널이 비활성 경로 응답에서 네이티브 exec 승인을 활성화하는 데 필요한 정확한 설정 키를 설명하고 싶다면 approvalCapability.describeExecApprovalSetup을 사용하세요. 이 훅은 { channel, channelLabel, accountId }를 받습니다. 이름 있는 계정 채널은 최상위 기본값 대신 channels.<channel>.accounts.<id>.execApprovals.* 같은 계정 범위 경로를 렌더링해야 합니다.
  • 채널이 기존 설정에서 안정적인 소유자형 DM 정체성을 추론할 수 있다면, 승인 전용 코어 로직을 추가하지 말고 openclaw/plugin-sdk/approval-runtimecreateResolvedApproverActionAuthAdapter를 사용해 동일 채팅 /approve를 제한하세요.
  • 채널에 네이티브 승인 전달이 필요하다면, 채널 코드는 대상 정규화와 전송/표시 관련 정보에만 집중시키세요. openclaw/plugin-sdk/approval-runtimecreateChannelExecApprovalProfile, createChannelNativeOriginTargetResolver, createChannelApproverDmTargetResolver, createApproverRestrictedNativeApprovalCapability를 사용하세요. 채널 전용 정보는 approvalCapability.nativeRuntime 뒤에 두고, 이상적으로는 createChannelApprovalNativeRuntimeAdapter(...) 또는 createLazyChannelApprovalNativeRuntimeAdapter(...)를 통해 두세요. 그러면 코어가 핸들러를 조립하고 요청 필터링, 라우팅, 중복 제거, 만료, gateway 구독, 다른 곳으로 라우팅됨 알림을 담당할 수 있습니다. nativeRuntime은 몇 개의 더 작은 연결 지점으로 나뉩니다:
  • availability — 계정이 설정되었는지, 요청을 처리해야 하는지 여부
  • presentation — 공유 승인 뷰 모델을 보류/해결됨/만료된 네이티브 페이로드 또는 최종 작업으로 매핑
  • transport — 대상을 준비하고 네이티브 승인 메시지를 전송/업데이트/삭제
  • interactions — 네이티브 버튼 또는 반응을 위한 선택적 bind/unbind/clear-action 훅
  • observe — 선택적 전달 진단 훅
  • 채널에 클라이언트, 토큰, Bolt 앱, 웹훅 리시버 같은 런타임 소유 객체가 필요하다면 openclaw/plugin-sdk/channel-runtime-context를 통해 등록하세요. 일반적인 runtime-context 레지스트리를 사용하면 승인 전용 래퍼 접착 코드를 추가하지 않고도 코어가 채널 시작 상태에서 기능 기반 핸들러를 부트스트랩할 수 있습니다.
  • 기능 기반 연결 지점으로 아직 충분히 표현할 수 없을 때만 더 낮은 수준의 createChannelApprovalHandler 또는 createChannelNativeApprovalRuntime을 사용하세요.
  • 네이티브 승인 채널은 accountIdapprovalKind를 모두 해당 헬퍼들을 통해 라우팅해야 합니다. accountId는 다중 계정 승인 정책을 올바른 봇 계정 범위로 유지하고, approvalKind는 코어에 하드코딩된 분기 없이도 채널에서 exec 대 plugin 승인 동작을 사용할 수 있게 합니다.
  • 이제 코어가 승인 재라우팅 알림도 담당합니다. 채널 plugin은 createChannelNativeApprovalRuntime에서 자체적인 “승인이 DM/다른 채널로 갔음” 후속 메시지를 보내지 말고, 공유 승인 기능 헬퍼를 통해 정확한 origin + approver-DM 라우팅을 노출한 뒤 코어가 시작 채팅에 알림을 게시하기 전에 실제 전달 결과를 집계하게 하세요.
  • 전달된 승인 id 종류를 처음부터 끝까지 보존하세요. 네이티브 클라이언트는 채널 로컬 상태를 기준으로 exec 대 plugin 승인 라우팅을 추측하거나 다시 쓰면 안 됩니다.
  • 서로 다른 승인 종류는 의도적으로 서로 다른 네이티브 표면을 노출할 수 있습니다. 현재 번들 예시는 다음과 같습니다:
    • Slack은 exec 및 plugin id 모두에 대해 네이티브 승인 라우팅을 계속 사용할 수 있게 유지합니다.
    • Matrix는 exec와 plugin 승인 모두에 대해 동일한 네이티브 DM/채널 라우팅과 반응 UX를 유지하면서도 승인 종류별로 인증이 다를 수 있게 합니다.
  • createApproverRestrictedNativeApprovalAdapter는 여전히 호환성 래퍼로 존재하지만, 새 코드에서는 기능 빌더를 선호하고 plugin에 approvalCapability를 노출해야 합니다.
핫 채널 엔트리포인트에서는 해당 계열 전체가 아니라 한 부분만 필요할 때 더 좁은 런타임 하위 경로를 선호하세요:
  • openclaw/plugin-sdk/approval-auth-runtime
  • openclaw/plugin-sdk/approval-client-runtime
  • openclaw/plugin-sdk/approval-delivery-runtime
  • openclaw/plugin-sdk/approval-gateway-runtime
  • openclaw/plugin-sdk/approval-handler-adapter-runtime
  • openclaw/plugin-sdk/approval-handler-runtime
  • openclaw/plugin-sdk/approval-native-runtime
  • openclaw/plugin-sdk/approval-reply-runtime
  • openclaw/plugin-sdk/channel-runtime-context
마찬가지로 더 넓은 우산형 표면이 필요하지 않을 때는 openclaw/plugin-sdk/setup-runtime, openclaw/plugin-sdk/setup-adapter-runtime, openclaw/plugin-sdk/reply-runtime, openclaw/plugin-sdk/reply-dispatch-runtime, openclaw/plugin-sdk/reply-reference, 그리고 openclaw/plugin-sdk/reply-chunking을 선호하세요. 특히 setup의 경우:
  • openclaw/plugin-sdk/setup-runtime은 런타임 안전 setup 헬퍼를 다룹니다: import-safe setup 패치 adapter(createPatchedAccountSetupAdapter, createEnvPatchedAccountSetupAdapter, createSetupInputPresenceValidator), lookup-note 출력, promptResolvedAllowFrom, splitSetupEntries, 그리고 위임된 setup-proxy 빌더
  • openclaw/plugin-sdk/setup-adapter-runtimecreateEnvPatchedAccountSetupAdapter를 위한 좁은 env 인식 adapter 연결 지점입니다
  • openclaw/plugin-sdk/channel-setup은 선택적 설치 setup 빌더와 몇 가지 setup 안전 기본 요소를 다룹니다: createOptionalChannelSetupSurface, createOptionalChannelSetupAdapter,
채널이 env 기반 setup 또는 auth를 지원하고 일반적인 시작/설정 흐름이 런타임 로드 전에 해당 env 이름을 알아야 한다면, plugin manifest의 channelEnvVars에 이를 선언하세요. 채널 런타임 envVars 또는 로컬 상수는 operator 대상 복사용으로만 유지하세요. createOptionalChannelSetupWizard, DEFAULT_ACCOUNT_ID, createTopLevelChannelDmPolicy, setSetupChannelEnabled, 그리고 splitSetupEntries
  • 더 무거운 공유 setup/config 헬퍼인 moveSingleAccountChannelSectionToDefaultAccount(...)도 필요할 때만 더 넓은 openclaw/plugin-sdk/setup 연결 지점을 사용하세요
채널이 setup 표면에서 “먼저 이 plugin을 설치하세요”만 광고하고 싶다면, createOptionalChannelSetupSurface(...)를 선호하세요. 생성된 adapter/wizard는 config 쓰기와 최종화에서 fail closed로 동작하며, 검증, 최종화, 문서 링크 복사 전반에 걸쳐 동일한 설치 필요 메시지를 재사용합니다. 다른 핫 채널 경로에서도 더 넓은 레거시 표면보다 좁은 헬퍼를 선호하세요:
  • 다중 계정 config 및 기본 계정 대체를 위한 openclaw/plugin-sdk/account-core, openclaw/plugin-sdk/account-id, openclaw/plugin-sdk/account-resolution, 그리고 openclaw/plugin-sdk/account-helpers
  • 인바운드 경로/envelope 및 record-and-dispatch 연결을 위한 openclaw/plugin-sdk/inbound-envelopeopenclaw/plugin-sdk/inbound-reply-dispatch
  • 대상 파싱/매칭을 위한 openclaw/plugin-sdk/messaging-targets
  • 미디어 로딩 및 아웃바운드 정체성/send 위임을 위한 openclaw/plugin-sdk/outbound-mediaopenclaw/plugin-sdk/outbound-runtime
  • 스레드 바인딩 수명 주기 및 adapter 등록을 위한 openclaw/plugin-sdk/thread-bindings-runtime
  • 레거시 agent/media 페이로드 필드 레이아웃이 여전히 필요할 때만 openclaw/plugin-sdk/agent-media-payload
  • Telegram 커스텀 명령 정규화, 중복/충돌 검증, 그리고 대체 안정 명령 config 계약을 위한 openclaw/plugin-sdk/telegram-command-config
인증 전용 채널은 보통 기본 경로에서 멈출 수 있습니다. 코어가 승인을 처리하고 plugin은 아웃바운드/auth 기능만 노출하면 됩니다. Matrix, Slack, Telegram, 커스텀 채팅 전송 계층 같은 네이티브 승인 채널은 자체 승인 수명 주기를 구현하지 말고 공유 네이티브 헬퍼를 사용해야 합니다.

인바운드 멘션 정책

인바운드 멘션 처리는 두 계층으로 나누어 유지하세요:
  • plugin 소유 증거 수집
  • 공유 정책 평가
공유 계층에는 openclaw/plugin-sdk/channel-inbound를 사용하세요. plugin 로컬 로직에 적합한 항목:
  • 봇에 대한 reply 감지
  • 봇 인용 감지
  • 스레드 참여 확인
  • 서비스/시스템 메시지 제외
  • 봇 참여를 입증하는 데 필요한 플랫폼 네이티브 캐시
공유 헬퍼에 적합한 항목:
  • requireMention
  • 명시적 멘션 결과
  • 암시적 멘션 허용 목록
  • 명령 우회
  • 최종 건너뛰기 결정
권장 흐름:
  1. 로컬 멘션 사실을 계산합니다.
  2. 그 사실을 resolveInboundMentionDecision({ facts, policy })에 전달합니다.
  3. 인바운드 게이트에서 decision.effectiveWasMentioned, decision.shouldBypassMention, decision.shouldSkip를 사용합니다.
import {
  implicitMentionKindWhen,
  matchesMentionWithExplicit,
  resolveInboundMentionDecision,
} from "openclaw/plugin-sdk/channel-inbound";

const mentionMatch = matchesMentionWithExplicit(text, {
  mentionRegexes,
  mentionPatterns,
});

const facts = {
  canDetectMention: true,
  wasMentioned: mentionMatch.matched,
  hasAnyMention: mentionMatch.hasExplicitMention,
  implicitMentionKinds: [
    ...implicitMentionKindWhen("reply_to_bot", isReplyToBot),
    ...implicitMentionKindWhen("quoted_bot", isQuoteOfBot),
  ],
};

const decision = resolveInboundMentionDecision({
  facts,
  policy: {
    isGroup,
    requireMention,
    allowedImplicitMentionKinds: requireExplicitMention ? [] : ["reply_to_bot", "quoted_bot"],
    allowTextCommands,
    hasControlCommand,
    commandAuthorized,
  },
});

if (decision.shouldSkip) return;
api.runtime.channel.mentions는 이미 런타임 주입에 의존하는 번들 채널 plugin을 위해 동일한 공유 멘션 헬퍼를 노출합니다:
  • buildMentionRegexes
  • matchesMentionPatterns
  • matchesMentionWithExplicit
  • implicitMentionKindWhen
  • resolveInboundMentionDecision
이전의 resolveMentionGating* 헬퍼는 openclaw/plugin-sdk/channel-inbound에 호환성 export로만 남아 있습니다. 새 코드는 resolveInboundMentionDecision({ facts, policy })를 사용해야 합니다.

따라 하기

1
2

패키지와 manifest

표준 plugin 파일을 만드세요. package.jsonchannel 필드가 이것을 채널 plugin으로 만듭니다. 전체 패키지 메타데이터 표면은 Plugin 설정 및 Config를 참고하세요:
{
  "name": "@myorg/openclaw-acme-chat",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "channel": {
      "id": "acme-chat",
      "label": "Acme Chat",
      "blurb": "Connect OpenClaw to Acme Chat."
    }
  }
}
3

채널 plugin 객체 구축

ChannelPlugin 인터페이스에는 선택적인 adapter 표면이 많이 있습니다. 먼저 최소 구성인 idsetup부터 시작한 뒤, 필요할 때 adapter를 추가하세요.src/channel.ts를 만드세요:
src/channel.ts
import {
  createChatChannelPlugin,
  createChannelPluginBase,
} from "openclaw/plugin-sdk/channel-core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/channel-core";
import { acmeChatApi } from "./client.js"; // your platform API client

type ResolvedAccount = {
  accountId: string | null;
  token: string;
  allowFrom: string[];
  dmPolicy: string | undefined;
};

function resolveAccount(
  cfg: OpenClawConfig,
  accountId?: string | null,
): ResolvedAccount {
  const section = (cfg.channels as Record<string, any>)?.["acme-chat"];
  const token = section?.token;
  if (!token) throw new Error("acme-chat: token is required");
  return {
    accountId: accountId ?? null,
    token,
    allowFrom: section?.allowFrom ?? [],
    dmPolicy: section?.dmSecurity,
  };
}

export const acmeChatPlugin = createChatChannelPlugin<ResolvedAccount>({
  base: createChannelPluginBase({
    id: "acme-chat",
    setup: {
      resolveAccount,
      inspectAccount(cfg, accountId) {
        const section =
          (cfg.channels as Record<string, any>)?.["acme-chat"];
        return {
          enabled: Boolean(section?.token),
          configured: Boolean(section?.token),
          tokenStatus: section?.token ? "available" : "missing",
        };
      },
    },
  }),

  // DM security: who can message the bot
  security: {
    dm: {
      channelKey: "acme-chat",
      resolvePolicy: (account) => account.dmPolicy,
      resolveAllowFrom: (account) => account.allowFrom,
      defaultPolicy: "allowlist",
    },
  },

  // Pairing: approval flow for new DM contacts
  pairing: {
    text: {
      idLabel: "Acme Chat username",
      message: "Send this code to verify your identity:",
      notify: async ({ target, code }) => {
        await acmeChatApi.sendDm(target, `Pairing code: ${code}`);
      },
    },
  },

  // Threading: how replies are delivered
  threading: { topLevelReplyToMode: "reply" },

  // Outbound: send messages to the platform
  outbound: {
    attachedResults: {
      sendText: async (params) => {
        const result = await acmeChatApi.sendMessage(
          params.to,
          params.text,
        );
        return { messageId: result.id };
      },
    },
    base: {
      sendMedia: async (params) => {
        await acmeChatApi.sendFile(params.to, params.filePath);
      },
    },
  },
});
저수준 adapter 인터페이스를 직접 구현하는 대신 선언적 옵션을 전달하면 빌더가 이를 조합해 줍니다:
Option연결되는 항목
security.dmconfig 필드에서 범위가 지정된 DM 보안 resolver
pairing.text코드 교환이 있는 텍스트 기반 DM 페어링 흐름
threadingreply-to-mode resolver(고정, 계정 범위, 또는 커스텀)
outbound.attachedResults결과 메타데이터(메시지 ID)를 반환하는 send 함수
완전한 제어가 필요하다면 선언적 옵션 대신 원시 adapter 객체를 전달할 수도 있습니다.
4

엔트리포인트 연결

index.ts를 만드세요:
index.ts
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { acmeChatPlugin } from "./src/channel.js";

export default defineChannelPluginEntry({
  id: "acme-chat",
  name: "Acme Chat",
  description: "Acme Chat channel plugin",
  plugin: acmeChatPlugin,
  registerCliMetadata(api) {
    api.registerCli(
      ({ program }) => {
        program
          .command("acme-chat")
          .description("Acme Chat management");
      },
      {
        descriptors: [
          {
            name: "acme-chat",
            description: "Acme Chat management",
            hasSubcommands: false,
          },
        ],
      },
    );
  },
  registerFull(api) {
    api.registerGatewayMethod(/* ... */);
  },
});
채널 소유 CLI descriptor는 registerCliMetadata(...)에 두세요. 그러면 OpenClaw가 전체 채널 런타임을 활성화하지 않고도 루트 도움말에 이를 표시할 수 있고, 일반적인 전체 로드도 실제 명령 등록을 위해 동일한 descriptor를 가져오게 됩니다. registerFull(...)은 런타임 전용 작업에 유지하세요. registerFull(...)이 gateway RPC 메서드를 등록한다면 plugin 전용 접두사를 사용하세요. 코어 admin 네임스페이스(config.*, exec.approvals.*, wizard.*, update.*)는 예약되어 있으며 항상 operator.admin으로 해석됩니다. defineChannelPluginEntry는 등록 모드 분리를 자동으로 처리합니다. 모든 옵션은 엔트리포인트를 참고하세요.
5

setup entry 추가

온보딩 중 경량 로딩을 위해 setup-entry.ts를 만드세요:
setup-entry.ts
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core";
import { acmeChatPlugin } from "./src/channel.js";

export default defineSetupPluginEntry(acmeChatPlugin);
OpenClaw는 채널이 비활성화되었거나 설정되지 않았을 때 전체 entry 대신 이것을 로드합니다. 이렇게 하면 setup 흐름에서 무거운 런타임 코드를 끌어오지 않게 됩니다. 자세한 내용은 Setup 및 Config를 참고하세요.
6

인바운드 메시지 처리

plugin은 플랫폼에서 메시지를 받아 OpenClaw로 전달해야 합니다. 일반적인 패턴은 요청을 검증하고 채널의 인바운드 핸들러를 통해 디스패치하는 웹훅입니다:
registerFull(api) {
  api.registerHttpRoute({
    path: "/acme-chat/webhook",
    auth: "plugin", // plugin-managed auth (verify signatures yourself)
    handler: async (req, res) => {
      const event = parseWebhookPayload(req);

      // Your inbound handler dispatches the message to OpenClaw.
      // The exact wiring depends on your platform SDK —
      // see a real example in the bundled Microsoft Teams or Google Chat plugin package.
      await handleAcmeChatInbound(api, event);

      res.statusCode = 200;
      res.end("ok");
      return true;
    },
  });
}
인바운드 메시지 처리는 채널별입니다. 각 채널 plugin이 자체 인바운드 파이프라인을 담당합니다. 실제 패턴은 번들 채널 plugin (예: Microsoft Teams 또는 Google Chat plugin 패키지)을 확인하세요.
7
8

테스트

동일 위치의 테스트를 src/channel.test.ts에 작성하세요:
src/channel.test.ts
import { describe, it, expect } from "vitest";
import { acmeChatPlugin } from "./channel.js";

describe("acme-chat plugin", () => {
  it("resolves account from config", () => {
    const cfg = {
      channels: {
        "acme-chat": { token: "test-token", allowFrom: ["user1"] },
      },
    } as any;
    const account = acmeChatPlugin.setup!.resolveAccount(cfg, undefined);
    expect(account.token).toBe("test-token");
  });

  it("inspects account without materializing secrets", () => {
    const cfg = {
      channels: { "acme-chat": { token: "test-token" } },
    } as any;
    const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
    expect(result.configured).toBe(true);
    expect(result.tokenStatus).toBe("available");
  });

  it("reports missing config", () => {
    const cfg = { channels: {} } as any;
    const result = acmeChatPlugin.setup!.inspectAccount!(cfg, undefined);
    expect(result.configured).toBe(false);
  });
});
pnpm test -- <bundled-plugin-root>/acme-chat/
공유 테스트 헬퍼는 테스팅을 참고하세요.

파일 구조

<bundled-plugin-root>/acme-chat/
├── package.json              # openclaw.channel metadata
├── openclaw.plugin.json      # config schema가 포함된 Manifest
├── index.ts                  # defineChannelPluginEntry
├── setup-entry.ts            # defineSetupPluginEntry
├── api.ts                    # 공개 export(선택 사항)
├── runtime-api.ts            # 내부 런타임 export(선택 사항)
└── src/
    ├── channel.ts            # createChatChannelPlugin을 통한 ChannelPlugin
    ├── channel.test.ts       # 테스트
    ├── client.ts             # 플랫폼 API 클라이언트
    └── runtime.ts            # 런타임 저장소(필요한 경우)

고급 주제

스레딩 옵션

고정, 계정 범위, 또는 커스텀 답장 모드

메시지 도구 통합

describeMessageTool 및 작업 검색

대상 해석

inferTargetChatType, looksLikeId, resolveTarget

런타임 헬퍼

api.runtime를 통한 TTS, STT, 미디어, 서브에이전트
일부 번들 헬퍼 연결 지점은 번들 plugin 유지 관리와 호환성을 위해 여전히 존재합니다. 새 채널 plugin에 권장되는 패턴은 아니며, 해당 번들 plugin 계열을 직접 유지 관리하는 경우가 아니라면 일반 SDK 표면의 범용 channel/setup/reply/runtime 하위 경로를 선호하세요.

다음 단계