import type { AgentToolResult } from "@earendil-works/pi-agent-core"; import type { ChannelAgentTool } from "../../channels/plugins/types.core.js"; import type { OpenClawConfig } from "../../config/types.openclaw.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 { createPluginModuleLoaderCache, type PluginModuleLoaderCache, } from "../plugin-module-loader-cache.js"; import type { PluginOrigin } from "../plugin-origin.types.js"; import { loadPluginBoundaryModule, resolvePluginRuntimeRecordByEntryBaseNames, resolvePluginRuntimeModulePath, } from "./runtime-plugin-boundary.js"; type WebChannelPluginRecord = { origin?: PluginOrigin; 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; startWebLoginWithQr: (...args: unknown[]) => Promise; waitForWaConnection: (sock: unknown) => Promise; waitForWebLogin: (...args: unknown[]) => Promise; extractMediaPlaceholder: (...args: unknown[]) => unknown; extractText: (...args: unknown[]) => unknown; }; type WebChannelRuntimeModuleKind = "heavy" | "light"; type CachedWebChannelRuntimeModule = { modulePath: string; module: WebChannelHeavyRuntimeModule | WebChannelLightRuntimeModule; }; const webChannelRuntimeModuleCache = new Map< WebChannelRuntimeModuleKind, CachedWebChannelRuntimeModule >(); const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache(); 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 record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api"); return loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }); } function getCachedWebChannelRuntimeModule( kind: WebChannelRuntimeModuleKind, modulePath: string, load: () => T, ): T { const cached = webChannelRuntimeModuleCache.get(kind); if (cached?.modulePath === modulePath) { return cached.module as T; } const loaded = load(); webChannelRuntimeModuleCache.set(kind, { modulePath, module: loaded }); return loaded; } function loadWebChannelLightModule(): WebChannelLightRuntimeModule { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "light-runtime-api"); return getCachedWebChannelRuntimeModule("light", modulePath, () => loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }), ); } async function loadWebChannelHeavyModule(): Promise { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api"); return getCachedWebChannelRuntimeModule("heavy", modulePath, () => loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }), ); } 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 '${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 '${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 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); }