refactor(src): split oversized modules

This commit is contained in:
Peter Steinberger
2026-01-14 01:08:15 +00:00
parent b2179de839
commit bcbfb357be
675 changed files with 91476 additions and 73453 deletions

View File

@@ -0,0 +1,245 @@
import {
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
resolveHeartbeatPrompt,
stripHeartbeatToken,
} from "../../auto-reply/heartbeat.js";
import { getReplyFromConfig } from "../../auto-reply/reply.js";
import type { ReplyPayload } from "../../auto-reply/types.js";
import { resolveWhatsAppHeartbeatRecipients } from "../../channels/plugins/whatsapp-heartbeat.js";
import { loadConfig } from "../../config/config.js";
import {
loadSessionStore,
resolveSessionKey,
resolveStorePath,
saveSessionStore,
} from "../../config/sessions.js";
import { emitHeartbeatEvent } from "../../infra/heartbeat-events.js";
import { getChildLogger } from "../../logging.js";
import { normalizeMainKey } from "../../routing/session-key.js";
import { sendMessageWhatsApp } from "../outbound.js";
import { newConnectionId } from "../reconnect.js";
import { formatError } from "../session.js";
import { whatsappHeartbeatLog } from "./loggers.js";
import { getSessionSnapshot } from "./session-snapshot.js";
import { elide } from "./util.js";
function resolveHeartbeatReplyPayload(
replyResult: ReplyPayload | ReplyPayload[] | undefined,
): ReplyPayload | undefined {
if (!replyResult) return undefined;
if (!Array.isArray(replyResult)) return replyResult;
for (let idx = replyResult.length - 1; idx >= 0; idx -= 1) {
const payload = replyResult[idx];
if (!payload) continue;
if (
payload.text ||
payload.mediaUrl ||
(payload.mediaUrls && payload.mediaUrls.length > 0)
) {
return payload;
}
}
return undefined;
}
export async function runWebHeartbeatOnce(opts: {
cfg?: ReturnType<typeof loadConfig>;
to: string;
verbose?: boolean;
replyResolver?: typeof getReplyFromConfig;
sender?: typeof sendMessageWhatsApp;
sessionId?: string;
overrideBody?: string;
dryRun?: boolean;
}) {
const {
cfg: cfgOverride,
to,
verbose = false,
sessionId,
overrideBody,
dryRun = false,
} = opts;
const replyResolver = opts.replyResolver ?? getReplyFromConfig;
const sender = opts.sender ?? sendMessageWhatsApp;
const runId = newConnectionId();
const heartbeatLogger = getChildLogger({
module: "web-heartbeat",
runId,
to,
});
const cfg = cfgOverride ?? loadConfig();
const sessionCfg = cfg.session;
const sessionScope = sessionCfg?.scope ?? "per-sender";
const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const sessionKey = resolveSessionKey(sessionScope, { From: to }, mainKey);
if (sessionId) {
const storePath = resolveStorePath(cfg.session?.store);
const store = loadSessionStore(storePath);
const current = store[sessionKey] ?? {};
store[sessionKey] = {
...current,
sessionId,
updatedAt: Date.now(),
};
await saveSessionStore(storePath, store);
}
const sessionSnapshot = getSessionSnapshot(cfg, to, true);
if (verbose) {
heartbeatLogger.info(
{
to,
sessionKey: sessionSnapshot.key,
sessionId: sessionId ?? sessionSnapshot.entry?.sessionId ?? null,
sessionFresh: sessionSnapshot.fresh,
idleMinutes: sessionSnapshot.idleMinutes,
},
"heartbeat session snapshot",
);
}
if (overrideBody && overrideBody.trim().length === 0) {
throw new Error("Override body must be non-empty when provided.");
}
try {
if (overrideBody) {
if (dryRun) {
whatsappHeartbeatLog.info(
`[dry-run] web send -> ${to}: ${elide(overrideBody.trim(), 200)} (manual message)`,
);
return;
}
const sendResult = await sender(to, overrideBody, { verbose });
emitHeartbeatEvent({
status: "sent",
to,
preview: overrideBody.slice(0, 160),
hasMedia: false,
});
heartbeatLogger.info(
{
to,
messageId: sendResult.messageId,
chars: overrideBody.length,
reason: "manual-message",
},
"manual heartbeat message sent",
);
whatsappHeartbeatLog.info(
`manual heartbeat sent to ${to} (id ${sendResult.messageId})`,
);
return;
}
const replyResult = await replyResolver(
{
Body: resolveHeartbeatPrompt(cfg.agents?.defaults?.heartbeat?.prompt),
From: to,
To: to,
MessageSid: sessionId ?? sessionSnapshot.entry?.sessionId,
},
{ isHeartbeat: true },
cfg,
);
const replyPayload = resolveHeartbeatReplyPayload(replyResult);
if (
!replyPayload ||
(!replyPayload.text &&
!replyPayload.mediaUrl &&
!replyPayload.mediaUrls?.length)
) {
heartbeatLogger.info(
{
to,
reason: "empty-reply",
sessionId: sessionSnapshot.entry?.sessionId ?? null,
},
"heartbeat skipped",
);
emitHeartbeatEvent({ status: "ok-empty", to });
return;
}
const hasMedia = Boolean(
replyPayload.mediaUrl || (replyPayload.mediaUrls?.length ?? 0) > 0,
);
const ackMaxChars = Math.max(
0,
cfg.agents?.defaults?.heartbeat?.ackMaxChars ??
DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
);
const stripped = stripHeartbeatToken(replyPayload.text, {
mode: "heartbeat",
maxAckChars: ackMaxChars,
});
if (stripped.shouldSkip && !hasMedia) {
// Don't let heartbeats keep sessions alive: restore previous updatedAt so idle expiry still works.
const storePath = resolveStorePath(cfg.session?.store);
const store = loadSessionStore(storePath);
if (sessionSnapshot.entry && store[sessionSnapshot.key]) {
store[sessionSnapshot.key].updatedAt = sessionSnapshot.entry.updatedAt;
await saveSessionStore(storePath, store);
}
heartbeatLogger.info(
{ to, reason: "heartbeat-token", rawLength: replyPayload.text?.length },
"heartbeat skipped",
);
emitHeartbeatEvent({ status: "ok-token", to });
return;
}
if (hasMedia) {
heartbeatLogger.warn(
{ to },
"heartbeat reply contained media; sending text only",
);
}
const finalText = stripped.text || replyPayload.text || "";
if (dryRun) {
heartbeatLogger.info(
{ to, reason: "dry-run", chars: finalText.length },
"heartbeat dry-run",
);
whatsappHeartbeatLog.info(
`[dry-run] heartbeat -> ${to}: ${elide(finalText, 200)}`,
);
return;
}
const sendResult = await sender(to, finalText, { verbose });
emitHeartbeatEvent({
status: "sent",
to,
preview: finalText.slice(0, 160),
hasMedia,
});
heartbeatLogger.info(
{
to,
messageId: sendResult.messageId,
chars: finalText.length,
preview: elide(finalText, 140),
},
"heartbeat sent",
);
whatsappHeartbeatLog.info(`heartbeat alert sent to ${to}`);
} catch (err) {
const reason = formatError(err);
heartbeatLogger.warn({ to, error: reason }, "heartbeat failed");
whatsappHeartbeatLog.warn(`heartbeat failed (${reason})`);
emitHeartbeatEvent({ status: "failed", to, reason });
throw err;
}
}
export function resolveHeartbeatRecipients(
cfg: ReturnType<typeof loadConfig>,
opts: { to?: string; all?: boolean } = {},
) {
return resolveWhatsAppHeartbeatRecipients(cfg, opts);
}