/** * Plugin Runtime — channelRuntime acquisition and readiness gate * * Mirrors the pattern established by openclaw-weixin v2.1.1 runtime.ts: * 1. register() stores the PluginRuntime via setClawlinkPluginRuntime() * 2. startAccount() resolves channelRuntime via resolveClawlinkChannelRuntime() * with a three-level fallback: ctx-injected → global → async wait * * Reference: docs/reference/plugins/openclaw-weixin/src/runtime.ts */ import type { PluginRuntime } from 'openclaw/plugin-sdk/core'; import { logger } from '../util/logger.js'; // ── Module-level singleton ── let pluginRuntime: PluginRuntime | null = null; export type PluginChannelRuntime = PluginRuntime['channel']; // ── Public API ── /** * Store the PluginRuntime received during register(). * Called once from index.ts register(). */ export function setClawlinkPluginRuntime(next: PluginRuntime): void { pluginRuntime = next; logger.info('[plugin-runtime] setClawlinkPluginRuntime called, runtime set'); } /** * Synchronous getter — throws if not initialized. * Use only when you are certain register() has completed. */ export function getClawlinkPluginRuntime(): PluginRuntime { if (!pluginRuntime) { throw new Error('ClawLink PluginRuntime not initialized'); } return pluginRuntime; } // ── Async wait ── const WAIT_INTERVAL_MS = 100; const DEFAULT_TIMEOUT_MS = 10_000; /** * Async polling wait for the runtime to become available. * Used during startAccount() if register() hasn't completed yet. */ export async function waitForClawlinkPluginRuntime( timeoutMs = DEFAULT_TIMEOUT_MS, ): Promise { const start = Date.now(); while (!pluginRuntime) { if (Date.now() - start > timeoutMs) { throw new Error('ClawLink PluginRuntime initialization timeout'); } await new Promise((resolve) => setTimeout(resolve, WAIT_INTERVAL_MS)); } return pluginRuntime; } // ── Three-level fallback resolver ── /** * Resolve PluginRuntime['channel'] with three-level fallback: * * 1. Gateway-injected channelRuntime on ctx (new OpenClaw versions) * 2. Module-global from register() * 3. Async wait for register() to complete * * This matches openclaw-weixin's resolveWeixinChannelRuntime() exactly. */ export async function resolveClawlinkChannelRuntime(params: { channelRuntime?: PluginChannelRuntime; waitTimeoutMs?: number; }): Promise { if (params.channelRuntime) { logger.debug('[plugin-runtime] channelRuntime from gateway context'); return params.channelRuntime; } if (pluginRuntime) { logger.debug('[plugin-runtime] channelRuntime from register() global'); return pluginRuntime.channel; } logger.warn( '[plugin-runtime] no channelRuntime on ctx and no global runtime yet; waiting for register()', ); const pr = await waitForClawlinkPluginRuntime(params.waitTimeoutMs ?? DEFAULT_TIMEOUT_MS); return pr.channel; }