From 049bb37c6296d6524e6ce041b7ae2d762608c134 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 16 Mar 2026 22:33:28 -0700 Subject: [PATCH] iMessage: lazy-load channel runtime paths --- extensions/imessage/src/channel.runtime.ts | 83 +++++++++++++++++++++- extensions/imessage/src/channel.ts | 72 ++++--------------- 2 files changed, 94 insertions(+), 61 deletions(-) diff --git a/extensions/imessage/src/channel.runtime.ts b/extensions/imessage/src/channel.runtime.ts index 81229e49ff9..99ce9f617a2 100644 --- a/extensions/imessage/src/channel.runtime.ts +++ b/extensions/imessage/src/channel.runtime.ts @@ -1 +1,82 @@ -export { imessageSetupWizard } from "./setup-surface.js"; +import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-runtime"; +import { + PAIRING_APPROVED_MESSAGE, + resolveChannelMediaMaxBytes, +} from "openclaw/plugin-sdk/imessage"; +import type { ResolvedIMessageAccount } from "./accounts.js"; +import { monitorIMessageProvider } from "./monitor.js"; +import { probeIMessage } from "./probe.js"; +import { getIMessageRuntime } from "./runtime.js"; +import { imessageSetupWizard } from "./setup-surface.js"; + +type IMessageSendFn = ReturnType< + typeof getIMessageRuntime +>["channel"]["imessage"]["sendMessageIMessage"]; + +export async function sendIMessageOutbound(params: { + cfg: Parameters[0]["cfg"]; + to: string; + text: string; + mediaUrl?: string; + mediaLocalRoots?: readonly string[]; + accountId?: string; + deps?: { [channelId: string]: unknown }; + replyToId?: string; +}) { + const send = + resolveOutboundSendDep(params.deps, "imessage") ?? + getIMessageRuntime().channel.imessage.sendMessageIMessage; + const maxBytes = resolveChannelMediaMaxBytes({ + cfg: params.cfg, + resolveChannelLimitMb: ({ cfg, accountId }) => + cfg.channels?.imessage?.accounts?.[accountId]?.mediaMaxMb ?? + cfg.channels?.imessage?.mediaMaxMb, + accountId: params.accountId, + }); + return await send(params.to, params.text, { + config: params.cfg, + ...(params.mediaUrl ? { mediaUrl: params.mediaUrl } : {}), + ...(params.mediaLocalRoots?.length ? { mediaLocalRoots: params.mediaLocalRoots } : {}), + maxBytes, + accountId: params.accountId ?? undefined, + replyToId: params.replyToId ?? undefined, + }); +} + +export async function notifyIMessageApproval(id: string): Promise { + await getIMessageRuntime().channel.imessage.sendMessageIMessage(id, PAIRING_APPROVED_MESSAGE); +} + +export async function probeIMessageAccount(timeoutMs?: number) { + return await probeIMessage(timeoutMs); +} + +export async function startIMessageGatewayAccount( + ctx: Parameters< + NonNullable< + NonNullable< + import("openclaw/plugin-sdk/imessage").ChannelPlugin["gateway"] + >["startAccount"] + > + >[0], +) { + const account = ctx.account; + const cliPath = account.config.cliPath?.trim() || "imsg"; + const dbPath = account.config.dbPath?.trim(); + ctx.setStatus({ + accountId: account.accountId, + cliPath, + dbPath: dbPath ?? null, + }); + ctx.log?.info?.( + `[${account.accountId}] starting provider (${cliPath}${dbPath ? ` db=${dbPath}` : ""})`, + ); + return await monitorIMessageProvider({ + accountId: account.accountId, + config: ctx.cfg, + runtime: ctx.runtime, + abortSignal: ctx.abortSignal, + }); +} + +export { imessageSetupWizard }; diff --git a/extensions/imessage/src/channel.ts b/extensions/imessage/src/channel.ts index bf7e6585d6c..49e8c289fae 100644 --- a/extensions/imessage/src/channel.ts +++ b/extensions/imessage/src/channel.ts @@ -3,15 +3,13 @@ import { buildAccountScopedDmSecurityPolicy, collectAllowlistProviderRestrictSendersWarnings, } from "openclaw/plugin-sdk/channel-config-helpers"; -import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-runtime"; import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/core"; import { collectStatusIssuesFromLastError, DEFAULT_ACCOUNT_ID, + formatTrimmedAllowFromEntries, looksLikeIMessageTargetId, normalizeIMessageMessagingTarget, - PAIRING_APPROVED_MESSAGE, - resolveChannelMediaMaxBytes, resolveIMessageGroupRequireMention, resolveIMessageGroupToolPolicy, type ChannelPlugin, @@ -23,38 +21,11 @@ import { imessageSetupAdapter } from "./setup-core.js"; import { createIMessagePluginBase, imessageSetupWizard } from "./shared.js"; import { normalizeIMessageHandle, parseIMessageTarget } from "./targets.js"; -type IMessageSendFn = ReturnType< - typeof getIMessageRuntime ->["channel"]["imessage"]["sendMessageIMessage"]; +let imessageChannelRuntimePromise: Promise | null = null; -async function sendIMessageOutbound(params: { - cfg: Parameters[0]["cfg"]; - to: string; - text: string; - mediaUrl?: string; - mediaLocalRoots?: readonly string[]; - accountId?: string; - deps?: { [channelId: string]: unknown }; - replyToId?: string; -}) { - const send = - resolveOutboundSendDep(params.deps, "imessage") ?? - getIMessageRuntime().channel.imessage.sendMessageIMessage; - const maxBytes = resolveChannelMediaMaxBytes({ - cfg: params.cfg, - resolveChannelLimitMb: ({ cfg, accountId }) => - cfg.channels?.imessage?.accounts?.[accountId]?.mediaMaxMb ?? - cfg.channels?.imessage?.mediaMaxMb, - accountId: params.accountId, - }); - return await send(params.to, params.text, { - config: params.cfg, - ...(params.mediaUrl ? { mediaUrl: params.mediaUrl } : {}), - ...(params.mediaLocalRoots?.length ? { mediaLocalRoots: params.mediaLocalRoots } : {}), - maxBytes, - accountId: params.accountId ?? undefined, - replyToId: params.replyToId ?? undefined, - }); +async function loadIMessageChannelRuntime() { + imessageChannelRuntimePromise ??= import("./channel.runtime.js"); + return imessageChannelRuntimePromise; } function buildIMessageBaseSessionKey(params: { @@ -141,9 +112,8 @@ export const imessagePlugin: ChannelPlugin = { }), pairing: { idLabel: "imessageSenderId", - notifyApproval: async ({ id }) => { - await getIMessageRuntime().channel.imessage.sendMessageIMessage(id, PAIRING_APPROVED_MESSAGE); - }, + notifyApproval: async ({ id }) => + await (await loadIMessageChannelRuntime()).notifyIMessageApproval(id), }, allowlist: { supportsScope: ({ scope }) => scope === "dm" || scope === "group" || scope === "all", @@ -202,14 +172,13 @@ export const imessagePlugin: ChannelPlugin = { hint: "", }, }, - setup: imessageSetupAdapter, outbound: { deliveryMode: "direct", chunker: (text, limit) => getIMessageRuntime().channel.text.chunkText(text, limit), chunkerMode: "text", textChunkLimit: 4000, sendText: async ({ cfg, to, text, accountId, deps, replyToId }) => { - const result = await sendIMessageOutbound({ + const result = await (await loadIMessageChannelRuntime()).sendIMessageOutbound({ cfg, to, text, @@ -220,7 +189,7 @@ export const imessagePlugin: ChannelPlugin = { return { channel: "imessage", ...result }; }, sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, accountId, deps, replyToId }) => { - const result = await sendIMessageOutbound({ + const result = await (await loadIMessageChannelRuntime()).sendIMessageOutbound({ cfg, to, text, @@ -250,7 +219,7 @@ export const imessagePlugin: ChannelPlugin = { dbPath: snapshot.dbPath ?? null, }), probeAccount: async ({ timeoutMs }) => - getIMessageRuntime().channel.imessage.probeIMessage(timeoutMs), + await (await loadIMessageChannelRuntime()).probeIMessageAccount(timeoutMs), buildAccountSnapshot: ({ account, runtime, probe }) => ({ accountId: account.accountId, name: account.name, @@ -269,24 +238,7 @@ export const imessagePlugin: ChannelPlugin = { resolveAccountState: ({ enabled }) => (enabled ? "enabled" : "disabled"), }, gateway: { - startAccount: async (ctx) => { - const account = ctx.account; - const cliPath = account.config.cliPath?.trim() || "imsg"; - const dbPath = account.config.dbPath?.trim(); - ctx.setStatus({ - accountId: account.accountId, - cliPath, - dbPath: dbPath ?? null, - }); - ctx.log?.info( - `[${account.accountId}] starting provider (${cliPath}${dbPath ? ` db=${dbPath}` : ""})`, - ); - return getIMessageRuntime().channel.imessage.monitorIMessageProvider({ - accountId: account.accountId, - config: ctx.cfg, - runtime: ctx.runtime, - abortSignal: ctx.abortSignal, - }); - }, + startAccount: async (ctx) => + await (await loadIMessageChannelRuntime()).startIMessageGatewayAccount(ctx), }, };