import type { AgentToolResult } from "@mariozechner/pi-agent-core"; import { createJiti } from "jiti"; import type { ChannelAgentTool } from "../../channels/plugins/types.core.js"; import type { OpenClawConfig } from "../../config/config.js"; import { getDefaultLocalRoots as getDefaultLocalRootsImpl, loadWebMedia as loadWebMediaImpl, loadWebMediaRaw as loadWebMediaRawImpl, optimizeImageToJpeg as optimizeImageToJpegImpl, } from "../../media/web-media.js"; import type { PollInput } from "../../polls.js"; import { loadPluginBoundaryModuleWithJiti, resolvePluginRuntimeRecordByEntryBaseNames, resolvePluginRuntimeModulePath, } from "./runtime-plugin-boundary.js"; type WebChannelPluginRecord = { origin?: string; rootDir?: string; source: string; }; type WebChannelLightRuntimeModule = { getActiveWebListener: (accountId?: string | null) => unknown; getWebAuthAgeMs: (authDir?: string) => number | null; logWebSelfId: (authDir?: string, runtime?: unknown, includeChannelPrefix?: boolean) => void; logoutWeb: (params: { authDir?: string; isLegacyAuthDir?: boolean; runtime?: unknown; }) => Promise; readWebSelfId: (authDir?: string) => { e164: string | null; jid: string | null; lid: string | null; }; webAuthExists: (authDir?: string) => Promise; createWhatsAppLoginTool: () => ChannelAgentTool; formatError: (error: unknown) => string; getStatusCode: (error: unknown) => number | undefined; pickWebChannel: (pref: string, authDir?: string) => Promise; WA_WEB_AUTH_DIR: string; }; type WebChannelHeavyRuntimeModule = { loginWeb: ( verbose: boolean, waitForConnection?: (sock: unknown) => Promise, runtime?: unknown, accountId?: string, ) => Promise; sendMessageWhatsApp: ( to: string, body: string, options: { verbose: boolean; cfg?: OpenClawConfig; mediaUrl?: string; mediaAccess?: { localRoots?: readonly string[]; readFile?: (filePath: string) => Promise; }; mediaLocalRoots?: readonly string[]; mediaReadFile?: (filePath: string) => Promise; gifPlayback?: boolean; accountId?: string; }, ) => Promise<{ messageId: string; toJid: string }>; sendPollWhatsApp: ( to: string, poll: PollInput, options: { verbose: boolean; accountId?: string; cfg?: OpenClawConfig }, ) => Promise<{ messageId: string; toJid: string }>; sendReactionWhatsApp: ( chatJid: string, messageId: string, emoji: string, options: { verbose: boolean; fromMe?: boolean; participant?: string; accountId?: string; }, ) => Promise; createWaSocket: ( printQr: boolean, verbose: boolean, opts?: { authDir?: string; onQr?: (qr: string) => void }, ) => Promise; handleWhatsAppAction: ( params: Record, cfg: OpenClawConfig, ) => Promise>; monitorWebChannel: (...args: unknown[]) => Promise; monitorWebInbox: (...args: unknown[]) => Promise; runWebHeartbeatOnce: (...args: unknown[]) => Promise; startWebLoginWithQr: (...args: unknown[]) => Promise; waitForWaConnection: (sock: unknown) => Promise; waitForWebLogin: (...args: unknown[]) => Promise; extractMediaPlaceholder: (...args: unknown[]) => unknown; extractText: (...args: unknown[]) => unknown; resolveHeartbeatRecipients: (...args: unknown[]) => unknown; }; let cachedHeavyModulePath: string | null = null; let cachedHeavyModule: WebChannelHeavyRuntimeModule | null = null; let cachedLightModulePath: string | null = null; let cachedLightModule: WebChannelLightRuntimeModule | null = null; const jitiLoaders = new Map>(); function resolveWebChannelPluginRecord(): WebChannelPluginRecord { return resolvePluginRuntimeRecordByEntryBaseNames(["light-runtime-api", "runtime-api"], () => { throw new Error( "web channel plugin runtime is unavailable: missing plugin that provides light-runtime-api and runtime-api", ); }) as WebChannelPluginRecord; } function resolveWebChannelRuntimeModulePath( record: WebChannelPluginRecord, entryBaseName: "light-runtime-api" | "runtime-api", ): string { const modulePath = resolvePluginRuntimeModulePath(record, entryBaseName, () => { throw new Error(`web channel plugin runtime is unavailable: missing ${entryBaseName}`); }); if (!modulePath) { throw new Error(`web channel plugin runtime is unavailable: missing ${entryBaseName}`); } return modulePath; } function loadCurrentHeavyModuleSync(): WebChannelHeavyRuntimeModule { const modulePath = resolveWebChannelRuntimeModulePath( resolveWebChannelPluginRecord(), "runtime-api", ); return loadPluginBoundaryModuleWithJiti(modulePath, jitiLoaders); } function loadWebChannelLightModule(): WebChannelLightRuntimeModule { const modulePath = resolveWebChannelRuntimeModulePath( resolveWebChannelPluginRecord(), "light-runtime-api", ); if (cachedLightModule && cachedLightModulePath === modulePath) { return cachedLightModule; } const loaded = loadPluginBoundaryModuleWithJiti( modulePath, jitiLoaders, ); cachedLightModulePath = modulePath; cachedLightModule = loaded; return loaded; } async function loadWebChannelHeavyModule(): Promise { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api"); if (cachedHeavyModule && cachedHeavyModulePath === modulePath) { return cachedHeavyModule; } const loaded = loadPluginBoundaryModuleWithJiti( modulePath, jitiLoaders, ); cachedHeavyModulePath = modulePath; cachedHeavyModule = loaded; return loaded; } function getLightExport( exportName: K, ): NonNullable { const loaded = loadWebChannelLightModule(); const value = loaded[exportName]; if (value == null) { throw new Error(`web channel plugin runtime is missing export '${String(exportName)}'`); } return value as NonNullable; } async function getHeavyExport( exportName: K, ): Promise> { const loaded = await loadWebChannelHeavyModule(); const value = loaded[exportName]; if (value == null) { throw new Error(`web channel plugin runtime is missing export '${String(exportName)}'`); } return value as NonNullable; } export function getActiveWebListener( ...args: Parameters ): ReturnType { return getLightExport("getActiveWebListener")(...args); } export function getWebAuthAgeMs( ...args: Parameters ): ReturnType { return getLightExport("getWebAuthAgeMs")(...args); } export function logWebSelfId( ...args: Parameters ): ReturnType { return getLightExport("logWebSelfId")(...args); } export function loginWeb( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.loginWeb(...args)); } export function logoutWeb( ...args: Parameters ): ReturnType { return getLightExport("logoutWeb")(...args); } export function readWebSelfId( ...args: Parameters ): ReturnType { return getLightExport("readWebSelfId")(...args); } export function webAuthExists( ...args: Parameters ): ReturnType { return getLightExport("webAuthExists")(...args); } export function sendWebChannelMessage( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.sendMessageWhatsApp(...args)); } export function sendWebChannelPoll( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.sendPollWhatsApp(...args)); } export function sendWebChannelReaction( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.sendReactionWhatsApp(...args)); } export function createRuntimeWebChannelLoginTool( ...args: Parameters ): ReturnType { return getLightExport("createWhatsAppLoginTool")(...args); } export function createWebChannelSocket( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.createWaSocket(...args)); } export function formatError( ...args: Parameters ): ReturnType { return getLightExport("formatError")(...args); } export function getStatusCode( ...args: Parameters ): ReturnType { return getLightExport("getStatusCode")(...args); } export function pickWebChannel( ...args: Parameters ): ReturnType { return getLightExport("pickWebChannel")(...args); } export function resolveWebChannelAuthDir(): WebChannelLightRuntimeModule["WA_WEB_AUTH_DIR"] { return getLightExport("WA_WEB_AUTH_DIR"); } export async function handleWebChannelAction( ...args: Parameters ): ReturnType { return (await getHeavyExport("handleWhatsAppAction"))(...args); } export async function loadWebMedia( ...args: Parameters ): ReturnType { return await loadWebMediaImpl(...args); } export async function loadWebMediaRaw( ...args: Parameters ): ReturnType { return await loadWebMediaRawImpl(...args); } export function monitorWebChannel( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.monitorWebChannel(...args)); } export async function monitorWebInbox( ...args: Parameters ): ReturnType { return (await getHeavyExport("monitorWebInbox"))(...args); } export async function optimizeImageToJpeg( ...args: Parameters ): ReturnType { return await optimizeImageToJpegImpl(...args); } export async function runWebHeartbeatOnce( ...args: Parameters ): ReturnType { return (await getHeavyExport("runWebHeartbeatOnce"))(...args); } export async function startWebLoginWithQr( ...args: Parameters ): ReturnType { return (await getHeavyExport("startWebLoginWithQr"))(...args); } export async function waitForWebChannelConnection( ...args: Parameters ): ReturnType { return (await getHeavyExport("waitForWaConnection"))(...args); } export async function waitForWebLogin( ...args: Parameters ): ReturnType { return (await getHeavyExport("waitForWebLogin"))(...args); } export const extractMediaPlaceholder = ( ...args: Parameters ) => loadCurrentHeavyModuleSync().extractMediaPlaceholder(...args); export const extractText = (...args: Parameters) => loadCurrentHeavyModuleSync().extractText(...args); export function getDefaultLocalRoots( ...args: Parameters ): ReturnType { return getDefaultLocalRootsImpl(...args); } export function resolveHeartbeatRecipients( ...args: Parameters ): ReturnType { return loadCurrentHeavyModuleSync().resolveHeartbeatRecipients(...args); }