diff --git a/src/auto-reply/reply/history.test.ts b/src/auto-reply/reply/history.test.ts index 9727d2d3c..c249d6b29 100644 --- a/src/auto-reply/reply/history.test.ts +++ b/src/auto-reply/reply/history.test.ts @@ -3,6 +3,7 @@ import { appendHistoryEntry, buildHistoryContext, buildHistoryContextFromEntries, + buildHistoryContextFromMap, HISTORY_CONTEXT_MARKER, } from "./history.js"; import { CURRENT_MESSAGE_MARKER } from "./mentions.js"; @@ -60,4 +61,31 @@ describe("history helpers", () => { "three", ]); }); + + it("builds context from map and appends entry", () => { + const historyMap = new Map(); + historyMap.set("room", [ + { sender: "A", body: "one" }, + { sender: "B", body: "two" }, + ]); + + const result = buildHistoryContextFromMap({ + historyMap, + historyKey: "room", + limit: 3, + entry: { sender: "C", body: "three" }, + currentMessage: "current", + formatEntry: (entry) => `${entry.sender}: ${entry.body}`, + }); + + expect(historyMap.get("room")?.map((entry) => entry.body)).toEqual([ + "one", + "two", + "three", + ]); + expect(result).toContain(HISTORY_CONTEXT_MARKER); + expect(result).toContain("A: one"); + expect(result).toContain("B: two"); + expect(result).not.toContain("C: three"); + }); }); diff --git a/src/auto-reply/reply/history.ts b/src/auto-reply/reply/history.ts index 92b740946..2e30c1d47 100644 --- a/src/auto-reply/reply/history.ts +++ b/src/auto-reply/reply/history.ts @@ -43,6 +43,41 @@ export function appendHistoryEntry(params: { return history; } +export function buildHistoryContextFromMap(params: { + historyMap: Map; + historyKey: string; + limit: number; + entry?: HistoryEntry; + currentMessage: string; + formatEntry: (entry: HistoryEntry) => string; + lineBreak?: string; + excludeLast?: boolean; +}): string { + if (params.limit <= 0) return params.currentMessage; + const entries = params.entry + ? appendHistoryEntry({ + historyMap: params.historyMap, + historyKey: params.historyKey, + entry: params.entry, + limit: params.limit, + }) + : (params.historyMap.get(params.historyKey) ?? []); + return buildHistoryContextFromEntries({ + entries, + currentMessage: params.currentMessage, + formatEntry: params.formatEntry, + lineBreak: params.lineBreak, + excludeLast: params.excludeLast, + }); +} + +export function clearHistoryEntries(params: { + historyMap: Map; + historyKey: string; +}): void { + params.historyMap.set(params.historyKey, []); +} + export function buildHistoryContextFromEntries(params: { entries: HistoryEntry[]; currentMessage: string; diff --git a/src/discord/monitor.ts b/src/discord/monitor.ts index d9ce5c971..11bfef571 100644 --- a/src/discord/monitor.ts +++ b/src/discord/monitor.ts @@ -35,8 +35,8 @@ import { } from "../auto-reply/envelope.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, type HistoryEntry, } from "../auto-reply/reply/history.js"; import { @@ -887,23 +887,19 @@ export function createDiscordMessageHandler(params: { const textForHistory = resolveDiscordMessageText(message, { includeForwarded: true, }); - if (isGuildMessage && historyLimit > 0 && textForHistory) { - appendHistoryEntry({ - historyMap: guildHistories, - historyKey: message.channelId, - limit: historyLimit, - entry: { - sender: - data.member?.nickname ?? - author.globalName ?? - author.username ?? - author.id, - body: textForHistory, - timestamp: resolveTimestampMs(message.timestamp), - messageId: message.id, - }, - }); - } + const historyEntry = + isGuildMessage && historyLimit > 0 && textForHistory + ? { + sender: + data.member?.nickname ?? + author.globalName ?? + author.username ?? + author.id, + body: textForHistory, + timestamp: resolveTimestampMs(message.timestamp), + messageId: message.id, + } + : undefined; const shouldRequireMention = channelConfig?.requireMention ?? guildInfo?.requireMention ?? true; @@ -1049,10 +1045,11 @@ export function createDiscordMessageHandler(params: { }); let shouldClearHistory = false; if (!isDirectMessage) { - const history = - historyLimit > 0 ? (guildHistories.get(message.channelId) ?? []) : []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, + combinedBody = buildHistoryContextFromMap({ + historyMap: guildHistories, + historyKey: message.channelId, + limit: historyLimit, + entry: historyEntry, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -1227,7 +1224,10 @@ export function createDiscordMessageHandler(params: { historyLimit > 0 && didSendReply ) { - guildHistories.set(message.channelId, []); + clearHistoryEntries({ + historyMap: guildHistories, + historyKey: message.channelId, + }); } return; } @@ -1262,7 +1262,10 @@ export function createDiscordMessageHandler(params: { historyLimit > 0 && didSendReply ) { - guildHistories.set(message.channelId, []); + clearHistoryEntries({ + historyMap: guildHistories, + historyKey: message.channelId, + }); } } catch (err) { runtime.error?.(danger(`handler failed: ${String(err)}`)); diff --git a/src/imessage/monitor.ts b/src/imessage/monitor.ts index 2a544a098..be8dcb5e9 100644 --- a/src/imessage/monitor.ts +++ b/src/imessage/monitor.ts @@ -7,8 +7,8 @@ import { hasControlCommand } from "../auto-reply/command-detection.js"; import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry, } from "../auto-reply/reply/history.js"; @@ -409,7 +409,7 @@ export async function monitorIMessageProvider( ? String(chatId ?? chatGuid ?? chatIdentifier ?? "unknown") : undefined; if (isGroup && historyKey && historyLimit > 0) { - appendHistoryEntry({ + combinedBody = buildHistoryContextFromMap({ historyMap: groupHistories, historyKey, limit: historyLimit, @@ -419,10 +419,6 @@ export async function monitorIMessageProvider( timestamp: createdAt, messageId: message.id ? String(message.id) : undefined, }, - }); - const history = groupHistories.get(historyKey) ?? []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -527,12 +523,12 @@ export async function monitorIMessageProvider( }); if (!queuedFinal) { if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } return; } if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } }; diff --git a/src/msteams/monitor-handler.ts b/src/msteams/monitor-handler.ts index 15349579d..ced3ae986 100644 --- a/src/msteams/monitor-handler.ts +++ b/src/msteams/monitor-handler.ts @@ -1,8 +1,8 @@ import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry, } from "../auto-reply/reply/history.js"; @@ -432,7 +432,7 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { const isRoomish = !isDirectMessage; const historyKey = isRoomish ? conversationId : undefined; if (isRoomish && historyKey && historyLimit > 0) { - appendHistoryEntry({ + combinedBody = buildHistoryContextFromMap({ historyMap: conversationHistories, historyKey, limit: historyLimit, @@ -442,10 +442,6 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { timestamp: timestamp?.getTime(), messageId: activity.id ?? undefined, }, - }); - const history = conversationHistories.get(historyKey) ?? []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -520,7 +516,10 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { const didSendReply = counts.final + counts.tool + counts.block > 0; if (!queuedFinal) { if (isRoomish && historyKey && historyLimit > 0 && didSendReply) { - conversationHistories.set(historyKey, []); + clearHistoryEntries({ + historyMap: conversationHistories, + historyKey, + }); } return; } @@ -531,7 +530,10 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) { ); } if (isRoomish && historyKey && historyLimit > 0 && didSendReply) { - conversationHistories.set(historyKey, []); + clearHistoryEntries({ + historyMap: conversationHistories, + historyKey, + }); } } catch (err) { log.error("dispatch failed", { error: String(err) }); diff --git a/src/signal/monitor.ts b/src/signal/monitor.ts index 112834ec0..bed4a0cde 100644 --- a/src/signal/monitor.ts +++ b/src/signal/monitor.ts @@ -6,8 +6,8 @@ import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js"; import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry, } from "../auto-reply/reply/history.js"; @@ -635,7 +635,7 @@ export async function monitorSignalProvider( let combinedBody = body; const historyKey = isGroup ? String(groupId ?? "unknown") : undefined; if (isGroup && historyKey && historyLimit > 0) { - appendHistoryEntry({ + combinedBody = buildHistoryContextFromMap({ historyMap: groupHistories, historyKey, limit: historyLimit, @@ -648,10 +648,6 @@ export async function monitorSignalProvider( ? String(envelope.timestamp) : undefined, }, - }); - const history = groupHistories.get(historyKey) ?? []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -763,12 +759,12 @@ export async function monitorSignalProvider( }); if (!queuedFinal) { if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } return; } if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } }; diff --git a/src/slack/monitor.ts b/src/slack/monitor.ts index da4c09acb..44ddd66ad 100644 --- a/src/slack/monitor.ts +++ b/src/slack/monitor.ts @@ -25,8 +25,8 @@ import { } from "../auto-reply/envelope.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry, } from "../auto-reply/reply/history.js"; @@ -969,21 +969,17 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`; const isRoomish = isRoom || isGroupDm; const historyKey = message.channel; - if (isRoomish && historyLimit > 0) { - appendHistoryEntry({ - historyMap: channelHistories, - historyKey, - limit: historyLimit, - entry: { - sender: senderName, - body: rawBody, - timestamp: message.ts - ? Math.round(Number(message.ts) * 1000) - : undefined, - messageId: message.ts, - }, - }); - } + const historyEntry = + isRoomish && historyLimit > 0 + ? { + sender: senderName, + body: rawBody, + timestamp: message.ts + ? Math.round(Number(message.ts) * 1000) + : undefined, + messageId: message.ts, + } + : undefined; const preview = rawBody.replace(/\s+/g, " ").slice(0, 160); const inboundLabel = isDirectMessage @@ -1021,9 +1017,11 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { let combinedBody = body; if (isRoomish && historyLimit > 0) { - const history = channelHistories.get(historyKey) ?? []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, + combinedBody = buildHistoryContextFromMap({ + historyMap: channelHistories, + historyKey, + limit: historyLimit, + entry: historyEntry, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -1220,7 +1218,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { } if (!queuedFinal) { if (isRoomish && historyLimit > 0 && didSendReply) { - channelHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: channelHistories, historyKey }); } return; } @@ -1245,7 +1243,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { }); } if (isRoomish && historyLimit > 0 && didSendReply) { - channelHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: channelHistories, historyKey }); } }; diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index d36fd3b2e..9eadd1469 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -21,8 +21,8 @@ import { import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import { resolveTelegramDraftStreamingChunking } from "../auto-reply/reply/block-streaming.js"; import { - appendHistoryEntry, - buildHistoryContextFromEntries, + buildHistoryContextFromMap, + clearHistoryEntries, DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry, } from "../auto-reply/reply/history.js"; @@ -671,7 +671,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { ? buildTelegramGroupPeerId(chatId, messageThreadId) : undefined; if (isGroup && historyKey && historyLimit > 0) { - appendHistoryEntry({ + combinedBody = buildHistoryContextFromMap({ historyMap: groupHistories, historyKey, limit: historyLimit, @@ -684,10 +684,6 @@ export function createTelegramBot(opts: TelegramBotOptions) { ? String(msg.message_id) : undefined, }, - }); - const history = groupHistories.get(historyKey) ?? []; - combinedBody = buildHistoryContextFromEntries({ - entries: history, currentMessage: combinedBody, formatEntry: (entry) => formatAgentEnvelope({ @@ -907,7 +903,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { draftStream?.stop(); if (!queuedFinal) { if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } return; } @@ -927,7 +923,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { }); } if (isGroup && historyKey && historyLimit > 0 && didSendReply) { - groupHistories.set(historyKey, []); + clearHistoryEntries({ historyMap: groupHistories, historyKey }); } };