perf(runtime): trim hot-path allocations and cache channel plugin lookups

This commit is contained in:
Peter Steinberger
2026-03-02 23:55:33 +00:00
parent dba47f349f
commit d3dc4e54f7
6 changed files with 148 additions and 74 deletions

View File

@@ -469,33 +469,31 @@ async function deliverOutboundPayloadsCore(
text: normalizedText,
};
};
const normalizedPayloads = normalizeReplyPayloadsForDelivery(payloads)
.map((payload) => {
// Strip HTML tags for plain-text surfaces (WhatsApp, Signal, etc.)
// Models occasionally produce <br>, <b>, etc. that render as literal text.
// See https://github.com/openclaw/openclaw/issues/31884
if (!isPlainTextSurface(channel) || !payload.text) {
return payload;
}
const normalizedPayloads: ReplyPayload[] = [];
for (const payload of normalizeReplyPayloadsForDelivery(payloads)) {
let sanitizedPayload = payload;
// Strip HTML tags for plain-text surfaces (WhatsApp, Signal, etc.)
// Models occasionally produce <br>, <b>, etc. that render as literal text.
// See https://github.com/openclaw/openclaw/issues/31884
if (isPlainTextSurface(channel) && payload.text) {
// Telegram sendPayload uses textMode:"html". Preserve raw HTML in this path.
if (channel === "telegram" && payload.channelData) {
return payload;
if (!(channel === "telegram" && payload.channelData)) {
sanitizedPayload = { ...payload, text: sanitizeForPlainText(payload.text) };
}
return { ...payload, text: sanitizeForPlainText(payload.text) };
})
.flatMap((payload) => {
const normalized = normalizePayloadForChannelDelivery(payload, channel);
return normalized ? [normalized] : [];
});
}
const normalized = normalizePayloadForChannelDelivery(sanitizedPayload, channel);
if (normalized) {
normalizedPayloads.push(normalized);
}
}
const hookRunner = getGlobalHookRunner();
const sessionKeyForInternalHooks = params.mirror?.sessionKey ?? params.session?.key;
const mirrorIsGroup = params.mirror?.isGroup;
const mirrorGroupId = params.mirror?.groupId;
if (
hookRunner?.hasHooks("message_sent") &&
params.session?.agentId &&
!sessionKeyForInternalHooks
) {
const hasMessageSentHooks = hookRunner?.hasHooks("message_sent") ?? false;
const hasMessageSendingHooks = hookRunner?.hasHooks("message_sending") ?? false;
const canEmitInternalHook = Boolean(sessionKeyForInternalHooks);
if (hasMessageSentHooks && params.session?.agentId && !sessionKeyForInternalHooks) {
log.warn(
"deliverOutboundPayloads: session.agentId present without session key; internal message:sent hook will be skipped",
{
@@ -517,6 +515,9 @@ async function deliverOutboundPayloadsCore(
error?: string;
messageId?: string;
}) => {
if (!hasMessageSentHooks && !canEmitInternalHook) {
return;
}
const canonical = buildCanonicalSentMessageHookContext({
to,
content: params.content,
@@ -529,9 +530,9 @@ async function deliverOutboundPayloadsCore(
isGroup: mirrorIsGroup,
groupId: mirrorGroupId,
});
if (hookRunner?.hasHooks("message_sent")) {
if (hasMessageSentHooks) {
fireAndForgetHook(
hookRunner.runMessageSent(
hookRunner!.runMessageSent(
toPluginMessageSentEvent(canonical),
toPluginMessageContext(canonical),
),
@@ -541,7 +542,7 @@ async function deliverOutboundPayloadsCore(
},
);
}
if (!sessionKeyForInternalHooks) {
if (!canEmitInternalHook) {
return;
}
fireAndForgetHook(
@@ -549,7 +550,7 @@ async function deliverOutboundPayloadsCore(
createInternalHookEvent(
"message",
"sent",
sessionKeyForInternalHooks,
sessionKeyForInternalHooks!,
toInternalMessageSentContext(canonical),
),
),
@@ -564,9 +565,9 @@ async function deliverOutboundPayloadsCore(
// Run message_sending plugin hook (may modify content or cancel)
let effectivePayload = payload;
if (hookRunner?.hasHooks("message_sending")) {
if (hasMessageSendingHooks) {
try {
const sendingResult = await hookRunner.runMessageSending(
const sendingResult = await hookRunner!.runMessageSending(
{
to,
content: payloadSummary.text,

View File

@@ -43,9 +43,10 @@ function mergeMediaUrls(...lists: Array<ReadonlyArray<string | undefined> | unde
export function normalizeReplyPayloadsForDelivery(
payloads: readonly ReplyPayload[],
): ReplyPayload[] {
return payloads.flatMap((payload) => {
const normalized: ReplyPayload[] = [];
for (const payload of payloads) {
if (shouldSuppressReasoningPayload(payload)) {
return [];
continue;
}
const parsed = parseReplyDirectives(payload.text ?? "");
const explicitMediaUrls = payload.mediaUrls ?? parsed.mediaUrls;
@@ -67,47 +68,50 @@ export function normalizeReplyPayloadsForDelivery(
audioAsVoice: Boolean(payload.audioAsVoice || parsed.audioAsVoice),
};
if (parsed.isSilent && mergedMedia.length === 0) {
return [];
continue;
}
if (!isRenderablePayload(next)) {
return [];
continue;
}
return [next];
});
normalized.push(next);
}
return normalized;
}
export function normalizeOutboundPayloads(
payloads: readonly ReplyPayload[],
): NormalizedOutboundPayload[] {
return normalizeReplyPayloadsForDelivery(payloads)
.map((payload) => {
const channelData = payload.channelData;
const normalized: NormalizedOutboundPayload = {
text: payload.text ?? "",
mediaUrls: payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []),
};
if (channelData && Object.keys(channelData).length > 0) {
normalized.channelData = channelData;
}
return normalized;
})
.filter(
(payload) =>
payload.text ||
payload.mediaUrls.length > 0 ||
Boolean(payload.channelData && Object.keys(payload.channelData).length > 0),
);
const normalizedPayloads: NormalizedOutboundPayload[] = [];
for (const payload of normalizeReplyPayloadsForDelivery(payloads)) {
const mediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
const channelData = payload.channelData;
const hasChannelData = Boolean(channelData && Object.keys(channelData).length > 0);
const text = payload.text ?? "";
if (!text && mediaUrls.length === 0 && !hasChannelData) {
continue;
}
normalizedPayloads.push({
text,
mediaUrls,
...(hasChannelData ? { channelData } : {}),
});
}
return normalizedPayloads;
}
export function normalizeOutboundPayloadsForJson(
payloads: readonly ReplyPayload[],
): OutboundPayloadJson[] {
return normalizeReplyPayloadsForDelivery(payloads).map((payload) => ({
text: payload.text ?? "",
mediaUrl: payload.mediaUrl ?? null,
mediaUrls: payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : undefined),
channelData: payload.channelData,
}));
const normalized: OutboundPayloadJson[] = [];
for (const payload of normalizeReplyPayloadsForDelivery(payloads)) {
normalized.push({
text: payload.text ?? "",
mediaUrl: payload.mediaUrl ?? null,
mediaUrls: payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : undefined),
channelData: payload.channelData,
});
}
return normalized;
}
export function formatOutboundPayloadLog(