mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:00:54 +00:00
refactor: untangle whatsapp runtime boundary
This commit is contained in:
12
extensions/whatsapp/light-runtime-api.ts
Normal file
12
extensions/whatsapp/light-runtime-api.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export { getActiveWebListener } from "./src/active-listener.js";
|
||||||
|
export {
|
||||||
|
getWebAuthAgeMs,
|
||||||
|
logWebSelfId,
|
||||||
|
logoutWeb,
|
||||||
|
pickWebChannel,
|
||||||
|
readWebSelfId,
|
||||||
|
WA_WEB_AUTH_DIR,
|
||||||
|
webAuthExists,
|
||||||
|
} from "./src/auth-store.js";
|
||||||
|
export { createWhatsAppLoginTool } from "./src/agent-tools-login.js";
|
||||||
|
export { formatError, getStatusCode } from "./src/session-errors.js";
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"description": "OpenClaw WhatsApp channel plugin",
|
"description": "OpenClaw WhatsApp channel plugin",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@whiskeysockets/baileys": "7.0.0-rc.9"
|
||||||
|
},
|
||||||
"openclaw": {
|
"openclaw": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"./index.ts"
|
"./index.ts"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export * from "./src/auth-store.js";
|
|||||||
export * from "./src/auto-reply.js";
|
export * from "./src/auto-reply.js";
|
||||||
export * from "./src/inbound.js";
|
export * from "./src/inbound.js";
|
||||||
export * from "./src/login.js";
|
export * from "./src/login.js";
|
||||||
|
export * from "./src/login-qr.js";
|
||||||
export * from "./src/media.js";
|
export * from "./src/media.js";
|
||||||
export * from "./src/send.js";
|
export * from "./src/send.js";
|
||||||
export * from "./src/session.js";
|
export * from "./src/session.js";
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-runtime";
|
import type { ChannelAgentTool } from "openclaw/plugin-sdk/channel-runtime";
|
||||||
|
import { startWebLoginWithQr, waitForWebLogin } from "openclaw/plugin-sdk/whatsapp-login-qr";
|
||||||
|
|
||||||
export function createWhatsAppLoginTool(): ChannelAgentTool {
|
export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||||
return {
|
return {
|
||||||
@@ -18,7 +19,6 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
|||||||
force: Type.Optional(Type.Boolean()),
|
force: Type.Optional(Type.Boolean()),
|
||||||
}),
|
}),
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const { startWebLoginWithQr, waitForWebLogin } = await import("./login-qr.js");
|
|
||||||
const action = (args as { action?: string })?.action ?? "start";
|
const action = (args as { action?: string })?.action ?? "start";
|
||||||
if (action === "wait") {
|
if (action === "wait") {
|
||||||
const result = await waitForWebLogin({
|
const result = await waitForWebLogin({
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
readWebSelfId as readWebSelfIdImpl,
|
readWebSelfId as readWebSelfIdImpl,
|
||||||
webAuthExists as webAuthExistsImpl,
|
webAuthExists as webAuthExistsImpl,
|
||||||
} from "./auth-store.js";
|
} from "./auth-store.js";
|
||||||
|
import { monitorWebChannel as monitorWebChannelImpl } from "./auto-reply/monitor.js";
|
||||||
import { loginWeb as loginWebImpl } from "./login.js";
|
import { loginWeb as loginWebImpl } from "./login.js";
|
||||||
import { monitorWebChannel as monitorWebChannelImpl } from "./runtime-api.js";
|
|
||||||
import { whatsappSetupWizard as whatsappSetupWizardImpl } from "./setup-surface.js";
|
import { whatsappSetupWizard as whatsappSetupWizardImpl } from "./setup-surface.js";
|
||||||
|
|
||||||
type GetActiveWebListener = typeof import("./active-listener.js").getActiveWebListener;
|
type GetActiveWebListener = typeof import("./active-listener.js").getActiveWebListener;
|
||||||
@@ -20,7 +20,7 @@ type LoginWeb = typeof import("./login.js").loginWeb;
|
|||||||
type StartWebLoginWithQr = typeof import("./login-qr.js").startWebLoginWithQr;
|
type StartWebLoginWithQr = typeof import("./login-qr.js").startWebLoginWithQr;
|
||||||
type WaitForWebLogin = typeof import("./login-qr.js").waitForWebLogin;
|
type WaitForWebLogin = typeof import("./login-qr.js").waitForWebLogin;
|
||||||
type WhatsAppSetupWizard = typeof import("./setup-surface.js").whatsappSetupWizard;
|
type WhatsAppSetupWizard = typeof import("./setup-surface.js").whatsappSetupWizard;
|
||||||
type MonitorWebChannel = typeof import("./runtime-api.js").monitorWebChannel;
|
type MonitorWebChannel = typeof import("./auto-reply/monitor.js").monitorWebChannel;
|
||||||
|
|
||||||
let loginQrPromise: Promise<typeof import("./login-qr.js")> | null = null;
|
let loginQrPromise: Promise<typeof import("./login-qr.js")> | null = null;
|
||||||
|
|
||||||
@@ -75,8 +75,8 @@ export async function waitForWebLogin(
|
|||||||
|
|
||||||
export const whatsappSetupWizard: WhatsAppSetupWizard = { ...whatsappSetupWizardImpl };
|
export const whatsappSetupWizard: WhatsAppSetupWizard = { ...whatsappSetupWizardImpl };
|
||||||
|
|
||||||
export async function monitorWebChannel(
|
export function monitorWebChannel(
|
||||||
...args: Parameters<MonitorWebChannel>
|
...args: Parameters<MonitorWebChannel>
|
||||||
): ReturnType<MonitorWebChannel> {
|
): ReturnType<MonitorWebChannel> {
|
||||||
return await monitorWebChannelImpl(...args);
|
return monitorWebChannelImpl(...args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
|
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||||
// WhatsApp-specific imports from local extension code (moved from src/web/ and src/channels/plugins/)
|
// WhatsApp-specific imports from local extension code (moved from src/web/ and src/channels/plugins/)
|
||||||
import { resolveWhatsAppAccount, type ResolvedWhatsAppAccount } from "./accounts.js";
|
import { resolveWhatsAppAccount, type ResolvedWhatsAppAccount } from "./accounts.js";
|
||||||
|
import type { WebChannelStatus } from "./auto-reply/types.js";
|
||||||
import {
|
import {
|
||||||
listWhatsAppDirectoryGroupsFromConfig,
|
listWhatsAppDirectoryGroupsFromConfig,
|
||||||
listWhatsAppDirectoryPeersFromConfig,
|
listWhatsAppDirectoryPeersFromConfig,
|
||||||
@@ -282,7 +283,8 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
|||||||
ctx.runtime,
|
ctx.runtime,
|
||||||
ctx.abortSignal,
|
ctx.abortSignal,
|
||||||
{
|
{
|
||||||
statusSink: (next) => ctx.setStatus({ accountId: ctx.accountId, ...next }),
|
statusSink: (next: WebChannelStatus) =>
|
||||||
|
ctx.setStatus({ accountId: ctx.accountId, ...next }),
|
||||||
accountId: account.accountId,
|
accountId: account.accountId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ export {
|
|||||||
type DmPolicy,
|
type DmPolicy,
|
||||||
type GroupPolicy,
|
type GroupPolicy,
|
||||||
type WhatsAppAccountConfig,
|
type WhatsAppAccountConfig,
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
} from "openclaw/plugin-sdk/whatsapp-shared";
|
||||||
|
|
||||||
export { monitorWebChannel } from "openclaw/plugin-sdk/whatsapp";
|
export { monitorWebChannel } from "./channel.runtime.js";
|
||||||
|
|||||||
123
extensions/whatsapp/src/session-errors.ts
Normal file
123
extensions/whatsapp/src/session-errors.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
function safeStringify(value: unknown, limit = 800): string {
|
||||||
|
try {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
const raw = JSON.stringify(
|
||||||
|
value,
|
||||||
|
(_key, v) => {
|
||||||
|
if (typeof v === "bigint") {
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
if (typeof v === "function") {
|
||||||
|
const maybeName = (v as { name?: unknown }).name;
|
||||||
|
const name =
|
||||||
|
typeof maybeName === "string" && maybeName.length > 0 ? maybeName : "anonymous";
|
||||||
|
return `[Function ${name}]`;
|
||||||
|
}
|
||||||
|
if (typeof v === "object" && v) {
|
||||||
|
if (seen.has(v)) {
|
||||||
|
return "[Circular]";
|
||||||
|
}
|
||||||
|
seen.add(v);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
if (!raw) {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
return raw.length > limit ? `${raw.slice(0, limit)}…` : raw;
|
||||||
|
} catch {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractBoomDetails(err: unknown): {
|
||||||
|
statusCode?: number;
|
||||||
|
error?: string;
|
||||||
|
message?: string;
|
||||||
|
} | null {
|
||||||
|
if (!err || typeof err !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const output = (err as { output?: unknown })?.output as
|
||||||
|
| { statusCode?: unknown; payload?: unknown }
|
||||||
|
| undefined;
|
||||||
|
if (!output || typeof output !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const payload = (output as { payload?: unknown }).payload as
|
||||||
|
| { error?: unknown; message?: unknown; statusCode?: unknown }
|
||||||
|
| undefined;
|
||||||
|
const statusCode =
|
||||||
|
typeof (output as { statusCode?: unknown }).statusCode === "number"
|
||||||
|
? ((output as { statusCode?: unknown }).statusCode as number)
|
||||||
|
: typeof payload?.statusCode === "number"
|
||||||
|
? payload.statusCode
|
||||||
|
: undefined;
|
||||||
|
const error = typeof payload?.error === "string" ? payload.error : undefined;
|
||||||
|
const message = typeof payload?.message === "string" ? payload.message : undefined;
|
||||||
|
if (!statusCode && !error && !message) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { statusCode, error, message };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStatusCode(err: unknown) {
|
||||||
|
return (
|
||||||
|
(err as { output?: { statusCode?: number } })?.output?.statusCode ??
|
||||||
|
(err as { status?: number })?.status ??
|
||||||
|
(err as { error?: { output?: { statusCode?: number } } })?.error?.output?.statusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatError(err: unknown): string {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
if (typeof err === "string") {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (!err || typeof err !== "object") {
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const boom =
|
||||||
|
extractBoomDetails(err) ??
|
||||||
|
extractBoomDetails((err as { error?: unknown })?.error) ??
|
||||||
|
extractBoomDetails((err as { lastDisconnect?: { error?: unknown } })?.lastDisconnect?.error);
|
||||||
|
|
||||||
|
const status = boom?.statusCode ?? getStatusCode(err);
|
||||||
|
const code = (err as { code?: unknown })?.code;
|
||||||
|
const codeText = typeof code === "string" || typeof code === "number" ? String(code) : undefined;
|
||||||
|
|
||||||
|
const messageCandidates = [
|
||||||
|
boom?.message,
|
||||||
|
typeof (err as { message?: unknown })?.message === "string"
|
||||||
|
? ((err as { message?: unknown }).message as string)
|
||||||
|
: undefined,
|
||||||
|
typeof (err as { error?: { message?: unknown } })?.error?.message === "string"
|
||||||
|
? ((err as { error?: { message?: unknown } }).error?.message as string)
|
||||||
|
: undefined,
|
||||||
|
].filter((value): value is string => Boolean(value && value.trim().length > 0));
|
||||||
|
const message = messageCandidates[0];
|
||||||
|
|
||||||
|
const pieces: string[] = [];
|
||||||
|
if (typeof status === "number") {
|
||||||
|
pieces.push(`status=${status}`);
|
||||||
|
}
|
||||||
|
if (boom?.error) {
|
||||||
|
pieces.push(boom.error);
|
||||||
|
}
|
||||||
|
if (message) {
|
||||||
|
pieces.push(message);
|
||||||
|
}
|
||||||
|
if (codeText) {
|
||||||
|
pieces.push(`code=${codeText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pieces.length > 0) {
|
||||||
|
return pieces.join(" ");
|
||||||
|
}
|
||||||
|
return safeStringify(err);
|
||||||
|
}
|
||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
resolveWebCredsBackupPath,
|
resolveWebCredsBackupPath,
|
||||||
resolveWebCredsPath,
|
resolveWebCredsPath,
|
||||||
} from "./auth-store.js";
|
} from "./auth-store.js";
|
||||||
|
import { formatError, getStatusCode } from "./session-errors.js";
|
||||||
|
export { formatError, getStatusCode } from "./session-errors.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
@@ -190,14 +192,6 @@ export async function waitForWaConnection(sock: ReturnType<typeof makeWASocket>)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStatusCode(err: unknown) {
|
|
||||||
return (
|
|
||||||
(err as { output?: { statusCode?: number } })?.output?.statusCode ??
|
|
||||||
(err as { status?: number })?.status ??
|
|
||||||
(err as { error?: { output?: { statusCode?: number } } })?.error?.output?.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Await pending credential saves — scoped to one authDir, or all if omitted. */
|
/** Await pending credential saves — scoped to one authDir, or all if omitted. */
|
||||||
export function waitForCredsSaveQueue(authDir?: string): Promise<void> {
|
export function waitForCredsSaveQueue(authDir?: string): Promise<void> {
|
||||||
if (authDir) {
|
if (authDir) {
|
||||||
@@ -224,123 +218,6 @@ export async function waitForCredsSaveQueueWithTimeout(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeStringify(value: unknown, limit = 800): string {
|
|
||||||
try {
|
|
||||||
const seen = new WeakSet();
|
|
||||||
const raw = JSON.stringify(
|
|
||||||
value,
|
|
||||||
(_key, v) => {
|
|
||||||
if (typeof v === "bigint") {
|
|
||||||
return v.toString();
|
|
||||||
}
|
|
||||||
if (typeof v === "function") {
|
|
||||||
const maybeName = (v as { name?: unknown }).name;
|
|
||||||
const name =
|
|
||||||
typeof maybeName === "string" && maybeName.length > 0 ? maybeName : "anonymous";
|
|
||||||
return `[Function ${name}]`;
|
|
||||||
}
|
|
||||||
if (typeof v === "object" && v) {
|
|
||||||
if (seen.has(v)) {
|
|
||||||
return "[Circular]";
|
|
||||||
}
|
|
||||||
seen.add(v);
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
if (!raw) {
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
return raw.length > limit ? `${raw.slice(0, limit)}…` : raw;
|
|
||||||
} catch {
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractBoomDetails(err: unknown): {
|
|
||||||
statusCode?: number;
|
|
||||||
error?: string;
|
|
||||||
message?: string;
|
|
||||||
} | null {
|
|
||||||
if (!err || typeof err !== "object") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const output = (err as { output?: unknown })?.output as
|
|
||||||
| { statusCode?: unknown; payload?: unknown }
|
|
||||||
| undefined;
|
|
||||||
if (!output || typeof output !== "object") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const payload = (output as { payload?: unknown }).payload as
|
|
||||||
| { error?: unknown; message?: unknown; statusCode?: unknown }
|
|
||||||
| undefined;
|
|
||||||
const statusCode =
|
|
||||||
typeof (output as { statusCode?: unknown }).statusCode === "number"
|
|
||||||
? ((output as { statusCode?: unknown }).statusCode as number)
|
|
||||||
: typeof payload?.statusCode === "number"
|
|
||||||
? payload.statusCode
|
|
||||||
: undefined;
|
|
||||||
const error = typeof payload?.error === "string" ? payload.error : undefined;
|
|
||||||
const message = typeof payload?.message === "string" ? payload.message : undefined;
|
|
||||||
if (!statusCode && !error && !message) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { statusCode, error, message };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatError(err: unknown): string {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
return err.message;
|
|
||||||
}
|
|
||||||
if (typeof err === "string") {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
if (!err || typeof err !== "object") {
|
|
||||||
return String(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Baileys frequently wraps errors under `error` with a Boom-like shape.
|
|
||||||
const boom =
|
|
||||||
extractBoomDetails(err) ??
|
|
||||||
extractBoomDetails((err as { error?: unknown })?.error) ??
|
|
||||||
extractBoomDetails((err as { lastDisconnect?: { error?: unknown } })?.lastDisconnect?.error);
|
|
||||||
|
|
||||||
const status = boom?.statusCode ?? getStatusCode(err);
|
|
||||||
const code = (err as { code?: unknown })?.code;
|
|
||||||
const codeText = typeof code === "string" || typeof code === "number" ? String(code) : undefined;
|
|
||||||
|
|
||||||
const messageCandidates = [
|
|
||||||
boom?.message,
|
|
||||||
typeof (err as { message?: unknown })?.message === "string"
|
|
||||||
? ((err as { message?: unknown }).message as string)
|
|
||||||
: undefined,
|
|
||||||
typeof (err as { error?: { message?: unknown } })?.error?.message === "string"
|
|
||||||
? ((err as { error?: { message?: unknown } }).error?.message as string)
|
|
||||||
: undefined,
|
|
||||||
].filter((v): v is string => Boolean(v && v.trim().length > 0));
|
|
||||||
const message = messageCandidates[0];
|
|
||||||
|
|
||||||
const pieces: string[] = [];
|
|
||||||
if (typeof status === "number") {
|
|
||||||
pieces.push(`status=${status}`);
|
|
||||||
}
|
|
||||||
if (boom?.error) {
|
|
||||||
pieces.push(boom.error);
|
|
||||||
}
|
|
||||||
if (message) {
|
|
||||||
pieces.push(message);
|
|
||||||
}
|
|
||||||
if (codeText) {
|
|
||||||
pieces.push(`code=${codeText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pieces.length > 0) {
|
|
||||||
return pieces.join(" ");
|
|
||||||
}
|
|
||||||
return safeStringify(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function newConnectionId() {
|
export function newConnectionId() {
|
||||||
return randomUUID();
|
return randomUUID();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,6 +265,10 @@
|
|||||||
"types": "./dist/plugin-sdk/whatsapp.d.ts",
|
"types": "./dist/plugin-sdk/whatsapp.d.ts",
|
||||||
"default": "./dist/plugin-sdk/whatsapp.js"
|
"default": "./dist/plugin-sdk/whatsapp.js"
|
||||||
},
|
},
|
||||||
|
"./plugin-sdk/whatsapp-shared": {
|
||||||
|
"types": "./dist/plugin-sdk/whatsapp-shared.d.ts",
|
||||||
|
"default": "./dist/plugin-sdk/whatsapp-shared.js"
|
||||||
|
},
|
||||||
"./plugin-sdk/whatsapp-action-runtime": {
|
"./plugin-sdk/whatsapp-action-runtime": {
|
||||||
"types": "./dist/plugin-sdk/whatsapp-action-runtime.d.ts",
|
"types": "./dist/plugin-sdk/whatsapp-action-runtime.d.ts",
|
||||||
"default": "./dist/plugin-sdk/whatsapp-action-runtime.js"
|
"default": "./dist/plugin-sdk/whatsapp-action-runtime.js"
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -590,7 +590,11 @@ importers:
|
|||||||
|
|
||||||
extensions/volcengine: {}
|
extensions/volcengine: {}
|
||||||
|
|
||||||
extensions/whatsapp: {}
|
extensions/whatsapp:
|
||||||
|
dependencies:
|
||||||
|
'@whiskeysockets/baileys':
|
||||||
|
specifier: 7.0.0-rc.9
|
||||||
|
version: 7.0.0-rc.9(audio-decode@2.2.3)(jimp@1.6.0)(sharp@0.34.5)
|
||||||
|
|
||||||
extensions/xai: {}
|
extensions/xai: {}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"qwen-portal-auth",
|
"qwen-portal-auth",
|
||||||
"signal",
|
"signal",
|
||||||
"whatsapp",
|
"whatsapp",
|
||||||
|
"whatsapp-shared",
|
||||||
"whatsapp-action-runtime",
|
"whatsapp-action-runtime",
|
||||||
"whatsapp-login-qr",
|
"whatsapp-login-qr",
|
||||||
"whatsapp-core",
|
"whatsapp-core",
|
||||||
|
|||||||
@@ -1,29 +1,51 @@
|
|||||||
// Barrel exports for the web channel pieces. Splitting the original 900+ line
|
// Barrel exports for the web channel pieces. Splitting the original 900+ line
|
||||||
// module keeps responsibilities small and testable.
|
// module keeps responsibilities small and testable.
|
||||||
export {
|
import { resolveWaWebAuthDir } from "./plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
DEFAULT_WEB_MEDIA_BYTES,
|
|
||||||
HEARTBEAT_PROMPT,
|
export { HEARTBEAT_PROMPT } from "./auto-reply/heartbeat.js";
|
||||||
HEARTBEAT_TOKEN,
|
export { HEARTBEAT_TOKEN } from "./auto-reply/tokens.js";
|
||||||
monitorWebChannel,
|
|
||||||
resolveHeartbeatRecipients,
|
|
||||||
runWebHeartbeatOnce,
|
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
|
||||||
export {
|
|
||||||
extractMediaPlaceholder,
|
|
||||||
extractText,
|
|
||||||
monitorWebInbox,
|
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
|
||||||
export { loginWeb } from "openclaw/plugin-sdk/whatsapp";
|
|
||||||
export { loadWebMedia, optimizeImageToJpeg } from "./media/web-media.js";
|
export { loadWebMedia, optimizeImageToJpeg } from "./media/web-media.js";
|
||||||
export { sendMessageWhatsApp } from "openclaw/plugin-sdk/whatsapp";
|
|
||||||
export {
|
export {
|
||||||
createWaSocket,
|
createWaSocket,
|
||||||
|
extractMediaPlaceholder,
|
||||||
|
extractText,
|
||||||
formatError,
|
formatError,
|
||||||
getStatusCode,
|
getStatusCode,
|
||||||
logoutWeb,
|
|
||||||
logWebSelfId,
|
logWebSelfId,
|
||||||
|
loginWeb,
|
||||||
|
logoutWeb,
|
||||||
|
monitorWebChannel,
|
||||||
|
monitorWebInbox,
|
||||||
pickWebChannel,
|
pickWebChannel,
|
||||||
WA_WEB_AUTH_DIR,
|
resolveHeartbeatRecipients,
|
||||||
|
runWebHeartbeatOnce,
|
||||||
|
sendMessageWhatsApp,
|
||||||
|
sendReactionWhatsApp,
|
||||||
waitForWaConnection,
|
waitForWaConnection,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
} from "./plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
|
||||||
|
// Keep the historic constant surface available, but resolve it through the
|
||||||
|
// plugin boundary only when a caller actually coerces the value to string.
|
||||||
|
class LazyWhatsAppAuthDir {
|
||||||
|
#value: string | null = null;
|
||||||
|
|
||||||
|
#read(): string {
|
||||||
|
this.#value ??= resolveWaWebAuthDir();
|
||||||
|
return this.#value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.#read();
|
||||||
|
}
|
||||||
|
|
||||||
|
valueOf(): string {
|
||||||
|
return this.#read();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.toPrimitive](): string {
|
||||||
|
return this.#read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WA_WEB_AUTH_DIR = new LazyWhatsAppAuthDir() as unknown as string;
|
||||||
|
|||||||
@@ -70,4 +70,4 @@ export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
|
|||||||
return createOutboundSendDepsFromCliSource(deps);
|
return createOutboundSendDepsFromCliSource(deps);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { logWebSelfId } from "openclaw/plugin-sdk/whatsapp";
|
export { logWebSelfId } from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { sendMessageWhatsApp as sendMessageWhatsAppImpl } from "openclaw/plugin-sdk/whatsapp";
|
import { sendMessageWhatsApp as sendMessageWhatsAppImpl } from "../../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
|
||||||
type RuntimeSend = {
|
type RuntimeSend = {
|
||||||
sendMessage: typeof import("openclaw/plugin-sdk/whatsapp").sendMessageWhatsApp;
|
sendMessage: typeof import("../../plugins/runtime/runtime-whatsapp-boundary.js").sendMessageWhatsApp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runtimeSend = {
|
export const runtimeSend = {
|
||||||
|
|||||||
@@ -21,12 +21,22 @@ vi.mock("../config/config.js", async (importOriginal) => {
|
|||||||
|
|
||||||
vi.mock("../config/sessions.js", () => ({
|
vi.mock("../config/sessions.js", () => ({
|
||||||
resolveStorePath: () => "/tmp/sessions.json",
|
resolveStorePath: () => "/tmp/sessions.json",
|
||||||
|
resolveSessionFilePath: vi.fn(() => "/tmp/sessions.json"),
|
||||||
loadSessionStore: () => testStore,
|
loadSessionStore: () => testStore,
|
||||||
|
saveSessionStore: vi.fn().mockResolvedValue(undefined),
|
||||||
readSessionUpdatedAt: vi.fn(() => undefined),
|
readSessionUpdatedAt: vi.fn(() => undefined),
|
||||||
recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined),
|
recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined),
|
||||||
updateLastRoute: vi.fn().mockResolvedValue(undefined),
|
updateLastRoute: vi.fn().mockResolvedValue(undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../extensions/telegram/src/fetch.js", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../../extensions/telegram/src/fetch.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
resolveTelegramFetch: () => fetch,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("../../extensions/whatsapp/src/auth-store.js", () => ({
|
vi.mock("../../extensions/whatsapp/src/auth-store.js", () => ({
|
||||||
webAuthExists: vi.fn(async () => true),
|
webAuthExists: vi.fn(async () => true),
|
||||||
getWebAuthAgeMs: vi.fn(() => 1234),
|
getWebAuthAgeMs: vi.fn(() => 1234),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { getReplyFromConfig } from "./auto-reply/reply.js";
|
import { getReplyFromConfig } from "./auto-reply/reply.js";
|
||||||
import { applyTemplate } from "./auto-reply/templating.js";
|
import { applyTemplate } from "./auto-reply/templating.js";
|
||||||
import { monitorWebChannel } from "./channel-web.js";
|
|
||||||
import { createDefaultDeps } from "./cli/deps.js";
|
import { createDefaultDeps } from "./cli/deps.js";
|
||||||
import { promptYesNo } from "./cli/prompt.js";
|
import { promptYesNo } from "./cli/prompt.js";
|
||||||
import { waitForever } from "./cli/wait.js";
|
import { waitForever } from "./cli/wait.js";
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
handlePortError,
|
handlePortError,
|
||||||
PortInUseError,
|
PortInUseError,
|
||||||
} from "./infra/ports.js";
|
} from "./infra/ports.js";
|
||||||
|
import { monitorWebChannel } from "./plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
import { runCommandWithTimeout, runExec } from "./process/exec.js";
|
import { runCommandWithTimeout, runExec } from "./process/exec.js";
|
||||||
import { assertWebChannel, normalizeE164, toWhatsappJid } from "./utils.js";
|
import { assertWebChannel, normalizeE164, toWhatsappJid } from "./utils.js";
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ describe("plugin-sdk subpath exports", () => {
|
|||||||
expect(typeof whatsappSdk.WhatsAppConfigSchema).toBe("object");
|
expect(typeof whatsappSdk.WhatsAppConfigSchema).toBe("object");
|
||||||
expect(typeof whatsappSdk.resolveWhatsAppOutboundTarget).toBe("function");
|
expect(typeof whatsappSdk.resolveWhatsAppOutboundTarget).toBe("function");
|
||||||
expect(typeof whatsappSdk.resolveWhatsAppMentionStripRegexes).toBe("function");
|
expect(typeof whatsappSdk.resolveWhatsAppMentionStripRegexes).toBe("function");
|
||||||
|
expect(typeof whatsappSdk.sendMessageWhatsApp).toBe("function");
|
||||||
|
expect(typeof whatsappSdk.loadWebMedia).toBe("function");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exports WhatsApp QR login helpers from the dedicated subpath", () => {
|
it("exports WhatsApp QR login helpers from the dedicated subpath", () => {
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ export {
|
|||||||
loadWebMedia,
|
loadWebMedia,
|
||||||
loadWebMediaRaw,
|
loadWebMediaRaw,
|
||||||
type WebMediaResult,
|
type WebMediaResult,
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
} from "../media/web-media.js";
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { handleWhatsAppAction } from "../../extensions/whatsapp/action-runtime-api.js";
|
export { handleWhatsAppAction } from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
export { startWebLoginWithQr, waitForWebLogin } from "../../extensions/whatsapp/login-qr-api.js";
|
export {
|
||||||
|
startWebLoginWithQr,
|
||||||
|
waitForWebLogin,
|
||||||
|
} from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
|||||||
9
src/plugin-sdk/whatsapp-shared.ts
Normal file
9
src/plugin-sdk/whatsapp-shared.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export type { ChannelMessageActionName } from "../channels/plugins/types.js";
|
||||||
|
export type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
||||||
|
export {
|
||||||
|
createWhatsAppOutboundBase,
|
||||||
|
resolveWhatsAppGroupIntroHint,
|
||||||
|
resolveWhatsAppMentionStripRegexes,
|
||||||
|
} from "../channels/plugins/whatsapp-shared.js";
|
||||||
|
export { resolveWhatsAppHeartbeatRecipients } from "../channels/plugins/whatsapp-heartbeat.js";
|
||||||
|
export { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
export type { ChannelMessageActionName } from "../channels/plugins/types.js";
|
export type { ChannelMessageActionName } from "../channels/plugins/types.js";
|
||||||
export type { OpenClawConfig } from "../config/config.js";
|
export type { OpenClawConfig } from "../config/config.js";
|
||||||
export type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
export type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
||||||
export type { WebChannelStatus, WebMonitorTuning } from "../../extensions/whatsapp/runtime-api.js";
|
export type {
|
||||||
|
WebChannelStatus,
|
||||||
|
WebMonitorTuning,
|
||||||
|
} from "../../extensions/whatsapp/src/auto-reply/types.js";
|
||||||
export type {
|
export type {
|
||||||
WebInboundMessage,
|
WebInboundMessage,
|
||||||
WebListenerCloseReason,
|
WebListenerCloseReason,
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
} from "../../extensions/whatsapp/src/inbound/types.js";
|
||||||
export type {
|
export type {
|
||||||
ChannelMessageActionContext,
|
ChannelMessageActionContext,
|
||||||
ChannelPlugin,
|
ChannelPlugin,
|
||||||
@@ -71,44 +74,40 @@ export {
|
|||||||
resolveWhatsAppAccount,
|
resolveWhatsAppAccount,
|
||||||
} from "../../extensions/whatsapp/api.js";
|
} from "../../extensions/whatsapp/api.js";
|
||||||
export {
|
export {
|
||||||
getActiveWebListener,
|
|
||||||
getWebAuthAgeMs,
|
|
||||||
WA_WEB_AUTH_DIR,
|
|
||||||
logWebSelfId,
|
|
||||||
logoutWeb,
|
|
||||||
pickWebChannel,
|
|
||||||
readWebSelfId,
|
|
||||||
webAuthExists,
|
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
|
||||||
export {
|
|
||||||
DEFAULT_WEB_MEDIA_BYTES,
|
|
||||||
HEARTBEAT_PROMPT,
|
HEARTBEAT_PROMPT,
|
||||||
HEARTBEAT_TOKEN,
|
HEARTBEAT_TOKEN,
|
||||||
|
WA_WEB_AUTH_DIR,
|
||||||
|
createWaSocket,
|
||||||
|
formatError,
|
||||||
|
loginWeb,
|
||||||
|
logWebSelfId,
|
||||||
|
logoutWeb,
|
||||||
monitorWebChannel,
|
monitorWebChannel,
|
||||||
|
pickWebChannel,
|
||||||
resolveHeartbeatRecipients,
|
resolveHeartbeatRecipients,
|
||||||
runWebHeartbeatOnce,
|
runWebHeartbeatOnce,
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
sendMessageWhatsApp,
|
||||||
|
sendReactionWhatsApp,
|
||||||
|
waitForWaConnection,
|
||||||
|
webAuthExists,
|
||||||
|
} from "../channel-web.js";
|
||||||
export {
|
export {
|
||||||
extractMediaPlaceholder,
|
extractMediaPlaceholder,
|
||||||
extractText,
|
extractText,
|
||||||
|
getActiveWebListener,
|
||||||
|
getWebAuthAgeMs,
|
||||||
monitorWebInbox,
|
monitorWebInbox,
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
readWebSelfId,
|
||||||
export { loginWeb } from "../../extensions/whatsapp/runtime-api.js";
|
sendPollWhatsApp,
|
||||||
|
startWebLoginWithQr,
|
||||||
|
waitForWebLogin,
|
||||||
|
} from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
|
export { DEFAULT_WEB_MEDIA_BYTES } from "../../extensions/whatsapp/src/auto-reply/constants.js";
|
||||||
export {
|
export {
|
||||||
getDefaultLocalRoots,
|
getDefaultLocalRoots,
|
||||||
loadWebMedia,
|
loadWebMedia,
|
||||||
loadWebMediaRaw,
|
loadWebMediaRaw,
|
||||||
optimizeImageToJpeg,
|
optimizeImageToJpeg,
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
} from "../media/web-media.js";
|
||||||
export {
|
export { getStatusCode } from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
sendMessageWhatsApp,
|
export { createRuntimeWhatsAppLoginTool as createWhatsAppLoginTool } from "../plugins/runtime/runtime-whatsapp-boundary.js";
|
||||||
sendPollWhatsApp,
|
|
||||||
sendReactionWhatsApp,
|
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
|
||||||
export {
|
|
||||||
createWaSocket,
|
|
||||||
formatError,
|
|
||||||
getStatusCode,
|
|
||||||
waitForWaConnection,
|
|
||||||
} from "../../extensions/whatsapp/runtime-api.js";
|
|
||||||
export { createWhatsAppLoginTool } from "../../extensions/whatsapp/runtime-api.js";
|
|
||||||
|
|||||||
@@ -22,18 +22,22 @@ describe("bundled plugin runtime dependencies", () => {
|
|||||||
expect(rootSpec).toBeUndefined();
|
expect(rootSpec).toBeUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectRootMirrorsPluginRuntimeDep(pluginPath: string, dependencyName: string) {
|
||||||
|
const rootManifest = readJson<PackageManifest>("package.json");
|
||||||
|
const pluginManifest = readJson<PackageManifest>(pluginPath);
|
||||||
|
const pluginSpec = pluginManifest.dependencies?.[dependencyName];
|
||||||
|
const rootSpec = rootManifest.dependencies?.[dependencyName];
|
||||||
|
|
||||||
|
expect(pluginSpec).toBeTruthy();
|
||||||
|
expect(rootSpec).toBe(pluginSpec);
|
||||||
|
}
|
||||||
|
|
||||||
it("keeps bundled Feishu runtime deps plugin-local instead of mirroring them into the root package", () => {
|
it("keeps bundled Feishu runtime deps plugin-local instead of mirroring them into the root package", () => {
|
||||||
expectPluginOwnsRuntimeDep("extensions/feishu/package.json", "@larksuiteoapi/node-sdk");
|
expectPluginOwnsRuntimeDep("extensions/feishu/package.json", "@larksuiteoapi/node-sdk");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps bundled memory-lancedb runtime deps available from the root package while its native runtime stays bundled", () => {
|
it("keeps bundled memory-lancedb runtime deps mirrored in the root package while its native runtime is still packaged that way", () => {
|
||||||
const rootManifest = readJson<PackageManifest>("package.json");
|
expectRootMirrorsPluginRuntimeDep("extensions/memory-lancedb/package.json", "@lancedb/lancedb");
|
||||||
const memoryManifest = readJson<PackageManifest>("extensions/memory-lancedb/package.json");
|
|
||||||
const memorySpec = memoryManifest.dependencies?.["@lancedb/lancedb"];
|
|
||||||
const rootSpec = rootManifest.dependencies?.["@lancedb/lancedb"];
|
|
||||||
|
|
||||||
expect(memorySpec).toBeTruthy();
|
|
||||||
expect(rootSpec).toBe(memorySpec);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps bundled Discord runtime deps plugin-local instead of mirroring them into the root package", () => {
|
it("keeps bundled Discord runtime deps plugin-local instead of mirroring them into the root package", () => {
|
||||||
@@ -48,6 +52,13 @@ describe("bundled plugin runtime dependencies", () => {
|
|||||||
expectPluginOwnsRuntimeDep("extensions/telegram/package.json", "grammy");
|
expectPluginOwnsRuntimeDep("extensions/telegram/package.json", "grammy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps bundled WhatsApp runtime deps mirrored in the root package while its heavy runtime still uses the legacy bundle path", () => {
|
||||||
|
expectRootMirrorsPluginRuntimeDep(
|
||||||
|
"extensions/whatsapp/package.json",
|
||||||
|
"@whiskeysockets/baileys",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps bundled proxy-agent deps plugin-local instead of mirroring them into the root package", () => {
|
it("keeps bundled proxy-agent deps plugin-local instead of mirroring them into the root package", () => {
|
||||||
expectPluginOwnsRuntimeDep("extensions/discord/package.json", "https-proxy-agent");
|
expectPluginOwnsRuntimeDep("extensions/discord/package.json", "https-proxy-agent");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { isChannelConfigured } from "../config/plugin-auto-enable.js";
|
|||||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
|
||||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
|
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
|
||||||
@@ -31,6 +30,17 @@ import { setActivePluginRegistry } from "./runtime.js";
|
|||||||
import type { CreatePluginRuntimeOptions } from "./runtime/index.js";
|
import type { CreatePluginRuntimeOptions } from "./runtime/index.js";
|
||||||
import type { PluginRuntime } from "./runtime/types.js";
|
import type { PluginRuntime } from "./runtime/types.js";
|
||||||
import { validateJsonSchemaValue } from "./schema-validator.js";
|
import { validateJsonSchemaValue } from "./schema-validator.js";
|
||||||
|
import {
|
||||||
|
buildPluginLoaderJitiOptions,
|
||||||
|
listPluginSdkAliasCandidates,
|
||||||
|
listPluginSdkExportedSubpaths,
|
||||||
|
resolveLoaderPackageRoot,
|
||||||
|
resolvePluginSdkAliasCandidateOrder,
|
||||||
|
resolvePluginSdkAliasFile,
|
||||||
|
resolvePluginSdkScopedAliasMap,
|
||||||
|
shouldPreferNativeJiti,
|
||||||
|
type LoaderModuleResolveParams,
|
||||||
|
} from "./sdk-alias.js";
|
||||||
import type {
|
import type {
|
||||||
OpenClawPluginDefinition,
|
OpenClawPluginDefinition,
|
||||||
OpenClawPluginModule,
|
OpenClawPluginModule,
|
||||||
@@ -90,130 +100,13 @@ export function clearPluginLoaderCache(): void {
|
|||||||
|
|
||||||
const defaultLogger = () => createSubsystemLogger("plugins");
|
const defaultLogger = () => createSubsystemLogger("plugins");
|
||||||
|
|
||||||
type PluginSdkAliasCandidateKind = "dist" | "src";
|
|
||||||
|
|
||||||
type LoaderModuleResolveParams = {
|
|
||||||
modulePath?: string;
|
|
||||||
argv1?: string;
|
|
||||||
cwd?: string;
|
|
||||||
moduleUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
|
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
|
||||||
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
|
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveLoaderPackageRoot(
|
|
||||||
params: LoaderModuleResolveParams & { modulePath: string },
|
|
||||||
): string | null {
|
|
||||||
const cwd = params.cwd ?? path.dirname(params.modulePath);
|
|
||||||
const fromModulePath = resolveOpenClawPackageRootSync({ cwd });
|
|
||||||
if (fromModulePath) {
|
|
||||||
return fromModulePath;
|
|
||||||
}
|
|
||||||
const argv1 = params.argv1 ?? process.argv[1];
|
|
||||||
const moduleUrl = params.moduleUrl ?? (params.modulePath ? undefined : import.meta.url);
|
|
||||||
return resolveOpenClawPackageRootSync({
|
|
||||||
cwd,
|
|
||||||
...(argv1 ? { argv1 } : {}),
|
|
||||||
...(moduleUrl ? { moduleUrl } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePluginSdkAliasCandidateOrder(params: {
|
|
||||||
modulePath: string;
|
|
||||||
isProduction: boolean;
|
|
||||||
}): PluginSdkAliasCandidateKind[] {
|
|
||||||
const normalizedModulePath = params.modulePath.replace(/\\/g, "/");
|
|
||||||
const isDistRuntime = normalizedModulePath.includes("/dist/");
|
|
||||||
return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function listPluginSdkAliasCandidates(params: {
|
|
||||||
srcFile: string;
|
|
||||||
distFile: string;
|
|
||||||
modulePath: string;
|
|
||||||
argv1?: string;
|
|
||||||
cwd?: string;
|
|
||||||
moduleUrl?: string;
|
|
||||||
}) {
|
|
||||||
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
|
|
||||||
modulePath: params.modulePath,
|
|
||||||
isProduction: process.env.NODE_ENV === "production",
|
|
||||||
});
|
|
||||||
const packageRoot = resolveLoaderPackageRoot(params);
|
|
||||||
if (packageRoot) {
|
|
||||||
const candidateMap = {
|
|
||||||
src: path.join(packageRoot, "src", "plugin-sdk", params.srcFile),
|
|
||||||
dist: path.join(packageRoot, "dist", "plugin-sdk", params.distFile),
|
|
||||||
} as const;
|
|
||||||
return orderedKinds.map((kind) => candidateMap[kind]);
|
|
||||||
}
|
|
||||||
let cursor = path.dirname(params.modulePath);
|
|
||||||
const candidates: string[] = [];
|
|
||||||
for (let i = 0; i < 6; i += 1) {
|
|
||||||
const candidateMap = {
|
|
||||||
src: path.join(cursor, "src", "plugin-sdk", params.srcFile),
|
|
||||||
dist: path.join(cursor, "dist", "plugin-sdk", params.distFile),
|
|
||||||
} as const;
|
|
||||||
for (const kind of orderedKinds) {
|
|
||||||
candidates.push(candidateMap[kind]);
|
|
||||||
}
|
|
||||||
const parent = path.dirname(cursor);
|
|
||||||
if (parent === cursor) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cursor = parent;
|
|
||||||
}
|
|
||||||
return candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvePluginSdkAliasFile = (params: {
|
|
||||||
srcFile: string;
|
|
||||||
distFile: string;
|
|
||||||
modulePath?: string;
|
|
||||||
argv1?: string;
|
|
||||||
cwd?: string;
|
|
||||||
moduleUrl?: string;
|
|
||||||
}): string | null => {
|
|
||||||
try {
|
|
||||||
const modulePath = resolveLoaderModulePath(params);
|
|
||||||
for (const candidate of listPluginSdkAliasCandidates({
|
|
||||||
srcFile: params.srcFile,
|
|
||||||
distFile: params.distFile,
|
|
||||||
modulePath,
|
|
||||||
argv1: params.argv1,
|
|
||||||
cwd: params.cwd,
|
|
||||||
moduleUrl: params.moduleUrl,
|
|
||||||
})) {
|
|
||||||
if (fs.existsSync(candidate)) {
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolvePluginSdkAlias = (): string | null =>
|
const resolvePluginSdkAlias = (): string | null =>
|
||||||
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
|
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
|
||||||
|
|
||||||
function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
|
|
||||||
return {
|
|
||||||
interopDefault: true,
|
|
||||||
// Prefer Node's native sync ESM loader for built dist/*.js modules so
|
|
||||||
// bundled plugins and plugin-sdk subpaths stay on the canonical module graph.
|
|
||||||
tryNative: true,
|
|
||||||
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
|
|
||||||
...(Object.keys(aliasMap).length > 0
|
|
||||||
? {
|
|
||||||
alias: aliasMap,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
|
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
|
||||||
try {
|
try {
|
||||||
const modulePath = resolveLoaderModulePath(params);
|
const modulePath = resolveLoaderModulePath(params);
|
||||||
@@ -243,63 +136,6 @@ function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}):
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedPluginSdkExportedSubpaths = new Map<string, string[]>();
|
|
||||||
|
|
||||||
function listPluginSdkExportedSubpaths(params: { modulePath?: string } = {}): string[] {
|
|
||||||
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
|
|
||||||
const packageRoot = resolveOpenClawPackageRootSync({
|
|
||||||
cwd: path.dirname(modulePath),
|
|
||||||
});
|
|
||||||
if (!packageRoot) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const cached = cachedPluginSdkExportedSubpaths.get(packageRoot);
|
|
||||||
if (cached) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const pkgRaw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8");
|
|
||||||
const pkg = JSON.parse(pkgRaw) as {
|
|
||||||
exports?: Record<string, unknown>;
|
|
||||||
};
|
|
||||||
const subpaths = Object.keys(pkg.exports ?? {})
|
|
||||||
.filter((key) => key.startsWith("./plugin-sdk/"))
|
|
||||||
.map((key) => key.slice("./plugin-sdk/".length))
|
|
||||||
.filter((subpath) => Boolean(subpath) && !subpath.includes("/"))
|
|
||||||
.toSorted();
|
|
||||||
cachedPluginSdkExportedSubpaths.set(packageRoot, subpaths);
|
|
||||||
return subpaths;
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvePluginSdkScopedAliasMap = (): Record<string, string> => {
|
|
||||||
const aliasMap: Record<string, string> = {};
|
|
||||||
for (const subpath of listPluginSdkExportedSubpaths()) {
|
|
||||||
const resolved = resolvePluginSdkAliasFile({
|
|
||||||
srcFile: `${subpath}.ts`,
|
|
||||||
distFile: `${subpath}.js`,
|
|
||||||
});
|
|
||||||
if (resolved) {
|
|
||||||
aliasMap[`openclaw/plugin-sdk/${subpath}`] = resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return aliasMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
function shouldPreferNativeJiti(modulePath: string): boolean {
|
|
||||||
switch (path.extname(modulePath).toLowerCase()) {
|
|
||||||
case ".js":
|
|
||||||
case ".mjs":
|
|
||||||
case ".cjs":
|
|
||||||
case ".json":
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const __testing = {
|
export const __testing = {
|
||||||
buildPluginLoaderJitiOptions,
|
buildPluginLoaderJitiOptions,
|
||||||
listPluginSdkAliasCandidates,
|
listPluginSdkAliasCandidates,
|
||||||
|
|||||||
339
src/plugins/runtime/runtime-whatsapp-boundary.ts
Normal file
339
src/plugins/runtime/runtime-whatsapp-boundary.ts
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { createJiti } from "jiti";
|
||||||
|
import { resolveWhatsAppHeartbeatRecipients } from "../../channels/plugins/whatsapp-heartbeat.js";
|
||||||
|
import { loadConfig } from "../../config/config.js";
|
||||||
|
import {
|
||||||
|
getDefaultLocalRoots as getDefaultLocalRootsImpl,
|
||||||
|
loadWebMedia as loadWebMediaImpl,
|
||||||
|
loadWebMediaRaw as loadWebMediaRawImpl,
|
||||||
|
optimizeImageToJpeg as optimizeImageToJpegImpl,
|
||||||
|
} from "../../media/web-media.js";
|
||||||
|
import { loadPluginManifestRegistry } from "../manifest-registry.js";
|
||||||
|
import {
|
||||||
|
buildPluginLoaderJitiOptions,
|
||||||
|
resolvePluginSdkAliasFile,
|
||||||
|
resolvePluginSdkScopedAliasMap,
|
||||||
|
shouldPreferNativeJiti,
|
||||||
|
} from "../sdk-alias.js";
|
||||||
|
|
||||||
|
const WHATSAPP_PLUGIN_ID = "whatsapp";
|
||||||
|
|
||||||
|
type WhatsAppLightModule = typeof import("../../../extensions/whatsapp/light-runtime-api.js");
|
||||||
|
type WhatsAppHeavyModule = typeof import("../../../extensions/whatsapp/runtime-api.js");
|
||||||
|
|
||||||
|
type WhatsAppPluginRecord = {
|
||||||
|
origin: string;
|
||||||
|
rootDir?: string;
|
||||||
|
source: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cachedHeavyModulePath: string | null = null;
|
||||||
|
let cachedHeavyModule: WhatsAppHeavyModule | null = null;
|
||||||
|
let cachedLightModulePath: string | null = null;
|
||||||
|
let cachedLightModule: WhatsAppLightModule | null = null;
|
||||||
|
|
||||||
|
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
|
||||||
|
|
||||||
|
function readConfigSafely() {
|
||||||
|
try {
|
||||||
|
return loadConfig();
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWhatsAppPluginRecord(): WhatsAppPluginRecord {
|
||||||
|
const manifestRegistry = loadPluginManifestRegistry({
|
||||||
|
config: readConfigSafely(),
|
||||||
|
cache: true,
|
||||||
|
});
|
||||||
|
const record = manifestRegistry.plugins.find((plugin) => plugin.id === WHATSAPP_PLUGIN_ID);
|
||||||
|
if (!record?.source) {
|
||||||
|
throw new Error(
|
||||||
|
`WhatsApp plugin runtime is unavailable: missing plugin '${WHATSAPP_PLUGIN_ID}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
origin: record.origin,
|
||||||
|
rootDir: record.rootDir,
|
||||||
|
source: record.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWhatsAppRuntimeModulePath(
|
||||||
|
record: WhatsAppPluginRecord,
|
||||||
|
entryBaseName: "light-runtime-api" | "runtime-api",
|
||||||
|
): string {
|
||||||
|
const candidates = [
|
||||||
|
path.join(path.dirname(record.source), `${entryBaseName}.js`),
|
||||||
|
path.join(path.dirname(record.source), `${entryBaseName}.ts`),
|
||||||
|
...(record.rootDir
|
||||||
|
? [
|
||||||
|
path.join(record.rootDir, `${entryBaseName}.js`),
|
||||||
|
path.join(record.rootDir, `${entryBaseName}.ts`),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`WhatsApp plugin runtime is unavailable: missing ${entryBaseName} for plugin '${WHATSAPP_PLUGIN_ID}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJiti(modulePath: string) {
|
||||||
|
const tryNative = shouldPreferNativeJiti(modulePath);
|
||||||
|
const cached = jitiLoaders.get(tryNative);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
const pluginSdkAlias = resolvePluginSdkAliasFile({
|
||||||
|
srcFile: "root-alias.cjs",
|
||||||
|
distFile: "root-alias.cjs",
|
||||||
|
modulePath: modulePath,
|
||||||
|
});
|
||||||
|
const aliasMap = {
|
||||||
|
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
|
||||||
|
...resolvePluginSdkScopedAliasMap({ modulePath }),
|
||||||
|
};
|
||||||
|
const loader = createJiti(import.meta.url, {
|
||||||
|
...buildPluginLoaderJitiOptions(aliasMap),
|
||||||
|
tryNative,
|
||||||
|
});
|
||||||
|
jitiLoaders.set(tryNative, loader);
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWithJiti<TModule>(modulePath: string): TModule {
|
||||||
|
return getJiti(modulePath)(modulePath) as TModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCurrentHeavyModuleSync(): WhatsAppHeavyModule {
|
||||||
|
const modulePath = resolveWhatsAppRuntimeModulePath(resolveWhatsAppPluginRecord(), "runtime-api");
|
||||||
|
return loadWithJiti<WhatsAppHeavyModule>(modulePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWhatsAppLightModule(): WhatsAppLightModule {
|
||||||
|
const modulePath = resolveWhatsAppRuntimeModulePath(
|
||||||
|
resolveWhatsAppPluginRecord(),
|
||||||
|
"light-runtime-api",
|
||||||
|
);
|
||||||
|
if (cachedLightModule && cachedLightModulePath === modulePath) {
|
||||||
|
return cachedLightModule;
|
||||||
|
}
|
||||||
|
const loaded = loadWithJiti<WhatsAppLightModule>(modulePath);
|
||||||
|
cachedLightModulePath = modulePath;
|
||||||
|
cachedLightModule = loaded;
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWhatsAppHeavyModule(): Promise<WhatsAppHeavyModule> {
|
||||||
|
const record = resolveWhatsAppPluginRecord();
|
||||||
|
const modulePath = resolveWhatsAppRuntimeModulePath(record, "runtime-api");
|
||||||
|
if (cachedHeavyModule && cachedHeavyModulePath === modulePath) {
|
||||||
|
return cachedHeavyModule;
|
||||||
|
}
|
||||||
|
const loaded = loadWithJiti<WhatsAppHeavyModule>(modulePath);
|
||||||
|
cachedHeavyModulePath = modulePath;
|
||||||
|
cachedHeavyModule = loaded;
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLightExport<K extends keyof WhatsAppLightModule>(
|
||||||
|
exportName: K,
|
||||||
|
): NonNullable<WhatsAppLightModule[K]> {
|
||||||
|
const loaded = loadWhatsAppLightModule();
|
||||||
|
const value = loaded[exportName];
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(`WhatsApp plugin runtime is missing export '${String(exportName)}'`);
|
||||||
|
}
|
||||||
|
return value as NonNullable<WhatsAppLightModule[K]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getHeavyExport<K extends keyof WhatsAppHeavyModule>(
|
||||||
|
exportName: K,
|
||||||
|
): Promise<NonNullable<WhatsAppHeavyModule[K]>> {
|
||||||
|
const loaded = await loadWhatsAppHeavyModule();
|
||||||
|
const value = loaded[exportName];
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(`WhatsApp plugin runtime is missing export '${String(exportName)}'`);
|
||||||
|
}
|
||||||
|
return value as NonNullable<WhatsAppHeavyModule[K]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActiveWebListener(
|
||||||
|
...args: Parameters<WhatsAppLightModule["getActiveWebListener"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["getActiveWebListener"]> {
|
||||||
|
return getLightExport("getActiveWebListener")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebAuthAgeMs(
|
||||||
|
...args: Parameters<WhatsAppLightModule["getWebAuthAgeMs"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["getWebAuthAgeMs"]> {
|
||||||
|
return getLightExport("getWebAuthAgeMs")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logWebSelfId(
|
||||||
|
...args: Parameters<WhatsAppLightModule["logWebSelfId"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["logWebSelfId"]> {
|
||||||
|
return getLightExport("logWebSelfId")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginWeb(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["loginWeb"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["loginWeb"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.loginWeb(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutWeb(
|
||||||
|
...args: Parameters<WhatsAppLightModule["logoutWeb"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["logoutWeb"]> {
|
||||||
|
return getLightExport("logoutWeb")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readWebSelfId(
|
||||||
|
...args: Parameters<WhatsAppLightModule["readWebSelfId"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["readWebSelfId"]> {
|
||||||
|
return getLightExport("readWebSelfId")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function webAuthExists(
|
||||||
|
...args: Parameters<WhatsAppLightModule["webAuthExists"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["webAuthExists"]> {
|
||||||
|
return getLightExport("webAuthExists")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMessageWhatsApp(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["sendMessageWhatsApp"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["sendMessageWhatsApp"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendMessageWhatsApp(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendPollWhatsApp(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["sendPollWhatsApp"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["sendPollWhatsApp"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendPollWhatsApp(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendReactionWhatsApp(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["sendReactionWhatsApp"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["sendReactionWhatsApp"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendReactionWhatsApp(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRuntimeWhatsAppLoginTool(
|
||||||
|
...args: Parameters<WhatsAppLightModule["createWhatsAppLoginTool"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["createWhatsAppLoginTool"]> {
|
||||||
|
return getLightExport("createWhatsAppLoginTool")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWaSocket(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["createWaSocket"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["createWaSocket"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.createWaSocket(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatError(
|
||||||
|
...args: Parameters<WhatsAppLightModule["formatError"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["formatError"]> {
|
||||||
|
return getLightExport("formatError")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStatusCode(
|
||||||
|
...args: Parameters<WhatsAppLightModule["getStatusCode"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["getStatusCode"]> {
|
||||||
|
return getLightExport("getStatusCode")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pickWebChannel(
|
||||||
|
...args: Parameters<WhatsAppLightModule["pickWebChannel"]>
|
||||||
|
): ReturnType<WhatsAppLightModule["pickWebChannel"]> {
|
||||||
|
return getLightExport("pickWebChannel")(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveWaWebAuthDir(): WhatsAppLightModule["WA_WEB_AUTH_DIR"] {
|
||||||
|
return getLightExport("WA_WEB_AUTH_DIR");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleWhatsAppAction(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["handleWhatsAppAction"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["handleWhatsAppAction"]> {
|
||||||
|
return (await getHeavyExport("handleWhatsAppAction"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadWebMedia(
|
||||||
|
...args: Parameters<typeof loadWebMediaImpl>
|
||||||
|
): ReturnType<typeof loadWebMediaImpl> {
|
||||||
|
return await loadWebMediaImpl(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadWebMediaRaw(
|
||||||
|
...args: Parameters<typeof loadWebMediaRawImpl>
|
||||||
|
): ReturnType<typeof loadWebMediaRawImpl> {
|
||||||
|
return await loadWebMediaRawImpl(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function monitorWebChannel(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["monitorWebChannel"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["monitorWebChannel"]> {
|
||||||
|
return loadWhatsAppHeavyModule().then((loaded) => loaded.monitorWebChannel(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function monitorWebInbox(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["monitorWebInbox"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["monitorWebInbox"]> {
|
||||||
|
return (await getHeavyExport("monitorWebInbox"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function optimizeImageToJpeg(
|
||||||
|
...args: Parameters<typeof optimizeImageToJpegImpl>
|
||||||
|
): ReturnType<typeof optimizeImageToJpegImpl> {
|
||||||
|
return await optimizeImageToJpegImpl(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runWebHeartbeatOnce(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["runWebHeartbeatOnce"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["runWebHeartbeatOnce"]> {
|
||||||
|
return (await getHeavyExport("runWebHeartbeatOnce"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startWebLoginWithQr(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["startWebLoginWithQr"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["startWebLoginWithQr"]> {
|
||||||
|
return (await getHeavyExport("startWebLoginWithQr"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForWaConnection(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["waitForWaConnection"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["waitForWaConnection"]> {
|
||||||
|
return (await getHeavyExport("waitForWaConnection"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForWebLogin(
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["waitForWebLogin"]>
|
||||||
|
): ReturnType<WhatsAppHeavyModule["waitForWebLogin"]> {
|
||||||
|
return (await getHeavyExport("waitForWebLogin"))(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractMediaPlaceholder = (
|
||||||
|
...args: Parameters<WhatsAppHeavyModule["extractMediaPlaceholder"]>
|
||||||
|
) => loadCurrentHeavyModuleSync().extractMediaPlaceholder(...args);
|
||||||
|
|
||||||
|
export const extractText = (...args: Parameters<WhatsAppHeavyModule["extractText"]>) =>
|
||||||
|
loadCurrentHeavyModuleSync().extractText(...args);
|
||||||
|
|
||||||
|
export function getDefaultLocalRoots(
|
||||||
|
...args: Parameters<typeof getDefaultLocalRootsImpl>
|
||||||
|
): ReturnType<typeof getDefaultLocalRootsImpl> {
|
||||||
|
return getDefaultLocalRootsImpl(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveHeartbeatRecipients(
|
||||||
|
...args: Parameters<typeof resolveWhatsAppHeartbeatRecipients>
|
||||||
|
): ReturnType<typeof resolveWhatsAppHeartbeatRecipients> {
|
||||||
|
return resolveWhatsAppHeartbeatRecipients(...args);
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
export { createWhatsAppLoginTool as createRuntimeWhatsAppLoginTool } from "openclaw/plugin-sdk/whatsapp";
|
export { createRuntimeWhatsAppLoginTool } from "./runtime-whatsapp-boundary.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { loginWeb as loginWebImpl } from "openclaw/plugin-sdk/whatsapp";
|
import { loginWeb as loginWebImpl } from "./runtime-whatsapp-boundary.js";
|
||||||
import type { PluginRuntime } from "./types.js";
|
import type { PluginRuntime } from "./types.js";
|
||||||
|
|
||||||
type RuntimeWhatsAppLogin = Pick<PluginRuntime["channel"]["whatsapp"], "loginWeb">;
|
type RuntimeWhatsAppLogin = Pick<PluginRuntime["channel"]["whatsapp"], "loginWeb">;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
sendMessageWhatsApp as sendMessageWhatsAppImpl,
|
sendMessageWhatsApp as sendMessageWhatsAppImpl,
|
||||||
sendPollWhatsApp as sendPollWhatsAppImpl,
|
sendPollWhatsApp as sendPollWhatsAppImpl,
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
} from "./runtime-whatsapp-boundary.js";
|
||||||
import type { PluginRuntime } from "./types.js";
|
import type { PluginRuntime } from "./types.js";
|
||||||
|
|
||||||
type RuntimeWhatsAppOutbound = Pick<
|
type RuntimeWhatsAppOutbound = Pick<
|
||||||
|
|||||||
@@ -1,90 +1,21 @@
|
|||||||
import { getActiveWebListener } from "openclaw/plugin-sdk/whatsapp";
|
|
||||||
import {
|
import {
|
||||||
|
createRuntimeWhatsAppLoginTool,
|
||||||
|
getActiveWebListener,
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
|
handleWhatsAppAction,
|
||||||
logWebSelfId,
|
logWebSelfId,
|
||||||
|
loginWeb,
|
||||||
logoutWeb,
|
logoutWeb,
|
||||||
|
monitorWebChannel,
|
||||||
readWebSelfId,
|
readWebSelfId,
|
||||||
|
sendMessageWhatsApp,
|
||||||
|
sendPollWhatsApp,
|
||||||
|
startWebLoginWithQr,
|
||||||
|
waitForWebLogin,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
} from "openclaw/plugin-sdk/whatsapp";
|
} from "./runtime-whatsapp-boundary.js";
|
||||||
import {
|
|
||||||
createLazyRuntimeMethodBinder,
|
|
||||||
createLazyRuntimeSurface,
|
|
||||||
} from "../../shared/lazy-runtime.js";
|
|
||||||
import { createRuntimeWhatsAppLoginTool } from "./runtime-whatsapp-login-tool.js";
|
|
||||||
import type { PluginRuntime } from "./types.js";
|
import type { PluginRuntime } from "./types.js";
|
||||||
|
|
||||||
const loadWebOutbound = createLazyRuntimeSurface(
|
|
||||||
() => import("./runtime-whatsapp-outbound.runtime.js"),
|
|
||||||
({ runtimeWhatsAppOutbound }) => runtimeWhatsAppOutbound,
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadWebLogin = createLazyRuntimeSurface(
|
|
||||||
() => import("./runtime-whatsapp-login.runtime.js"),
|
|
||||||
({ runtimeWhatsAppLogin }) => runtimeWhatsAppLogin,
|
|
||||||
);
|
|
||||||
|
|
||||||
const bindWhatsAppOutboundMethod = createLazyRuntimeMethodBinder(loadWebOutbound);
|
|
||||||
const bindWhatsAppLoginMethod = createLazyRuntimeMethodBinder(loadWebLogin);
|
|
||||||
|
|
||||||
const sendMessageWhatsAppLazy = bindWhatsAppOutboundMethod(
|
|
||||||
(runtimeWhatsAppOutbound) => runtimeWhatsAppOutbound.sendMessageWhatsApp,
|
|
||||||
);
|
|
||||||
const sendPollWhatsAppLazy = bindWhatsAppOutboundMethod(
|
|
||||||
(runtimeWhatsAppOutbound) => runtimeWhatsAppOutbound.sendPollWhatsApp,
|
|
||||||
);
|
|
||||||
const loginWebLazy = bindWhatsAppLoginMethod(
|
|
||||||
(runtimeWhatsAppLogin) => runtimeWhatsAppLogin.loginWeb,
|
|
||||||
);
|
|
||||||
|
|
||||||
const startWebLoginWithQrLazy: PluginRuntime["channel"]["whatsapp"]["startWebLoginWithQr"] = async (
|
|
||||||
...args
|
|
||||||
) => {
|
|
||||||
const { startWebLoginWithQr } = await loadWebLoginQr();
|
|
||||||
return startWebLoginWithQr(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const waitForWebLoginLazy: PluginRuntime["channel"]["whatsapp"]["waitForWebLogin"] = async (
|
|
||||||
...args
|
|
||||||
) => {
|
|
||||||
const { waitForWebLogin } = await loadWebLoginQr();
|
|
||||||
return waitForWebLogin(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const monitorWebChannelLazy: PluginRuntime["channel"]["whatsapp"]["monitorWebChannel"] = async (
|
|
||||||
...args
|
|
||||||
) => {
|
|
||||||
const { monitorWebChannel } = await loadWebChannel();
|
|
||||||
return monitorWebChannel(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleWhatsAppActionLazy: PluginRuntime["channel"]["whatsapp"]["handleWhatsAppAction"] =
|
|
||||||
async (...args) => {
|
|
||||||
const { handleWhatsAppAction } = await loadWhatsAppActions();
|
|
||||||
return handleWhatsAppAction(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
let webLoginQrPromise: Promise<typeof import("openclaw/plugin-sdk/whatsapp-login-qr")> | null =
|
|
||||||
null;
|
|
||||||
let webChannelPromise: Promise<typeof import("../../channels/web/index.js")> | null = null;
|
|
||||||
let whatsappActionsPromise: Promise<
|
|
||||||
typeof import("openclaw/plugin-sdk/whatsapp-action-runtime")
|
|
||||||
> | null = null;
|
|
||||||
|
|
||||||
function loadWebLoginQr() {
|
|
||||||
webLoginQrPromise ??= import("openclaw/plugin-sdk/whatsapp-login-qr");
|
|
||||||
return webLoginQrPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadWebChannel() {
|
|
||||||
webChannelPromise ??= import("../../channels/web/index.js");
|
|
||||||
return webChannelPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadWhatsAppActions() {
|
|
||||||
whatsappActionsPromise ??= import("openclaw/plugin-sdk/whatsapp-action-runtime");
|
|
||||||
return whatsappActionsPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createRuntimeWhatsApp(): PluginRuntime["channel"]["whatsapp"] {
|
export function createRuntimeWhatsApp(): PluginRuntime["channel"]["whatsapp"] {
|
||||||
return {
|
return {
|
||||||
getActiveWebListener,
|
getActiveWebListener,
|
||||||
@@ -93,13 +24,13 @@ export function createRuntimeWhatsApp(): PluginRuntime["channel"]["whatsapp"] {
|
|||||||
logWebSelfId,
|
logWebSelfId,
|
||||||
readWebSelfId,
|
readWebSelfId,
|
||||||
webAuthExists,
|
webAuthExists,
|
||||||
sendMessageWhatsApp: sendMessageWhatsAppLazy,
|
sendMessageWhatsApp,
|
||||||
sendPollWhatsApp: sendPollWhatsAppLazy,
|
sendPollWhatsApp,
|
||||||
loginWeb: loginWebLazy,
|
loginWeb,
|
||||||
startWebLoginWithQr: startWebLoginWithQrLazy,
|
startWebLoginWithQr,
|
||||||
waitForWebLogin: waitForWebLoginLazy,
|
waitForWebLogin,
|
||||||
monitorWebChannel: monitorWebChannelLazy,
|
monitorWebChannel,
|
||||||
handleWhatsAppAction: handleWhatsAppActionLazy,
|
handleWhatsAppAction,
|
||||||
createLoginTool: createRuntimeWhatsAppLoginTool,
|
createLoginTool: createRuntimeWhatsAppLoginTool,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,19 +205,19 @@ export type PluginRuntimeChannel = {
|
|||||||
sendMessageIMessage: typeof import("../../../extensions/imessage/runtime-api.js").sendMessageIMessage;
|
sendMessageIMessage: typeof import("../../../extensions/imessage/runtime-api.js").sendMessageIMessage;
|
||||||
};
|
};
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
getActiveWebListener: typeof import("openclaw/plugin-sdk/whatsapp").getActiveWebListener;
|
getActiveWebListener: typeof import("./runtime-whatsapp-boundary.js").getActiveWebListener;
|
||||||
getWebAuthAgeMs: typeof import("openclaw/plugin-sdk/whatsapp").getWebAuthAgeMs;
|
getWebAuthAgeMs: typeof import("./runtime-whatsapp-boundary.js").getWebAuthAgeMs;
|
||||||
logoutWeb: typeof import("openclaw/plugin-sdk/whatsapp").logoutWeb;
|
logoutWeb: typeof import("./runtime-whatsapp-boundary.js").logoutWeb;
|
||||||
logWebSelfId: typeof import("openclaw/plugin-sdk/whatsapp").logWebSelfId;
|
logWebSelfId: typeof import("./runtime-whatsapp-boundary.js").logWebSelfId;
|
||||||
readWebSelfId: typeof import("openclaw/plugin-sdk/whatsapp").readWebSelfId;
|
readWebSelfId: typeof import("./runtime-whatsapp-boundary.js").readWebSelfId;
|
||||||
webAuthExists: typeof import("openclaw/plugin-sdk/whatsapp").webAuthExists;
|
webAuthExists: typeof import("./runtime-whatsapp-boundary.js").webAuthExists;
|
||||||
sendMessageWhatsApp: typeof import("openclaw/plugin-sdk/whatsapp").sendMessageWhatsApp;
|
sendMessageWhatsApp: typeof import("./runtime-whatsapp-boundary.js").sendMessageWhatsApp;
|
||||||
sendPollWhatsApp: typeof import("openclaw/plugin-sdk/whatsapp").sendPollWhatsApp;
|
sendPollWhatsApp: typeof import("./runtime-whatsapp-boundary.js").sendPollWhatsApp;
|
||||||
loginWeb: typeof import("openclaw/plugin-sdk/whatsapp").loginWeb;
|
loginWeb: typeof import("./runtime-whatsapp-boundary.js").loginWeb;
|
||||||
startWebLoginWithQr: typeof import("openclaw/plugin-sdk/whatsapp-login-qr").startWebLoginWithQr;
|
startWebLoginWithQr: typeof import("./runtime-whatsapp-boundary.js").startWebLoginWithQr;
|
||||||
waitForWebLogin: typeof import("openclaw/plugin-sdk/whatsapp-login-qr").waitForWebLogin;
|
waitForWebLogin: typeof import("./runtime-whatsapp-boundary.js").waitForWebLogin;
|
||||||
monitorWebChannel: typeof import("../../channels/web/index.js").monitorWebChannel;
|
monitorWebChannel: typeof import("./runtime-whatsapp-boundary.js").monitorWebChannel;
|
||||||
handleWhatsAppAction: typeof import("openclaw/plugin-sdk/whatsapp-action-runtime").handleWhatsAppAction;
|
handleWhatsAppAction: typeof import("./runtime-whatsapp-boundary.js").handleWhatsAppAction;
|
||||||
createLoginTool: typeof import("./runtime-whatsapp-login-tool.js").createRuntimeWhatsAppLoginTool;
|
createLoginTool: typeof import("./runtime-whatsapp-login-tool.js").createRuntimeWhatsAppLoginTool;
|
||||||
};
|
};
|
||||||
line: {
|
line: {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export type PluginRuntimeCore = {
|
|||||||
formatNativeDependencyHint: typeof import("./native-deps.js").formatNativeDependencyHint;
|
formatNativeDependencyHint: typeof import("./native-deps.js").formatNativeDependencyHint;
|
||||||
};
|
};
|
||||||
media: {
|
media: {
|
||||||
loadWebMedia: typeof import("../../../extensions/whatsapp/runtime-api.js").loadWebMedia;
|
loadWebMedia: typeof import("../../media/web-media.js").loadWebMedia;
|
||||||
detectMime: typeof import("../../media/mime.js").detectMime;
|
detectMime: typeof import("../../media/mime.js").detectMime;
|
||||||
mediaKindFromMime: typeof import("../../media/constants.js").mediaKindFromMime;
|
mediaKindFromMime: typeof import("../../media/constants.js").mediaKindFromMime;
|
||||||
isVoiceCompatibleAudio: typeof import("../../media/audio.js").isVoiceCompatibleAudio;
|
isVoiceCompatibleAudio: typeof import("../../media/audio.js").isVoiceCompatibleAudio;
|
||||||
|
|||||||
185
src/plugins/sdk-alias.ts
Normal file
185
src/plugins/sdk-alias.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||||
|
|
||||||
|
type PluginSdkAliasCandidateKind = "dist" | "src";
|
||||||
|
|
||||||
|
export type LoaderModuleResolveParams = {
|
||||||
|
modulePath?: string;
|
||||||
|
argv1?: string;
|
||||||
|
cwd?: string;
|
||||||
|
moduleUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
|
||||||
|
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveLoaderPackageRoot(
|
||||||
|
params: LoaderModuleResolveParams & { modulePath: string },
|
||||||
|
): string | null {
|
||||||
|
const cwd = params.cwd ?? path.dirname(params.modulePath);
|
||||||
|
const fromModulePath = resolveOpenClawPackageRootSync({ cwd });
|
||||||
|
if (fromModulePath) {
|
||||||
|
return fromModulePath;
|
||||||
|
}
|
||||||
|
const argv1 = params.argv1 ?? process.argv[1];
|
||||||
|
const moduleUrl = params.moduleUrl ?? (params.modulePath ? undefined : import.meta.url);
|
||||||
|
return resolveOpenClawPackageRootSync({
|
||||||
|
cwd,
|
||||||
|
...(argv1 ? { argv1 } : {}),
|
||||||
|
...(moduleUrl ? { moduleUrl } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePluginSdkAliasCandidateOrder(params: {
|
||||||
|
modulePath: string;
|
||||||
|
isProduction: boolean;
|
||||||
|
}): PluginSdkAliasCandidateKind[] {
|
||||||
|
const normalizedModulePath = params.modulePath.replace(/\\/g, "/");
|
||||||
|
const isDistRuntime = normalizedModulePath.includes("/dist/");
|
||||||
|
return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listPluginSdkAliasCandidates(params: {
|
||||||
|
srcFile: string;
|
||||||
|
distFile: string;
|
||||||
|
modulePath: string;
|
||||||
|
argv1?: string;
|
||||||
|
cwd?: string;
|
||||||
|
moduleUrl?: string;
|
||||||
|
}) {
|
||||||
|
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
|
||||||
|
modulePath: params.modulePath,
|
||||||
|
isProduction: process.env.NODE_ENV === "production",
|
||||||
|
});
|
||||||
|
const packageRoot = resolveLoaderPackageRoot(params);
|
||||||
|
if (packageRoot) {
|
||||||
|
const candidateMap = {
|
||||||
|
src: path.join(packageRoot, "src", "plugin-sdk", params.srcFile),
|
||||||
|
dist: path.join(packageRoot, "dist", "plugin-sdk", params.distFile),
|
||||||
|
} as const;
|
||||||
|
return orderedKinds.map((kind) => candidateMap[kind]);
|
||||||
|
}
|
||||||
|
let cursor = path.dirname(params.modulePath);
|
||||||
|
const candidates: string[] = [];
|
||||||
|
for (let i = 0; i < 6; i += 1) {
|
||||||
|
const candidateMap = {
|
||||||
|
src: path.join(cursor, "src", "plugin-sdk", params.srcFile),
|
||||||
|
dist: path.join(cursor, "dist", "plugin-sdk", params.distFile),
|
||||||
|
} as const;
|
||||||
|
for (const kind of orderedKinds) {
|
||||||
|
candidates.push(candidateMap[kind]);
|
||||||
|
}
|
||||||
|
const parent = path.dirname(cursor);
|
||||||
|
if (parent === cursor) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor = parent;
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePluginSdkAliasFile(params: {
|
||||||
|
srcFile: string;
|
||||||
|
distFile: string;
|
||||||
|
modulePath?: string;
|
||||||
|
argv1?: string;
|
||||||
|
cwd?: string;
|
||||||
|
moduleUrl?: string;
|
||||||
|
}): string | null {
|
||||||
|
try {
|
||||||
|
const modulePath = resolveLoaderModulePath(params);
|
||||||
|
for (const candidate of listPluginSdkAliasCandidates({
|
||||||
|
srcFile: params.srcFile,
|
||||||
|
distFile: params.distFile,
|
||||||
|
modulePath,
|
||||||
|
argv1: params.argv1,
|
||||||
|
cwd: params.cwd,
|
||||||
|
moduleUrl: params.moduleUrl,
|
||||||
|
})) {
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachedPluginSdkExportedSubpaths = new Map<string, string[]>();
|
||||||
|
|
||||||
|
export function listPluginSdkExportedSubpaths(params: { modulePath?: string } = {}): string[] {
|
||||||
|
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
|
||||||
|
const packageRoot = resolveOpenClawPackageRootSync({
|
||||||
|
cwd: path.dirname(modulePath),
|
||||||
|
});
|
||||||
|
if (!packageRoot) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const cached = cachedPluginSdkExportedSubpaths.get(packageRoot);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const pkgRaw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8");
|
||||||
|
const pkg = JSON.parse(pkgRaw) as {
|
||||||
|
exports?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
const subpaths = Object.keys(pkg.exports ?? {})
|
||||||
|
.filter((key) => key.startsWith("./plugin-sdk/"))
|
||||||
|
.map((key) => key.slice("./plugin-sdk/".length))
|
||||||
|
.filter((subpath) => Boolean(subpath) && !subpath.includes("/"))
|
||||||
|
.toSorted();
|
||||||
|
cachedPluginSdkExportedSubpaths.set(packageRoot, subpaths);
|
||||||
|
return subpaths;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePluginSdkScopedAliasMap(
|
||||||
|
params: { modulePath?: string } = {},
|
||||||
|
): Record<string, string> {
|
||||||
|
const aliasMap: Record<string, string> = {};
|
||||||
|
for (const subpath of listPluginSdkExportedSubpaths(params)) {
|
||||||
|
const resolved = resolvePluginSdkAliasFile({
|
||||||
|
srcFile: `${subpath}.ts`,
|
||||||
|
distFile: `${subpath}.js`,
|
||||||
|
modulePath: params.modulePath,
|
||||||
|
});
|
||||||
|
if (resolved) {
|
||||||
|
aliasMap[`openclaw/plugin-sdk/${subpath}`] = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aliasMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
|
||||||
|
return {
|
||||||
|
interopDefault: true,
|
||||||
|
// Prefer Node's native sync ESM loader for built dist/*.js modules so
|
||||||
|
// bundled plugins and plugin-sdk subpaths stay on the canonical module graph.
|
||||||
|
tryNative: true,
|
||||||
|
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
|
||||||
|
...(Object.keys(aliasMap).length > 0
|
||||||
|
? {
|
||||||
|
alias: aliasMap,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldPreferNativeJiti(modulePath: string): boolean {
|
||||||
|
switch (path.extname(modulePath).toLowerCase()) {
|
||||||
|
case ".js":
|
||||||
|
case ".mjs":
|
||||||
|
case ".cjs":
|
||||||
|
case ".json":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user