refactor: modularize plugin runtime and test hooks
This commit is contained in:
@@ -1,148 +1,14 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||
import { createMemoryGetTool, createMemorySearchTool } from "../../agents/tools/memory-tool.js";
|
||||
import { handleSlackAction } from "../../agents/tools/slack-actions.js";
|
||||
import {
|
||||
chunkByNewline,
|
||||
chunkMarkdownText,
|
||||
chunkMarkdownTextWithMode,
|
||||
chunkText,
|
||||
chunkTextWithMode,
|
||||
resolveChunkMode,
|
||||
resolveTextChunkLimit,
|
||||
} from "../../auto-reply/chunk.js";
|
||||
import {
|
||||
hasControlCommand,
|
||||
isControlCommandMessage,
|
||||
shouldComputeCommandAuthorized,
|
||||
} from "../../auto-reply/command-detection.js";
|
||||
import { shouldHandleTextCommands } from "../../auto-reply/commands-registry.js";
|
||||
import { withReplyDispatcher } from "../../auto-reply/dispatch.js";
|
||||
import {
|
||||
formatAgentEnvelope,
|
||||
formatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions,
|
||||
} from "../../auto-reply/envelope.js";
|
||||
import {
|
||||
createInboundDebouncer,
|
||||
resolveInboundDebounceMs,
|
||||
} from "../../auto-reply/inbound-debounce.js";
|
||||
import { dispatchReplyFromConfig } from "../../auto-reply/reply/dispatch-from-config.js";
|
||||
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
matchesMentionWithExplicit,
|
||||
} from "../../auto-reply/reply/mentions.js";
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
||||
import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { removeAckReactionAfterReply, shouldAckReaction } from "../../channels/ack-reactions.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
||||
import { discordMessageActions } from "../../channels/plugins/actions/discord.js";
|
||||
import { signalMessageActions } from "../../channels/plugins/actions/signal.js";
|
||||
import { telegramMessageActions } from "../../channels/plugins/actions/telegram.js";
|
||||
import { createWhatsAppLoginTool } from "../../channels/plugins/agent-tools/whatsapp-login.js";
|
||||
import { recordInboundSession } from "../../channels/session.js";
|
||||
import { registerMemoryCli } from "../../cli/memory-cli.js";
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
||||
import {
|
||||
resolveChannelGroupPolicy,
|
||||
resolveChannelGroupRequireMention,
|
||||
} from "../../config/group-policy.js";
|
||||
import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
|
||||
import { resolveStateDir } from "../../config/paths.js";
|
||||
import {
|
||||
readSessionUpdatedAt,
|
||||
recordSessionMetaFromInbound,
|
||||
resolveStorePath,
|
||||
updateLastRoute,
|
||||
} from "../../config/sessions.js";
|
||||
import { auditDiscordChannelPermissions } from "../../discord/audit.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsLive,
|
||||
listDiscordDirectoryPeersLive,
|
||||
} from "../../discord/directory-live.js";
|
||||
import { monitorDiscordProvider } from "../../discord/monitor.js";
|
||||
import { probeDiscord } from "../../discord/probe.js";
|
||||
import { resolveDiscordChannelAllowlist } from "../../discord/resolve-channels.js";
|
||||
import { resolveDiscordUserAllowlist } from "../../discord/resolve-users.js";
|
||||
import { sendMessageDiscord, sendPollDiscord } from "../../discord/send.js";
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import { monitorIMessageProvider } from "../../imessage/monitor.js";
|
||||
import { probeIMessage } from "../../imessage/probe.js";
|
||||
import { sendMessageIMessage } from "../../imessage/send.js";
|
||||
import { onAgentEvent } from "../../infra/agent-events.js";
|
||||
import { getChannelActivity, recordChannelActivity } from "../../infra/channel-activity.js";
|
||||
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import {
|
||||
listLineAccountIds,
|
||||
normalizeAccountId as normalizeLineAccountId,
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
} from "../../line/accounts.js";
|
||||
import { monitorLineProvider } from "../../line/monitor.js";
|
||||
import { probeLineBot } from "../../line/probe.js";
|
||||
import {
|
||||
createQuickReplyItems,
|
||||
pushMessageLine,
|
||||
pushMessagesLine,
|
||||
pushFlexMessage,
|
||||
pushTemplateMessage,
|
||||
pushLocationMessage,
|
||||
pushTextMessageWithQuickReplies,
|
||||
sendMessageLine,
|
||||
} from "../../line/send.js";
|
||||
import { buildTemplateMessageFromPayload } from "../../line/template-messages.js";
|
||||
import { getChildLogger } from "../../logging.js";
|
||||
import { normalizeLogLevel } from "../../logging/levels.js";
|
||||
import { convertMarkdownTables } from "../../markdown/tables.js";
|
||||
import { transcribeAudioFile } from "../../media-understanding/transcribe-audio.js";
|
||||
import { isVoiceCompatibleAudio } from "../../media/audio.js";
|
||||
import { mediaKindFromMime } from "../../media/constants.js";
|
||||
import { fetchRemoteMedia } from "../../media/fetch.js";
|
||||
import { getImageMetadata, resizeToJpeg } from "../../media/image-ops.js";
|
||||
import { detectMime } from "../../media/mime.js";
|
||||
import { saveMediaBuffer } from "../../media/store.js";
|
||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||
import {
|
||||
readChannelAllowFromStore,
|
||||
upsertChannelPairingRequest,
|
||||
} from "../../pairing/pairing-store.js";
|
||||
import { runCommandWithTimeout } from "../../process/exec.js";
|
||||
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||
import { onSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
|
||||
import { monitorSignalProvider } from "../../signal/index.js";
|
||||
import { probeSignal } from "../../signal/probe.js";
|
||||
import { sendMessageSignal } from "../../signal/send.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsLive,
|
||||
listSlackDirectoryPeersLive,
|
||||
} from "../../slack/directory-live.js";
|
||||
import { monitorSlackProvider } from "../../slack/index.js";
|
||||
import { probeSlack } from "../../slack/probe.js";
|
||||
import { resolveSlackChannelAllowlist } from "../../slack/resolve-channels.js";
|
||||
import { resolveSlackUserAllowlist } from "../../slack/resolve-users.js";
|
||||
import { sendMessageSlack } from "../../slack/send.js";
|
||||
import {
|
||||
auditTelegramGroupMembership,
|
||||
collectTelegramUnmentionedGroupIds,
|
||||
} from "../../telegram/audit.js";
|
||||
import { monitorTelegramProvider } from "../../telegram/monitor.js";
|
||||
import { probeTelegram } from "../../telegram/probe.js";
|
||||
import { sendMessageTelegram, sendPollTelegram } from "../../telegram/send.js";
|
||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||
import { textToSpeechTelephony } from "../../tts/tts.js";
|
||||
import { getActiveWebListener } from "../../web/active-listener.js";
|
||||
import {
|
||||
getWebAuthAgeMs,
|
||||
logoutWeb,
|
||||
logWebSelfId,
|
||||
readWebSelfId,
|
||||
webAuthExists,
|
||||
} from "../../web/auth-store.js";
|
||||
import { loadWebMedia } from "../../web/media.js";
|
||||
import { formatNativeDependencyHint } from "./native-deps.js";
|
||||
import { createRuntimeChannel } from "./runtime-channel.js";
|
||||
import { createRuntimeConfig } from "./runtime-config.js";
|
||||
import { createRuntimeEvents } from "./runtime-events.js";
|
||||
import { createRuntimeLogging } from "./runtime-logging.js";
|
||||
import { createRuntimeMedia } from "./runtime-media.js";
|
||||
import { createRuntimeSystem } from "./runtime-system.js";
|
||||
import { createRuntimeTools } from "./runtime-tools.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
let cachedVersion: string | null = null;
|
||||
@@ -162,87 +28,8 @@ function resolveVersion(): string {
|
||||
}
|
||||
}
|
||||
|
||||
const sendMessageWhatsAppLazy: PluginRuntime["channel"]["whatsapp"]["sendMessageWhatsApp"] = async (
|
||||
...args
|
||||
) => {
|
||||
const { sendMessageWhatsApp } = await loadWebOutbound();
|
||||
return sendMessageWhatsApp(...args);
|
||||
};
|
||||
|
||||
const sendPollWhatsAppLazy: PluginRuntime["channel"]["whatsapp"]["sendPollWhatsApp"] = async (
|
||||
...args
|
||||
) => {
|
||||
const { sendPollWhatsApp } = await loadWebOutbound();
|
||||
return sendPollWhatsApp(...args);
|
||||
};
|
||||
|
||||
const loginWebLazy: PluginRuntime["channel"]["whatsapp"]["loginWeb"] = async (...args) => {
|
||||
const { loginWeb } = await loadWebLogin();
|
||||
return loginWeb(...args);
|
||||
};
|
||||
|
||||
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 webOutboundPromise: Promise<typeof import("../../web/outbound.js")> | null = null;
|
||||
let webLoginPromise: Promise<typeof import("../../web/login.js")> | null = null;
|
||||
let webLoginQrPromise: Promise<typeof import("../../web/login-qr.js")> | null = null;
|
||||
let webChannelPromise: Promise<typeof import("../../channels/web/index.js")> | null = null;
|
||||
let whatsappActionsPromise: Promise<
|
||||
typeof import("../../agents/tools/whatsapp-actions.js")
|
||||
> | null = null;
|
||||
|
||||
function loadWebOutbound() {
|
||||
webOutboundPromise ??= import("../../web/outbound.js");
|
||||
return webOutboundPromise;
|
||||
}
|
||||
|
||||
function loadWebLogin() {
|
||||
webLoginPromise ??= import("../../web/login.js");
|
||||
return webLoginPromise;
|
||||
}
|
||||
|
||||
function loadWebLoginQr() {
|
||||
webLoginQrPromise ??= import("../../web/login-qr.js");
|
||||
return webLoginQrPromise;
|
||||
}
|
||||
|
||||
function loadWebChannel() {
|
||||
webChannelPromise ??= import("../../channels/web/index.js");
|
||||
return webChannelPromise;
|
||||
}
|
||||
|
||||
function loadWhatsAppActions() {
|
||||
whatsappActionsPromise ??= import("../../agents/tools/whatsapp-actions.js");
|
||||
return whatsappActionsPromise;
|
||||
}
|
||||
|
||||
export function createPluginRuntime(): PluginRuntime {
|
||||
return {
|
||||
const runtime = {
|
||||
version: resolveVersion(),
|
||||
config: createRuntimeConfig(),
|
||||
system: createRuntimeSystem(),
|
||||
@@ -251,226 +38,12 @@ export function createPluginRuntime(): PluginRuntime {
|
||||
stt: { transcribeAudioFile },
|
||||
tools: createRuntimeTools(),
|
||||
channel: createRuntimeChannel(),
|
||||
events: {
|
||||
onAgentEvent,
|
||||
onSessionTranscriptUpdate,
|
||||
},
|
||||
events: createRuntimeEvents(),
|
||||
logging: createRuntimeLogging(),
|
||||
state: { resolveStateDir },
|
||||
};
|
||||
}
|
||||
} satisfies PluginRuntime;
|
||||
|
||||
function createRuntimeConfig(): PluginRuntime["config"] {
|
||||
return {
|
||||
loadConfig,
|
||||
writeConfigFile,
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeSystem(): PluginRuntime["system"] {
|
||||
return {
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow,
|
||||
runCommandWithTimeout,
|
||||
formatNativeDependencyHint,
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeMedia(): PluginRuntime["media"] {
|
||||
return {
|
||||
loadWebMedia,
|
||||
detectMime,
|
||||
mediaKindFromMime,
|
||||
isVoiceCompatibleAudio,
|
||||
getImageMetadata,
|
||||
resizeToJpeg,
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeTools(): PluginRuntime["tools"] {
|
||||
return {
|
||||
createMemoryGetTool,
|
||||
createMemorySearchTool,
|
||||
registerMemoryCli,
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeChannel(): PluginRuntime["channel"] {
|
||||
return {
|
||||
text: {
|
||||
chunkByNewline,
|
||||
chunkMarkdownText,
|
||||
chunkMarkdownTextWithMode,
|
||||
chunkText,
|
||||
chunkTextWithMode,
|
||||
resolveChunkMode,
|
||||
resolveTextChunkLimit,
|
||||
hasControlCommand,
|
||||
resolveMarkdownTableMode,
|
||||
convertMarkdownTables,
|
||||
},
|
||||
reply: {
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
createReplyDispatcherWithTyping,
|
||||
resolveEffectiveMessagesConfig,
|
||||
resolveHumanDelayConfig,
|
||||
dispatchReplyFromConfig,
|
||||
withReplyDispatcher,
|
||||
finalizeInboundContext,
|
||||
formatAgentEnvelope,
|
||||
/** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */
|
||||
formatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions,
|
||||
},
|
||||
routing: {
|
||||
resolveAgentRoute,
|
||||
},
|
||||
pairing: {
|
||||
buildPairingReply,
|
||||
readAllowFromStore: ({ channel, accountId, env }) =>
|
||||
readChannelAllowFromStore(channel, env, accountId),
|
||||
upsertPairingRequest: ({ channel, id, accountId, meta, env, pairingAdapter }) =>
|
||||
upsertChannelPairingRequest({
|
||||
channel,
|
||||
id,
|
||||
accountId,
|
||||
meta,
|
||||
env,
|
||||
pairingAdapter,
|
||||
}),
|
||||
},
|
||||
media: {
|
||||
fetchRemoteMedia,
|
||||
saveMediaBuffer,
|
||||
},
|
||||
activity: {
|
||||
record: recordChannelActivity,
|
||||
get: getChannelActivity,
|
||||
},
|
||||
session: {
|
||||
resolveStorePath,
|
||||
readSessionUpdatedAt,
|
||||
recordSessionMetaFromInbound,
|
||||
recordInboundSession,
|
||||
updateLastRoute,
|
||||
},
|
||||
mentions: {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
matchesMentionWithExplicit,
|
||||
},
|
||||
reactions: {
|
||||
shouldAckReaction,
|
||||
removeAckReactionAfterReply,
|
||||
},
|
||||
groups: {
|
||||
resolveGroupPolicy: resolveChannelGroupPolicy,
|
||||
resolveRequireMention: resolveChannelGroupRequireMention,
|
||||
},
|
||||
debounce: {
|
||||
createInboundDebouncer,
|
||||
resolveInboundDebounceMs,
|
||||
},
|
||||
commands: {
|
||||
resolveCommandAuthorizedFromAuthorizers,
|
||||
isControlCommandMessage,
|
||||
shouldComputeCommandAuthorized,
|
||||
shouldHandleTextCommands,
|
||||
},
|
||||
discord: {
|
||||
messageActions: discordMessageActions,
|
||||
auditChannelPermissions: auditDiscordChannelPermissions,
|
||||
listDirectoryGroupsLive: listDiscordDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listDiscordDirectoryPeersLive,
|
||||
probeDiscord,
|
||||
resolveChannelAllowlist: resolveDiscordChannelAllowlist,
|
||||
resolveUserAllowlist: resolveDiscordUserAllowlist,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
monitorDiscordProvider,
|
||||
},
|
||||
slack: {
|
||||
listDirectoryGroupsLive: listSlackDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listSlackDirectoryPeersLive,
|
||||
probeSlack,
|
||||
resolveChannelAllowlist: resolveSlackChannelAllowlist,
|
||||
resolveUserAllowlist: resolveSlackUserAllowlist,
|
||||
sendMessageSlack,
|
||||
monitorSlackProvider,
|
||||
handleSlackAction,
|
||||
},
|
||||
telegram: {
|
||||
auditGroupMembership: auditTelegramGroupMembership,
|
||||
collectUnmentionedGroupIds: collectTelegramUnmentionedGroupIds,
|
||||
probeTelegram,
|
||||
resolveTelegramToken,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
monitorTelegramProvider,
|
||||
messageActions: telegramMessageActions,
|
||||
},
|
||||
signal: {
|
||||
probeSignal,
|
||||
sendMessageSignal,
|
||||
monitorSignalProvider,
|
||||
messageActions: signalMessageActions,
|
||||
},
|
||||
imessage: {
|
||||
monitorIMessageProvider,
|
||||
probeIMessage,
|
||||
sendMessageIMessage,
|
||||
},
|
||||
whatsapp: {
|
||||
getActiveWebListener,
|
||||
getWebAuthAgeMs,
|
||||
logoutWeb,
|
||||
logWebSelfId,
|
||||
readWebSelfId,
|
||||
webAuthExists,
|
||||
sendMessageWhatsApp: sendMessageWhatsAppLazy,
|
||||
sendPollWhatsApp: sendPollWhatsAppLazy,
|
||||
loginWeb: loginWebLazy,
|
||||
startWebLoginWithQr: startWebLoginWithQrLazy,
|
||||
waitForWebLogin: waitForWebLoginLazy,
|
||||
monitorWebChannel: monitorWebChannelLazy,
|
||||
handleWhatsAppAction: handleWhatsAppActionLazy,
|
||||
createLoginTool: createWhatsAppLoginTool,
|
||||
},
|
||||
line: {
|
||||
listLineAccountIds,
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
normalizeAccountId: normalizeLineAccountId,
|
||||
probeLineBot,
|
||||
sendMessageLine,
|
||||
pushMessageLine,
|
||||
pushMessagesLine,
|
||||
pushFlexMessage,
|
||||
pushTemplateMessage,
|
||||
pushLocationMessage,
|
||||
pushTextMessageWithQuickReplies,
|
||||
createQuickReplyItems,
|
||||
buildTemplateMessageFromPayload,
|
||||
monitorLineProvider,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeLogging(): PluginRuntime["logging"] {
|
||||
return {
|
||||
shouldLogVerbose,
|
||||
getChildLogger: (bindings, opts) => {
|
||||
const logger = getChildLogger(bindings, {
|
||||
level: opts?.level ? normalizeLogLevel(opts.level) : undefined,
|
||||
});
|
||||
return {
|
||||
debug: (message) => logger.debug?.(message),
|
||||
info: (message) => logger.info(message),
|
||||
warn: (message) => logger.warn(message),
|
||||
error: (message) => logger.error(message),
|
||||
};
|
||||
},
|
||||
};
|
||||
return runtime;
|
||||
}
|
||||
|
||||
export type { PluginRuntime } from "./types.js";
|
||||
|
||||
263
src/plugins/runtime/runtime-channel.ts
Normal file
263
src/plugins/runtime/runtime-channel.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { resolveEffectiveMessagesConfig, resolveHumanDelayConfig } from "../../agents/identity.js";
|
||||
import { handleSlackAction } from "../../agents/tools/slack-actions.js";
|
||||
import {
|
||||
chunkByNewline,
|
||||
chunkMarkdownText,
|
||||
chunkMarkdownTextWithMode,
|
||||
chunkText,
|
||||
chunkTextWithMode,
|
||||
resolveChunkMode,
|
||||
resolveTextChunkLimit,
|
||||
} from "../../auto-reply/chunk.js";
|
||||
import {
|
||||
hasControlCommand,
|
||||
isControlCommandMessage,
|
||||
shouldComputeCommandAuthorized,
|
||||
} from "../../auto-reply/command-detection.js";
|
||||
import { shouldHandleTextCommands } from "../../auto-reply/commands-registry.js";
|
||||
import { withReplyDispatcher } from "../../auto-reply/dispatch.js";
|
||||
import {
|
||||
formatAgentEnvelope,
|
||||
formatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions,
|
||||
} from "../../auto-reply/envelope.js";
|
||||
import {
|
||||
createInboundDebouncer,
|
||||
resolveInboundDebounceMs,
|
||||
} from "../../auto-reply/inbound-debounce.js";
|
||||
import { dispatchReplyFromConfig } from "../../auto-reply/reply/dispatch-from-config.js";
|
||||
import { finalizeInboundContext } from "../../auto-reply/reply/inbound-context.js";
|
||||
import {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
matchesMentionWithExplicit,
|
||||
} from "../../auto-reply/reply/mentions.js";
|
||||
import { dispatchReplyWithBufferedBlockDispatcher } from "../../auto-reply/reply/provider-dispatcher.js";
|
||||
import { createReplyDispatcherWithTyping } from "../../auto-reply/reply/reply-dispatcher.js";
|
||||
import { removeAckReactionAfterReply, shouldAckReaction } from "../../channels/ack-reactions.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
||||
import { discordMessageActions } from "../../channels/plugins/actions/discord.js";
|
||||
import { signalMessageActions } from "../../channels/plugins/actions/signal.js";
|
||||
import { telegramMessageActions } from "../../channels/plugins/actions/telegram.js";
|
||||
import { recordInboundSession } from "../../channels/session.js";
|
||||
import {
|
||||
resolveChannelGroupPolicy,
|
||||
resolveChannelGroupRequireMention,
|
||||
} from "../../config/group-policy.js";
|
||||
import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
|
||||
import {
|
||||
readSessionUpdatedAt,
|
||||
recordSessionMetaFromInbound,
|
||||
resolveStorePath,
|
||||
updateLastRoute,
|
||||
} from "../../config/sessions.js";
|
||||
import { auditDiscordChannelPermissions } from "../../discord/audit.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsLive,
|
||||
listDiscordDirectoryPeersLive,
|
||||
} from "../../discord/directory-live.js";
|
||||
import { monitorDiscordProvider } from "../../discord/monitor.js";
|
||||
import { probeDiscord } from "../../discord/probe.js";
|
||||
import { resolveDiscordChannelAllowlist } from "../../discord/resolve-channels.js";
|
||||
import { resolveDiscordUserAllowlist } from "../../discord/resolve-users.js";
|
||||
import { sendMessageDiscord, sendPollDiscord } from "../../discord/send.js";
|
||||
import { monitorIMessageProvider } from "../../imessage/monitor.js";
|
||||
import { probeIMessage } from "../../imessage/probe.js";
|
||||
import { sendMessageIMessage } from "../../imessage/send.js";
|
||||
import { getChannelActivity, recordChannelActivity } from "../../infra/channel-activity.js";
|
||||
import {
|
||||
listLineAccountIds,
|
||||
normalizeAccountId as normalizeLineAccountId,
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
} from "../../line/accounts.js";
|
||||
import { monitorLineProvider } from "../../line/monitor.js";
|
||||
import { probeLineBot } from "../../line/probe.js";
|
||||
import {
|
||||
createQuickReplyItems,
|
||||
pushFlexMessage,
|
||||
pushLocationMessage,
|
||||
pushMessageLine,
|
||||
pushMessagesLine,
|
||||
pushTemplateMessage,
|
||||
pushTextMessageWithQuickReplies,
|
||||
sendMessageLine,
|
||||
} from "../../line/send.js";
|
||||
import { buildTemplateMessageFromPayload } from "../../line/template-messages.js";
|
||||
import { convertMarkdownTables } from "../../markdown/tables.js";
|
||||
import { fetchRemoteMedia } from "../../media/fetch.js";
|
||||
import { saveMediaBuffer } from "../../media/store.js";
|
||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||
import {
|
||||
readChannelAllowFromStore,
|
||||
upsertChannelPairingRequest,
|
||||
} from "../../pairing/pairing-store.js";
|
||||
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||
import { monitorSignalProvider } from "../../signal/index.js";
|
||||
import { probeSignal } from "../../signal/probe.js";
|
||||
import { sendMessageSignal } from "../../signal/send.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsLive,
|
||||
listSlackDirectoryPeersLive,
|
||||
} from "../../slack/directory-live.js";
|
||||
import { monitorSlackProvider } from "../../slack/index.js";
|
||||
import { probeSlack } from "../../slack/probe.js";
|
||||
import { resolveSlackChannelAllowlist } from "../../slack/resolve-channels.js";
|
||||
import { resolveSlackUserAllowlist } from "../../slack/resolve-users.js";
|
||||
import { sendMessageSlack } from "../../slack/send.js";
|
||||
import {
|
||||
auditTelegramGroupMembership,
|
||||
collectTelegramUnmentionedGroupIds,
|
||||
} from "../../telegram/audit.js";
|
||||
import { monitorTelegramProvider } from "../../telegram/monitor.js";
|
||||
import { probeTelegram } from "../../telegram/probe.js";
|
||||
import { sendMessageTelegram, sendPollTelegram } from "../../telegram/send.js";
|
||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||
import { createRuntimeWhatsApp } from "./runtime-whatsapp.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeChannel(): PluginRuntime["channel"] {
|
||||
return {
|
||||
text: {
|
||||
chunkByNewline,
|
||||
chunkMarkdownText,
|
||||
chunkMarkdownTextWithMode,
|
||||
chunkText,
|
||||
chunkTextWithMode,
|
||||
resolveChunkMode,
|
||||
resolveTextChunkLimit,
|
||||
hasControlCommand,
|
||||
resolveMarkdownTableMode,
|
||||
convertMarkdownTables,
|
||||
},
|
||||
reply: {
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
createReplyDispatcherWithTyping,
|
||||
resolveEffectiveMessagesConfig,
|
||||
resolveHumanDelayConfig,
|
||||
dispatchReplyFromConfig,
|
||||
withReplyDispatcher,
|
||||
finalizeInboundContext,
|
||||
formatAgentEnvelope,
|
||||
/** @deprecated Prefer `BodyForAgent` + structured user-context blocks (do not build plaintext envelopes for prompts). */
|
||||
formatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions,
|
||||
},
|
||||
routing: {
|
||||
resolveAgentRoute,
|
||||
},
|
||||
pairing: {
|
||||
buildPairingReply,
|
||||
readAllowFromStore: ({ channel, accountId, env }) =>
|
||||
readChannelAllowFromStore(channel, env, accountId),
|
||||
upsertPairingRequest: ({ channel, id, accountId, meta, env, pairingAdapter }) =>
|
||||
upsertChannelPairingRequest({
|
||||
channel,
|
||||
id,
|
||||
accountId,
|
||||
meta,
|
||||
env,
|
||||
pairingAdapter,
|
||||
}),
|
||||
},
|
||||
media: {
|
||||
fetchRemoteMedia,
|
||||
saveMediaBuffer,
|
||||
},
|
||||
activity: {
|
||||
record: recordChannelActivity,
|
||||
get: getChannelActivity,
|
||||
},
|
||||
session: {
|
||||
resolveStorePath,
|
||||
readSessionUpdatedAt,
|
||||
recordSessionMetaFromInbound,
|
||||
recordInboundSession,
|
||||
updateLastRoute,
|
||||
},
|
||||
mentions: {
|
||||
buildMentionRegexes,
|
||||
matchesMentionPatterns,
|
||||
matchesMentionWithExplicit,
|
||||
},
|
||||
reactions: {
|
||||
shouldAckReaction,
|
||||
removeAckReactionAfterReply,
|
||||
},
|
||||
groups: {
|
||||
resolveGroupPolicy: resolveChannelGroupPolicy,
|
||||
resolveRequireMention: resolveChannelGroupRequireMention,
|
||||
},
|
||||
debounce: {
|
||||
createInboundDebouncer,
|
||||
resolveInboundDebounceMs,
|
||||
},
|
||||
commands: {
|
||||
resolveCommandAuthorizedFromAuthorizers,
|
||||
isControlCommandMessage,
|
||||
shouldComputeCommandAuthorized,
|
||||
shouldHandleTextCommands,
|
||||
},
|
||||
discord: {
|
||||
messageActions: discordMessageActions,
|
||||
auditChannelPermissions: auditDiscordChannelPermissions,
|
||||
listDirectoryGroupsLive: listDiscordDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listDiscordDirectoryPeersLive,
|
||||
probeDiscord,
|
||||
resolveChannelAllowlist: resolveDiscordChannelAllowlist,
|
||||
resolveUserAllowlist: resolveDiscordUserAllowlist,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
monitorDiscordProvider,
|
||||
},
|
||||
slack: {
|
||||
listDirectoryGroupsLive: listSlackDirectoryGroupsLive,
|
||||
listDirectoryPeersLive: listSlackDirectoryPeersLive,
|
||||
probeSlack,
|
||||
resolveChannelAllowlist: resolveSlackChannelAllowlist,
|
||||
resolveUserAllowlist: resolveSlackUserAllowlist,
|
||||
sendMessageSlack,
|
||||
monitorSlackProvider,
|
||||
handleSlackAction,
|
||||
},
|
||||
telegram: {
|
||||
auditGroupMembership: auditTelegramGroupMembership,
|
||||
collectUnmentionedGroupIds: collectTelegramUnmentionedGroupIds,
|
||||
probeTelegram,
|
||||
resolveTelegramToken,
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
monitorTelegramProvider,
|
||||
messageActions: telegramMessageActions,
|
||||
},
|
||||
signal: {
|
||||
probeSignal,
|
||||
sendMessageSignal,
|
||||
monitorSignalProvider,
|
||||
messageActions: signalMessageActions,
|
||||
},
|
||||
imessage: {
|
||||
monitorIMessageProvider,
|
||||
probeIMessage,
|
||||
sendMessageIMessage,
|
||||
},
|
||||
whatsapp: createRuntimeWhatsApp(),
|
||||
line: {
|
||||
listLineAccountIds,
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
normalizeAccountId: normalizeLineAccountId,
|
||||
probeLineBot,
|
||||
sendMessageLine,
|
||||
pushMessageLine,
|
||||
pushMessagesLine,
|
||||
pushFlexMessage,
|
||||
pushTemplateMessage,
|
||||
pushLocationMessage,
|
||||
pushTextMessageWithQuickReplies,
|
||||
createQuickReplyItems,
|
||||
buildTemplateMessageFromPayload,
|
||||
monitorLineProvider,
|
||||
},
|
||||
};
|
||||
}
|
||||
9
src/plugins/runtime/runtime-config.ts
Normal file
9
src/plugins/runtime/runtime-config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeConfig(): PluginRuntime["config"] {
|
||||
return {
|
||||
loadConfig,
|
||||
writeConfigFile,
|
||||
};
|
||||
}
|
||||
10
src/plugins/runtime/runtime-events.ts
Normal file
10
src/plugins/runtime/runtime-events.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { onAgentEvent } from "../../infra/agent-events.js";
|
||||
import { onSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeEvents(): PluginRuntime["events"] {
|
||||
return {
|
||||
onAgentEvent,
|
||||
onSessionTranscriptUpdate,
|
||||
};
|
||||
}
|
||||
21
src/plugins/runtime/runtime-logging.ts
Normal file
21
src/plugins/runtime/runtime-logging.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import { getChildLogger } from "../../logging.js";
|
||||
import { normalizeLogLevel } from "../../logging/levels.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeLogging(): PluginRuntime["logging"] {
|
||||
return {
|
||||
shouldLogVerbose,
|
||||
getChildLogger: (bindings, opts) => {
|
||||
const logger = getChildLogger(bindings, {
|
||||
level: opts?.level ? normalizeLogLevel(opts.level) : undefined,
|
||||
});
|
||||
return {
|
||||
debug: (message) => logger.debug?.(message),
|
||||
info: (message) => logger.info(message),
|
||||
warn: (message) => logger.warn(message),
|
||||
error: (message) => logger.error(message),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
17
src/plugins/runtime/runtime-media.ts
Normal file
17
src/plugins/runtime/runtime-media.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { isVoiceCompatibleAudio } from "../../media/audio.js";
|
||||
import { mediaKindFromMime } from "../../media/constants.js";
|
||||
import { getImageMetadata, resizeToJpeg } from "../../media/image-ops.js";
|
||||
import { detectMime } from "../../media/mime.js";
|
||||
import { loadWebMedia } from "../../web/media.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeMedia(): PluginRuntime["media"] {
|
||||
return {
|
||||
loadWebMedia,
|
||||
detectMime,
|
||||
mediaKindFromMime,
|
||||
isVoiceCompatibleAudio,
|
||||
getImageMetadata,
|
||||
resizeToJpeg,
|
||||
};
|
||||
}
|
||||
14
src/plugins/runtime/runtime-system.ts
Normal file
14
src/plugins/runtime/runtime-system.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
|
||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||
import { runCommandWithTimeout } from "../../process/exec.js";
|
||||
import { formatNativeDependencyHint } from "./native-deps.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeSystem(): PluginRuntime["system"] {
|
||||
return {
|
||||
enqueueSystemEvent,
|
||||
requestHeartbeatNow,
|
||||
runCommandWithTimeout,
|
||||
formatNativeDependencyHint,
|
||||
};
|
||||
}
|
||||
11
src/plugins/runtime/runtime-tools.ts
Normal file
11
src/plugins/runtime/runtime-tools.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createMemoryGetTool, createMemorySearchTool } from "../../agents/tools/memory-tool.js";
|
||||
import { registerMemoryCli } from "../../cli/memory-cli.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
export function createRuntimeTools(): PluginRuntime["tools"] {
|
||||
return {
|
||||
createMemoryGetTool,
|
||||
createMemorySearchTool,
|
||||
registerMemoryCli,
|
||||
};
|
||||
}
|
||||
108
src/plugins/runtime/runtime-whatsapp.ts
Normal file
108
src/plugins/runtime/runtime-whatsapp.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { createWhatsAppLoginTool } from "../../channels/plugins/agent-tools/whatsapp-login.js";
|
||||
import { getActiveWebListener } from "../../web/active-listener.js";
|
||||
import {
|
||||
getWebAuthAgeMs,
|
||||
logoutWeb,
|
||||
logWebSelfId,
|
||||
readWebSelfId,
|
||||
webAuthExists,
|
||||
} from "../../web/auth-store.js";
|
||||
import type { PluginRuntime } from "./types.js";
|
||||
|
||||
const sendMessageWhatsAppLazy: PluginRuntime["channel"]["whatsapp"]["sendMessageWhatsApp"] = async (
|
||||
...args
|
||||
) => {
|
||||
const { sendMessageWhatsApp } = await loadWebOutbound();
|
||||
return sendMessageWhatsApp(...args);
|
||||
};
|
||||
|
||||
const sendPollWhatsAppLazy: PluginRuntime["channel"]["whatsapp"]["sendPollWhatsApp"] = async (
|
||||
...args
|
||||
) => {
|
||||
const { sendPollWhatsApp } = await loadWebOutbound();
|
||||
return sendPollWhatsApp(...args);
|
||||
};
|
||||
|
||||
const loginWebLazy: PluginRuntime["channel"]["whatsapp"]["loginWeb"] = async (...args) => {
|
||||
const { loginWeb } = await loadWebLogin();
|
||||
return loginWeb(...args);
|
||||
};
|
||||
|
||||
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 webOutboundPromise: Promise<typeof import("../../web/outbound.js")> | null = null;
|
||||
let webLoginPromise: Promise<typeof import("../../web/login.js")> | null = null;
|
||||
let webLoginQrPromise: Promise<typeof import("../../web/login-qr.js")> | null = null;
|
||||
let webChannelPromise: Promise<typeof import("../../channels/web/index.js")> | null = null;
|
||||
let whatsappActionsPromise: Promise<
|
||||
typeof import("../../agents/tools/whatsapp-actions.js")
|
||||
> | null = null;
|
||||
|
||||
function loadWebOutbound() {
|
||||
webOutboundPromise ??= import("../../web/outbound.js");
|
||||
return webOutboundPromise;
|
||||
}
|
||||
|
||||
function loadWebLogin() {
|
||||
webLoginPromise ??= import("../../web/login.js");
|
||||
return webLoginPromise;
|
||||
}
|
||||
|
||||
function loadWebLoginQr() {
|
||||
webLoginQrPromise ??= import("../../web/login-qr.js");
|
||||
return webLoginQrPromise;
|
||||
}
|
||||
|
||||
function loadWebChannel() {
|
||||
webChannelPromise ??= import("../../channels/web/index.js");
|
||||
return webChannelPromise;
|
||||
}
|
||||
|
||||
function loadWhatsAppActions() {
|
||||
whatsappActionsPromise ??= import("../../agents/tools/whatsapp-actions.js");
|
||||
return whatsappActionsPromise;
|
||||
}
|
||||
|
||||
export function createRuntimeWhatsApp(): PluginRuntime["channel"]["whatsapp"] {
|
||||
return {
|
||||
getActiveWebListener,
|
||||
getWebAuthAgeMs,
|
||||
logoutWeb,
|
||||
logWebSelfId,
|
||||
readWebSelfId,
|
||||
webAuthExists,
|
||||
sendMessageWhatsApp: sendMessageWhatsAppLazy,
|
||||
sendPollWhatsApp: sendPollWhatsAppLazy,
|
||||
loginWeb: loginWebLazy,
|
||||
startWebLoginWithQr: startWebLoginWithQrLazy,
|
||||
waitForWebLogin: waitForWebLoginLazy,
|
||||
monitorWebChannel: monitorWebChannelLazy,
|
||||
handleWhatsAppAction: handleWhatsAppActionLazy,
|
||||
createLoginTool: createWhatsAppLoginTool,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user