mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
docs: add plugin sdk namespace plan
This commit is contained in:
455
experiments/plugin-sdk-namespaces-plan.md
Normal file
455
experiments/plugin-sdk-namespaces-plan.md
Normal file
@@ -0,0 +1,455 @@
|
||||
# Plugin SDK Namespaces Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Introduce public namespaces to the OpenClaw Plugin SDK so the surface feels
|
||||
closer to the VS Code extension API, while keeping the implementation tight,
|
||||
isolated, and resistant to circular imports.
|
||||
|
||||
This plan is about the public SDK shape. It is not a proposal to merge
|
||||
everything into one giant barrel.
|
||||
|
||||
## Why This Is Worth Doing
|
||||
|
||||
Today the Plugin SDK has three visible problems:
|
||||
|
||||
- The public package export surface is large and mostly flat.
|
||||
- `src/plugin-sdk/core.ts` and `src/plugin-sdk/index.ts` carry too many
|
||||
unrelated meanings.
|
||||
- `OpenClawPluginApi` is still a flat registration API even though
|
||||
`api.runtime` already proves grouped namespaces work well.
|
||||
|
||||
The result is harder docs, harder discovery, and too many helper names that
|
||||
look equally important even when they are not.
|
||||
|
||||
## Current Facts In The Repo
|
||||
|
||||
- Package exports are generated from a flat entrypoint list in
|
||||
`src/plugin-sdk/entrypoints.ts` and `scripts/lib/plugin-sdk-entrypoints.json`.
|
||||
- The root `openclaw/plugin-sdk` entry is intentionally tiny in
|
||||
`src/plugin-sdk/index.ts`.
|
||||
- `api.runtime` is already a successful namespace model. It groups behavior as
|
||||
`agent`, `subagent`, `media`, `imageGeneration`, `webSearch`, `tools`,
|
||||
`channel`, `events`, `logging`, `state`, `tts`, `mediaUnderstanding`, and
|
||||
`modelAuth` in `src/plugins/runtime/index.ts`.
|
||||
- The main plugin registration API is still flat in `OpenClawPluginApi` in
|
||||
`src/plugins/types.ts`.
|
||||
- The concrete API object is assembled in `src/plugins/registry.ts`, and a
|
||||
second partial copy exists in `src/plugins/captured-registration.ts`.
|
||||
|
||||
Those facts suggest a path that is low-risk:
|
||||
|
||||
- keep leaf modules as the source of truth
|
||||
- add namespace facades on top
|
||||
- move docs and examples to the namespace model
|
||||
- keep flat compatibility aliases during migration
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. Do Not Use TypeScript `namespace`
|
||||
|
||||
Use normal ESM modules and package exports.
|
||||
|
||||
The SDK already ships as package export subpaths. The namespace model should be
|
||||
implemented as public facade modules, not TypeScript `namespace` syntax.
|
||||
|
||||
### 2. Keep The Root Tiny
|
||||
|
||||
Do not turn `openclaw/plugin-sdk` into a giant VS Code-style monolith.
|
||||
|
||||
The closest safe equivalent is:
|
||||
|
||||
- a tiny root for shared types and a few universal values
|
||||
- a small number of explicit namespace entrypoints
|
||||
- optional ergonomic aggregation only after the namespace surfaces settle
|
||||
|
||||
### 3. Namespace Facades Must Be Thin
|
||||
|
||||
Namespace entrypoints should contain no real business logic.
|
||||
|
||||
They should only:
|
||||
|
||||
- re-export stable leaves
|
||||
- assemble small namespace objects
|
||||
- provide compatibility aliases
|
||||
|
||||
That keeps cycles and accidental coupling down.
|
||||
|
||||
### 4. Types Stay Direct And Easy To Import
|
||||
|
||||
Like VS Code, namespaces should mostly group behavior. Common types should stay
|
||||
directly importable from the root or the owning domain surface.
|
||||
|
||||
Examples:
|
||||
|
||||
- `ChannelPlugin`
|
||||
- `ProviderPlugin`
|
||||
- `OpenClawPluginApi`
|
||||
- `PluginRuntime`
|
||||
|
||||
### 5. Do Not Namespace Everything At Once
|
||||
|
||||
Only namespace areas that already have a clear public identity.
|
||||
|
||||
Phase 1 should focus on:
|
||||
|
||||
- `plugin`
|
||||
- `channel`
|
||||
- `provider`
|
||||
|
||||
`runtime` already has a good public namespace shape on `api.runtime` and should
|
||||
not be reopened as a giant package-export family in the first pass.
|
||||
|
||||
## Proposed Public Model
|
||||
|
||||
### Namespace Entry Points
|
||||
|
||||
Canonical public entrypoints:
|
||||
|
||||
- `openclaw/plugin-sdk/plugin`
|
||||
- `openclaw/plugin-sdk/channel`
|
||||
- `openclaw/plugin-sdk/provider`
|
||||
- `openclaw/plugin-sdk/runtime`
|
||||
- `openclaw/plugin-sdk/testing`
|
||||
|
||||
What each should mean:
|
||||
|
||||
- `plugin`
|
||||
- plugin entry helpers
|
||||
- shared plugin definition helpers
|
||||
- plugin-facing config schema helpers that are truly universal
|
||||
- `channel`
|
||||
- channel entry helpers
|
||||
- chat-channel builders
|
||||
- stable channel-facing contracts and helpers
|
||||
- `provider`
|
||||
- provider entry helpers
|
||||
- auth, catalog, models, onboard, stream, usage, and provider registration helpers
|
||||
- `runtime`
|
||||
- the existing `api.runtime` story and runtime-related public helpers that are
|
||||
truly stable
|
||||
- `testing`
|
||||
- plugin author testing helpers
|
||||
|
||||
### Nested Leaves
|
||||
|
||||
Under those namespaces, the long-term canonical leaves should become nested:
|
||||
|
||||
- `openclaw/plugin-sdk/channel/setup`
|
||||
- `openclaw/plugin-sdk/channel/pairing`
|
||||
- `openclaw/plugin-sdk/channel/reply-pipeline`
|
||||
- `openclaw/plugin-sdk/channel/contract`
|
||||
- `openclaw/plugin-sdk/channel/targets`
|
||||
- `openclaw/plugin-sdk/channel/actions`
|
||||
- `openclaw/plugin-sdk/channel/inbound`
|
||||
- `openclaw/plugin-sdk/channel/lifecycle`
|
||||
- `openclaw/plugin-sdk/channel/policy`
|
||||
- `openclaw/plugin-sdk/channel/feedback`
|
||||
- `openclaw/plugin-sdk/channel/config-schema`
|
||||
- `openclaw/plugin-sdk/channel/config-helpers`
|
||||
|
||||
- `openclaw/plugin-sdk/provider/auth`
|
||||
- `openclaw/plugin-sdk/provider/catalog`
|
||||
- `openclaw/plugin-sdk/provider/models`
|
||||
- `openclaw/plugin-sdk/provider/onboard`
|
||||
- `openclaw/plugin-sdk/provider/stream`
|
||||
- `openclaw/plugin-sdk/provider/usage`
|
||||
- `openclaw/plugin-sdk/provider/web-search`
|
||||
|
||||
Not every current flat subpath needs a namespaced replacement. The goal is to
|
||||
promote the stable public domains, not to preserve every current export forever.
|
||||
|
||||
## What Happens To `core`
|
||||
|
||||
`core` is overloaded today. In a namespace model it should shrink, not grow.
|
||||
|
||||
Target split:
|
||||
|
||||
- plugin-wide entry helpers move toward `plugin`
|
||||
- channel builders and channel-oriented shared helpers move toward `channel`
|
||||
- `core` remains as a migration surface and compatibility alias for one release
|
||||
cycle
|
||||
|
||||
Rule: no new public API should be added to `core` once namespace entrypoints
|
||||
exist.
|
||||
|
||||
## Proposed `OpenClawPluginApi` Shape
|
||||
|
||||
Keep context fields flat:
|
||||
|
||||
- `id`
|
||||
- `name`
|
||||
- `version`
|
||||
- `description`
|
||||
- `source`
|
||||
- `rootDir`
|
||||
- `registrationMode`
|
||||
- `config`
|
||||
- `pluginConfig`
|
||||
- `runtime`
|
||||
- `logger`
|
||||
- `resolvePath`
|
||||
|
||||
Move registration behavior behind namespaces:
|
||||
|
||||
| Current flat method | Proposed namespace alias |
|
||||
| ------------------------------------ | ----------------------------------------- |
|
||||
| `registerTool` | `api.tool.register` |
|
||||
| `registerHook` | `api.hook.register` |
|
||||
| `on` | `api.hook.on` |
|
||||
| `registerHttpRoute` | `api.http.registerRoute` |
|
||||
| `registerChannel` | `api.channel.register` |
|
||||
| `registerProvider` | `api.provider.register` |
|
||||
| `registerSpeechProvider` | `api.provider.registerSpeech` |
|
||||
| `registerMediaUnderstandingProvider` | `api.provider.registerMediaUnderstanding` |
|
||||
| `registerImageGenerationProvider` | `api.provider.registerImageGeneration` |
|
||||
| `registerWebSearchProvider` | `api.provider.registerWebSearch` |
|
||||
| `registerGatewayMethod` | `api.gateway.registerMethod` |
|
||||
| `registerCli` | `api.cli.register` |
|
||||
| `registerService` | `api.service.register` |
|
||||
| `registerInteractiveHandler` | `api.interactive.register` |
|
||||
| `registerCommand` | `api.command.register` |
|
||||
| `registerContextEngine` | `api.contextEngine.register` |
|
||||
| `registerMemoryPromptSection` | `api.memory.registerPromptSection` |
|
||||
|
||||
Keep the flat methods as direct compatibility aliases during migration.
|
||||
|
||||
That gives plugin authors a clearer public shape without forcing immediate
|
||||
rewrites across the repo.
|
||||
|
||||
## Example Public Usage
|
||||
|
||||
Proposed style:
|
||||
|
||||
```ts
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin";
|
||||
import { channel } from "openclaw/plugin-sdk/channel";
|
||||
import { provider } from "openclaw/plugin-sdk/provider";
|
||||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const chatPlugin: ChannelPlugin = channel.createChatPlugin({
|
||||
id: "demo",
|
||||
/* ... */
|
||||
});
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "demo",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.channel.register(chatPlugin);
|
||||
api.command.register({
|
||||
name: "status",
|
||||
description: "Show plugin status",
|
||||
run: async () => ({ text: "ok" }),
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This is close to the VS Code mental model:
|
||||
|
||||
- grouped behavior
|
||||
- direct types
|
||||
- obvious public areas
|
||||
|
||||
without requiring a single monolithic root import.
|
||||
|
||||
## Optional Ergonomic Surface
|
||||
|
||||
If the project later wants the closest possible VS Code feel, add a dedicated
|
||||
opt-in facade such as `openclaw/plugin-sdk/sdk`.
|
||||
|
||||
That facade can assemble:
|
||||
|
||||
- `plugin`
|
||||
- `channel`
|
||||
- `provider`
|
||||
- `runtime`
|
||||
- `testing`
|
||||
|
||||
It should not be phase 1.
|
||||
|
||||
Why:
|
||||
|
||||
- it is the highest-risk barrel from a cycle and weight perspective
|
||||
- it is easier to add once the namespace surfaces already exist
|
||||
- it preserves the root `openclaw/plugin-sdk` entry as a small type-oriented
|
||||
surface
|
||||
|
||||
## Internal Implementation Rules
|
||||
|
||||
These rules are the important part. Without them, namespaces will rot into
|
||||
barrels and cycles.
|
||||
|
||||
### Rule 1: Namespace Facades Are One-Way
|
||||
|
||||
Namespace entrypoints may import leaf modules.
|
||||
|
||||
Leaf modules may not import their namespace entrypoint.
|
||||
|
||||
Examples:
|
||||
|
||||
- allowed: `src/plugin-sdk/channel.ts` importing `./channel-setup.ts`
|
||||
- forbidden: `src/plugin-sdk/channel-setup.ts` importing `./channel.ts`
|
||||
|
||||
### Rule 2: No Public-Specifier Self-Imports Inside The SDK
|
||||
|
||||
Files inside `src/plugin-sdk/**` should never import from
|
||||
`openclaw/plugin-sdk/...`.
|
||||
|
||||
They should import local source files directly.
|
||||
|
||||
### Rule 3: Shared Code Lives In Shared Leaves
|
||||
|
||||
If `channel` and `provider` need the same implementation detail, move that code
|
||||
to a shared leaf instead of importing one namespace from the other.
|
||||
|
||||
Good shared homes:
|
||||
|
||||
- a narrowed `core` during migration
|
||||
- a dedicated internal shared leaf
|
||||
- existing domain-neutral helpers
|
||||
|
||||
Bad pattern:
|
||||
|
||||
- `provider/*` importing from `channel/index`
|
||||
- `channel/*` importing from `provider/index`
|
||||
|
||||
### Rule 4: Assemble The API Surface Once
|
||||
|
||||
`OpenClawPluginApi` should be built by one canonical factory.
|
||||
|
||||
`src/plugins/registry.ts` and `src/plugins/captured-registration.ts` should stop
|
||||
hand-building separate versions of the API object.
|
||||
|
||||
That factory can expose:
|
||||
|
||||
- flat methods
|
||||
- namespace aliases
|
||||
|
||||
from the same underlying implementation.
|
||||
|
||||
### Rule 5: Namespace Entry Files Stay Small
|
||||
|
||||
Namespace facades should stay close to pure exports. If a namespace file grows
|
||||
real orchestration logic, split that logic back into leaf modules.
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
## Phase 1: Add Namespace Aliases To `OpenClawPluginApi`
|
||||
|
||||
Do this first.
|
||||
|
||||
Why:
|
||||
|
||||
- lowest migration risk
|
||||
- no package export churn required yet
|
||||
- plugin authors immediately get the better public shape
|
||||
- docs can start using namespaces without moving leaf files
|
||||
|
||||
Implementation:
|
||||
|
||||
- extend `OpenClawPluginApi` in `src/plugins/types.ts`
|
||||
- assemble namespace aliases in the canonical API builder
|
||||
- keep all existing flat methods
|
||||
|
||||
## Phase 2: Add Canonical Namespace Entrypoints
|
||||
|
||||
Add:
|
||||
|
||||
- `plugin`
|
||||
- `channel`
|
||||
- `provider`
|
||||
|
||||
as thin public facades over existing flat leaves.
|
||||
|
||||
Implementation detail:
|
||||
|
||||
- the first pass can re-export current flat files
|
||||
- do not move source layout and package exports in the same commit if it can be
|
||||
avoided
|
||||
|
||||
Examples:
|
||||
|
||||
- `src/plugin-sdk/channel/setup.ts` can initially re-export from
|
||||
`../channel-setup.js`
|
||||
- `src/plugin-sdk/provider/auth.ts` can initially re-export from
|
||||
`../provider-auth.js`
|
||||
|
||||
This lets the public namespace story land before the internal source move.
|
||||
|
||||
## Phase 3: Move The Canonical Docs And Templates
|
||||
|
||||
Once aliases exist:
|
||||
|
||||
- docs prefer namespaced entrypoints
|
||||
- templates prefer namespaced imports
|
||||
- new SDK additions land under namespaces first
|
||||
|
||||
At this point the old flat leaves still work but stop being the preferred story.
|
||||
|
||||
## Phase 4: Deprecate Flat Leaves
|
||||
|
||||
After one release cycle of overlap:
|
||||
|
||||
- mark flat leaves as compatibility aliases
|
||||
- keep the highest-value ones for longer if third-party plugin breakage risk is
|
||||
high
|
||||
- stop documenting them as first-class API
|
||||
|
||||
## What Should Not Be Namespaced In Phase 1
|
||||
|
||||
To keep the refactor tight, do not force these into the first milestone:
|
||||
|
||||
- every `*-runtime` helper subpath
|
||||
- extension-branded public subpaths
|
||||
- one-off utilities that do not yet have a stable domain home
|
||||
- the root `openclaw/plugin-sdk` barrel
|
||||
|
||||
If a subpath is only public because history leaked it, namespace work should not
|
||||
promote it.
|
||||
|
||||
## Guardrails And Validation
|
||||
|
||||
The namespace rollout should ship with explicit checks.
|
||||
|
||||
### Existing Checks To Reuse
|
||||
|
||||
- `src/plugin-sdk/subpaths.test.ts`
|
||||
- `src/plugin-sdk/runtime-api-guardrails.test.ts`
|
||||
- `pnpm build` for `[CIRCULAR_REEXPORT]` warnings
|
||||
- `pnpm plugin-sdk:api:check`
|
||||
|
||||
### New Checks To Add
|
||||
|
||||
- namespace facade files may only re-export or compose approved leaves
|
||||
- leaf files under a namespace may not import their parent `index` facade
|
||||
- no new API should be added to `core` once namespace facades exist
|
||||
- compatibility aliases must stay type-equivalent to canonical namespaced leaves
|
||||
|
||||
## Recommended End State
|
||||
|
||||
The elegant end state is:
|
||||
|
||||
- a tiny root
|
||||
- a few first-class namespaces
|
||||
- direct types
|
||||
- a grouped `api` registration surface
|
||||
- stable leaves under each namespace
|
||||
- no reverse imports from leaves back into namespace facades
|
||||
|
||||
That gives OpenClaw a VS Code-like feel where the public SDK has clear domains,
|
||||
but still respects the repo's existing build, lazy-loading, and package-boundary
|
||||
constraints.
|
||||
|
||||
## Short Recommendation
|
||||
|
||||
If this work starts soon, the first implementation step should be:
|
||||
|
||||
1. extract one canonical `OpenClawPluginApi` builder
|
||||
2. add namespace aliases there
|
||||
3. add `plugin`, `channel`, and `provider` facade entrypoints
|
||||
4. move docs and examples to those names
|
||||
5. only then decide which flat leaves deserve long-term compatibility
|
||||
|
||||
That sequence keeps the refactor elegant and minimizes the chance that
|
||||
namespaces become another layer of accidental coupling.
|
||||
Reference in New Issue
Block a user