메인 콘텐츠로 건너뛰기
공개 기능 모델, Plugin 형태, 소유권/실행 계약은 Plugin architecture를 참조하세요. 이 페이지는 내부 메커니즘의 참조 문서입니다: 로드 파이프라인, 레지스트리, 런타임 훅, Gateway HTTP 라우트, import 경로, 스키마 표.

로드 파이프라인

시작 시 OpenClaw는 대략 다음을 수행합니다:
  1. 후보 Plugin 루트를 탐색합니다
  2. 네이티브 또는 호환 번들 매니페스트와 패키지 메타데이터를 읽습니다
  3. 안전하지 않은 후보를 거부합니다
  4. Plugin config를 정규화합니다(plugins.enabled, allow, deny, entries, slots, load.paths)
  5. 각 후보의 활성화 여부를 결정합니다
  6. 활성화된 네이티브 모듈을 로드합니다: 빌드된 번들 모듈은 네이티브 로더를 사용하고, 빌드되지 않은 네이티브 Plugin은 jiti를 사용합니다
  7. 네이티브 register(api) 훅을 호출하고 등록 항목을 Plugin 레지스트리에 수집합니다
  8. 레지스트리를 명령/런타임 표면에 노출합니다
activateregister의 레거시 별칭입니다 — 로더는 존재하는 항목을 확인해(def.register ?? def.activate) 같은 시점에 호출합니다. 모든 번들 Plugin은 register를 사용합니다. 새 Plugin에는 register를 사용하세요.
안전성 게이트는 런타임 실행 이전에 수행됩니다. 진입점이 Plugin 루트를 벗어나거나, 경로가 world-writable이거나, 번들되지 않은 Plugin에 대해 경로 소유권이 의심스러워 보이면 후보는 차단됩니다.

Manifest-first 동작

매니페스트는 control plane의 source of truth입니다. OpenClaw는 이를 사용해 다음을 수행합니다:
  • Plugin을 식별합니다
  • 선언된 채널/Skills/config schema 또는 번들 기능을 탐색합니다
  • plugins.entries.<id>.config를 검증합니다
  • Control UI 라벨/placeholder를 보강합니다
  • 설치/카탈로그 메타데이터를 표시합니다
  • Plugin 런타임을 로드하지 않고도 가벼운 활성화 및 설정 descriptor를 유지합니다
네이티브 Plugin의 경우 런타임 모듈이 data plane 부분입니다. 여기서 훅, 도구, 명령 또는 provider 흐름 같은 실제 동작을 등록합니다. 선택적 매니페스트 activationsetup 블록은 control plane에 남아 있습니다. 이들은 활성화 계획 및 설정 탐색을 위한 메타데이터 전용 descriptor이며, 런타임 등록, register(...), 또는 setupEntry를 대체하지 않습니다. 최초의 실제 활성화 소비자는 이제 매니페스트 명령, 채널, provider 힌트를 사용해 더 넓은 레지스트리 구체화 전에 Plugin 로드를 좁힙니다:
  • CLI 로딩은 요청된 기본 명령을 소유한 Plugin으로 범위를 좁힙니다
  • 채널 설정/Plugin 확인은 요청된 채널 id를 소유한 Plugin으로 범위를 좁힙니다
  • 명시적 provider 설정/런타임 확인은 요청된 provider id를 소유한 Plugin으로 범위를 좁힙니다
활성화 플래너는 기존 호출자를 위한 ids-only API와 새 진단을 위한 plan API를 모두 노출합니다. plan 항목은 Plugin이 선택된 이유를 보고하며, 명시적 activation.* 플래너 힌트와 providers, channels, commandAliases, setup.providers, contracts.tools, 훅 같은 매니페스트 소유권 대체 항목을 구분합니다. 이 이유 구분은 호환성 경계입니다: 기존 Plugin 메타데이터는 계속 동작하고, 새 코드는 런타임 로딩 시맨틱을 바꾸지 않고도 광범위한 힌트 또는 대체 동작을 감지할 수 있습니다. 이제 설정 탐색은 setup.providerssetup.cliBackends 같은 descriptor 소유 id를 우선 사용해 후보 Plugin 범위를 좁히고, 설정 시점 런타임 훅이 여전히 필요한 Plugin에 대해서만 setup-api로 대체합니다. 탐색된 Plugin이 둘 이상 같은 정규화된 설정 provider 또는 CLI backend id를 주장하면, 설정 조회는 탐색 순서에 의존하는 대신 그 모호한 소유자를 거부합니다.

로더가 캐시하는 항목

OpenClaw는 다음에 대해 짧은 인프로세스 캐시를 유지합니다:
  • 탐색 결과
  • 매니페스트 레지스트리 데이터
  • 로드된 Plugin 레지스트리
이 캐시는 급격한 시작 부하와 반복 명령 오버헤드를 줄여 줍니다. 이는 영속성이 아니라 수명이 짧은 성능 캐시로 생각하는 것이 안전합니다. 성능 참고:
  • OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1 또는 OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1로 이 캐시를 비활성화할 수 있습니다.
  • OPENCLAW_PLUGIN_DISCOVERY_CACHE_MSOPENCLAW_PLUGIN_MANIFEST_CACHE_MS로 캐시 기간을 조정합니다.

레지스트리 모델

로드된 Plugin은 임의의 코어 전역 상태를 직접 변경하지 않습니다. 대신 중앙 Plugin 레지스트리에 등록합니다. 레지스트리는 다음을 추적합니다:
  • Plugin 레코드(식별자, 소스, 출처, 상태, 진단)
  • 도구
  • 레거시 훅 및 타입 지정 훅
  • 채널
  • provider
  • gateway RPC 핸들러
  • HTTP 라우트
  • CLI registrar
  • 백그라운드 서비스
  • Plugin 소유 명령
그런 다음 코어 기능은 Plugin 모듈과 직접 통신하는 대신 이 레지스트리에서 읽습니다. 이렇게 하면 로딩이 단방향으로 유지됩니다:
  • Plugin 모듈 -> 레지스트리 등록
  • 코어 런타임 -> 레지스트리 사용
이 분리는 유지보수성에 중요합니다. 대부분의 코어 표면은 “레지스트리를 읽기”라는 하나의 통합 지점만 필요하며, “모든 Plugin 모듈을 특별 취급”할 필요가 없음을 의미합니다.

대화 바인딩 콜백

대화를 바인딩하는 Plugin은 승인이 해결될 때 반응할 수 있습니다. 바인드 요청이 승인되거나 거부된 후 콜백을 받으려면 api.onConversationBindingResolved(...)를 사용하세요:
export default {
  id: "my-plugin",
  register(api) {
    api.onConversationBindingResolved(async (event) => {
      if (event.status === "approved") {
        // 이제 이 plugin + 대화에 대한 바인딩이 존재합니다.
        console.log(event.binding?.conversationId);
        return;
      }

      // 요청이 거부되었습니다. 로컬 보류 상태를 모두 정리합니다.
      console.log(event.request.conversation.conversationId);
    });
  },
};
콜백 payload 필드:
  • status: "approved" 또는 "denied"
  • decision: "allow-once", "allow-always", 또는 "deny"
  • binding: 승인된 요청에 대해 해결된 바인딩
  • request: 원래 요청 요약, detach 힌트, 발신자 id, 및 대화 메타데이터
이 콜백은 알림 전용입니다. 누가 대화를 바인딩할 수 있는지는 바꾸지 않으며, 코어 승인 처리가 끝난 뒤 실행됩니다.

Provider 런타임 훅

Provider Plugin에는 세 가지 계층이 있습니다:
  • 매니페스트 메타데이터: 저비용 사전 런타임 조회용: providerAuthEnvVars, providerAuthAliases, providerAuthChoices, channelEnvVars.
  • config 시점 훅: catalog(레거시 discovery) 및 applyConfigDefaults.
  • 런타임 훅: 인증, 모델 확인, 스트림 래핑, 사고 수준, 재생 정책, 사용량 엔드포인트를 다루는 40개 이상의 선택적 훅. 전체 목록은 훅 순서 및 사용법 아래를 참조하세요.
OpenClaw는 여전히 일반적인 agent 루프, failover, transcript 처리, 도구 정책을 소유합니다. 이 훅은 완전히 사용자 지정된 추론 전송이 없어도 provider별 동작을 확장할 수 있는 표면입니다. provider에 env 기반 자격 증명이 있어서 일반 인증/상태/모델 선택기 경로가 Plugin 런타임을 로드하지 않고도 이를 볼 수 있어야 한다면 매니페스트 providerAuthEnvVars를 사용하세요. 하나의 provider id가 다른 provider id의 env vars, 인증 프로필, config 기반 인증, API 키 온보딩 선택을 재사용해야 한다면 매니페스트 providerAuthAliases를 사용하세요. 온보딩/인증 선택 CLI 표면이 provider의 선택 id, 그룹 라벨, 단일 플래그 기반 인증 연결을 Plugin 런타임 로드 없이 알아야 한다면 매니페스트 providerAuthChoices를 사용하세요. Provider 런타임 envVars는 온보딩 라벨 또는 OAuth client-id/client-secret 설정 변수 같은 운영자 대상 힌트용으로 유지하세요. 채널에 env 기반 인증 또는 설정이 있어 일반 shell-env 대체, config/status 검사, 또는 설정 프롬프트가 채널 런타임을 로드하지 않고도 이를 볼 수 있어야 한다면 매니페스트 channelEnvVars를 사용하세요.

훅 순서 및 사용법

모델/provider Plugin의 경우, OpenClaw는 대략 다음 순서로 훅을 호출합니다. “사용 시점” 열은 빠른 판단 가이드입니다.
#역할사용 시점
1catalogmodels.json 생성 중 models.providers에 provider config를 게시provider가 카탈로그 또는 기본 base URL 값을 소유하는 경우
2applyConfigDefaultsconfig 구체화 중 provider 소유 전역 config 기본값을 적용기본값이 인증 모드, env 또는 provider 모델 패밀리 시맨틱에 따라 달라지는 경우
(내장 모델 조회)OpenClaw가 먼저 일반 레지스트리/카탈로그 경로를 시도(Plugin 훅 아님)
3normalizeModelId조회 전에 레거시 또는 프리뷰 model-id 별칭을 정규화provider가 정식 모델 확인 전에 별칭 정리를 소유하는 경우
4normalizeTransport일반 모델 조립 전에 provider 패밀리 api / baseUrl을 정규화provider가 동일한 전송 패밀리 내 사용자 지정 provider id의 전송 정리를 소유하는 경우
5normalizeConfig런타임/provider 확인 전에 models.providers.<id>를 정규화provider에 Plugin과 함께 있어야 하는 config 정리가 필요할 때; 번들된 Google 패밀리 도우미도 지원되는 Google config 항목에 대한 안전장치 역할을 수행
6applyNativeStreamingUsageCompatconfig provider에 네이티브 streaming-usage 호환성 재작성을 적용provider에 엔드포인트 기반 네이티브 streaming usage 메타데이터 수정이 필요한 경우
7resolveConfigApiKey런타임 인증 로드 전에 config provider의 env-marker 인증을 확인provider에 provider 소유 env-marker API 키 확인이 있는 경우; amazon-bedrock도 여기서 내장 AWS env-marker 확인기를 가짐
8resolveSyntheticAuth평문을 저장하지 않고 로컬/셀프 호스팅 또는 config 기반 인증을 노출provider가 synthetic/local 자격 증명 marker로 동작할 수 있는 경우
9resolveExternalAuthProfilesprovider 소유 외부 인증 프로필을 오버레이; 기본 persistence는 CLI/앱 소유 자격 증명에 대해 runtime-onlyprovider가 복사된 refresh token을 저장하지 않고 외부 인증 자격 증명을 재사용하는 경우; 매니페스트에 contracts.externalAuthProviders를 선언
10shouldDeferSyntheticProfileAuth저장된 synthetic 프로필 placeholder를 env/config 기반 인증보다 뒤로 낮춤provider가 우선순위를 가져가면 안 되는 synthetic placeholder 프로필을 저장하는 경우
11resolveDynamicModel아직 로컬 레지스트리에 없는 provider 소유 모델 id에 대한 동기 대체 경로provider가 임의의 업스트림 모델 id를 허용하는 경우
12prepareDynamicModel비동기 워밍업 후 resolveDynamicModel을 다시 실행provider가 알 수 없는 id를 확인하기 전에 네트워크 메타데이터가 필요한 경우
13normalizeResolvedModel임베디드 러너가 확인된 모델을 사용하기 전 최종 재작성provider에 전송 재작성이 필요하지만 여전히 코어 전송을 사용하는 경우
14contributeResolvedModelCompat다른 호환 전송 뒤에 있는 벤더 모델에 대한 compat 플래그를 제공provider가 provider를 인수하지 않고도 프록시 전송에서 자체 모델을 인식하는 경우
15capabilities공유 코어 로직이 사용하는 provider 소유 transcript/도구 메타데이터provider에 transcript/provider 패밀리별 특이점이 필요한 경우
16normalizeToolSchemas임베디드 러너가 보기 전에 도구 schema를 정규화provider에 전송 패밀리 schema 정리가 필요한 경우
17inspectToolSchemas정규화 후 provider 소유 schema 진단을 노출코어에 provider별 규칙을 가르치지 않고 provider가 키워드 경고를 제공하려는 경우
18resolveReasoningOutputMode네이티브 또는 태그 기반 reasoning-output 계약을 선택provider에 네이티브 필드 대신 태그 기반 reasoning/final output이 필요한 경우
19prepareExtraParams일반 스트림 옵션 래퍼 전에 요청 파라미터를 정규화provider에 기본 요청 파라미터 또는 provider별 파라미터 정리가 필요한 경우
20createStreamFn일반 스트림 경로를 사용자 지정 전송으로 완전히 대체provider에 단순 래퍼가 아닌 사용자 지정 wire protocol이 필요한 경우
21wrapStreamFn일반 래퍼가 적용된 후 스트림을 래핑provider에 사용자 지정 전송 없이 요청 헤더/본문/모델 호환성 래퍼가 필요한 경우
22resolveTransportTurnState네이티브 턴별 전송 헤더 또는 메타데이터를 연결provider가 일반 전송이 provider 고유 턴 식별을 보내도록 하려는 경우
23resolveWebSocketSessionPolicy네이티브 WebSocket 헤더 또는 세션 쿨다운 정책을 연결provider가 일반 WS 전송에서 세션 헤더 또는 대체 정책을 조정하려는 경우
24formatApiKey인증 프로필 포매터: 저장된 프로필을 런타임 apiKey 문자열로 변환provider가 추가 인증 메타데이터를 저장하고 사용자 지정 런타임 token 형태가 필요한 경우
25refreshOAuth사용자 지정 refresh 엔드포인트 또는 refresh 실패 정책을 위한 OAuth refresh 재정의provider가 공유 pi-ai refresher에 맞지 않는 경우
26buildAuthDoctorHintOAuth refresh 실패 시 추가되는 복구 힌트provider에 refresh 실패 후 provider 소유 인증 복구 가이드가 필요한 경우
27matchesContextOverflowErrorprovider 소유 컨텍스트 창 overflow 매처provider에 일반 휴리스틱이 놓치는 원시 overflow 오류가 있는 경우
28classifyFailoverReasonprovider 소유 failover 이유 분류provider가 원시 API/전송 오류를 rate-limit/overload 등으로 매핑할 수 있는 경우
29isCacheTtlEligible프록시/백홀 provider용 프롬프트 캐시 정책provider에 프록시별 캐시 TTL 게이팅이 필요한 경우
30buildMissingAuthMessage일반 누락 인증 복구 메시지를 대체provider에 provider별 누락 인증 복구 힌트가 필요한 경우
31suppressBuiltInModel오래된 업스트림 모델 억제 및 선택적 사용자 대상 오류 힌트provider에 오래된 업스트림 행을 숨기거나 벤더 힌트로 대체해야 하는 경우
32augmentModelCatalog탐색 후 synthetic/final 카탈로그 행을 추가provider에 models list 및 선택기에서 synthetic forward-compat 행이 필요한 경우
33resolveThinkingProfile모델별 /think 수준 집합, 표시 라벨, 기본값provider가 선택된 모델에 대해 사용자 지정 사고 단계 또는 이진 라벨을 노출하는 경우
34isBinaryThinking켜짐/꺼짐 reasoning 토글 호환성 훅provider가 이진 사고 켜짐/꺼짐만 노출하는 경우
35supportsXHighThinkingxhigh reasoning 지원 호환성 훅provider가 일부 모델에서만 xhigh를 제공하려는 경우
36resolveDefaultThinkingLevel기본 /think 수준 호환성 훅provider가 모델 패밀리의 기본 /think 정책을 소유하는 경우
37isModernModelRef라이브 프로필 필터 및 스모크 선택을 위한 modern-model 매처provider가 라이브/스모크 선호 모델 매칭을 소유하는 경우
38prepareRuntimeAuth추론 직전에 구성된 자격 증명을 실제 런타임 token/key로 교환provider에 token 교환 또는 수명이 짧은 요청 자격 증명이 필요한 경우
39resolveUsageAuth/usage 및 관련 상태 표면용 사용량/청구 자격 증명을 확인provider에 사용자 지정 usage/quota token 파싱 또는 다른 usage 자격 증명이 필요한 경우
40fetchUsageSnapshot인증이 확인된 후 provider별 usage/quota 스냅샷을 가져와 정규화provider에 provider별 usage 엔드포인트 또는 payload 파서가 필요한 경우
41createEmbeddingProvider메모리/검색을 위한 provider 소유 임베딩 어댑터를 빌드메모리 임베딩 동작이 provider Plugin에 속하는 경우
42buildReplayPolicyprovider의 transcript 처리를 제어하는 재생 정책을 반환provider에 사용자 지정 transcript 정책이 필요한 경우(예: 사고 블록 제거)
43sanitizeReplayHistory일반 transcript 정리 후 재생 기록을 재작성provider에 공유 Compaction 도우미를 넘어서는 provider별 재생 재작성이 필요한 경우
44validateReplayTurns임베디드 러너 이전의 최종 재생 턴 검증 또는 재구성provider 전송에 일반 정리 이후 더 엄격한 턴 검증이 필요한 경우
45onModelSelected모델이 선택된 후 provider 소유 후처리 부작용을 실행모델이 활성화될 때 provider에 텔레메트리 또는 provider 소유 상태가 필요한 경우
normalizeModelId, normalizeTransport, normalizeConfig는 먼저 일치하는 provider Plugin을 확인한 다음, model id 또는 transport/config를 실제로 변경하는 훅을 찾을 때까지 다른 훅 가능 provider Plugin으로 계속 진행합니다. 이렇게 하면 호출자가 어떤 번들 Plugin이 재작성을 소유하는지 몰라도 별칭/호환 provider shim이 계속 동작할 수 있습니다. 어떤 provider 훅도 지원되는 Google 패밀리 config 항목을 재작성하지 않으면, 번들된 Google config normalizer가 여전히 그 호환성 정리를 적용합니다. provider에 완전히 사용자 지정 wire protocol 또는 사용자 지정 요청 실행기가 필요하다면, 그것은 다른 종류의 확장입니다. 이 훅은 OpenClaw의 일반 추론 루프에서 계속 실행되는 provider 동작을 위한 것입니다.

Provider 예시

api.registerProvider({
  id: "example-proxy",
  label: "Example Proxy",
  auth: [],
  catalog: {
    order: "simple",
    run: async (ctx) => {
      const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
      if (!apiKey) {
        return null;
      }
      return {
        provider: {
          baseUrl: "https://proxy.example.com/v1",
          apiKey,
          api: "openai-completions",
          models: [{ id: "auto", name: "Auto" }],
        },
      };
    },
  },
  resolveDynamicModel: (ctx) => ({
    id: ctx.modelId,
    name: ctx.modelId,
    provider: "example-proxy",
    api: "openai-completions",
    baseUrl: "https://proxy.example.com/v1",
    reasoning: false,
    input: ["text"],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow: 128000,
    maxTokens: 8192,
  }),
  prepareRuntimeAuth: async (ctx) => {
    const exchanged = await exchangeToken(ctx.apiKey);
    return {
      apiKey: exchanged.token,
      baseUrl: exchanged.baseUrl,
      expiresAt: exchanged.expiresAt,
    };
  },
  resolveUsageAuth: async (ctx) => {
    const auth = await ctx.resolveOAuthToken();
    return auth ? { token: auth.token } : null;
  },
  fetchUsageSnapshot: async (ctx) => {
    return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
  },
});

내장 예시

번들된 provider Plugin은 위 훅을 조합해 각 벤더의 카탈로그, 인증, 사고, 재생, 사용량 요구 사항에 맞춥니다. 권위 있는 훅 집합은 각 Plugin의 extensions/ 아래에 있으며, 이 페이지는 목록을 그대로 복제하기보다 형태를 설명합니다.
OpenRouter, Kilocode, Z.AI, xAI는 catalog와 함께 resolveDynamicModel / prepareDynamicModel을 등록하여 OpenClaw의 정적 카탈로그보다 먼저 업스트림 모델 id를 노출할 수 있습니다.
GitHub Copilot, Gemini CLI, ChatGPT Codex, MiniMax, Xiaomi, z.ai는 prepareRuntimeAuth 또는 formatApiKeyresolveUsageAuth + fetchUsageSnapshot과 함께 사용해 token 교환과 /usage 통합을 소유합니다.
공유된 이름 있는 패밀리(google-gemini, passthrough-gemini, anthropic-by-model, hybrid-anthropic-openai)를 통해 provider는 각 Plugin이 정리를 다시 구현하는 대신 buildReplayPolicy를 통해 transcript 정책을 선택할 수 있습니다.
byteplus, cloudflare-ai-gateway, huggingface, kimi-coding, nvidia, qianfan, synthetic, together, venice, vercel-ai-gateway, volcenginecatalog만 등록하고 공유 추론 루프를 사용합니다.
베타 헤더, /fast / serviceTier, context1m은 일반 SDK가 아니라 Anthropic Plugin의 공개 api.ts / contract-api.ts 경계 (wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier) 안에 있습니다.

런타임 도우미

Plugin은 api.runtime를 통해 선택된 코어 도우미에 접근할 수 있습니다. TTS의 경우:
const clip = await api.runtime.tts.textToSpeech({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const result = await api.runtime.tts.textToSpeechTelephony({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const voices = await api.runtime.tts.listVoices({
  provider: "elevenlabs",
  cfg: api.config,
});
참고:
  • textToSpeech는 파일/voice-note 표면을 위한 일반 코어 TTS 출력 payload를 반환합니다.
  • 코어 messages.tts 구성과 provider 선택을 사용합니다.
  • PCM 오디오 버퍼 + 샘플 속도를 반환합니다. Plugin은 provider에 맞게 리샘플링/인코딩해야 합니다.
  • listVoices는 provider별로 선택 사항입니다. 벤더 소유 음성 선택기 또는 설정 흐름에 사용하세요.
  • 음성 목록에는 provider 인식 선택기를 위한 locale, gender, personality 태그 같은 더 풍부한 메타데이터가 포함될 수 있습니다.
  • 현재 telephony는 OpenAI와 ElevenLabs를 지원합니다. Microsoft는 지원하지 않습니다.
Plugin은 api.registerSpeechProvider(...)를 통해 음성 provider를 등록할 수도 있습니다.
api.registerSpeechProvider({
  id: "acme-speech",
  label: "Acme Speech",
  isConfigured: ({ config }) => Boolean(config.messages?.tts),
  synthesize: async (req) => {
    return {
      audioBuffer: Buffer.from([]),
      outputFormat: "mp3",
      fileExtension: ".mp3",
      voiceCompatible: false,
    };
  },
});
참고:
  • TTS 정책, 대체, 응답 전달은 코어에 유지하세요.
  • 벤더 소유 합성 동작에는 speech provider를 사용하세요.
  • 레거시 Microsoft edge 입력은 microsoft provider id로 정규화됩니다.
  • 선호되는 소유권 모델은 회사 중심입니다. OpenClaw가 해당 기능 계약을 추가함에 따라 하나의 벤더 Plugin이 텍스트, 음성, 이미지, 미래의 미디어 provider까지 소유할 수 있습니다.
이미지/오디오/비디오 이해를 위해 Plugin은 일반 key/value bag 대신 하나의 타입 지정된 미디어 이해 provider를 등록합니다:
api.registerMediaUnderstandingProvider({
  id: "google",
  capabilities: ["image", "audio", "video"],
  describeImage: async (req) => ({ text: "..." }),
  transcribeAudio: async (req) => ({ text: "..." }),
  describeVideo: async (req) => ({ text: "..." }),
});
참고:
  • 오케스트레이션, 대체, config, 채널 연결은 코어에 유지하세요.
  • 벤더 동작은 provider Plugin에 유지하세요.
  • 점진적 확장은 타입 지정된 상태를 유지해야 합니다: 새로운 선택적 메서드, 새로운 선택적 결과 필드, 새로운 선택적 기능.
  • 비디오 생성도 이미 같은 패턴을 따릅니다:
    • 코어가 기능 계약과 런타임 도우미를 소유합니다
    • 벤더 Plugin이 api.registerVideoGenerationProvider(...)를 등록합니다
    • 기능/채널 Plugin이 api.runtime.videoGeneration.*를 사용합니다
미디어 이해 런타임 도우미의 경우 Plugin은 다음을 호출할 수 있습니다:
const image = await api.runtime.mediaUnderstanding.describeImageFile({
  filePath: "/tmp/inbound-photo.jpg",
  cfg: api.config,
  agentDir: "/tmp/agent",
});

const video = await api.runtime.mediaUnderstanding.describeVideoFile({
  filePath: "/tmp/inbound-video.mp4",
  cfg: api.config,
});
오디오 전사의 경우 Plugin은 미디어 이해 런타임 또는 이전 STT 별칭 중 어느 쪽이든 사용할 수 있습니다:
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
  filePath: "/tmp/inbound-audio.ogg",
  cfg: api.config,
  // MIME을 안정적으로 추론할 수 없을 때 선택 사항:
  mime: "audio/ogg",
});
참고:
  • api.runtime.mediaUnderstanding.*는 이미지/오디오/비디오 이해를 위한 선호되는 공유 표면입니다.
  • 코어 미디어 이해 오디오 구성(tools.media.audio)과 provider 대체 순서를 사용합니다.
  • 전사 출력이 생성되지 않으면 { text: undefined }를 반환합니다(예: 입력이 건너뛰어졌거나 지원되지 않는 경우).
  • api.runtime.stt.transcribeAudioFile(...)는 호환성 별칭으로 남아 있습니다.
Plugin은 api.runtime.subagent를 통해 백그라운드 subagent 실행을 시작할 수도 있습니다:
const result = await api.runtime.subagent.run({
  sessionKey: "agent:main:subagent:search-helper",
  message: "Expand this query into focused follow-up searches.",
  provider: "openai",
  model: "gpt-4.1-mini",
  deliver: false,
});
참고:
  • providermodel은 영구 세션 변경이 아니라 실행별 선택적 재정의입니다.
  • OpenClaw는 신뢰된 호출자에 대해서만 이러한 재정의 필드를 적용합니다.
  • Plugin 소유 대체 실행의 경우 운영자는 plugins.entries.<id>.subagent.allowModelOverride: true로 명시적으로 허용해야 합니다.
  • plugins.entries.<id>.subagent.allowedModels를 사용해 신뢰된 Plugin을 특정 정식 provider/model 대상으로 제한하거나, 명시적으로 모든 대상을 허용하려면 "*"를 사용하세요.
  • 신뢰되지 않은 Plugin subagent 실행도 계속 동작하지만, 재정의 요청은 조용히 대체되는 대신 거부됩니다.
웹 검색의 경우 Plugin은 agent 도구 연결에 직접 접근하는 대신 공유 런타임 도우미를 사용할 수 있습니다:
const providers = api.runtime.webSearch.listProviders({
  config: api.config,
});

const result = await api.runtime.webSearch.search({
  config: api.config,
  args: {
    query: "OpenClaw plugin runtime helpers",
    count: 5,
  },
});
Plugin은 또한 api.registerWebSearchProvider(...)를 통해 웹 검색 provider를 등록할 수 있습니다. 참고:
  • provider 선택, 자격 증명 확인, 공유 요청 시맨틱은 코어에 유지하세요.
  • 벤더별 검색 전송에는 웹 검색 provider를 사용하세요.
  • api.runtime.webSearch.*는 agent 도구 래퍼에 의존하지 않고 검색 동작이 필요한 기능/채널 Plugin을 위한 선호되는 공유 표면입니다.

api.runtime.imageGeneration

const result = await api.runtime.imageGeneration.generate({
  config: api.config,
  args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});

const providers = api.runtime.imageGeneration.listProviders({
  config: api.config,
});
  • generate(...): 구성된 이미지 생성 provider 체인을 사용해 이미지를 생성합니다.
  • listProviders(...): 사용 가능한 이미지 생성 provider와 해당 기능을 나열합니다.

Gateway HTTP 라우트

Plugin은 api.registerHttpRoute(...)를 사용해 HTTP 엔드포인트를 노출할 수 있습니다.
api.registerHttpRoute({
  path: "/acme/webhook",
  auth: "plugin",
  match: "exact",
  handler: async (_req, res) => {
    res.statusCode = 200;
    res.end("ok");
    return true;
  },
});
라우트 필드:
  • path: gateway HTTP 서버 아래의 라우트 경로입니다.
  • auth: 필수입니다. 일반 gateway 인증이 필요하면 "gateway"를, Plugin 관리 인증/webhook 검증에는 "plugin"을 사용하세요.
  • match: 선택 사항입니다. "exact"(기본값) 또는 "prefix".
  • replaceExisting: 선택 사항입니다. 같은 Plugin이 기존 라우트 등록을 대체할 수 있게 합니다.
  • handler: 라우트가 요청을 처리했으면 true를 반환합니다.
참고:
  • api.registerHttpHandler(...)는 제거되었으며 Plugin 로드 오류를 발생시킵니다. 대신 api.registerHttpRoute(...)를 사용하세요.
  • Plugin 라우트는 반드시 auth를 명시적으로 선언해야 합니다.
  • 정확히 같은 path + match 충돌은 replaceExisting: true가 아닌 한 거부되며, 한 Plugin이 다른 Plugin의 라우트를 대체할 수는 없습니다.
  • auth 수준이 다른 겹치는 라우트는 거부됩니다. exact/prefix 폴스루 체인은 같은 인증 수준에서만 유지하세요.
  • auth: "plugin" 라우트는 운영자 런타임 scope를 자동으로 받지 않습니다. 특권 있는 Gateway helper 호출이 아니라 Plugin 관리 webhook/서명 검증용입니다.
  • auth: "gateway" 라우트는 Gateway 요청 런타임 scope 안에서 실행되지만, 그 scope는 의도적으로 보수적입니다:
    • 공유 secret bearer 인증(gateway.auth.mode = "token" / "password")은 호출자가 x-openclaw-scopes를 보내더라도 plugin-route 런타임 scope를 operator.write에 고정합니다
    • 신뢰된 identity 전달 HTTP 모드(예: trusted-proxy 또는 사설 ingress의 gateway.auth.mode = "none")는 헤더가 명시적으로 있을 때만 x-openclaw-scopes를 적용합니다
    • 이런 identity 전달 plugin-route 요청에서 x-openclaw-scopes가 없으면 런타임 scope는 operator.write로 대체됩니다
  • 실용적인 규칙: gateway 인증 Plugin 라우트를 암시적인 관리자 표면으로 가정하지 마세요. 라우트에 관리자 전용 동작이 필요하다면 identity 전달 인증 모드를 요구하고 명시적인 x-openclaw-scopes 헤더 계약을 문서화하세요.

Plugin SDK import 경로

새 Plugin을 작성할 때는 단일체 openclaw/plugin-sdk 루트 barrel 대신 좁은 SDK 서브패스를 사용하세요. 코어 서브패스:
서브패스용도
openclaw/plugin-sdk/plugin-entryPlugin 등록 기본 요소
openclaw/plugin-sdk/channel-core채널 엔트리/빌드 도우미
openclaw/plugin-sdk/core일반 공유 도우미 및 umbrella 계약
openclaw/plugin-sdk/config-schema루트 openclaw.json Zod schema (OpenClawSchema)
채널 Plugin은 좁은 경계 패밀리에서 선택합니다 — channel-setup, setup-runtime, setup-adapter-runtime, setup-tools, channel-pairing, channel-contract, channel-feedback, channel-inbound, channel-lifecycle, channel-reply-pipeline, command-auth, secret-input, webhook-ingress, channel-targets, channel-actions. 승인 동작은 관련 없는 Plugin 필드에 섞지 말고 하나의 approvalCapability 계약으로 통합해야 합니다. 자세한 내용은 Channel plugins를 참조하세요. 런타임 및 config 도우미는 대응되는 *-runtime 서브패스 아래에 있습니다 (approval-runtime, config-runtime, infra-runtime, agent-runtime, lazy-runtime, directory-runtime, text-runtime, runtime-store 등).
openclaw/plugin-sdk/channel-runtime는 deprecated 상태입니다 — 이전 Plugin을 위한 호환성 shim입니다. 새 코드는 대신 더 좁은 일반 기본 요소를 import해야 합니다.
리포지토리 내부 엔트리 포인트(번들 Plugin 패키지 루트별):
  • index.js — 번들 Plugin 엔트리
  • api.js — 도우미/타입 barrel
  • runtime-api.js — 런타임 전용 barrel
  • setup-entry.js — 설정 Plugin 엔트리
외부 Plugin은 openclaw/plugin-sdk/* 서브패스만 import해야 합니다. 코어 또는 다른 Plugin에서 다른 Plugin 패키지의 src/*를 절대 import하지 마세요. facade 로드 엔트리 포인트는 활성 런타임 config 스냅샷이 있으면 이를 우선 사용하고, 없으면 디스크의 확인된 config 파일로 대체합니다. image-generation, media-understanding, speech 같은 기능별 서브패스는 오늘날 번들 Plugin이 이를 사용하기 때문에 존재합니다. 이것이 자동으로 장기적으로 고정된 외부 계약이 되는 것은 아닙니다 — 의존하기 전에 관련 SDK 참조 페이지를 확인하세요.

메시지 도구 schema

Plugin은 반응, 읽음, 투표 같은 비메시지 기본 요소에 대해 채널별 describeMessageTool(...) schema 기여를 소유해야 합니다. 공유 전송 표현은 provider 고유 버튼, 컴포넌트, 블록, 카드 필드 대신 일반 MessagePresentation 계약을 사용해야 합니다. 계약, 대체 규칙, provider 매핑, Plugin 작성자 체크리스트는 Message Presentation을 참조하세요. 전송 가능한 Plugin은 메시지 기능을 통해 자신이 렌더링할 수 있는 항목을 선언합니다:
  • 의미 기반 프레젠테이션 블록용 presentation (text, context, divider, buttons, select)
  • 고정 전달 요청용 delivery-pin
코어는 프레젠테이션을 네이티브로 렌더링할지 텍스트로 저하할지 결정합니다. 일반 메시지 도구에서 provider 고유 UI 탈출구를 노출하지 마세요. 레거시 네이티브 schema용 deprecated SDK 도우미는 기존 서드파티 Plugin을 위해 계속 export되지만, 새 Plugin은 이를 사용하지 않아야 합니다.

채널 대상 확인

채널 Plugin은 채널별 대상 시맨틱을 소유해야 합니다. 공유 아웃바운드 호스트는 일반적으로 유지하고 provider 규칙에는 메시징 어댑터 표면을 사용하세요:
  • messaging.inferTargetChatType({ to })는 정규화된 대상을 디렉터리 조회 전에 direct, group, channel 중 무엇으로 다룰지 결정합니다.
  • messaging.targetResolver.looksLikeId(raw, normalized)는 입력이 디렉터리 검색 대신 바로 id 유사 확인으로 건너뛰어야 하는지 코어에 알려줍니다.
  • messaging.targetResolver.resolveTarget(...)는 정규화 후 또는 디렉터리 미스 후 코어에 최종 provider 소유 확인이 필요할 때 Plugin 대체 경로입니다.
  • messaging.resolveOutboundSessionRoute(...)는 대상이 확인된 뒤 provider별 세션 라우트 구성을 소유합니다.
권장 분리:
  • 피어/그룹 검색 전에 이루어져야 하는 범주 결정에는 inferTargetChatType을 사용합니다.
  • “이를 명시적/네이티브 대상 id로 취급” 확인에는 looksLikeId를 사용합니다.
  • 광범위한 디렉터리 검색이 아니라 provider별 정규화 대체 경로에는 resolveTarget을 사용합니다.
  • 채팅 id, 스레드 id, JID, 핸들, 방 id 같은 provider 고유 id는 일반 SDK 필드가 아니라 target 값 또는 provider별 파라미터 안에 유지하세요.

config 기반 디렉터리

config에서 디렉터리 항목을 파생하는 Plugin은 그 로직을 Plugin 안에 유지하고 openclaw/plugin-sdk/directory-runtime의 공유 도우미를 재사용해야 합니다. 채널에 다음과 같은 config 기반 피어/그룹이 필요할 때 이를 사용하세요:
  • 허용 목록 기반 DM 피어
  • 구성된 채널/그룹 맵
  • 계정 범위의 정적 디렉터리 대체 항목
directory-runtime의 공유 도우미는 일반 작업만 처리합니다:
  • 쿼리 필터링
  • 제한 적용
  • 중복 제거/정규화 도우미
  • ChannelDirectoryEntry[] 빌드
채널별 계정 검사와 id 정규화는 Plugin 구현에 남아 있어야 합니다.

Provider 카탈로그

Provider Plugin은 registerProvider({ catalog: { run(...) { ... } } })로 추론용 모델 카탈로그를 정의할 수 있습니다. catalog.run(...)은 OpenClaw가 models.providers에 쓰는 것과 같은 형태를 반환합니다:
  • 하나의 provider 항목에 대해 { provider }
  • 여러 provider 항목에 대해 { providers }
Plugin이 provider별 모델 id, 기본 base URL 값, 또는 인증에 따라 달라지는 모델 메타데이터를 소유할 때 catalog를 사용하세요. catalog.order는 Plugin의 카탈로그가 OpenClaw의 내장 암시적 provider에 비해 언제 병합되는지 제어합니다:
  • simple: 일반 API 키 또는 env 기반 provider
  • profile: 인증 프로필이 있을 때 나타나는 provider
  • paired: 관련된 여러 provider 항목을 합성하는 provider
  • late: 다른 암시적 provider 이후의 마지막 단계
나중 provider가 키 충돌에서 우선하므로, Plugin은 같은 provider id를 가진 내장 provider 항목을 의도적으로 재정의할 수 있습니다. 호환성:
  • discovery는 레거시 별칭으로 계속 동작합니다
  • catalogdiscovery가 모두 등록되면 OpenClaw는 catalog를 사용합니다

읽기 전용 채널 검사

Plugin이 채널을 등록하는 경우, resolveAccount(...)와 함께 plugin.config.inspectAccount(cfg, accountId) 구현을 우선 고려하세요. 이유:
  • resolveAccount(...)는 런타임 경로입니다. 자격 증명이 완전히 구체화되었다고 가정해도 되며 필요한 secret이 없으면 즉시 실패할 수 있습니다.
  • openclaw status, openclaw status --all, openclaw channels status, openclaw channels resolve, doctor/config 복구 흐름 같은 읽기 전용 명령 경로는 단지 구성을 설명하기 위해 런타임 자격 증명을 구체화할 필요가 없어야 합니다.
권장 inspectAccount(...) 동작:
  • 설명용 계정 상태만 반환합니다.
  • enabledconfigured를 유지합니다.
  • 관련 있는 경우 자격 증명 소스/상태 필드를 포함합니다. 예:
    • tokenSource, tokenStatus
    • botTokenSource, botTokenStatus
    • appTokenSource, appTokenStatus
    • signingSecretSource, signingSecretStatus
  • 읽기 전용 사용 가능 여부를 보고하기 위해 원시 token 값을 반환할 필요는 없습니다. 상태 스타일 명령에는 tokenStatus: "available"(및 대응되는 소스 필드)만 반환하면 충분합니다.
  • 자격 증명이 SecretRef를 통해 구성되었지만 현재 명령 경로에서 사용할 수 없으면 configured_unavailable을 사용하세요.
이렇게 하면 읽기 전용 명령이 충돌하거나 계정이 구성되지 않은 것으로 잘못 보고하는 대신 “구성되었지만 이 명령 경로에서는 사용할 수 없음”을 보고할 수 있습니다.

패키지 pack

Plugin 디렉터리에는 openclaw.extensions가 포함된 package.json이 있을 수 있습니다:
{
  "name": "my-pack",
  "openclaw": {
    "extensions": ["./src/safety.ts", "./src/tools.ts"],
    "setupEntry": "./src/setup-entry.ts"
  }
}
각 엔트리는 하나의 Plugin이 됩니다. pack에 여러 extension이 나열되면 Plugin id는 name/<fileBase>가 됩니다. Plugin이 npm 의존성을 import한다면, 해당 디렉터리에 node_modules를 사용할 수 있도록 그곳에서 설치하세요(npm install / pnpm install). 보안 가드레일: 모든 openclaw.extensions 엔트리는 심볼릭 링크 확인 후에도 Plugin 디렉터리 내부에 남아 있어야 합니다. 패키지 디렉터리를 벗어나는 엔트리는 거부됩니다. 보안 참고: openclaw plugins installnpm install --omit=dev --ignore-scripts로 Plugin 의존성을 설치합니다 (라이프사이클 스크립트 없음, 런타임 시 dev dependency 없음). Plugin dependency 트리는 “순수 JS/TS”로 유지하고 postinstall 빌드가 필요한 패키지는 피하세요. 선택 사항: openclaw.setupEntry는 가벼운 설정 전용 모듈을 가리킬 수 있습니다. OpenClaw가 비활성화된 채널 Plugin에 대한 설정 표면이 필요하거나, 채널 Plugin이 활성화되어 있지만 아직 구성되지 않은 경우, 전체 Plugin 엔트리 대신 setupEntry를 로드합니다. 이렇게 하면 기본 Plugin 엔트리가 도구, 훅 또는 기타 런타임 전용 코드도 연결할 때 시작과 설정이 더 가벼워집니다. 선택 사항: openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen은 채널 Plugin이 이미 구성된 경우에도 gateway의 listen 이전 시작 단계에서 동일한 setupEntry 경로를 선택하도록 할 수 있습니다. 이 옵션은 setupEntry가 gateway가 수신을 시작하기 전에 존재해야 하는 시작 표면을 완전히 다루는 경우에만 사용하세요. 실제로는 설정 엔트리가 시작 시 의존하는 모든 채널 소유 기능을 등록해야 한다는 뜻입니다. 예:
  • 채널 등록 자체
  • gateway가 수신을 시작하기 전에 사용 가능해야 하는 모든 HTTP 라우트
  • 같은 시점에 존재해야 하는 모든 gateway 메서드, 도구 또는 서비스
전체 엔트리가 여전히 필수 시작 기능을 하나라도 소유하고 있다면 이 플래그를 사용하지 마세요. 기본 동작을 유지하고 OpenClaw가 시작 중 전체 엔트리를 로드하도록 하세요. 번들 채널은 코어가 전체 채널 런타임이 로드되기 전에 참조할 수 있는 설정 전용 계약 표면 도우미도 게시할 수 있습니다. 현재 설정 승격 표면은 다음과 같습니다:
  • singleAccountKeysToMove
  • namedAccountPromotionKeys
  • resolveSingleAccountPromotionTarget(...)
코어는 레거시 단일 계정 채널 config를 전체 Plugin 엔트리를 로드하지 않고 channels.<id>.accounts.*로 승격해야 할 때 이 표면을 사용합니다. 현재 Matrix가 번들 예시입니다: 이름 있는 계정이 이미 존재할 때 인증/부트스트랩 키만 이름 있는 승격 계정으로 이동하며, 항상 accounts.default를 만드는 대신 구성된 비정규 기본 계정 키를 유지할 수 있습니다. 이러한 설정 패치 어댑터는 번들 계약 표면 탐색을 지연 상태로 유지합니다. import 시점은 가볍게 유지되고, 승격 표면은 모듈 import 시 번들 채널 시작을 다시 진입하는 대신 처음 사용할 때만 로드됩니다. 이러한 시작 표면에 gateway RPC 메서드가 포함될 때는 Plugin 전용 prefix를 유지하세요. 코어 관리자 네임스페이스(config.*, exec.approvals.*, wizard.*, update.*)는 예약되어 있으며 Plugin이 더 좁은 scope를 요청하더라도 항상 operator.admin으로 확인됩니다. 예:
{
  "name": "@scope/my-channel",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "startup": {
      "deferConfiguredChannelFullLoadUntilAfterListen": true
    }
  }
}

채널 카탈로그 메타데이터

채널 Plugin은 openclaw.channel을 통해 설정/탐색 메타데이터를, openclaw.install을 통해 설치 힌트를 광고할 수 있습니다. 이렇게 하면 코어 카탈로그에 데이터를 넣지 않아도 됩니다. 예:
{
  "name": "@openclaw/nextcloud-talk",
  "openclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "nextcloud-talk",
      "label": "Nextcloud Talk",
      "selectionLabel": "Nextcloud Talk (셀프 호스팅)",
      "docsPath": "/channels/nextcloud-talk",
      "docsLabel": "nextcloud-talk",
      "blurb": "Nextcloud Talk webhook bot을 통한 셀프 호스팅 채팅.",
      "order": 65,
      "aliases": ["nc-talk", "nc"]
    },
    "install": {
      "npmSpec": "@openclaw/nextcloud-talk",
      "localPath": "<bundled-plugin-local-path>",
      "defaultChoice": "npm"
    }
  }
}
최소 예시 외에 유용한 openclaw.channel 필드:
  • detailLabel: 더 풍부한 카탈로그/상태 표면을 위한 보조 라벨
  • docsLabel: 문서 링크의 링크 텍스트 재정의
  • preferOver: 이 카탈로그 항목이 더 우선해야 하는 낮은 우선순위 Plugin/채널 id
  • selectionDocsPrefix, selectionDocsOmitLabel, selectionExtras: 선택 표면용 문구 제어
  • markdownCapable: 아웃바운드 포맷 결정 시 채널을 마크다운 가능 채널로 표시
  • exposure.configured: false로 설정하면 구성된 채널 목록 표면에서 채널 숨김
  • exposure.setup: false로 설정하면 대화형 설정/구성 선택기에서 채널 숨김
  • exposure.docs: 문서 탐색 표면에서 채널을 내부/비공개로 표시
  • showConfigured / showInSetup: 호환성을 위해 여전히 허용되는 레거시 별칭. exposure 사용 권장
  • quickstartAllowFrom: 채널을 표준 빠른 시작 allowFrom 흐름에 포함
  • forceAccountBinding: 계정이 하나만 있어도 명시적 계정 바인딩 요구
  • preferSessionLookupForAnnounceTarget: 공지 대상을 확인할 때 세션 조회를 우선
OpenClaw는 외부 채널 카탈로그(예: MPM 레지스트리 export)도 병합할 수 있습니다. 다음 위치 중 하나에 JSON 파일을 두세요:
  • ~/.openclaw/mpm/plugins.json
  • ~/.openclaw/mpm/catalog.json
  • ~/.openclaw/plugins/catalog.json
또는 OPENCLAW_PLUGIN_CATALOG_PATHS(또는 OPENCLAW_MPM_CATALOG_PATHS)를 하나 이상의 JSON 파일로 지정하세요(쉼표/세미콜론/PATH 구분). 각 파일은 { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }를 포함해야 합니다. 파서는 "entries" 키의 레거시 별칭으로 "packages" 또는 "plugins"도 허용합니다. 생성된 채널 카탈로그 항목과 provider 설치 카탈로그 항목은 원시 openclaw.install 블록 옆에 정규화된 설치 소스 사실도 노출합니다. 정규화된 사실은 npm spec이 정확한 버전인지 부동 선택자인지, 기대되는 무결성 메타데이터가 있는지, 로컬 소스 경로도 사용 가능한지를 식별합니다. 소비자는 installSource를 가산적인 선택 필드로 취급해야 하며, 그래야 오래된 수작업 항목과 호환성 shim이 이를 합성할 필요가 없습니다. 이렇게 하면 온보딩과 진단이 Plugin 런타임을 import하지 않고도 소스 plane 상태를 설명할 수 있습니다. 공식 외부 npm 항목은 정확한 npmSpecexpectedIntegrity를 사용하는 것이 좋습니다. 순수 패키지 이름과 dist-tag도 호환성을 위해 계속 동작하지만, 소스 plane 경고를 노출하므로 기존 Plugin을 깨뜨리지 않고도 카탈로그를 pinning되고 무결성 검증된 설치로 이동시킬 수 있습니다. 온보딩이 로컬 카탈로그 경로에서 설치할 때는 가능한 경우 source: "path"와 워크스페이스 상대 sourcePath를 포함한 plugins.installs 항목을 기록합니다. 실제 운영 로드 경로는 plugins.load.paths에 남고, 설치 기록은 장기 config에 로컬 워크스테이션 경로를 중복 기록하지 않습니다. 이렇게 하면 로컬 개발 설치가 두 번째 원시 파일 시스템 경로 노출 표면을 추가하지 않고도 소스 plane 진단에서 보이게 유지됩니다.

컨텍스트 엔진 Plugin

컨텍스트 엔진 Plugin은 세션 컨텍스트 오케스트레이션의 수집, 조립, Compaction을 소유합니다. Plugin에서 api.registerContextEngine(id, factory)로 등록한 다음, 활성 엔진은 plugins.slots.contextEngine으로 선택하세요. 기본 컨텍스트 파이프라인을 단순히 메모리 검색이나 훅으로 확장하는 대신 교체하거나 확장해야 할 때 이를 사용합니다.
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("lossless-claw", () => ({
    info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact() {
      return { ok: true, compacted: false };
    },
  }));
}
엔진이 Compaction 알고리즘을 소유하지 않는다면, compact()를 구현한 상태로 유지하고 이를 명시적으로 위임하세요:
import {
  buildMemorySystemPromptAddition,
  delegateCompactionToRuntime,
} from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("my-memory-engine", () => ({
    info: {
      id: "my-memory-engine",
      name: "My Memory Engine",
      ownsCompaction: false,
    },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact(params) {
      return await delegateCompactionToRuntime(params);
    },
  }));
}

새 기능 추가

Plugin에 현재 API에 맞지 않는 동작이 필요하다면, 비공개 내부 접근으로 Plugin 시스템을 우회하지 마세요. 누락된 기능을 추가하세요. 권장 순서:
  1. 코어 계약 정의 코어가 소유해야 하는 공유 동작을 결정합니다: 정책, 대체, config 병합, 수명 주기, 채널 대상 시맨틱, 런타임 도우미 형태.
  2. 타입 지정된 Plugin 등록/런타임 표면 추가 가장 작지만 유용한 타입 지정 기능 표면으로 OpenClawPluginApi 및/또는 api.runtime를 확장합니다.
  3. 코어 + 채널/기능 소비자 연결 채널과 기능 Plugin은 벤더 구현을 직접 import하지 말고, 코어를 통해 새 기능을 사용해야 합니다.
  4. 벤더 구현 등록 그런 다음 벤더 Plugin이 해당 기능에 대해 백엔드를 등록합니다.
  5. 계약 커버리지 추가 시간 경과에 따라 소유권과 등록 형태가 명시적으로 유지되도록 테스트를 추가합니다.
이것이 OpenClaw가 하나의 provider 세계관에 하드코딩되지 않으면서도 일관된 방향성을 유지하는 방식입니다. 구체적인 파일 체크리스트와 예시는 Capability Cookbook를 참조하세요.

기능 체크리스트

새 기능을 추가할 때 구현은 보통 다음 표면을 함께 수정해야 합니다:
  • src/<capability>/types.ts의 코어 계약 타입
  • src/<capability>/runtime.ts의 코어 러너/런타임 도우미
  • src/plugins/types.ts의 Plugin API 등록 표면
  • src/plugins/registry.ts의 Plugin 레지스트리 연결
  • 기능/채널 Plugin이 이를 사용해야 할 때의 src/plugins/runtime/* 내 Plugin 런타임 노출
  • src/test-utils/plugin-registration.ts의 캡처/테스트 도우미
  • src/plugins/contracts/registry.ts의 소유권/계약 assertion
  • docs/의 운영자/Plugin 문서
이 표면 중 하나가 빠져 있다면, 보통 그 기능이 아직 완전히 통합되지 않았다는 신호입니다.

기능 템플릿

최소 패턴:
// core contract
export type VideoGenerationProviderPlugin = {
  id: string;
  label: string;
  generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};

// plugin API
api.registerVideoGenerationProvider({
  id: "openai",
  label: "OpenAI",
  async generateVideo(req) {
    return await generateOpenAiVideo(req);
  },
});

// feature/channel plugin용 공유 런타임 도우미
const clip = await api.runtime.videoGeneration.generate({
  prompt: "Show the robot walking through the lab.",
  cfg,
});
계약 테스트 패턴:
expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);
이렇게 하면 규칙이 단순해집니다:
  • 코어가 기능 계약 + 오케스트레이션을 소유
  • 벤더 Plugin이 벤더 구현을 소유
  • 기능/채널 Plugin이 런타임 도우미를 사용
  • 계약 테스트가 소유권을 명시적으로 유지

관련 문서