refactor(plugin-runtime): remove plugin-specific core seams

This commit is contained in:
Peter Steinberger
2026-04-03 13:07:04 +01:00
parent 4846ebce12
commit f59d0eac68
79 changed files with 1062 additions and 2628 deletions

View File

@@ -29,17 +29,17 @@ describe("loadSiblingRuntimeModuleSync", () => {
it("loads a sibling runtime module from the caller directory", () => {
const root = createTempDir();
const moduleUrl = pathToFileURL(
path.join(root, "src", "plugins", "runtime", "runtime-line.js"),
path.join(root, "src", "plugins", "runtime", "runtime.js"),
).href;
writeFile(
path.join(root, "src", "plugins", "runtime", "runtime-line.contract.js"),
path.join(root, "src", "plugins", "runtime", "runtime.contract.js"),
"module.exports = { runtimeLine: { source: 'sibling' } };",
);
const loaded = loadSiblingRuntimeModuleSync<{ runtimeLine: { source: string } }>({
moduleUrl,
relativeBase: "./runtime-line.contract",
relativeBase: "./runtime.contract",
});
expect(loaded.runtimeLine.source).toBe("sibling");
@@ -50,13 +50,13 @@ describe("loadSiblingRuntimeModuleSync", () => {
const moduleUrl = pathToFileURL(path.join(root, "dist", "runtime-9DLN_Ai5.js")).href;
writeFile(
path.join(root, "dist", "plugins", "runtime", "runtime-line.contract.js"),
path.join(root, "dist", "plugins", "runtime", "runtime.contract.js"),
"module.exports = { runtimeLine: { source: 'dist-runtime' } };",
);
const loaded = loadSiblingRuntimeModuleSync<{ runtimeLine: { source: string } }>({
moduleUrl,
relativeBase: "./runtime-line.contract",
relativeBase: "./runtime.contract",
});
expect(loaded.runtimeLine.source).toBe("dist-runtime");
@@ -69,8 +69,8 @@ describe("loadSiblingRuntimeModuleSync", () => {
expect(() =>
loadSiblingRuntimeModuleSync({
moduleUrl,
relativeBase: "./runtime-line.contract",
relativeBase: "./runtime.contract",
}),
).toThrow("Unable to resolve runtime module ./runtime-line.contract");
).toThrow("Unable to resolve runtime module ./runtime.contract");
});
});

View File

@@ -61,17 +61,7 @@ import {
readChannelAllowFromStore,
upsertChannelPairingRequest,
} from "../../pairing/pairing-store.js";
import {
setThreadBindingIdleTimeoutBySessionKey,
setThreadBindingMaxAgeBySessionKey,
} from "../../plugin-sdk/discord-runtime-surface.js";
import { buildAgentSessionKey, resolveAgentRoute } from "../../routing/resolve-route.js";
import { defineCachedValue } from "./runtime-cache.js";
import { createRuntimeDiscord } from "./runtime-discord.js";
import { createRuntimeLine } from "./runtime-line.js";
import { createRuntimeMatrix } from "./runtime-matrix.js";
import { createRuntimeSignal } from "./runtime-signal.js";
import { createRuntimeSlack } from "./runtime-slack.js";
import type { PluginRuntime } from "./types.js";
export function createRuntimeChannel(): PluginRuntime["channel"] {
@@ -161,63 +151,22 @@ export function createRuntimeChannel(): PluginRuntime["channel"] {
loadAdapter: loadChannelOutboundAdapter,
},
threadBindings: {
setIdleTimeoutBySessionKey: ({ channelId, targetSessionKey, accountId, idleTimeoutMs }) => {
switch (channelId) {
case "discord":
return setThreadBindingIdleTimeoutBySessionKey({
targetSessionKey,
accountId,
idleTimeoutMs,
});
case "matrix":
return setChannelConversationBindingIdleTimeoutBySessionKey({
channelId,
targetSessionKey,
accountId: accountId ?? "",
idleTimeoutMs,
});
case "telegram":
return setChannelConversationBindingIdleTimeoutBySessionKey({
channelId,
targetSessionKey,
accountId,
idleTimeoutMs,
});
}
},
setMaxAgeBySessionKey: ({ channelId, targetSessionKey, accountId, maxAgeMs }) => {
switch (channelId) {
case "discord":
return setThreadBindingMaxAgeBySessionKey({
targetSessionKey,
accountId,
maxAgeMs,
});
case "matrix":
return setChannelConversationBindingMaxAgeBySessionKey({
channelId,
targetSessionKey,
accountId: accountId ?? "",
maxAgeMs,
});
case "telegram":
return setChannelConversationBindingMaxAgeBySessionKey({
channelId,
targetSessionKey,
accountId,
maxAgeMs,
});
}
},
setIdleTimeoutBySessionKey: ({ channelId, targetSessionKey, accountId, idleTimeoutMs }) =>
setChannelConversationBindingIdleTimeoutBySessionKey({
channelId,
targetSessionKey,
accountId,
idleTimeoutMs,
}),
setMaxAgeBySessionKey: ({ channelId, targetSessionKey, accountId, maxAgeMs }) =>
setChannelConversationBindingMaxAgeBySessionKey({
channelId,
targetSessionKey,
accountId,
maxAgeMs,
}),
},
} satisfies Omit<PluginRuntime["channel"], "discord" | "slack" | "matrix" | "signal" | "line"> &
Partial<Pick<PluginRuntime["channel"], "discord" | "slack" | "matrix" | "signal" | "line">>;
defineCachedValue(channelRuntime, "discord", createRuntimeDiscord);
defineCachedValue(channelRuntime, "slack", createRuntimeSlack);
defineCachedValue(channelRuntime, "matrix", createRuntimeMatrix);
defineCachedValue(channelRuntime, "signal", createRuntimeSignal);
defineCachedValue(channelRuntime, "line", createRuntimeLine);
} satisfies PluginRuntime["channel"];
return channelRuntime as PluginRuntime["channel"];
}

View File

@@ -1,64 +0,0 @@
import {
auditDiscordChannelPermissions as auditDiscordChannelPermissionsImpl,
listDiscordDirectoryGroupsLive as listDiscordDirectoryGroupsLiveImpl,
listDiscordDirectoryPeersLive as listDiscordDirectoryPeersLiveImpl,
monitorDiscordProvider as monitorDiscordProviderImpl,
probeDiscord as probeDiscordImpl,
resolveDiscordChannelAllowlist as resolveDiscordChannelAllowlistImpl,
resolveDiscordUserAllowlist as resolveDiscordUserAllowlistImpl,
createThreadDiscord as createThreadDiscordImpl,
deleteMessageDiscord as deleteMessageDiscordImpl,
editChannelDiscord as editChannelDiscordImpl,
editMessageDiscord as editMessageDiscordImpl,
pinMessageDiscord as pinMessageDiscordImpl,
sendDiscordComponentMessage as sendDiscordComponentMessageImpl,
sendMessageDiscord as sendMessageDiscordImpl,
sendPollDiscord as sendPollDiscordImpl,
sendTypingDiscord as sendTypingDiscordImpl,
unpinMessageDiscord as unpinMessageDiscordImpl,
} from "../../plugin-sdk/discord-runtime-surface.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
type RuntimeDiscordOps = Pick<
PluginRuntimeChannel["discord"],
| "auditChannelPermissions"
| "listDirectoryGroupsLive"
| "listDirectoryPeersLive"
| "probeDiscord"
| "resolveChannelAllowlist"
| "resolveUserAllowlist"
| "sendComponentMessage"
| "sendMessageDiscord"
| "sendPollDiscord"
| "monitorDiscordProvider"
> & {
typing: Pick<PluginRuntimeChannel["discord"]["typing"], "pulse">;
conversationActions: Pick<
PluginRuntimeChannel["discord"]["conversationActions"],
"editMessage" | "deleteMessage" | "pinMessage" | "unpinMessage" | "createThread" | "editChannel"
>;
};
export const runtimeDiscordOps = {
auditChannelPermissions: auditDiscordChannelPermissionsImpl,
listDirectoryGroupsLive: listDiscordDirectoryGroupsLiveImpl,
listDirectoryPeersLive: listDiscordDirectoryPeersLiveImpl,
probeDiscord: probeDiscordImpl,
resolveChannelAllowlist: resolveDiscordChannelAllowlistImpl,
resolveUserAllowlist: resolveDiscordUserAllowlistImpl,
sendComponentMessage: sendDiscordComponentMessageImpl,
sendMessageDiscord: sendMessageDiscordImpl,
sendPollDiscord: sendPollDiscordImpl,
monitorDiscordProvider: monitorDiscordProviderImpl,
typing: {
pulse: sendTypingDiscordImpl,
},
conversationActions: {
editMessage: editMessageDiscordImpl,
deleteMessage: deleteMessageDiscordImpl,
pinMessage: pinMessageDiscordImpl,
unpinMessage: unpinMessageDiscordImpl,
createThread: createThreadDiscordImpl,
editChannel: editChannelDiscordImpl,
},
} satisfies RuntimeDiscordOps;

View File

@@ -1,122 +0,0 @@
import {
discordMessageActions,
getThreadBindingManager,
resolveThreadBindingIdleTimeoutMs,
resolveThreadBindingInactivityExpiresAt,
resolveThreadBindingMaxAgeExpiresAt,
resolveThreadBindingMaxAgeMs,
setThreadBindingIdleTimeoutBySessionKey,
setThreadBindingMaxAgeBySessionKey,
unbindThreadBindingsBySessionKey,
} from "../../plugin-sdk/discord-runtime-surface.js";
import {
createLazyRuntimeMethodBinder,
createLazyRuntimeSurface,
} from "../../shared/lazy-runtime.js";
import { createDiscordTypingLease } from "./runtime-discord-typing.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
const loadRuntimeDiscordOps = createLazyRuntimeSurface(
() => import("./runtime-discord-ops.runtime.js"),
({ runtimeDiscordOps }) => runtimeDiscordOps,
);
const bindDiscordRuntimeMethod = createLazyRuntimeMethodBinder(loadRuntimeDiscordOps);
const auditChannelPermissionsLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.auditChannelPermissions,
);
const listDirectoryGroupsLiveLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.listDirectoryGroupsLive,
);
const listDirectoryPeersLiveLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.listDirectoryPeersLive,
);
const probeDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.probeDiscord,
);
const resolveChannelAllowlistLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.resolveChannelAllowlist,
);
const resolveUserAllowlistLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.resolveUserAllowlist,
);
const sendComponentMessageLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendComponentMessage,
);
const sendMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendMessageDiscord,
);
const sendPollDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendPollDiscord,
);
const monitorDiscordProviderLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.monitorDiscordProvider,
);
const sendTypingDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.typing.pulse,
);
const editMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.editMessage,
);
const deleteMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.deleteMessage,
);
const pinMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.pinMessage,
);
const unpinMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.unpinMessage,
);
const createThreadDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.createThread,
);
const editChannelDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.editChannel,
);
export function createRuntimeDiscord(): PluginRuntimeChannel["discord"] {
return {
messageActions: discordMessageActions,
auditChannelPermissions: auditChannelPermissionsLazy,
listDirectoryGroupsLive: listDirectoryGroupsLiveLazy,
listDirectoryPeersLive: listDirectoryPeersLiveLazy,
probeDiscord: probeDiscordLazy,
resolveChannelAllowlist: resolveChannelAllowlistLazy,
resolveUserAllowlist: resolveUserAllowlistLazy,
sendComponentMessage: sendComponentMessageLazy,
sendMessageDiscord: sendMessageDiscordLazy,
sendPollDiscord: sendPollDiscordLazy,
monitorDiscordProvider: monitorDiscordProviderLazy,
threadBindings: {
getManager: getThreadBindingManager,
resolveIdleTimeoutMs: resolveThreadBindingIdleTimeoutMs,
resolveInactivityExpiresAt: resolveThreadBindingInactivityExpiresAt,
resolveMaxAgeMs: resolveThreadBindingMaxAgeMs,
resolveMaxAgeExpiresAt: resolveThreadBindingMaxAgeExpiresAt,
setIdleTimeoutBySessionKey: setThreadBindingIdleTimeoutBySessionKey,
setMaxAgeBySessionKey: setThreadBindingMaxAgeBySessionKey,
unbindBySessionKey: unbindThreadBindingsBySessionKey,
},
typing: {
pulse: sendTypingDiscordLazy,
start: async ({ channelId, accountId, cfg, intervalMs }) =>
await createDiscordTypingLease({
channelId,
accountId,
cfg,
intervalMs,
pulse: async ({ channelId, accountId, cfg }) =>
void (await sendTypingDiscordLazy(channelId, { accountId, cfg })),
}),
},
conversationActions: {
editMessage: editMessageDiscordLazy,
deleteMessage: deleteMessageDiscordLazy,
pinMessage: pinMessageDiscordLazy,
unpinMessage: unpinMessageDiscordLazy,
createThread: createThreadDiscordLazy,
editChannel: editChannelDiscordLazy,
},
};
}

View File

@@ -1,38 +0,0 @@
import {
buildTemplateMessageFromPayload,
createQuickReplyItems,
monitorLineProvider,
probeLineBot,
pushFlexMessage,
pushLocationMessage,
pushMessageLine,
pushMessagesLine,
pushTemplateMessage,
pushTextMessageWithQuickReplies,
sendMessageLine,
} from "../../plugin-sdk/line-runtime.js";
import {
listLineAccountIds,
normalizeAccountId,
resolveDefaultLineAccountId,
resolveLineAccount,
} from "../../plugin-sdk/line.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export const runtimeLine = {
listLineAccountIds,
resolveDefaultLineAccountId,
resolveLineAccount,
normalizeAccountId,
probeLineBot,
sendMessageLine,
pushMessageLine,
pushMessagesLine,
pushFlexMessage,
pushTemplateMessage,
pushLocationMessage,
pushTextMessageWithQuickReplies,
createQuickReplyItems,
buildTemplateMessageFromPayload,
monitorLineProvider,
} satisfies PluginRuntimeChannel["line"];

View File

@@ -1,11 +0,0 @@
export {
monitorLineProvider,
probeLineBot,
pushFlexMessage,
pushLocationMessage,
pushMessageLine,
pushMessagesLine,
pushTemplateMessage,
pushTextMessageWithQuickReplies,
sendMessageLine,
} from "../../plugin-sdk/line-runtime.js";

View File

@@ -1,46 +0,0 @@
import { loadSiblingRuntimeModuleSync } from "./local-runtime-module.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
type RuntimeLineModule = {
runtimeLine: PluginRuntimeChannel["line"];
};
let cachedRuntimeLineModule: RuntimeLineModule | null = null;
function loadRuntimeLineModule(): RuntimeLineModule {
cachedRuntimeLineModule ??= loadSiblingRuntimeModuleSync<RuntimeLineModule>({
moduleUrl: import.meta.url,
relativeBase: "./runtime-line.contract",
});
return cachedRuntimeLineModule;
}
export function createRuntimeLine(): PluginRuntimeChannel["line"] {
return {
listLineAccountIds: (...args) =>
loadRuntimeLineModule().runtimeLine.listLineAccountIds(...args),
resolveDefaultLineAccountId: (...args) =>
loadRuntimeLineModule().runtimeLine.resolveDefaultLineAccountId(...args),
resolveLineAccount: (...args) =>
loadRuntimeLineModule().runtimeLine.resolveLineAccount(...args),
normalizeAccountId: (...args) =>
loadRuntimeLineModule().runtimeLine.normalizeAccountId(...args),
probeLineBot: (...args) => loadRuntimeLineModule().runtimeLine.probeLineBot(...args),
sendMessageLine: (...args) => loadRuntimeLineModule().runtimeLine.sendMessageLine(...args),
pushMessageLine: (...args) => loadRuntimeLineModule().runtimeLine.pushMessageLine(...args),
pushMessagesLine: (...args) => loadRuntimeLineModule().runtimeLine.pushMessagesLine(...args),
pushFlexMessage: (...args) => loadRuntimeLineModule().runtimeLine.pushFlexMessage(...args),
pushTemplateMessage: (...args) =>
loadRuntimeLineModule().runtimeLine.pushTemplateMessage(...args),
pushLocationMessage: (...args) =>
loadRuntimeLineModule().runtimeLine.pushLocationMessage(...args),
pushTextMessageWithQuickReplies: (...args) =>
loadRuntimeLineModule().runtimeLine.pushTextMessageWithQuickReplies(...args),
createQuickReplyItems: (...args) =>
loadRuntimeLineModule().runtimeLine.createQuickReplyItems(...args),
buildTemplateMessageFromPayload: (...args) =>
loadRuntimeLineModule().runtimeLine.buildTemplateMessageFromPayload(...args),
monitorLineProvider: (...args) =>
loadRuntimeLineModule().runtimeLine.monitorLineProvider(...args),
};
}

View File

@@ -1,68 +0,0 @@
import { createJiti } from "jiti";
import type { MatrixRuntimeBoundaryModule } from "./runtime-matrix-surface.js";
import {
loadPluginBoundaryModuleWithJiti,
resolvePluginRuntimeModulePath,
resolvePluginRuntimeRecord,
} from "./runtime-plugin-boundary.js";
const MATRIX_PLUGIN_ID = "matrix";
type MatrixPluginRecord = {
rootDir?: string;
source: string;
};
let cachedModulePath: string | null = null;
let cachedModule: MatrixRuntimeBoundaryModule | null = null;
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
function resolveMatrixPluginRecord(): MatrixPluginRecord | null {
return resolvePluginRuntimeRecord(MATRIX_PLUGIN_ID) as MatrixPluginRecord | null;
}
function resolveMatrixRuntimeModulePath(record: MatrixPluginRecord): string | null {
return resolvePluginRuntimeModulePath(record, "runtime-api");
}
function loadMatrixModule(): MatrixRuntimeBoundaryModule | null {
const record = resolveMatrixPluginRecord();
if (!record) {
return null;
}
const modulePath = resolveMatrixRuntimeModulePath(record);
if (!modulePath) {
return null;
}
if (cachedModule && cachedModulePath === modulePath) {
return cachedModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<MatrixRuntimeBoundaryModule>(
modulePath,
jitiLoaders,
);
cachedModulePath = modulePath;
cachedModule = loaded;
return loaded;
}
export function setMatrixThreadBindingIdleTimeoutBySessionKey(
...args: Parameters<MatrixRuntimeBoundaryModule["setMatrixThreadBindingIdleTimeoutBySessionKey"]>
): ReturnType<MatrixRuntimeBoundaryModule["setMatrixThreadBindingIdleTimeoutBySessionKey"]> {
const fn = loadMatrixModule()?.setMatrixThreadBindingIdleTimeoutBySessionKey;
if (typeof fn !== "function") {
return [];
}
return fn(...args);
}
export function setMatrixThreadBindingMaxAgeBySessionKey(
...args: Parameters<MatrixRuntimeBoundaryModule["setMatrixThreadBindingMaxAgeBySessionKey"]>
): ReturnType<MatrixRuntimeBoundaryModule["setMatrixThreadBindingMaxAgeBySessionKey"]> {
const fn = loadMatrixModule()?.setMatrixThreadBindingMaxAgeBySessionKey;
if (typeof fn !== "function") {
return [];
}
return fn(...args);
}

View File

@@ -1,178 +0,0 @@
// Narrow plugin-sdk surface for the bundled Matrix plugin.
// Keep this list additive and scoped to the runtime contract only.
import { createOptionalChannelSetupSurface } from "../../plugin-sdk/channel-setup.js";
export {
createActionGate,
jsonResult,
readNumberParam,
readReactionParams,
readStringArrayParam,
readStringParam,
} from "../../agents/tools/common.js";
export type { ReplyPayload } from "../../auto-reply/types.js";
export { resolveAckReaction } from "../../agents/identity.js";
export {
compileAllowlist,
resolveCompiledAllowlistMatch,
resolveAllowlistCandidates,
resolveAllowlistMatchByCandidates,
} from "../../channels/allowlist-match.js";
export {
addAllowlistUserEntriesFromConfigEntry,
buildAllowlistResolutionSummary,
canonicalizeAllowlistWithResolvedIds,
mergeAllowlist,
patchAllowlistUsersInConfigEntries,
summarizeMapping,
} from "../../channels/allowlists/resolve-utils.js";
export { ensureConfiguredAcpBindingReady } from "../../acp/persistent-bindings.lifecycle.js";
export { resolveConfiguredAcpBindingRecord } from "../../acp/persistent-bindings.resolve.js";
export { resolveControlCommandGate } from "../../channels/command-gating.js";
export type { NormalizedLocation } from "../../channels/location.js";
export { formatLocationText, toLocationContext } from "../../channels/location.js";
export { logInboundDrop, logTypingFailure } from "../../channels/logging.js";
export type { AllowlistMatch } from "../../channels/plugins/allowlist-match.js";
export { formatAllowlistMatchMeta } from "../../channels/plugins/allowlist-match.js";
export {
buildChannelKeyCandidates,
resolveChannelEntryMatch,
} from "../../channels/plugins/channel-config.js";
export { createAccountListHelpers } from "../../channels/plugins/account-helpers.js";
export {
deleteAccountFromConfigSection,
setAccountEnabledInConfigSection,
} from "../../channels/plugins/config-helpers.js";
export { buildChannelConfigSchema } from "../../channels/plugins/config-schema.js";
export { formatPairingApproveHint } from "../../channels/plugins/helpers.js";
export {
buildSingleChannelSecretPromptState,
addWildcardAllowFrom,
mergeAllowFromEntries,
promptAccountId,
promptSingleChannelSecretInput,
setTopLevelChannelGroupPolicy,
} from "../../channels/plugins/setup-wizard-helpers.js";
export { promptChannelAccessConfig } from "../../channels/plugins/setup-group-access.js";
export { PAIRING_APPROVED_MESSAGE } from "../../channels/plugins/pairing-message.js";
export {
applyAccountNameToChannelSection,
moveSingleAccountChannelSectionToDefaultAccount,
} from "../../channels/plugins/setup-helpers.js";
export type {
BaseProbeResult,
ChannelDirectoryEntry,
ChannelGroupContext,
ChannelMessageActionAdapter,
ChannelMessageActionContext,
ChannelMessageActionName,
ChannelMessageToolDiscovery,
ChannelMessageToolSchemaContribution,
ChannelOutboundAdapter,
ChannelResolveKind,
ChannelResolveResult,
ChannelSetupInput,
ChannelToolSend,
} from "../../channels/plugins/types.js";
export type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
export { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
export { resolveThreadBindingFarewellText } from "../../channels/thread-bindings-messages.js";
export {
resolveThreadBindingIdleTimeoutMsForChannel,
resolveThreadBindingMaxAgeMsForChannel,
} from "../../channels/thread-bindings-policy.js";
export {
setMatrixThreadBindingIdleTimeoutBySessionKey,
setMatrixThreadBindingMaxAgeBySessionKey,
} from "../../plugin-sdk/matrix-thread-bindings.js";
export { createTypingCallbacks } from "../../channels/typing.js";
export { createChannelReplyPipeline } from "../../plugin-sdk/channel-reply-pipeline.js";
export type { OpenClawConfig } from "../../config/config.js";
export {
GROUP_POLICY_BLOCKED_LABEL,
resolveAllowlistProviderRuntimeGroupPolicy,
resolveDefaultGroupPolicy,
warnMissingProviderGroupPolicyFallbackOnce,
} from "../../config/runtime-group-policy.js";
export type {
DmPolicy,
GroupPolicy,
GroupToolPolicyConfig,
MarkdownTableMode,
} from "../../config/types.js";
export type { SecretInput } from "../../plugin-sdk/secret-input.js";
export {
buildSecretInputSchema,
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
normalizeSecretInputString,
} from "../../plugin-sdk/secret-input.js";
export { ToolPolicySchema } from "../../config/zod-schema.agent-runtime.js";
export { MarkdownConfigSchema } from "../../config/zod-schema.core.js";
export { formatZonedTimestamp } from "../../infra/format-time/format-datetime.js";
export { fetchWithSsrFGuard } from "../../infra/net/fetch-guard.js";
export { maybeCreateMatrixMigrationSnapshot } from "../../infra/matrix-migration-snapshot.js";
export {
getSessionBindingService,
registerSessionBindingAdapter,
unregisterSessionBindingAdapter,
} from "../../infra/outbound/session-binding-service.js";
export { resolveOutboundSendDep } from "../../infra/outbound/send-deps.js";
export type {
BindingTargetKind,
SessionBindingRecord,
} from "../../infra/outbound/session-binding-service.js";
export { isPrivateOrLoopbackHost } from "../../gateway/net.js";
export { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
export { emptyPluginConfigSchema } from "../config-schema.js";
export type { PluginRuntime, RuntimeLogger } from "./types.js";
export type { OpenClawPluginApi } from "../types.js";
export type { PollInput } from "../../polls.js";
export { normalizePollInput } from "../../polls.js";
export {
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
normalizeOptionalAccountId,
resolveAgentIdFromSessionKey,
} from "../../routing/session-key.js";
export type { RuntimeEnv } from "../../runtime.js";
export { normalizeStringEntries } from "../../shared/string-normalization.js";
export { formatDocsLink } from "../../terminal/links.js";
export { redactSensitiveText } from "../../logging/redact.js";
export type { WizardPrompter } from "../../wizard/prompts.js";
export {
evaluateGroupRouteAccessForPolicy,
resolveSenderScopedGroupPolicy,
} from "../../plugin-sdk/group-access.js";
export { createChannelPairingController } from "../../plugin-sdk/channel-pairing.js";
export { readJsonFileWithFallback, writeJsonFileAtomically } from "../../plugin-sdk/json-store.js";
export { formatResolvedUnresolvedNote } from "../../plugin-sdk/resolution-notes.js";
export { runPluginCommandWithTimeout } from "../../plugin-sdk/run-command.js";
export { createLoggerBackedRuntime, resolveRuntimeEnv } from "../../plugin-sdk/runtime.js";
export { dispatchReplyFromConfigWithSettledDispatcher } from "../../plugin-sdk/inbound-reply-dispatch.js";
export {
buildProbeChannelStatusSummary,
collectStatusIssuesFromLastError,
} from "../../plugin-sdk/status-helpers.js";
export {
resolveMatrixAccountStorageRoot,
resolveMatrixCredentialsDir,
resolveMatrixCredentialsPath,
resolveMatrixLegacyFlatStoragePaths,
} from "../../plugin-sdk/matrix-helper.js";
export { getMatrixScopedEnvVarNames } from "../../plugin-sdk/matrix-helper.js";
export {
requiresExplicitMatrixDefaultAccount,
resolveMatrixDefaultOrOnlyAccountId,
} from "../../plugin-sdk/matrix-helper.js";
const matrixSetup = createOptionalChannelSetupSurface({
channel: "matrix",
label: "Matrix",
npmSpec: "@openclaw/matrix",
docsPath: "/channels/matrix",
});
export const matrixSetupWizard = matrixSetup.setupWizard;
export const matrixSetupAdapter = matrixSetup.setupAdapter;

View File

@@ -1,22 +0,0 @@
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
export type MatrixThreadBindingIdleTimeoutParams = {
accountId: string;
targetSessionKey: string;
idleTimeoutMs: number;
};
export type MatrixThreadBindingMaxAgeParams = {
accountId: string;
targetSessionKey: string;
maxAgeMs: number;
};
export type MatrixRuntimeBoundaryModule = {
setMatrixThreadBindingIdleTimeoutBySessionKey: (
params: MatrixThreadBindingIdleTimeoutParams,
) => SessionBindingRecord[];
setMatrixThreadBindingMaxAgeBySessionKey: (
params: MatrixThreadBindingMaxAgeParams,
) => SessionBindingRecord[];
};

View File

@@ -1,14 +0,0 @@
import {
setMatrixThreadBindingIdleTimeoutBySessionKey,
setMatrixThreadBindingMaxAgeBySessionKey,
} from "./runtime-matrix-boundary.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export function createRuntimeMatrix(): PluginRuntimeChannel["matrix"] {
return {
threadBindings: {
setIdleTimeoutBySessionKey: setMatrixThreadBindingIdleTimeoutBySessionKey,
setMaxAgeBySessionKey: setMatrixThreadBindingMaxAgeBySessionKey,
},
};
}

View File

@@ -16,6 +16,13 @@ type PluginRuntimeRecord = {
source: string;
};
type CachedPluginBoundaryLoaderParams = {
pluginId: string;
entryBaseName: string;
required?: boolean;
missingLabel?: string;
};
export function readPluginBoundaryConfigSafely() {
try {
return loadConfig();
@@ -104,3 +111,47 @@ export function loadPluginBoundaryModuleWithJiti<TModule>(
): TModule {
return getPluginBoundaryJiti(modulePath, loaders)(modulePath) as TModule;
}
export function createCachedPluginBoundaryModuleLoader<TModule>(
params: CachedPluginBoundaryLoaderParams,
): () => TModule | null {
let cachedModulePath: string | null = null;
let cachedModule: TModule | null = null;
const loaders = new Map<boolean, ReturnType<typeof createJiti>>();
return () => {
const missingLabel = params.missingLabel ?? `${params.pluginId} plugin runtime`;
const record = resolvePluginRuntimeRecord(
params.pluginId,
params.required
? () => {
throw new Error(`${missingLabel} is unavailable: missing plugin '${params.pluginId}'`);
}
: undefined,
);
if (!record) {
return null;
}
const modulePath = resolvePluginRuntimeModulePath(
record,
params.entryBaseName,
params.required
? () => {
throw new Error(
`${missingLabel} is unavailable: missing ${params.entryBaseName} for plugin '${params.pluginId}'`,
);
}
: undefined,
);
if (!modulePath) {
return null;
}
if (cachedModule && cachedModulePath === modulePath) {
return cachedModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<TModule>(modulePath, loaders);
cachedModulePath = modulePath;
cachedModule = loaded;
return loaded;
};
}

View File

@@ -1,16 +0,0 @@
import {
monitorSignalProvider,
probeSignal,
signalMessageActions,
sendMessageSignal,
} from "../../plugin-sdk/signal-runtime-surface.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export function createRuntimeSignal(): PluginRuntimeChannel["signal"] {
return {
probeSignal,
sendMessageSignal,
monitorSignalProvider,
messageActions: signalMessageActions,
};
}

View File

@@ -1,34 +0,0 @@
import {
listSlackDirectoryGroupsLive as listSlackDirectoryGroupsLiveImpl,
listSlackDirectoryPeersLive as listSlackDirectoryPeersLiveImpl,
monitorSlackProvider as monitorSlackProviderImpl,
probeSlack as probeSlackImpl,
resolveSlackChannelAllowlist as resolveSlackChannelAllowlistImpl,
resolveSlackUserAllowlist as resolveSlackUserAllowlistImpl,
sendMessageSlack as sendMessageSlackImpl,
handleSlackAction as handleSlackActionImpl,
} from "../../plugin-sdk/slack-runtime-surface.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
type RuntimeSlackOps = Pick<
PluginRuntimeChannel["slack"],
| "listDirectoryGroupsLive"
| "listDirectoryPeersLive"
| "probeSlack"
| "resolveChannelAllowlist"
| "resolveUserAllowlist"
| "sendMessageSlack"
| "monitorSlackProvider"
| "handleSlackAction"
>;
export const runtimeSlackOps = {
listDirectoryGroupsLive: listSlackDirectoryGroupsLiveImpl,
listDirectoryPeersLive: listSlackDirectoryPeersLiveImpl,
probeSlack: probeSlackImpl,
resolveChannelAllowlist: resolveSlackChannelAllowlistImpl,
resolveUserAllowlist: resolveSlackUserAllowlistImpl,
sendMessageSlack: sendMessageSlackImpl,
monitorSlackProvider: monitorSlackProviderImpl,
handleSlackAction: handleSlackActionImpl,
} satisfies RuntimeSlackOps;

View File

@@ -1,48 +0,0 @@
import {
createLazyRuntimeMethodBinder,
createLazyRuntimeSurface,
} from "../../shared/lazy-runtime.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
const loadRuntimeSlackOps = createLazyRuntimeSurface(
() => import("./runtime-slack-ops.runtime.js"),
({ runtimeSlackOps }) => runtimeSlackOps,
);
const bindSlackRuntimeMethod = createLazyRuntimeMethodBinder(loadRuntimeSlackOps);
const listDirectoryGroupsLiveLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.listDirectoryGroupsLive,
);
const listDirectoryPeersLiveLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.listDirectoryPeersLive,
);
const probeSlackLazy = bindSlackRuntimeMethod((runtimeSlackOps) => runtimeSlackOps.probeSlack);
const resolveChannelAllowlistLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.resolveChannelAllowlist,
);
const resolveUserAllowlistLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.resolveUserAllowlist,
);
const sendMessageSlackLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.sendMessageSlack,
);
const monitorSlackProviderLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.monitorSlackProvider,
);
const handleSlackActionLazy = bindSlackRuntimeMethod(
(runtimeSlackOps) => runtimeSlackOps.handleSlackAction,
);
export function createRuntimeSlack(): PluginRuntimeChannel["slack"] {
return {
listDirectoryGroupsLive: listDirectoryGroupsLiveLazy,
listDirectoryPeersLive: listDirectoryPeersLiveLazy,
probeSlack: probeSlackLazy,
resolveChannelAllowlist: resolveChannelAllowlistLazy,
resolveUserAllowlist: resolveUserAllowlistLazy,
sendMessageSlack: sendMessageSlackLazy,
monitorSlackProvider: monitorSlackProviderLazy,
handleSlackAction: handleSlackActionLazy,
};
}

View File

@@ -1,286 +0,0 @@
import { createJiti } from "jiti";
type WhatsAppHeavyRuntimeModule = typeof import("@openclaw/whatsapp/runtime-api.js");
type WhatsAppLightRuntimeModule = typeof import("@openclaw/whatsapp/light-runtime-api.js");
import {
getDefaultLocalRoots as getDefaultLocalRootsImpl,
loadWebMedia as loadWebMediaImpl,
loadWebMediaRaw as loadWebMediaRawImpl,
optimizeImageToJpeg as optimizeImageToJpegImpl,
} from "../../media/web-media.js";
import {
loadPluginBoundaryModuleWithJiti,
resolvePluginRuntimeModulePath,
resolvePluginRuntimeRecord,
} from "./runtime-plugin-boundary.js";
const WHATSAPP_PLUGIN_ID = "whatsapp";
type WhatsAppPluginRecord = {
origin: string;
rootDir?: string;
source: string;
};
let cachedHeavyModulePath: string | null = null;
let cachedHeavyModule: WhatsAppHeavyRuntimeModule | null = null;
let cachedLightModulePath: string | null = null;
let cachedLightModule: WhatsAppLightRuntimeModule | null = null;
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
function resolveWhatsAppPluginRecord(): WhatsAppPluginRecord {
return resolvePluginRuntimeRecord(WHATSAPP_PLUGIN_ID, () => {
throw new Error(
`WhatsApp plugin runtime is unavailable: missing plugin '${WHATSAPP_PLUGIN_ID}'`,
);
}) as WhatsAppPluginRecord;
}
function resolveWhatsAppRuntimeModulePath(
record: WhatsAppPluginRecord,
entryBaseName: "light-runtime-api" | "runtime-api",
): string {
const modulePath = resolvePluginRuntimeModulePath(record, entryBaseName, () => {
throw new Error(
`WhatsApp plugin runtime is unavailable: missing ${entryBaseName} for plugin '${WHATSAPP_PLUGIN_ID}'`,
);
});
if (!modulePath) {
throw new Error(
`WhatsApp plugin runtime is unavailable: missing ${entryBaseName} for plugin '${WHATSAPP_PLUGIN_ID}'`,
);
}
return modulePath;
}
function loadCurrentHeavyModuleSync(): WhatsAppHeavyRuntimeModule {
const modulePath = resolveWhatsAppRuntimeModulePath(resolveWhatsAppPluginRecord(), "runtime-api");
return loadPluginBoundaryModuleWithJiti<WhatsAppHeavyRuntimeModule>(modulePath, jitiLoaders);
}
function loadWhatsAppLightModule(): WhatsAppLightRuntimeModule {
const modulePath = resolveWhatsAppRuntimeModulePath(
resolveWhatsAppPluginRecord(),
"light-runtime-api",
);
if (cachedLightModule && cachedLightModulePath === modulePath) {
return cachedLightModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<WhatsAppLightRuntimeModule>(
modulePath,
jitiLoaders,
);
cachedLightModulePath = modulePath;
cachedLightModule = loaded;
return loaded;
}
async function loadWhatsAppHeavyModule(): Promise<WhatsAppHeavyRuntimeModule> {
const record = resolveWhatsAppPluginRecord();
const modulePath = resolveWhatsAppRuntimeModulePath(record, "runtime-api");
if (cachedHeavyModule && cachedHeavyModulePath === modulePath) {
return cachedHeavyModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<WhatsAppHeavyRuntimeModule>(
modulePath,
jitiLoaders,
);
cachedHeavyModulePath = modulePath;
cachedHeavyModule = loaded;
return loaded;
}
function getLightExport<K extends keyof WhatsAppLightRuntimeModule>(
exportName: K,
): NonNullable<WhatsAppLightRuntimeModule[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<WhatsAppLightRuntimeModule[K]>;
}
async function getHeavyExport<K extends keyof WhatsAppHeavyRuntimeModule>(
exportName: K,
): Promise<NonNullable<WhatsAppHeavyRuntimeModule[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<WhatsAppHeavyRuntimeModule[K]>;
}
export function getActiveWebListener(
...args: Parameters<WhatsAppLightRuntimeModule["getActiveWebListener"]>
): ReturnType<WhatsAppLightRuntimeModule["getActiveWebListener"]> {
return getLightExport("getActiveWebListener")(...args);
}
export function getWebAuthAgeMs(
...args: Parameters<WhatsAppLightRuntimeModule["getWebAuthAgeMs"]>
): ReturnType<WhatsAppLightRuntimeModule["getWebAuthAgeMs"]> {
return getLightExport("getWebAuthAgeMs")(...args);
}
export function logWebSelfId(
...args: Parameters<WhatsAppLightRuntimeModule["logWebSelfId"]>
): ReturnType<WhatsAppLightRuntimeModule["logWebSelfId"]> {
return getLightExport("logWebSelfId")(...args);
}
export function loginWeb(
...args: Parameters<WhatsAppHeavyRuntimeModule["loginWeb"]>
): ReturnType<WhatsAppHeavyRuntimeModule["loginWeb"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.loginWeb(...args));
}
export function logoutWeb(
...args: Parameters<WhatsAppLightRuntimeModule["logoutWeb"]>
): ReturnType<WhatsAppLightRuntimeModule["logoutWeb"]> {
return getLightExport("logoutWeb")(...args);
}
export function readWebSelfId(
...args: Parameters<WhatsAppLightRuntimeModule["readWebSelfId"]>
): ReturnType<WhatsAppLightRuntimeModule["readWebSelfId"]> {
return getLightExport("readWebSelfId")(...args);
}
export function webAuthExists(
...args: Parameters<WhatsAppLightRuntimeModule["webAuthExists"]>
): ReturnType<WhatsAppLightRuntimeModule["webAuthExists"]> {
return getLightExport("webAuthExists")(...args);
}
export function sendMessageWhatsApp(
...args: Parameters<WhatsAppHeavyRuntimeModule["sendMessageWhatsApp"]>
): ReturnType<WhatsAppHeavyRuntimeModule["sendMessageWhatsApp"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendMessageWhatsApp(...args));
}
export function sendPollWhatsApp(
...args: Parameters<WhatsAppHeavyRuntimeModule["sendPollWhatsApp"]>
): ReturnType<WhatsAppHeavyRuntimeModule["sendPollWhatsApp"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendPollWhatsApp(...args));
}
export function sendReactionWhatsApp(
...args: Parameters<WhatsAppHeavyRuntimeModule["sendReactionWhatsApp"]>
): ReturnType<WhatsAppHeavyRuntimeModule["sendReactionWhatsApp"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.sendReactionWhatsApp(...args));
}
export function createRuntimeWhatsAppLoginTool(
...args: Parameters<WhatsAppLightRuntimeModule["createWhatsAppLoginTool"]>
): ReturnType<WhatsAppLightRuntimeModule["createWhatsAppLoginTool"]> {
return getLightExport("createWhatsAppLoginTool")(...args);
}
export function createWaSocket(
...args: Parameters<WhatsAppHeavyRuntimeModule["createWaSocket"]>
): ReturnType<WhatsAppHeavyRuntimeModule["createWaSocket"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.createWaSocket(...args));
}
export function formatError(
...args: Parameters<WhatsAppLightRuntimeModule["formatError"]>
): ReturnType<WhatsAppLightRuntimeModule["formatError"]> {
return getLightExport("formatError")(...args);
}
export function getStatusCode(
...args: Parameters<WhatsAppLightRuntimeModule["getStatusCode"]>
): ReturnType<WhatsAppLightRuntimeModule["getStatusCode"]> {
return getLightExport("getStatusCode")(...args);
}
export function pickWebChannel(
...args: Parameters<WhatsAppLightRuntimeModule["pickWebChannel"]>
): ReturnType<WhatsAppLightRuntimeModule["pickWebChannel"]> {
return getLightExport("pickWebChannel")(...args);
}
export function resolveWaWebAuthDir(): WhatsAppLightRuntimeModule["WA_WEB_AUTH_DIR"] {
return getLightExport("WA_WEB_AUTH_DIR");
}
export async function handleWhatsAppAction(
...args: Parameters<WhatsAppHeavyRuntimeModule["handleWhatsAppAction"]>
): ReturnType<WhatsAppHeavyRuntimeModule["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<WhatsAppHeavyRuntimeModule["monitorWebChannel"]>
): ReturnType<WhatsAppHeavyRuntimeModule["monitorWebChannel"]> {
return loadWhatsAppHeavyModule().then((loaded) => loaded.monitorWebChannel(...args));
}
export async function monitorWebInbox(
...args: Parameters<WhatsAppHeavyRuntimeModule["monitorWebInbox"]>
): ReturnType<WhatsAppHeavyRuntimeModule["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<WhatsAppHeavyRuntimeModule["runWebHeartbeatOnce"]>
): ReturnType<WhatsAppHeavyRuntimeModule["runWebHeartbeatOnce"]> {
return (await getHeavyExport("runWebHeartbeatOnce"))(...args);
}
export async function startWebLoginWithQr(
...args: Parameters<WhatsAppHeavyRuntimeModule["startWebLoginWithQr"]>
): ReturnType<WhatsAppHeavyRuntimeModule["startWebLoginWithQr"]> {
return (await getHeavyExport("startWebLoginWithQr"))(...args);
}
export async function waitForWaConnection(
...args: Parameters<WhatsAppHeavyRuntimeModule["waitForWaConnection"]>
): ReturnType<WhatsAppHeavyRuntimeModule["waitForWaConnection"]> {
return (await getHeavyExport("waitForWaConnection"))(...args);
}
export async function waitForWebLogin(
...args: Parameters<WhatsAppHeavyRuntimeModule["waitForWebLogin"]>
): ReturnType<WhatsAppHeavyRuntimeModule["waitForWebLogin"]> {
return (await getHeavyExport("waitForWebLogin"))(...args);
}
export const extractMediaPlaceholder = (
...args: Parameters<WhatsAppHeavyRuntimeModule["extractMediaPlaceholder"]>
) => loadCurrentHeavyModuleSync().extractMediaPlaceholder(...args);
export const extractText = (...args: Parameters<WhatsAppHeavyRuntimeModule["extractText"]>) =>
loadCurrentHeavyModuleSync().extractText(...args);
export function getDefaultLocalRoots(
...args: Parameters<typeof getDefaultLocalRootsImpl>
): ReturnType<typeof getDefaultLocalRootsImpl> {
return getDefaultLocalRootsImpl(...args);
}
export function resolveHeartbeatRecipients(
...args: Parameters<WhatsAppHeavyRuntimeModule["resolveHeartbeatRecipients"]>
): ReturnType<WhatsAppHeavyRuntimeModule["resolveHeartbeatRecipients"]> {
return loadCurrentHeavyModuleSync().resolveHeartbeatRecipients(...args);
}

View File

@@ -0,0 +1,289 @@
import { createJiti } from "jiti";
type WebChannelHeavyRuntimeModule = typeof import("@openclaw/whatsapp/runtime-api.js");
type WebChannelLightRuntimeModule = typeof import("@openclaw/whatsapp/light-runtime-api.js");
import {
getDefaultLocalRoots as getDefaultLocalRootsImpl,
loadWebMedia as loadWebMediaImpl,
loadWebMediaRaw as loadWebMediaRawImpl,
optimizeImageToJpeg as optimizeImageToJpegImpl,
} from "../../media/web-media.js";
import {
loadPluginBoundaryModuleWithJiti,
resolvePluginRuntimeModulePath,
resolvePluginRuntimeRecord,
} from "./runtime-plugin-boundary.js";
const WEB_CHANNEL_PLUGIN_ID = "whatsapp";
type WebChannelPluginRecord = {
origin: string;
rootDir?: string;
source: string;
};
let cachedHeavyModulePath: string | null = null;
let cachedHeavyModule: WebChannelHeavyRuntimeModule | null = null;
let cachedLightModulePath: string | null = null;
let cachedLightModule: WebChannelLightRuntimeModule | null = null;
const jitiLoaders = new Map<boolean, ReturnType<typeof createJiti>>();
function resolveWebChannelPluginRecord(): WebChannelPluginRecord {
return resolvePluginRuntimeRecord(WEB_CHANNEL_PLUGIN_ID, () => {
throw new Error(
`web channel plugin runtime is unavailable: missing plugin '${WEB_CHANNEL_PLUGIN_ID}'`,
);
}) 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} for plugin '${WEB_CHANNEL_PLUGIN_ID}'`,
);
});
if (!modulePath) {
throw new Error(
`web channel plugin runtime is unavailable: missing ${entryBaseName} for plugin '${WEB_CHANNEL_PLUGIN_ID}'`,
);
}
return modulePath;
}
function loadCurrentHeavyModuleSync(): WebChannelHeavyRuntimeModule {
const modulePath = resolveWebChannelRuntimeModulePath(
resolveWebChannelPluginRecord(),
"runtime-api",
);
return loadPluginBoundaryModuleWithJiti<WebChannelHeavyRuntimeModule>(modulePath, jitiLoaders);
}
function loadWebChannelLightModule(): WebChannelLightRuntimeModule {
const modulePath = resolveWebChannelRuntimeModulePath(
resolveWebChannelPluginRecord(),
"light-runtime-api",
);
if (cachedLightModule && cachedLightModulePath === modulePath) {
return cachedLightModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<WebChannelLightRuntimeModule>(
modulePath,
jitiLoaders,
);
cachedLightModulePath = modulePath;
cachedLightModule = loaded;
return loaded;
}
async function loadWebChannelHeavyModule(): Promise<WebChannelHeavyRuntimeModule> {
const record = resolveWebChannelPluginRecord();
const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api");
if (cachedHeavyModule && cachedHeavyModulePath === modulePath) {
return cachedHeavyModule;
}
const loaded = loadPluginBoundaryModuleWithJiti<WebChannelHeavyRuntimeModule>(
modulePath,
jitiLoaders,
);
cachedHeavyModulePath = modulePath;
cachedHeavyModule = loaded;
return loaded;
}
function getLightExport<K extends keyof WebChannelLightRuntimeModule>(
exportName: K,
): NonNullable<WebChannelLightRuntimeModule[K]> {
const loaded = loadWebChannelLightModule();
const value = loaded[exportName];
if (value == null) {
throw new Error(`web channel plugin runtime is missing export '${String(exportName)}'`);
}
return value as NonNullable<WebChannelLightRuntimeModule[K]>;
}
async function getHeavyExport<K extends keyof WebChannelHeavyRuntimeModule>(
exportName: K,
): Promise<NonNullable<WebChannelHeavyRuntimeModule[K]>> {
const loaded = await loadWebChannelHeavyModule();
const value = loaded[exportName];
if (value == null) {
throw new Error(`web channel plugin runtime is missing export '${String(exportName)}'`);
}
return value as NonNullable<WebChannelHeavyRuntimeModule[K]>;
}
export function getActiveWebListener(
...args: Parameters<WebChannelLightRuntimeModule["getActiveWebListener"]>
): ReturnType<WebChannelLightRuntimeModule["getActiveWebListener"]> {
return getLightExport("getActiveWebListener")(...args);
}
export function getWebAuthAgeMs(
...args: Parameters<WebChannelLightRuntimeModule["getWebAuthAgeMs"]>
): ReturnType<WebChannelLightRuntimeModule["getWebAuthAgeMs"]> {
return getLightExport("getWebAuthAgeMs")(...args);
}
export function logWebSelfId(
...args: Parameters<WebChannelLightRuntimeModule["logWebSelfId"]>
): ReturnType<WebChannelLightRuntimeModule["logWebSelfId"]> {
return getLightExport("logWebSelfId")(...args);
}
export function loginWeb(
...args: Parameters<WebChannelHeavyRuntimeModule["loginWeb"]>
): ReturnType<WebChannelHeavyRuntimeModule["loginWeb"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.loginWeb(...args));
}
export function logoutWeb(
...args: Parameters<WebChannelLightRuntimeModule["logoutWeb"]>
): ReturnType<WebChannelLightRuntimeModule["logoutWeb"]> {
return getLightExport("logoutWeb")(...args);
}
export function readWebSelfId(
...args: Parameters<WebChannelLightRuntimeModule["readWebSelfId"]>
): ReturnType<WebChannelLightRuntimeModule["readWebSelfId"]> {
return getLightExport("readWebSelfId")(...args);
}
export function webAuthExists(
...args: Parameters<WebChannelLightRuntimeModule["webAuthExists"]>
): ReturnType<WebChannelLightRuntimeModule["webAuthExists"]> {
return getLightExport("webAuthExists")(...args);
}
export function sendWebChannelMessage(
...args: Parameters<WebChannelHeavyRuntimeModule["sendMessageWhatsApp"]>
): ReturnType<WebChannelHeavyRuntimeModule["sendMessageWhatsApp"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.sendMessageWhatsApp(...args));
}
export function sendWebChannelPoll(
...args: Parameters<WebChannelHeavyRuntimeModule["sendPollWhatsApp"]>
): ReturnType<WebChannelHeavyRuntimeModule["sendPollWhatsApp"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.sendPollWhatsApp(...args));
}
export function sendWebChannelReaction(
...args: Parameters<WebChannelHeavyRuntimeModule["sendReactionWhatsApp"]>
): ReturnType<WebChannelHeavyRuntimeModule["sendReactionWhatsApp"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.sendReactionWhatsApp(...args));
}
export function createRuntimeWebChannelLoginTool(
...args: Parameters<WebChannelLightRuntimeModule["createWhatsAppLoginTool"]>
): ReturnType<WebChannelLightRuntimeModule["createWhatsAppLoginTool"]> {
return getLightExport("createWhatsAppLoginTool")(...args);
}
export function createWebChannelSocket(
...args: Parameters<WebChannelHeavyRuntimeModule["createWaSocket"]>
): ReturnType<WebChannelHeavyRuntimeModule["createWaSocket"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.createWaSocket(...args));
}
export function formatError(
...args: Parameters<WebChannelLightRuntimeModule["formatError"]>
): ReturnType<WebChannelLightRuntimeModule["formatError"]> {
return getLightExport("formatError")(...args);
}
export function getStatusCode(
...args: Parameters<WebChannelLightRuntimeModule["getStatusCode"]>
): ReturnType<WebChannelLightRuntimeModule["getStatusCode"]> {
return getLightExport("getStatusCode")(...args);
}
export function pickWebChannel(
...args: Parameters<WebChannelLightRuntimeModule["pickWebChannel"]>
): ReturnType<WebChannelLightRuntimeModule["pickWebChannel"]> {
return getLightExport("pickWebChannel")(...args);
}
export function resolveWebChannelAuthDir(): WebChannelLightRuntimeModule["WA_WEB_AUTH_DIR"] {
return getLightExport("WA_WEB_AUTH_DIR");
}
export async function handleWebChannelAction(
...args: Parameters<WebChannelHeavyRuntimeModule["handleWhatsAppAction"]>
): ReturnType<WebChannelHeavyRuntimeModule["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<WebChannelHeavyRuntimeModule["monitorWebChannel"]>
): ReturnType<WebChannelHeavyRuntimeModule["monitorWebChannel"]> {
return loadWebChannelHeavyModule().then((loaded) => loaded.monitorWebChannel(...args));
}
export async function monitorWebInbox(
...args: Parameters<WebChannelHeavyRuntimeModule["monitorWebInbox"]>
): ReturnType<WebChannelHeavyRuntimeModule["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<WebChannelHeavyRuntimeModule["runWebHeartbeatOnce"]>
): ReturnType<WebChannelHeavyRuntimeModule["runWebHeartbeatOnce"]> {
return (await getHeavyExport("runWebHeartbeatOnce"))(...args);
}
export async function startWebLoginWithQr(
...args: Parameters<WebChannelHeavyRuntimeModule["startWebLoginWithQr"]>
): ReturnType<WebChannelHeavyRuntimeModule["startWebLoginWithQr"]> {
return (await getHeavyExport("startWebLoginWithQr"))(...args);
}
export async function waitForWebChannelConnection(
...args: Parameters<WebChannelHeavyRuntimeModule["waitForWaConnection"]>
): ReturnType<WebChannelHeavyRuntimeModule["waitForWaConnection"]> {
return (await getHeavyExport("waitForWaConnection"))(...args);
}
export async function waitForWebLogin(
...args: Parameters<WebChannelHeavyRuntimeModule["waitForWebLogin"]>
): ReturnType<WebChannelHeavyRuntimeModule["waitForWebLogin"]> {
return (await getHeavyExport("waitForWebLogin"))(...args);
}
export const extractMediaPlaceholder = (
...args: Parameters<WebChannelHeavyRuntimeModule["extractMediaPlaceholder"]>
) => loadCurrentHeavyModuleSync().extractMediaPlaceholder(...args);
export const extractText = (...args: Parameters<WebChannelHeavyRuntimeModule["extractText"]>) =>
loadCurrentHeavyModuleSync().extractText(...args);
export function getDefaultLocalRoots(
...args: Parameters<typeof getDefaultLocalRootsImpl>
): ReturnType<typeof getDefaultLocalRootsImpl> {
return getDefaultLocalRootsImpl(...args);
}
export function resolveHeartbeatRecipients(
...args: Parameters<WebChannelHeavyRuntimeModule["resolveHeartbeatRecipients"]>
): ReturnType<WebChannelHeavyRuntimeModule["resolveHeartbeatRecipients"]> {
return loadCurrentHeavyModuleSync().resolveHeartbeatRecipients(...args);
}

View File

@@ -1,17 +1,14 @@
/**
* Runtime helpers for native channel plugins.
*
* This surface exposes core and channel-specific helpers used by bundled
* plugins. Prefer hooks unless you need tight in-process coupling with the
* OpenClaw messaging/runtime stack.
* This surface exposes generic core helpers only. Plugin-owned behavior stays
* inside the owning plugin package instead of hanging off core runtime slots
* like `channel.discord` or `channel.slack`.
*/
type ReadChannelAllowFromStore =
typeof import("../../pairing/pairing-store.js").readChannelAllowFromStore;
type UpsertChannelPairingRequest =
typeof import("../../pairing/pairing-store.js").upsertChannelPairingRequest;
type DiscordRuntimeSurface = typeof import("../../../extensions/discord/runtime-api.js");
type DiscordThreadBindings = typeof import("../../../extensions/discord/runtime-api.js");
type MatrixThreadBindings = typeof import("../../../extensions/matrix/api.js");
type ReadChannelAllowFromStoreForAccount = (params: {
channel: Parameters<ReadChannelAllowFromStore>[0];
@@ -110,98 +107,16 @@ export type PluginRuntimeChannel = {
};
threadBindings: {
setIdleTimeoutBySessionKey: (params: {
channelId: "discord" | "matrix" | "telegram";
channelId: string;
targetSessionKey: string;
accountId?: string;
idleTimeoutMs: number;
}) => RuntimeThreadBindingLifecycleRecord[];
setMaxAgeBySessionKey: (params: {
channelId: "discord" | "matrix" | "telegram";
channelId: string;
targetSessionKey: string;
accountId?: string;
maxAgeMs: number;
}) => RuntimeThreadBindingLifecycleRecord[];
};
discord: {
messageActions: DiscordRuntimeSurface["discordMessageActions"];
auditChannelPermissions: DiscordRuntimeSurface["auditDiscordChannelPermissions"];
listDirectoryGroupsLive: DiscordRuntimeSurface["listDiscordDirectoryGroupsLive"];
listDirectoryPeersLive: DiscordRuntimeSurface["listDiscordDirectoryPeersLive"];
probeDiscord: DiscordRuntimeSurface["probeDiscord"];
resolveChannelAllowlist: DiscordRuntimeSurface["resolveDiscordChannelAllowlist"];
resolveUserAllowlist: DiscordRuntimeSurface["resolveDiscordUserAllowlist"];
sendComponentMessage: DiscordRuntimeSurface["sendDiscordComponentMessage"];
sendMessageDiscord: DiscordRuntimeSurface["sendMessageDiscord"];
sendPollDiscord: DiscordRuntimeSurface["sendPollDiscord"];
monitorDiscordProvider: DiscordRuntimeSurface["monitorDiscordProvider"];
threadBindings: {
getManager: DiscordThreadBindings["getThreadBindingManager"];
resolveIdleTimeoutMs: DiscordThreadBindings["resolveThreadBindingIdleTimeoutMs"];
resolveInactivityExpiresAt: DiscordThreadBindings["resolveThreadBindingInactivityExpiresAt"];
resolveMaxAgeMs: DiscordThreadBindings["resolveThreadBindingMaxAgeMs"];
resolveMaxAgeExpiresAt: DiscordThreadBindings["resolveThreadBindingMaxAgeExpiresAt"];
setIdleTimeoutBySessionKey: DiscordThreadBindings["setThreadBindingIdleTimeoutBySessionKey"];
setMaxAgeBySessionKey: DiscordThreadBindings["setThreadBindingMaxAgeBySessionKey"];
unbindBySessionKey: DiscordThreadBindings["unbindThreadBindingsBySessionKey"];
};
typing: {
pulse: DiscordRuntimeSurface["sendTypingDiscord"];
start: (params: {
channelId: string;
accountId?: string;
cfg?: ReturnType<typeof import("../../config/config.js").loadConfig>;
intervalMs?: number;
}) => Promise<{
refresh: () => Promise<void>;
stop: () => void;
}>;
};
conversationActions: {
editMessage: DiscordRuntimeSurface["editMessageDiscord"];
deleteMessage: DiscordRuntimeSurface["deleteMessageDiscord"];
pinMessage: DiscordRuntimeSurface["pinMessageDiscord"];
unpinMessage: DiscordRuntimeSurface["unpinMessageDiscord"];
createThread: DiscordRuntimeSurface["createThreadDiscord"];
editChannel: DiscordRuntimeSurface["editChannelDiscord"];
};
};
slack: {
listDirectoryGroupsLive: typeof import("../../../extensions/slack/runtime-api.js").listSlackDirectoryGroupsLive;
listDirectoryPeersLive: typeof import("../../../extensions/slack/runtime-api.js").listSlackDirectoryPeersLive;
probeSlack: typeof import("../../../extensions/slack/runtime-api.js").probeSlack;
resolveChannelAllowlist: typeof import("../../../extensions/slack/runtime-api.js").resolveSlackChannelAllowlist;
resolveUserAllowlist: typeof import("../../../extensions/slack/runtime-api.js").resolveSlackUserAllowlist;
sendMessageSlack: typeof import("../../../extensions/slack/runtime-api.js").sendMessageSlack;
monitorSlackProvider: typeof import("../../../extensions/slack/runtime-api.js").monitorSlackProvider;
handleSlackAction: typeof import("../../../extensions/slack/runtime-api.js").handleSlackAction;
};
matrix: {
threadBindings: {
setIdleTimeoutBySessionKey: MatrixThreadBindings["setMatrixThreadBindingIdleTimeoutBySessionKey"];
setMaxAgeBySessionKey: MatrixThreadBindings["setMatrixThreadBindingMaxAgeBySessionKey"];
};
};
signal: {
probeSignal: typeof import("../../../extensions/signal/runtime-api.js").probeSignal;
sendMessageSignal: typeof import("../../../extensions/signal/runtime-api.js").sendMessageSignal;
monitorSignalProvider: typeof import("../../../extensions/signal/runtime-api.js").monitorSignalProvider;
messageActions: typeof import("../../../extensions/signal/runtime-api.js").signalMessageActions;
};
line: {
listLineAccountIds: typeof import("../../plugin-sdk/line.js").listLineAccountIds;
resolveDefaultLineAccountId: typeof import("../../plugin-sdk/line.js").resolveDefaultLineAccountId;
resolveLineAccount: typeof import("../../plugin-sdk/line.js").resolveLineAccount;
normalizeAccountId: typeof import("../../plugin-sdk/line.js").normalizeAccountId;
probeLineBot: typeof import("../../plugin-sdk/line-runtime.js").probeLineBot;
sendMessageLine: typeof import("../../plugin-sdk/line-runtime.js").sendMessageLine;
pushMessageLine: typeof import("../../plugin-sdk/line-runtime.js").pushMessageLine;
pushMessagesLine: typeof import("../../plugin-sdk/line-runtime.js").pushMessagesLine;
pushFlexMessage: typeof import("../../plugin-sdk/line-runtime.js").pushFlexMessage;
pushTemplateMessage: typeof import("../../plugin-sdk/line-runtime.js").pushTemplateMessage;
pushLocationMessage: typeof import("../../plugin-sdk/line-runtime.js").pushLocationMessage;
pushTextMessageWithQuickReplies: typeof import("../../plugin-sdk/line-runtime.js").pushTextMessageWithQuickReplies;
createQuickReplyItems: typeof import("../../plugin-sdk/line-runtime.js").createQuickReplyItems;
buildTemplateMessageFromPayload: typeof import("../../plugin-sdk/line-runtime.js").buildTemplateMessageFromPayload;
monitorLineProvider: typeof import("../../plugin-sdk/line-runtime.js").monitorLineProvider;
};
};