diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 05a6108ce43..dbb9e91cea9 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -01967db7ff4d52e187f557a21f6433f7640542b96872eee9d4e97c72e7a4e30c plugin-sdk-api-baseline.json -c88fe1415b3fde1ba1ede62222022768695054c2ad5c97a5975dbe28cd03df75 plugin-sdk-api-baseline.jsonl +0e7b5b1515042e63598a05b23f13e9dedfdfcc2d760e51ffc940d84ff57d284a plugin-sdk-api-baseline.json +82496afcfb75d3c45df5ead23e8aa4140153f502ccb1dfe053794f2411a5a3cd plugin-sdk-api-baseline.jsonl diff --git a/extensions/discord/src/monitor/inbound-context.ts b/extensions/discord/src/monitor/inbound-context.ts index 6cc3a7da545..532a03f9587 100644 --- a/extensions/discord/src/monitor/inbound-context.ts +++ b/extensions/discord/src/monitor/inbound-context.ts @@ -51,20 +51,17 @@ export function buildDiscordUntrustedContext(params: { if (!params.isGuild) { return undefined; } - const entries = [ - typeof params.channelTopic === "string" && params.channelTopic.trim().length > 0 - ? { - label: "Discord channel metadata", - source: "discord", - type: "channel_metadata", - payload: { - topic: params.channelTopic.trim(), - }, - } - : undefined, - ].filter((entry): entry is NonNullable[number] => - Boolean(entry), - ); + const entries: NonNullable = []; + if (typeof params.channelTopic === "string" && params.channelTopic.trim().length > 0) { + entries.push({ + label: "Discord channel metadata", + source: "discord", + type: "channel_metadata", + payload: { + topic: params.channelTopic.trim(), + }, + }); + } return entries.length > 0 ? entries : undefined; } diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index d90209e2932..77fb6a0b7f8 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -7,11 +7,8 @@ import { } from "openclaw/plugin-sdk/conversation-runtime"; import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/outbound-runtime"; import { - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, - clearHistoryEntriesIfEnabled, DEFAULT_GROUP_HISTORY_LIMIT, - recordPendingHistoryEntryIfEnabled, + createChannelHistoryWindow, type HistoryEntry, } from "openclaw/plugin-sdk/reply-history"; import { @@ -679,10 +676,9 @@ export async function handleFeishuMessage(params: { // Record to pending history for non-broadcast groups only. For broadcast groups, // the mentioned handler's broadcast dispatch writes the turn directly into all // agent sessions — buffering here would cause duplicate replay when this account - // later becomes active via buildPendingHistoryContextFromMap. + // later becomes active via the channel history window. if (!broadcastAgents && chatHistories && groupHistoryKey) { - recordPendingHistoryEntryIfEnabled({ - historyMap: chatHistories, + createChannelHistoryWindow({ historyMap: chatHistories }).record({ historyKey: groupHistoryKey, limit: historyLimit, entry: { @@ -1058,8 +1054,8 @@ export async function handleFeishuMessage(params: { const historyKey = groupHistoryKey; if (isGroup && historyKey && chatHistories) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: chatHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: chatHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey, limit: historyLimit, currentMessage: combinedBody, @@ -1077,8 +1073,7 @@ export async function handleFeishuMessage(params: { const inboundHistory = isGroup && historyKey && historyLimit > 0 && chatHistories - ? buildInboundHistoryFromMap({ - historyMap: chatHistories, + ? createChannelHistoryWindow({ historyMap: chatHistories }).buildInboundHistory({ historyKey, limit: historyLimit, }) @@ -1534,8 +1529,7 @@ export async function handleFeishuMessage(params: { } if (isGroup && historyKey && chatHistories) { - clearHistoryEntriesIfEnabled({ - historyMap: chatHistories, + createChannelHistoryWindow({ historyMap: chatHistories }).clear({ historyKey, limit: historyLimit, }); diff --git a/extensions/imessage/src/monitor/inbound-processing.ts b/extensions/imessage/src/monitor/inbound-processing.ts index c0fdfd6f608..7c47bee13ae 100644 --- a/extensions/imessage/src/monitor/inbound-processing.ts +++ b/extensions/imessage/src/monitor/inbound-processing.ts @@ -20,12 +20,7 @@ import { import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native"; import type { DmPolicy, GroupPolicy, OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime"; -import { - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, - recordPendingHistoryEntryIfEnabled, - type HistoryEntry, -} from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow, type HistoryEntry } from "openclaw/plugin-sdk/reply-history"; import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime"; @@ -846,8 +841,7 @@ export async function resolveIMessageInboundDecision(params: { const effectiveWasMentioned = mentionDecision.effectiveWasMentioned; if (isGroup && requireMention && canDetectMention && mentionDecision.shouldSkip) { params.logVerbose?.(`imessage: skipping group message (no mention)`); - recordPendingHistoryEntryIfEnabled({ - historyMap: params.groupHistories, + createChannelHistoryWindow({ historyMap: params.groupHistories }).record({ historyKey: historyKey ?? "", limit: params.historyLimit, entry: historyKey @@ -969,8 +963,8 @@ export function buildIMessageInboundContext(params: { let combinedBody = body; if (decision.isGroup && decision.historyKey) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: params.groupHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: params.groupHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey: decision.historyKey, limit: params.historyLimit, currentMessage: combinedBody, @@ -990,8 +984,7 @@ export function buildIMessageInboundContext(params: { const imessageTo = (decision.isGroup ? chatTarget : undefined) || `imessage:${decision.sender}`; const inboundHistory = decision.isGroup && decision.historyKey && params.historyLimit > 0 - ? buildInboundHistoryFromMap({ - historyMap: params.groupHistories, + ? createChannelHistoryWindow({ historyMap: params.groupHistories }).buildInboundHistory({ historyKey: decision.historyKey, limit: params.historyLimit, }) diff --git a/extensions/line/src/bot-handlers.test.ts b/extensions/line/src/bot-handlers.test.ts index 61bd15213bf..09656e0be75 100644 --- a/extensions/line/src/bot-handlers.test.ts +++ b/extensions/line/src/bot-handlers.test.ts @@ -54,6 +54,29 @@ vi.mock("openclaw/plugin-sdk/runtime-env", () => ({ })); vi.mock("openclaw/plugin-sdk/reply-history", () => ({ DEFAULT_GROUP_HISTORY_LIMIT: 20, + createChannelHistoryWindow: ({ historyMap }: { historyMap: Map }) => ({ + record: ({ + historyKey, + limit, + entry, + }: { + historyKey: string; + limit: number; + entry: HistoryEntry; + }) => { + const existing = historyMap.get(historyKey) ?? []; + historyMap.set(historyKey, [...existing, entry].slice(-limit)); + }, + buildInboundHistory: ({ historyKey, limit }: { historyKey: string; limit: number }) => { + if (limit <= 0) { + return undefined; + } + return (historyMap.get(historyKey) ?? []).slice(-limit); + }, + clear: ({ historyKey }: { historyKey: string }) => { + historyMap.delete(historyKey); + }, + }), buildInboundHistoryFromMap: ({ historyMap, historyKey, diff --git a/extensions/line/src/bot-handlers.ts b/extensions/line/src/bot-handlers.ts index fb9f84cdf6f..14d596edbe5 100644 --- a/extensions/line/src/bot-handlers.ts +++ b/extensions/line/src/bot-handlers.ts @@ -12,8 +12,7 @@ import { import { createClaimableDedupe, type ClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe"; import { DEFAULT_GROUP_HISTORY_LIMIT, - clearHistoryEntriesIfEnabled, - recordPendingHistoryEntryIfEnabled, + createChannelHistoryWindow, type HistoryEntry, } from "openclaw/plugin-sdk/reply-history"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; @@ -450,8 +449,7 @@ async function handleMessageEvent(event: MessageEvent, context: LineHandlerConte const historyKey = groupId ?? roomId; const senderId = sourceInfo.userId ?? "unknown"; if (historyKey && context.groupHistories) { - recordPendingHistoryEntryIfEnabled({ - historyMap: context.groupHistories, + createChannelHistoryWindow({ historyMap: context.groupHistories }).record({ historyKey, limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT, entry: { @@ -503,8 +501,7 @@ async function handleMessageEvent(event: MessageEvent, context: LineHandlerConte if (isGroup && context.groupHistories) { const historyKey = groupId ?? roomId; if (historyKey && context.groupHistories.has(historyKey)) { - clearHistoryEntriesIfEnabled({ - historyMap: context.groupHistories, + createChannelHistoryWindow({ historyMap: context.groupHistories }).clear({ historyKey, limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT, }); diff --git a/extensions/line/src/bot-message-context.ts b/extensions/line/src/bot-message-context.ts index 8b276d9c614..bb5aefeaa3e 100644 --- a/extensions/line/src/bot-message-context.ts +++ b/extensions/line/src/bot-message-context.ts @@ -14,7 +14,7 @@ import { resolveRuntimeConversationBindingRoute, } from "openclaw/plugin-sdk/conversation-runtime"; import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime"; -import { buildInboundHistoryFromMap, type HistoryEntry } from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow, type HistoryEntry } from "openclaw/plugin-sdk/reply-history"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; @@ -469,8 +469,7 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar const historyKey = isGroup ? peerId : undefined; const inboundHistory = historyKey && groupHistories && (historyLimit ?? 0) > 0 - ? buildInboundHistoryFromMap({ - historyMap: groupHistories, + ? createChannelHistoryWindow({ historyMap: groupHistories }).buildInboundHistory({ historyKey, limit: historyLimit ?? 0, }) diff --git a/extensions/mattermost/runtime-api.ts b/extensions/mattermost/runtime-api.ts index fe608ea1c52..8d19b2d805a 100644 --- a/extensions/mattermost/runtime-api.ts +++ b/extensions/mattermost/runtime-api.ts @@ -57,6 +57,7 @@ export { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress"; export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking"; export { DEFAULT_GROUP_HISTORY_LIMIT, + createChannelHistoryWindow, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled, diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index 5c36eb3de7e..5bf90de4214 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -88,14 +88,12 @@ import type { import { buildAgentMediaPayload, buildModelsProviderData, - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, + createChannelHistoryWindow, createChannelPairingController, createChannelMessageReplyPipeline, DEFAULT_GROUP_HISTORY_LIMIT, logInboundDrop, logTypingFailure, - recordPendingHistoryEntryIfEnabled, registerPluginHttpRoute, resolveAllowlistProviderRuntimeGroupPolicy, resolveChannelMediaMaxBytes, @@ -1413,8 +1411,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} const pendingSender = senderName; const recordPendingHistory = () => { const trimmed = pendingBody.trim(); - recordPendingHistoryEntryIfEnabled({ - historyMap: channelHistories, + createChannelHistoryWindow({ historyMap: channelHistories }).record({ limit: historyLimit, historyKey: historyKey ?? "", entry: @@ -1506,8 +1503,8 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); let combinedBody = body; if (historyKey) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: channelHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: channelHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey, limit: historyLimit, currentMessage: combinedBody, @@ -1530,8 +1527,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} const commandBody = rawText.trim(); const inboundHistory = historyKey && historyLimit > 0 - ? buildInboundHistoryFromMap({ - historyMap: channelHistories, + ? createChannelHistoryWindow({ historyMap: channelHistories }).buildInboundHistory({ historyKey, limit: historyLimit, }) diff --git a/extensions/mattermost/src/mattermost/runtime-api.ts b/extensions/mattermost/src/mattermost/runtime-api.ts index 12511914ae0..229d582e24e 100644 --- a/extensions/mattermost/src/mattermost/runtime-api.ts +++ b/extensions/mattermost/src/mattermost/runtime-api.ts @@ -30,6 +30,7 @@ export { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime"; export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media"; export { DEFAULT_GROUP_HISTORY_LIMIT, + createChannelHistoryWindow, buildInboundHistoryFromMap, buildPendingHistoryContextFromMap, recordPendingHistoryEntryIfEnabled, diff --git a/extensions/mattermost/src/runtime-api.ts b/extensions/mattermost/src/runtime-api.ts index 6d88ab1d968..6b5b8c3fb19 100644 --- a/extensions/mattermost/src/runtime-api.ts +++ b/extensions/mattermost/src/runtime-api.ts @@ -16,6 +16,7 @@ export { type ChatType, chunkTextForOutbound, clearHistoryEntriesIfEnabled, + createChannelHistoryWindow, createAccountStatusSink, createChannelPairingController, createChannelMessageReplyPipeline, diff --git a/extensions/msteams/src/monitor-handler/message-handler.ts b/extensions/msteams/src/monitor-handler/message-handler.ts index 0eba0f78372..60660ec91e4 100644 --- a/extensions/msteams/src/monitor-handler/message-handler.ts +++ b/extensions/msteams/src/monitor-handler/message-handler.ts @@ -15,10 +15,8 @@ import { resolveInboundReplyDispatchCounts, } from "openclaw/plugin-sdk/inbound-reply-dispatch"; import { - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, DEFAULT_GROUP_HISTORY_LIMIT, - recordPendingHistoryEntryIfEnabled, + createChannelHistoryWindow, type HistoryEntry, } from "openclaw/plugin-sdk/reply-history"; import { @@ -538,8 +536,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { requireMention, mentioned, }); - recordPendingHistoryEntryIfEnabled({ - historyMap: conversationHistories, + createChannelHistoryWindow({ historyMap: conversationHistories }).record({ historyKey: conversationId, limit: historyLimit, entry: { @@ -712,8 +709,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { const isRoomish = !isDirectMessage; const historyKey = isRoomish ? conversationId : undefined; if (isRoomish && historyKey) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: conversationHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: conversationHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey, limit: historyLimit, currentMessage: combinedBody, @@ -730,8 +727,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { const inboundHistory = isRoomish && historyKey && historyLimit > 0 - ? buildInboundHistoryFromMap({ - historyMap: conversationHistories, + ? createChannelHistoryWindow({ historyMap: conversationHistories }).buildInboundHistory({ historyKey, limit: historyLimit, }) diff --git a/extensions/qqbot/src/bridge/sdk-adapter.ts b/extensions/qqbot/src/bridge/sdk-adapter.ts index 96008c023a9..de545034508 100644 --- a/extensions/qqbot/src/bridge/sdk-adapter.ts +++ b/extensions/qqbot/src/bridge/sdk-adapter.ts @@ -5,9 +5,7 @@ import { import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { - buildPendingHistoryContextFromMap, - clearHistoryEntriesIfEnabled, - recordPendingHistoryEntryIfEnabled, + createChannelHistoryWindow, type HistoryEntry as SdkHistoryEntry, } from "openclaw/plugin-sdk/reply-history"; import { resolveQQBotEffectivePolicies } from "../engine/access/resolve-policy.js"; @@ -34,8 +32,7 @@ export function createSdkHistoryAdapter(): HistoryPort { entry?: T | null; limit: number; }) { - return recordPendingHistoryEntryIfEnabled({ - historyMap: asSdkMap(params.historyMap), + return createChannelHistoryWindow({ historyMap: asSdkMap(params.historyMap) }).record({ historyKey: params.historyKey, entry: params.entry as SdkHistoryEntry | undefined, limit: params.limit, @@ -43,8 +40,9 @@ export function createSdkHistoryAdapter(): HistoryPort { }, buildPendingHistoryContext(params) { - return buildPendingHistoryContextFromMap({ + return createChannelHistoryWindow({ historyMap: asSdkMap(params.historyMap), + }).buildPendingContext({ historyKey: params.historyKey, limit: params.limit, currentMessage: params.currentMessage, @@ -54,8 +52,7 @@ export function createSdkHistoryAdapter(): HistoryPort { }, clearPendingHistory(params) { - clearHistoryEntriesIfEnabled({ - historyMap: asSdkMap(params.historyMap), + createChannelHistoryWindow({ historyMap: asSdkMap(params.historyMap) }).clear({ historyKey: params.historyKey, limit: params.limit, }); diff --git a/extensions/signal/src/monitor/event-handler.ts b/extensions/signal/src/monitor/event-handler.ts index d2b499c6c0e..335a063a63c 100644 --- a/extensions/signal/src/monitor/event-handler.ts +++ b/extensions/signal/src/monitor/event-handler.ts @@ -26,11 +26,7 @@ import { } from "openclaw/plugin-sdk/hook-runtime"; import { runInboundReplyTurn } from "openclaw/plugin-sdk/inbound-reply-dispatch"; import { kindFromMime } from "openclaw/plugin-sdk/media-runtime"; -import { - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, - recordPendingHistoryEntryIfEnabled, -} from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history"; import { dispatchInboundMessage } from "openclaw/plugin-sdk/reply-runtime"; import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime"; import { createReplyDispatcherWithTyping } from "openclaw/plugin-sdk/reply-runtime"; @@ -162,8 +158,8 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { let combinedBody = body; const historyKey = entry.isGroup ? (entry.groupId ?? "unknown") : undefined; if (entry.isGroup && historyKey) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: deps.groupHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: deps.groupHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey, limit: deps.historyLimit, currentMessage: combinedBody, @@ -187,8 +183,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { const signalTo = normalizeSignalMessagingTarget(signalToRaw) ?? signalToRaw; const inboundHistory = entry.isGroup && historyKey && deps.historyLimit > 0 - ? buildInboundHistoryFromMap({ - historyMap: deps.groupHistories, + ? createChannelHistoryWindow({ historyMap: deps.groupHistories }).buildInboundHistory({ historyKey, limit: deps.historyLimit, }) @@ -723,8 +718,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { })(); const pendingBodyText = messageText || pendingPlaceholder || visibleQuoteText; const historyKey = groupId ?? "unknown"; - recordPendingHistoryEntryIfEnabled({ - historyMap: deps.groupHistories, + createChannelHistoryWindow({ historyMap: deps.groupHistories }).record({ historyKey, limit: deps.historyLimit, entry: { diff --git a/extensions/telegram/src/bot-message-context.body.ts b/extensions/telegram/src/bot-message-context.body.ts index 4a36a2a33c4..eeee8acf5c3 100644 --- a/extensions/telegram/src/bot-message-context.body.ts +++ b/extensions/telegram/src/bot-message-context.body.ts @@ -21,10 +21,7 @@ import { toInternalMessageReceivedContext, triggerInternalHook, } from "openclaw/plugin-sdk/hook-runtime"; -import { - recordPendingHistoryEntryIfEnabled, - type HistoryEntry, -} from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow, type HistoryEntry } from "openclaw/plugin-sdk/reply-history"; import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime"; @@ -357,8 +354,7 @@ export async function resolveTelegramInboundBody(params: { const effectiveWasMentioned = mentionDecision.effectiveWasMentioned; if (isGroup && requireMention && canDetectMention && mentionDecision.shouldSkip) { logger.info({ chatId, reason: "no-mention" }, "skipping group message"); - recordPendingHistoryEntryIfEnabled({ - historyMap: groupHistories, + createChannelHistoryWindow({ historyMap: groupHistories }).record({ historyKey: historyKey ?? "", limit: historyLimit, entry: historyKey diff --git a/extensions/telegram/src/bot-message-context.session.ts b/extensions/telegram/src/bot-message-context.session.ts index b1bdb211ced..3d77657eb67 100644 --- a/extensions/telegram/src/bot-message-context.session.ts +++ b/extensions/telegram/src/bot-message-context.session.ts @@ -14,11 +14,7 @@ import type { TelegramTopicConfig, } from "openclaw/plugin-sdk/config-contracts"; import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime"; -import { - buildInboundHistoryFromMap, - buildPendingHistoryContextFromMap, - type HistoryEntry, -} from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow, type HistoryEntry } from "openclaw/plugin-sdk/reply-history"; import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing"; import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime"; @@ -374,8 +370,8 @@ export async function buildTelegramInboundContextPayload(params: { }); let combinedBody = body; if (isGroup && historyKey && historyLimit > 0) { - combinedBody = buildPendingHistoryContextFromMap({ - historyMap: groupHistories, + const channelHistory = createChannelHistoryWindow({ historyMap: groupHistories }); + combinedBody = channelHistory.buildPendingContext({ historyKey, limit: historyLimit, currentMessage: combinedBody, @@ -401,8 +397,7 @@ export async function buildTelegramInboundContextPayload(params: { }); const inboundHistory = isGroup && historyKey && historyLimit > 0 - ? buildInboundHistoryFromMap({ - historyMap: groupHistories, + ? createChannelHistoryWindow({ historyMap: groupHistories }).buildInboundHistory({ historyKey, limit: historyLimit, }) diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index e8790e7517d..5fbc5fe4d76 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -40,7 +40,7 @@ import { projectOutboundPayloadPlanForDelivery, } from "openclaw/plugin-sdk/outbound-runtime"; import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking"; -import { clearHistoryEntriesIfEnabled } from "openclaw/plugin-sdk/reply-history"; +import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history"; import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload"; import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; @@ -876,8 +876,7 @@ export const dispatchTelegramMessage = async ({ ); const clearGroupHistory = () => { if (isGroup && historyKey) { - clearHistoryEntriesIfEnabled({ - historyMap: groupHistories, + createChannelHistoryWindow({ historyMap: groupHistories }).clear({ historyKey, limit: historyLimit, }); diff --git a/extensions/whatsapp/src/auto-reply/monitor/group-gating.runtime.ts b/extensions/whatsapp/src/auto-reply/monitor/group-gating.runtime.ts index 07497919942..e20d5645034 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/group-gating.runtime.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/group-gating.runtime.ts @@ -3,6 +3,6 @@ export { resolveInboundMentionDecision, } from "openclaw/plugin-sdk/channel-mention-gating"; export { hasControlCommand } from "openclaw/plugin-sdk/command-detection"; -export { recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history"; +export { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history"; export { parseActivationCommand } from "openclaw/plugin-sdk/group-activation"; export { normalizeE164 } from "../../text-runtime.js"; diff --git a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts index 5ebba563ecb..4675d11a264 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/group-gating.ts @@ -17,7 +17,7 @@ import { implicitMentionKindWhen, normalizeE164, parseActivationCommand, - recordPendingHistoryEntryIfEnabled, + createChannelHistoryWindow, resolveInboundMentionDecision, } from "./group-gating.runtime.js"; import { noteGroupMember } from "./group-members.js"; @@ -73,8 +73,7 @@ function recordPendingGroupHistoryEntry(params: { senderIdentity.e164 ?? getPrimaryIdentityId(senderIdentity) ?? "Unknown"); - recordPendingHistoryEntryIfEnabled({ - historyMap: params.groupHistories, + createChannelHistoryWindow({ historyMap: params.groupHistories }).record({ historyKey: params.groupHistoryKey, limit: params.groupHistoryLimit, entry: { diff --git a/src/channels/turn/history-window.ts b/src/channels/turn/history-window.ts index c8b055d8658..d579b277d16 100644 --- a/src/channels/turn/history-window.ts +++ b/src/channels/turn/history-window.ts @@ -9,15 +9,11 @@ import type { HistoryEntry, HistoryMediaEntry } from "../../auto-reply/reply/his type MaybePromise = T | Promise; -export type ChannelHistoryWindow = { - record: (params: { - historyKey: string; - entry?: HistoryEntry | null; - limit: number; - }) => HistoryEntry[]; +export type ChannelHistoryWindow = { + record: (params: { historyKey: string; entry?: T | null; limit: number }) => T[]; recordWithMedia: (params: { historyKey: string; - entry?: HistoryEntry | null; + entry?: T | null; limit: number; media?: | readonly HistoryMediaEntry[] @@ -26,12 +22,12 @@ export type ChannelHistoryWindow = { mediaLimit?: number; messageId?: string; shouldRecord?: () => boolean; - }) => Promise; + }) => Promise; buildPendingContext: (params: { historyKey: string; limit: number; currentMessage: string; - formatEntry: (entry: HistoryEntry) => string; + formatEntry: (entry: T) => string; lineBreak?: string; }) => string; buildInboundHistory: (params: { @@ -41,9 +37,9 @@ export type ChannelHistoryWindow = { clear: (params: { historyKey: string; limit: number }) => void; }; -export function createChannelHistoryWindow(params: { - historyMap: Map; -}): ChannelHistoryWindow { +export function createChannelHistoryWindow(params: { + historyMap: Map; +}): ChannelHistoryWindow { const { historyMap } = params; return { record: (recordParams) => @@ -70,7 +66,7 @@ export function createChannelHistoryWindow(params: { historyKey: contextParams.historyKey, limit: contextParams.limit, currentMessage: contextParams.currentMessage, - formatEntry: contextParams.formatEntry, + formatEntry: contextParams.formatEntry as (entry: HistoryEntry) => string, lineBreak: contextParams.lineBreak, }), buildInboundHistory: (historyParams) => diff --git a/src/channels/turn/message-turn-guardrails.test.ts b/src/channels/turn/message-turn-guardrails.test.ts index 65780cafe37..1878bcdc3dc 100644 --- a/src/channels/turn/message-turn-guardrails.test.ts +++ b/src/channels/turn/message-turn-guardrails.test.ts @@ -8,13 +8,36 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../ const migratedMessageTurnFiles = [ "extensions/discord/src/monitor/message-handler.context.ts", "extensions/discord/src/monitor/message-handler.preflight.ts", + "extensions/feishu/src/bot.ts", + "extensions/imessage/src/monitor/inbound-processing.ts", + "extensions/line/src/bot-handlers.ts", + "extensions/line/src/bot-message-context.ts", + "extensions/mattermost/src/mattermost/monitor.ts", + "extensions/msteams/src/monitor-handler/message-handler.ts", + "extensions/signal/src/monitor/event-handler.ts", "extensions/slack/src/monitor/message-handler/prepare.ts", + "extensions/telegram/src/bot-message-context.body.ts", + "extensions/telegram/src/bot-message-context.session.ts", + "extensions/telegram/src/bot-message-dispatch.ts", + "extensions/whatsapp/src/auto-reply/monitor/group-gating.ts", "extensions/zalouser/src/monitor.ts", ]; const historyWindowFiles = [ "extensions/discord/src/monitor/message-handler.context.ts", + "extensions/feishu/src/bot.ts", + "extensions/imessage/src/monitor/inbound-processing.ts", + "extensions/line/src/bot-handlers.ts", + "extensions/line/src/bot-message-context.ts", + "extensions/mattermost/src/mattermost/monitor.ts", + "extensions/msteams/src/monitor-handler/message-handler.ts", + "extensions/qqbot/src/bridge/sdk-adapter.ts", + "extensions/signal/src/monitor/event-handler.ts", "extensions/slack/src/monitor/message-handler/prepare.ts", + "extensions/telegram/src/bot-message-context.body.ts", + "extensions/telegram/src/bot-message-context.session.ts", + "extensions/telegram/src/bot-message-dispatch.ts", + "extensions/whatsapp/src/auto-reply/monitor/group-gating.ts", "extensions/zalouser/src/monitor.ts", ];