refactor: add plugin sdk runtime scaffolding
This commit is contained in:
187
docs/refactor/plugin-sdk.md
Normal file
187
docs/refactor/plugin-sdk.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
---
|
||||||
|
# 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 `@clawdbot/plugin-sdk` (or export from core under `clawdbot/plugin-sdk`).
|
||||||
|
- Semver with explicit stability guarantees.
|
||||||
|
|
||||||
|
### 2) Plugin Runtime (execution surface, injected)
|
||||||
|
Scope: everything that touches core runtime behavior.
|
||||||
|
Accessed via `ClawdbotPluginApi.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: ClawdbotConfig, channel: string, accountId?: string): number;
|
||||||
|
hasControlCommand(text: string, cfg: ClawdbotConfig): 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: "dm" | "group" | "channel"; 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: ClawdbotConfig, agentId?: string): RegExp[];
|
||||||
|
matchesMentionPatterns(text: string, regexes: RegExp[]): boolean;
|
||||||
|
};
|
||||||
|
groups: {
|
||||||
|
resolveGroupPolicy(cfg: ClawdbotConfig, channel: string, accountId: string, groupId: string): {
|
||||||
|
allowlistEnabled: boolean;
|
||||||
|
allowed: boolean;
|
||||||
|
groupConfig?: unknown;
|
||||||
|
defaultConfig?: unknown;
|
||||||
|
};
|
||||||
|
resolveRequireMention(
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
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: ClawdbotConfig, 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: ClawdbotConfig): 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 `@clawdbot/plugin-sdk`.
|
||||||
|
- Add `api.runtime` to `ClawdbotPluginApi` 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., `clawdbotRuntime: ">=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](/plugin), [Channels](/channels/index), [Configuration](/gateway/configuration).
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"dist/media-understanding/**",
|
"dist/media-understanding/**",
|
||||||
"dist/process/**",
|
"dist/process/**",
|
||||||
"dist/plugins/**",
|
"dist/plugins/**",
|
||||||
|
"dist/plugin-sdk/**",
|
||||||
"dist/security/**",
|
"dist/security/**",
|
||||||
"dist/sessions/**",
|
"dist/sessions/**",
|
||||||
"dist/providers/**",
|
"dist/providers/**",
|
||||||
|
|||||||
78
src/plugin-sdk/index.ts
Normal file
78
src/plugin-sdk/index.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export { CHANNEL_MESSAGE_ACTION_NAMES } from "../channels/plugins/message-action-names.js";
|
||||||
|
export type {
|
||||||
|
ChannelAccountSnapshot,
|
||||||
|
ChannelAccountState,
|
||||||
|
ChannelAgentTool,
|
||||||
|
ChannelAgentToolFactory,
|
||||||
|
ChannelAuthAdapter,
|
||||||
|
ChannelCapabilities,
|
||||||
|
ChannelCommandAdapter,
|
||||||
|
ChannelConfigAdapter,
|
||||||
|
ChannelDirectoryAdapter,
|
||||||
|
ChannelDirectoryEntry,
|
||||||
|
ChannelDirectoryEntryKind,
|
||||||
|
ChannelElevatedAdapter,
|
||||||
|
ChannelGatewayAdapter,
|
||||||
|
ChannelGatewayContext,
|
||||||
|
ChannelGroupAdapter,
|
||||||
|
ChannelGroupContext,
|
||||||
|
ChannelHeartbeatAdapter,
|
||||||
|
ChannelHeartbeatDeps,
|
||||||
|
ChannelId,
|
||||||
|
ChannelLogSink,
|
||||||
|
ChannelLoginWithQrStartResult,
|
||||||
|
ChannelLoginWithQrWaitResult,
|
||||||
|
ChannelLogoutContext,
|
||||||
|
ChannelLogoutResult,
|
||||||
|
ChannelMentionAdapter,
|
||||||
|
ChannelMessageActionAdapter,
|
||||||
|
ChannelMessageActionContext,
|
||||||
|
ChannelMessageActionName,
|
||||||
|
ChannelMessagingAdapter,
|
||||||
|
ChannelMeta,
|
||||||
|
ChannelOutboundAdapter,
|
||||||
|
ChannelOutboundContext,
|
||||||
|
ChannelOutboundTargetMode,
|
||||||
|
ChannelPairingAdapter,
|
||||||
|
ChannelPollContext,
|
||||||
|
ChannelPollResult,
|
||||||
|
ChannelResolveKind,
|
||||||
|
ChannelResolveResult,
|
||||||
|
ChannelResolverAdapter,
|
||||||
|
ChannelSecurityAdapter,
|
||||||
|
ChannelSecurityContext,
|
||||||
|
ChannelSecurityDmPolicy,
|
||||||
|
ChannelSetupAdapter,
|
||||||
|
ChannelSetupInput,
|
||||||
|
ChannelStatusAdapter,
|
||||||
|
ChannelStatusIssue,
|
||||||
|
ChannelStreamingAdapter,
|
||||||
|
ChannelThreadingAdapter,
|
||||||
|
ChannelThreadingContext,
|
||||||
|
ChannelThreadingToolContext,
|
||||||
|
ChannelToolSend,
|
||||||
|
} from "../channels/plugins/types.js";
|
||||||
|
export type { ChannelConfigSchema, ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||||
|
|
||||||
|
export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||||
|
export {
|
||||||
|
deleteAccountFromConfigSection,
|
||||||
|
setAccountEnabledInConfigSection,
|
||||||
|
} from "../channels/plugins/config-helpers.js";
|
||||||
|
export { applyAccountNameToChannelSection } from "../channels/plugins/setup-helpers.js";
|
||||||
|
export { formatPairingApproveHint } from "../channels/plugins/helpers.js";
|
||||||
|
export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js";
|
||||||
|
|
||||||
|
export type { ChannelOnboardingAdapter } from "../channels/plugins/onboarding-types.js";
|
||||||
|
export { addWildcardAllowFrom } from "../channels/plugins/onboarding/helpers.js";
|
||||||
|
export { promptChannelAccessConfig } from "../channels/plugins/onboarding/channel-access.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
createActionGate,
|
||||||
|
jsonResult,
|
||||||
|
readNumberParam,
|
||||||
|
readReactionParams,
|
||||||
|
readStringParam,
|
||||||
|
} from "../agents/tools/common.js";
|
||||||
|
|
||||||
|
export { formatDocsLink } from "../terminal/links.js";
|
||||||
@@ -6,6 +6,7 @@ import { createSubsystemLogger } from "../logging.js";
|
|||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
import { discoverClawdbotPlugins } from "./discovery.js";
|
import { discoverClawdbotPlugins } from "./discovery.js";
|
||||||
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
||||||
|
import { createPluginRuntime } from "./runtime/index.js";
|
||||||
import { setActivePluginRegistry } from "./runtime.js";
|
import { setActivePluginRegistry } from "./runtime.js";
|
||||||
import type {
|
import type {
|
||||||
ClawdbotPluginConfigSchema,
|
ClawdbotPluginConfigSchema,
|
||||||
@@ -275,8 +276,10 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runtime = createPluginRuntime();
|
||||||
const { registry, createApi } = createPluginRegistry({
|
const { registry, createApi } = createPluginRegistry({
|
||||||
logger,
|
logger,
|
||||||
|
runtime,
|
||||||
coreGatewayHandlers: options.coreGatewayHandlers as Record<string, GatewayRequestHandler>,
|
coreGatewayHandlers: options.coreGatewayHandlers as Record<string, GatewayRequestHandler>,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type {
|
|||||||
PluginOrigin,
|
PluginOrigin,
|
||||||
PluginKind,
|
PluginKind,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
import type { PluginRuntime } from "./runtime/types.js";
|
||||||
|
|
||||||
export type PluginToolRegistration = {
|
export type PluginToolRegistration = {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
@@ -100,6 +101,7 @@ export type PluginRegistry = {
|
|||||||
export type PluginRegistryParams = {
|
export type PluginRegistryParams = {
|
||||||
logger: PluginLogger;
|
logger: PluginLogger;
|
||||||
coreGatewayHandlers?: GatewayRequestHandlers;
|
coreGatewayHandlers?: GatewayRequestHandlers;
|
||||||
|
runtime: PluginRuntime;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||||
@@ -279,6 +281,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||||||
source: record.source,
|
source: record.source,
|
||||||
config: params.config,
|
config: params.config,
|
||||||
pluginConfig: params.pluginConfig,
|
pluginConfig: params.pluginConfig,
|
||||||
|
runtime: registryParams.runtime,
|
||||||
logger: normalizeLogger(registryParams.logger),
|
logger: normalizeLogger(registryParams.logger),
|
||||||
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
registerTool: (tool, opts) => registerTool(record, tool, opts),
|
||||||
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
|
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
|
||||||
|
|||||||
96
src/plugins/runtime/index.ts
Normal file
96
src/plugins/runtime/index.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { createRequire } from "node:module";
|
||||||
|
|
||||||
|
import { chunkMarkdownText, resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||||
|
import { hasControlCommand } from "../../auto-reply/command-detection.js";
|
||||||
|
import { createInboundDebouncer, resolveInboundDebounceMs } from "../../auto-reply/inbound-debounce.js";
|
||||||
|
import { buildMentionRegexes, matchesMentionPatterns } from "../../auto-reply/reply/mentions.js";
|
||||||
|
import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
||||||
|
import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||||
|
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
||||||
|
import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "../../config/group-policy.js";
|
||||||
|
import { resolveStateDir } from "../../config/paths.js";
|
||||||
|
import { shouldLogVerbose } from "../../globals.js";
|
||||||
|
import { getChildLogger } from "../../logging.js";
|
||||||
|
import { fetchRemoteMedia } from "../../media/fetch.js";
|
||||||
|
import { saveMediaBuffer } from "../../media/store.js";
|
||||||
|
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||||
|
import { readChannelAllowFromStore, upsertChannelPairingRequest } from "../../pairing/pairing-store.js";
|
||||||
|
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||||
|
|
||||||
|
import type { PluginRuntime } from "./types.js";
|
||||||
|
|
||||||
|
let cachedVersion: string | null = null;
|
||||||
|
|
||||||
|
function resolveVersion(): string {
|
||||||
|
if (cachedVersion) return cachedVersion;
|
||||||
|
try {
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const pkg = require("../../../package.json") as { version?: string };
|
||||||
|
cachedVersion = pkg.version ?? "unknown";
|
||||||
|
return cachedVersion;
|
||||||
|
} catch {
|
||||||
|
cachedVersion = "unknown";
|
||||||
|
return cachedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPluginRuntime(): PluginRuntime {
|
||||||
|
return {
|
||||||
|
version: resolveVersion(),
|
||||||
|
channel: {
|
||||||
|
text: {
|
||||||
|
chunkMarkdownText,
|
||||||
|
resolveTextChunkLimit,
|
||||||
|
hasControlCommand,
|
||||||
|
},
|
||||||
|
reply: {
|
||||||
|
dispatchReplyWithBufferedBlockDispatcher,
|
||||||
|
createReplyDispatcherWithTyping,
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
resolveAgentRoute,
|
||||||
|
},
|
||||||
|
pairing: {
|
||||||
|
buildPairingReply,
|
||||||
|
readAllowFromStore: readChannelAllowFromStore,
|
||||||
|
upsertPairingRequest: upsertChannelPairingRequest,
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
fetchRemoteMedia,
|
||||||
|
saveMediaBuffer,
|
||||||
|
},
|
||||||
|
mentions: {
|
||||||
|
buildMentionRegexes,
|
||||||
|
matchesMentionPatterns,
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
resolveGroupPolicy: resolveChannelGroupPolicy,
|
||||||
|
resolveRequireMention: resolveChannelGroupRequireMention,
|
||||||
|
},
|
||||||
|
debounce: {
|
||||||
|
createInboundDebouncer,
|
||||||
|
resolveInboundDebounceMs,
|
||||||
|
},
|
||||||
|
commands: {
|
||||||
|
resolveCommandAuthorizedFromAuthorizers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logging: {
|
||||||
|
shouldLogVerbose,
|
||||||
|
getChildLogger: (bindings, opts) => {
|
||||||
|
const logger = getChildLogger(bindings, opts);
|
||||||
|
return {
|
||||||
|
debug: (message) => logger.debug?.(message),
|
||||||
|
info: (message) => logger.info(message),
|
||||||
|
warn: (message) => logger.warn(message),
|
||||||
|
error: (message) => logger.error(message),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
resolveStateDir,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { PluginRuntime } from "./types.js";
|
||||||
103
src/plugins/runtime/types.ts
Normal file
103
src/plugins/runtime/types.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
|
|
||||||
|
export type RuntimeLogger = {
|
||||||
|
debug?: (message: string) => void;
|
||||||
|
info: (message: string) => void;
|
||||||
|
warn: (message: string) => void;
|
||||||
|
error: (message: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PluginRuntime = {
|
||||||
|
version: string;
|
||||||
|
channel: {
|
||||||
|
text: {
|
||||||
|
chunkMarkdownText: (text: string, limit: number) => string[];
|
||||||
|
resolveTextChunkLimit: (cfg: ClawdbotConfig, channel: string, accountId?: string) => number;
|
||||||
|
hasControlCommand: (text: string, cfg: ClawdbotConfig) => 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: (...args: unknown[]) => unknown;
|
||||||
|
};
|
||||||
|
routing: {
|
||||||
|
resolveAgentRoute: (params: {
|
||||||
|
cfg: unknown;
|
||||||
|
channel: string;
|
||||||
|
accountId: string;
|
||||||
|
peer: { kind: "dm" | "group" | "channel"; 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: ClawdbotConfig, agentId?: string) => RegExp[];
|
||||||
|
matchesMentionPatterns: (text: string, regexes: RegExp[]) => boolean;
|
||||||
|
};
|
||||||
|
groups: {
|
||||||
|
resolveGroupPolicy: (
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
channel: string,
|
||||||
|
accountId: string,
|
||||||
|
groupId: string,
|
||||||
|
) => {
|
||||||
|
allowlistEnabled: boolean;
|
||||||
|
allowed: boolean;
|
||||||
|
groupConfig?: unknown;
|
||||||
|
defaultConfig?: unknown;
|
||||||
|
};
|
||||||
|
resolveRequireMention: (
|
||||||
|
cfg: ClawdbotConfig,
|
||||||
|
channel: string,
|
||||||
|
accountId: string,
|
||||||
|
groupId: string,
|
||||||
|
override?: boolean,
|
||||||
|
) => boolean;
|
||||||
|
};
|
||||||
|
debounce: {
|
||||||
|
createInboundDebouncer: <T>(opts: {
|
||||||
|
debounceMs: number;
|
||||||
|
buildKey: (value: T) => string | null;
|
||||||
|
shouldDebounce: (value: T) => boolean;
|
||||||
|
onFlush: (entries: T[]) => Promise<void>;
|
||||||
|
onError?: (err: unknown) => void;
|
||||||
|
}) => { push: (value: T) => void; flush: () => Promise<void> };
|
||||||
|
resolveInboundDebounceMs: (cfg: ClawdbotConfig, channel: string) => number;
|
||||||
|
};
|
||||||
|
commands: {
|
||||||
|
resolveCommandAuthorizedFromAuthorizers: (params: {
|
||||||
|
useAccessGroups: boolean;
|
||||||
|
authorizers: Array<{ configured: boolean; allowed: boolean }>;
|
||||||
|
}) => boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
logging: {
|
||||||
|
shouldLogVerbose: () => boolean;
|
||||||
|
getChildLogger: (bindings?: Record<string, unknown>, opts?: { level?: string }) => RuntimeLogger;
|
||||||
|
};
|
||||||
|
state: {
|
||||||
|
resolveStateDir: (cfg: ClawdbotConfig) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -11,6 +11,9 @@ import type { RuntimeEnv } from "../runtime.js";
|
|||||||
import type { WizardPrompter } from "../wizard/prompts.js";
|
import type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
import type { createVpsAwareOAuthHandlers } from "../commands/oauth-flow.js";
|
import type { createVpsAwareOAuthHandlers } from "../commands/oauth-flow.js";
|
||||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||||
|
import type { PluginRuntime } from "./runtime/types.js";
|
||||||
|
|
||||||
|
export type { PluginRuntime } from "./runtime/types.js";
|
||||||
|
|
||||||
export type PluginLogger = {
|
export type PluginLogger = {
|
||||||
debug?: (message: string) => void;
|
debug?: (message: string) => void;
|
||||||
@@ -164,6 +167,7 @@ export type ClawdbotPluginApi = {
|
|||||||
source: string;
|
source: string;
|
||||||
config: ClawdbotConfig;
|
config: ClawdbotConfig;
|
||||||
pluginConfig?: Record<string, unknown>;
|
pluginConfig?: Record<string, unknown>;
|
||||||
|
runtime: PluginRuntime;
|
||||||
logger: PluginLogger;
|
logger: PluginLogger;
|
||||||
registerTool: (
|
registerTool: (
|
||||||
tool: AnyAgentTool | ClawdbotPluginToolFactory,
|
tool: AnyAgentTool | ClawdbotPluginToolFactory,
|
||||||
|
|||||||
Reference in New Issue
Block a user