From 3d89f0f14ae0fe487e0525d4fee124e1f7f628ca Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 12 Feb 2026 14:44:53 -0500 Subject: [PATCH] fix(reply): auto-inject replyToCurrent for reply threading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit replyToMode "first"/"all" only filters replyToId but never generates it — that required the LLM to emit [[reply_to_current]] tags. Inject replyToCurrent:true on all payloads so applyReplyTagsToPayload sets replyToId=currentMessageId, then let the existing mode filter decide which replies keep threading (first only, all, or off). Covers both final reply path (reply-payloads.ts) and block streaming path (agent-runner-execution.ts). --- .../reply/agent-runner-execution.ts | 2 +- .../reply-payloads.auto-threading.test.ts | 50 +++++++++++++++++++ src/auto-reply/reply/reply-payloads.ts | 4 +- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/auto-reply/reply/reply-payloads.auto-threading.test.ts diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index c1e1b4c66c..9990a1ec58 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -378,7 +378,7 @@ export async function runAgentTurnWithFallback(params: { mediaUrl: payload.mediaUrls?.[0], replyToId: payload.replyToId, replyToTag: payload.replyToTag, - replyToCurrent: payload.replyToCurrent, + replyToCurrent: true, }, currentMessageId, ); diff --git a/src/auto-reply/reply/reply-payloads.auto-threading.test.ts b/src/auto-reply/reply/reply-payloads.auto-threading.test.ts new file mode 100644 index 0000000000..9cd795bc41 --- /dev/null +++ b/src/auto-reply/reply/reply-payloads.auto-threading.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; +import { applyReplyThreading } from "./reply-payloads.js"; + +describe("applyReplyThreading auto-injects replyToCurrent", () => { + it("sets replyToId to currentMessageId even without [[reply_to_current]] tag", () => { + const result = applyReplyThreading({ + payloads: [{ text: "Hello" }], + replyToMode: "first", + currentMessageId: "42", + }); + + expect(result).toHaveLength(1); + expect(result[0].replyToId).toBe("42"); + }); + + it("threads only first payload when mode is 'first'", () => { + const result = applyReplyThreading({ + payloads: [{ text: "A" }, { text: "B" }], + replyToMode: "first", + currentMessageId: "42", + }); + + expect(result).toHaveLength(2); + expect(result[0].replyToId).toBe("42"); + expect(result[1].replyToId).toBeUndefined(); + }); + + it("threads all payloads when mode is 'all'", () => { + const result = applyReplyThreading({ + payloads: [{ text: "A" }, { text: "B" }], + replyToMode: "all", + currentMessageId: "42", + }); + + expect(result).toHaveLength(2); + expect(result[0].replyToId).toBe("42"); + expect(result[1].replyToId).toBe("42"); + }); + + it("strips replyToId when mode is 'off'", () => { + const result = applyReplyThreading({ + payloads: [{ text: "A" }], + replyToMode: "off", + currentMessageId: "42", + }); + + expect(result).toHaveLength(1); + expect(result[0].replyToId).toBeUndefined(); + }); +}); diff --git a/src/auto-reply/reply/reply-payloads.ts b/src/auto-reply/reply/reply-payloads.ts index 231bfb9bad..c5e5572262 100644 --- a/src/auto-reply/reply/reply-payloads.ts +++ b/src/auto-reply/reply/reply-payloads.ts @@ -63,7 +63,9 @@ export function applyReplyThreading(params: { const { payloads, replyToMode, replyToChannel, currentMessageId } = params; const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel); return payloads - .map((payload) => applyReplyTagsToPayload(payload, currentMessageId)) + .map((payload) => + applyReplyTagsToPayload({ ...payload, replyToCurrent: true }, currentMessageId), + ) .filter(isRenderablePayload) .map(applyReplyToMode); }