mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-16 15:19:33 +00:00
* fix: use .js extension for ESM imports of RoutePeerKind
The imports incorrectly used .ts extension which doesn't resolve
with moduleResolution: NodeNext. Changed to .js and added 'type'
import modifier.
* fix tsconfig
* refactor: unify peer kind to ChatType, rename dm to direct
- Replace RoutePeerKind with ChatType throughout codebase
- Change 'dm' literal values to 'direct' in routing/session keys
- Keep backward compat: normalizeChatType accepts 'dm' -> 'direct'
- Add ChatType export to plugin-sdk, deprecate RoutePeerKind
- Update session key parsing to accept both 'dm' and 'direct' markers
- Update all channel monitors and extensions to use ChatType
BREAKING CHANGE: Session keys now use 'direct' instead of 'dm'.
Existing 'dm' keys still work via backward compat layer.
* fix tests
* test: update session key expectations for dmdirect migration
- Fix test expectations to expect :direct: in generated output
- Add explicit backward compat test for normalizeChatType('dm')
- Keep input test data with :dm: keys to verify backward compat
* fix: accept legacy 'dm' in session key parsing for backward compat
getDmHistoryLimitFromSessionKey now accepts both :dm: and :direct:
to ensure old session keys continue to work correctly.
* test: add explicit backward compat tests for dmdirect migration
- session-key.test.ts: verify both :dm: and :direct: keys are valid
- getDmHistoryLimitFromSessionKey: verify both formats work
* feat: backward compat for resetByType.dm config key
* test: skip unix-path Nix tests on Windows
215 lines
7.2 KiB
Markdown
215 lines
7.2 KiB
Markdown
---
|
|
summary: "Plan: one clean plugin SDK + runtime for all messaging connectors"
|
|
read_when:
|
|
- Defining or refactoring the plugin architecture
|
|
- Migrating channel connectors to the plugin SDK/runtime
|
|
title: "Plugin SDK Refactor"
|
|
---
|
|
|
|
# Plugin SDK + Runtime Refactor Plan
|
|
|
|
Goal: every messaging connector is a plugin (bundled or external) using one stable API.
|
|
No plugin imports from `src/**` directly. All dependencies go through the SDK or runtime.
|
|
|
|
## Why now
|
|
|
|
- Current connectors mix patterns: direct core imports, dist-only bridges, and custom helpers.
|
|
- This makes upgrades brittle and blocks a clean external plugin surface.
|
|
|
|
## Target architecture (two layers)
|
|
|
|
### 1) Plugin SDK (compile-time, stable, publishable)
|
|
|
|
Scope: types, helpers, and config utilities. No runtime state, no side effects.
|
|
|
|
Contents (examples):
|
|
|
|
- Types: `ChannelPlugin`, adapters, `ChannelMeta`, `ChannelCapabilities`, `ChannelDirectoryEntry`.
|
|
- Config helpers: `buildChannelConfigSchema`, `setAccountEnabledInConfigSection`, `deleteAccountFromConfigSection`,
|
|
`applyAccountNameToChannelSection`.
|
|
- Pairing helpers: `PAIRING_APPROVED_MESSAGE`, `formatPairingApproveHint`.
|
|
- Onboarding helpers: `promptChannelAccessConfig`, `addWildcardAllowFrom`, onboarding types.
|
|
- Tool param helpers: `createActionGate`, `readStringParam`, `readNumberParam`, `readReactionParams`, `jsonResult`.
|
|
- Docs link helper: `formatDocsLink`.
|
|
|
|
Delivery:
|
|
|
|
- Publish as `openclaw/plugin-sdk` (or export from core under `openclaw/plugin-sdk`).
|
|
- Semver with explicit stability guarantees.
|
|
|
|
### 2) Plugin Runtime (execution surface, injected)
|
|
|
|
Scope: everything that touches core runtime behavior.
|
|
Accessed via `OpenClawPluginApi.runtime` so plugins never import `src/**`.
|
|
|
|
Proposed surface (minimal but complete):
|
|
|
|
```ts
|
|
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; // 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: 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;
|
|
};
|
|
};
|
|
```
|
|
|
|
Notes:
|
|
|
|
- Runtime is the only way to access core behavior.
|
|
- SDK is intentionally small and stable.
|
|
- Each runtime method maps to an existing core implementation (no duplication).
|
|
|
|
## Migration plan (phased, safe)
|
|
|
|
### Phase 0: scaffolding
|
|
|
|
- Introduce `openclaw/plugin-sdk`.
|
|
- Add `api.runtime` to `OpenClawPluginApi` with the surface above.
|
|
- Maintain existing imports during a transition window (deprecation warnings).
|
|
|
|
### Phase 1: bridge cleanup (low risk)
|
|
|
|
- Replace per-extension `core-bridge.ts` with `api.runtime`.
|
|
- Migrate BlueBubbles, Zalo, Zalo Personal first (already close).
|
|
- Remove duplicated bridge code.
|
|
|
|
### Phase 2: light direct-import plugins
|
|
|
|
- Migrate Matrix to SDK + runtime.
|
|
- Validate onboarding, directory, group mention logic.
|
|
|
|
### Phase 3: heavy direct-import plugins
|
|
|
|
- Migrate MS Teams (largest set of runtime helpers).
|
|
- Ensure reply/typing semantics match current behavior.
|
|
|
|
### Phase 4: iMessage pluginization
|
|
|
|
- Move iMessage into `extensions/imessage`.
|
|
- Replace direct core calls with `api.runtime`.
|
|
- Keep config keys, CLI behavior, and docs intact.
|
|
|
|
### Phase 5: enforcement
|
|
|
|
- Add lint rule / CI check: no `extensions/**` imports from `src/**`.
|
|
- Add plugin SDK/version compatibility checks (runtime + SDK semver).
|
|
|
|
## Compatibility and versioning
|
|
|
|
- SDK: semver, published, documented changes.
|
|
- Runtime: versioned per core release. Add `api.runtime.version`.
|
|
- Plugins declare a required runtime range (e.g., `openclawRuntime: ">=2026.2.0"`).
|
|
|
|
## Testing strategy
|
|
|
|
- Adapter-level unit tests (runtime functions exercised with real core implementation).
|
|
- Golden tests per plugin: ensure no behavior drift (routing, pairing, allowlist, mention gating).
|
|
- A single end-to-end plugin sample used in CI (install + run + smoke).
|
|
|
|
## Open questions
|
|
|
|
- Where to host SDK types: separate package or core export?
|
|
- Runtime type distribution: in SDK (types only) or in core?
|
|
- How to expose docs links for bundled vs external plugins?
|
|
- Do we allow limited direct core imports for in-repo plugins during transition?
|
|
|
|
## Success criteria
|
|
|
|
- All channel connectors are plugins using SDK + runtime.
|
|
- No `extensions/**` imports from `src/**`.
|
|
- New connector templates depend only on SDK + runtime.
|
|
- External plugins can be developed and updated without core source access.
|
|
|
|
Related docs: [Plugins](/tools/plugin), [Channels](/channels/index), [Configuration](/gateway/configuration).
|