mirror of https://github.com/openclaw/openclaw.git
feat(plugin-sdk): Add channelRuntime support for external channel plugins
## Overview This PR enables external channel plugins (loaded via Plugin SDK) to access advanced runtime features like AI response dispatching, which were previously only available to built-in channels. ## Changes ### src/gateway/server-channels.ts - Import PluginRuntime type - Add optional channelRuntime parameter to ChannelManagerOptions - Pass channelRuntime to channel startAccount calls via conditional spread - Ensures backward compatibility (field is optional) ### src/gateway/server.impl.ts - Import createPluginRuntime from plugins/runtime - Create and pass channelRuntime to channel manager ### src/channels/plugins/types.adapters.ts - Import PluginRuntime type - Add comprehensive documentation for channelRuntime field - Document available features, use cases, and examples - Improve type safety (use imported PluginRuntime type vs inline import) ## Benefits External channel plugins can now: - Generate AI-powered responses using dispatchReplyWithBufferedBlockDispatcher - Access routing, text processing, and session management utilities - Use command authorization and group policy resolution - Maintain feature parity with built-in channels ## Backward Compatibility - channelRuntime field is optional in ChannelGatewayContext - Conditional spread ensures it's only passed when explicitly provided - Existing channels without channelRuntime support continue to work unchanged - No breaking changes to channel plugin API ## Testing - Email channel plugin successfully uses channelRuntime for AI responses - All existing built-in channels (slack, discord, telegram, etc.) work unchanged - Gateway loads and runs without errors when channelRuntime is provided
This commit is contained in:
parent
666073ee46
commit
469cd5b464
|
|
@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
|||
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
|
||||
import type { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
|
||||
import type { OutboundIdentity } from "../../infra/outbound/identity.js";
|
||||
import type { PluginRuntime } from "../../plugins/runtime/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
|
|
@ -172,6 +173,68 @@ export type ChannelGatewayContext<ResolvedAccount = unknown> = {
|
|||
log?: ChannelLogSink;
|
||||
getStatus: () => ChannelAccountSnapshot;
|
||||
setStatus: (next: ChannelAccountSnapshot) => void;
|
||||
/**
|
||||
* Optional channel runtime helpers for external channel plugins.
|
||||
*
|
||||
* This field provides access to advanced Plugin SDK features that are
|
||||
* available to external plugins but not to built-in channels (which can
|
||||
* directly import internal modules).
|
||||
*
|
||||
* ## Available Features
|
||||
*
|
||||
* - **reply**: AI response dispatching, formatting, and delivery
|
||||
* - **routing**: Agent route resolution and matching
|
||||
* - **text**: Text chunking, markdown processing, and control command detection
|
||||
* - **session**: Session management and metadata tracking
|
||||
* - **media**: Remote media fetching and buffer saving
|
||||
* - **commands**: Command authorization and control command handling
|
||||
* - **groups**: Group policy resolution and mention requirements
|
||||
* - **pairing**: Channel pairing and allow-from management
|
||||
*
|
||||
* ## Use Cases
|
||||
*
|
||||
* External channel plugins (e.g., email, SMS, custom integrations) that need:
|
||||
* - AI-powered response generation and delivery
|
||||
* - Advanced text processing and formatting
|
||||
* - Session tracking and management
|
||||
* - Agent routing and policy resolution
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```typescript
|
||||
* const emailGatewayAdapter: ChannelGatewayAdapter<EmailAccount> = {
|
||||
* startAccount: async (ctx) => {
|
||||
* // Check availability (for backward compatibility)
|
||||
* if (!ctx.channelRuntime) {
|
||||
* ctx.log?.warn?.("channelRuntime not available - skipping AI features");
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* // Use AI dispatch
|
||||
* await ctx.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
* ctx: { ... },
|
||||
* cfg: ctx.cfg,
|
||||
* dispatcherOptions: {
|
||||
* deliver: async (payload) => {
|
||||
* // Send reply via email
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
* },
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* ## Backward Compatibility
|
||||
*
|
||||
* - This field is **optional** - channels that don't need it can ignore it
|
||||
* - Built-in channels (slack, discord, etc.) typically don't use this field
|
||||
* because they can directly import internal modules
|
||||
* - External plugins should check for undefined before using
|
||||
*
|
||||
* @since Plugin SDK 2026.2.19
|
||||
* @see {@link https://docs.openclaw.ai/plugins/developing-plugins | Plugin SDK documentation}
|
||||
*/
|
||||
channelRuntime?: PluginRuntime["channel"];
|
||||
};
|
||||
|
||||
export type ChannelLogoutResult = {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { type BackoffPolicy, computeBackoff, sleepWithAbort } from "../infra/bac
|
|||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { resetDirectoryCache } from "../infra/outbound/target-resolver.js";
|
||||
import type { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
|
|
@ -59,6 +60,36 @@ type ChannelManagerOptions = {
|
|||
loadConfig: () => OpenClawConfig;
|
||||
channelLogs: Record<ChannelId, SubsystemLogger>;
|
||||
channelRuntimeEnvs: Record<ChannelId, RuntimeEnv>;
|
||||
/**
|
||||
* Optional channel runtime helpers for external channel plugins.
|
||||
*
|
||||
* When provided, this value is passed to all channel plugins via the
|
||||
* `channelRuntime` field in `ChannelGatewayContext`, enabling external
|
||||
* plugins to access advanced Plugin SDK features (AI dispatch, routing,
|
||||
* text processing, etc.).
|
||||
*
|
||||
* Built-in channels (slack, discord, telegram) typically don't use this
|
||||
* because they can directly import internal modules from the monorepo.
|
||||
*
|
||||
* This field is optional - omitting it maintains backward compatibility
|
||||
* with existing channels.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { createPluginRuntime } from "../plugins/runtime/index.js";
|
||||
*
|
||||
* const channelManager = createChannelManager({
|
||||
* loadConfig,
|
||||
* channelLogs,
|
||||
* channelRuntimeEnvs,
|
||||
* channelRuntime: createPluginRuntime().channel,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @since Plugin SDK 2026.2.19
|
||||
* @see {@link ChannelGatewayContext.channelRuntime}
|
||||
*/
|
||||
channelRuntime?: PluginRuntime["channel"];
|
||||
};
|
||||
|
||||
type StartChannelOptions = {
|
||||
|
|
@ -78,7 +109,7 @@ export type ChannelManager = {
|
|||
|
||||
// Channel docking: lifecycle hooks (`plugin.gateway`) flow through this manager.
|
||||
export function createChannelManager(opts: ChannelManagerOptions): ChannelManager {
|
||||
const { loadConfig, channelLogs, channelRuntimeEnvs } = opts;
|
||||
const { loadConfig, channelLogs, channelRuntimeEnvs, channelRuntime } = opts;
|
||||
|
||||
const channelStores = new Map<ChannelId, ChannelRuntimeStore>();
|
||||
// Tracks restart attempts per channel:account. Reset on successful start.
|
||||
|
|
@ -199,6 +230,7 @@ export function createChannelManager(opts: ChannelManagerOptions): ChannelManage
|
|||
log,
|
||||
getStatus: () => getRuntime(channelId, id),
|
||||
setStatus: (next) => setRuntime(channelId, id, next),
|
||||
...(channelRuntime ? { channelRuntime } : {}),
|
||||
});
|
||||
const trackedPromise = Promise.resolve(task)
|
||||
.catch((err) => {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import { startDiagnosticHeartbeat, stopDiagnosticHeartbeat } from "../logging/di
|
|||
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
|
||||
import { getGlobalHookRunner, runGlobalGatewayStopSafely } from "../plugins/hook-runner-global.js";
|
||||
import { createEmptyPluginRegistry } from "../plugins/registry.js";
|
||||
import { createPluginRuntime } from "../plugins/runtime/index.js";
|
||||
import type { PluginServicesHandle } from "../plugins/services.js";
|
||||
import { getTotalQueueSize } from "../process/command-queue.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
|
@ -554,6 +555,7 @@ export async function startGatewayServer(
|
|||
loadConfig,
|
||||
channelLogs,
|
||||
channelRuntimeEnvs,
|
||||
channelRuntime: createPluginRuntime().channel,
|
||||
});
|
||||
const { getRuntimeSnapshot, startChannels, startChannel, stopChannel, markChannelLoggedOut } =
|
||||
channelManager;
|
||||
|
|
|
|||
Loading…
Reference in New Issue