diff --git a/src/auto-reply/reply/get-reply-run.media-only.test.ts b/src/auto-reply/reply/get-reply-run.media-only.test.ts index e8c1c69cc9ac..8bcaa85cbf5f 100644 --- a/src/auto-reply/reply/get-reply-run.media-only.test.ts +++ b/src/auto-reply/reply/get-reply-run.media-only.test.ts @@ -371,6 +371,15 @@ describe("runPreparedReply media-only handling", () => { expect(directContextParams?.sessionCtx?.Provider).toBe("telegram"); expect(directContextParams?.sessionCtx?.ChatType).toBe("direct"); expect(directContextParams?.sourceReplyDeliveryMode).toBe("message_tool_only"); + expect(buildInboundUserContextPrefix).toHaveBeenCalledWith( + expect.objectContaining({ + ChatType: "direct", + OriginatingChannel: "telegram", + OriginatingTo: "telegram-direct-test-id", + }), + expect.anything(), + { sourceReplyDeliveryMode: "message_tool_only" }, + ); }); it.each(["direct", "dm"] as const)( diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index 49b88005b61c..8732ea830293 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -618,6 +618,7 @@ export async function runPreparedReply( } : { ...sessionCtx, ThreadStarterBody: undefined }, envelopeOptions, + { sourceReplyDeliveryMode: opts?.sourceReplyDeliveryMode }, ); const inboundUserContextPromptJoiner = resolveInboundUserContextPromptJoiner(sessionCtx); const hasUserBody = diff --git a/src/auto-reply/reply/inbound-meta.test.ts b/src/auto-reply/reply/inbound-meta.test.ts index 0f00e60a5127..a50daf11a4f1 100644 --- a/src/auto-reply/reply/inbound-meta.test.ts +++ b/src/auto-reply/reply/inbound-meta.test.ts @@ -287,6 +287,40 @@ describe("buildInboundUserContextPrefix", () => { expect(conversationInfo["conversation_label"]).toBeUndefined(); }); + it("adds delivery guidance beside inbound source context for message-tool-only turns", () => { + const text = buildInboundUserContextPrefix( + { + ChatType: "direct", + OriginatingChannel: "telegram", + OriginatingTo: "telegram:849985193", + MessageSid: "776", + SenderName: "Nik", + } as TemplateContext, + undefined, + { sourceReplyDeliveryMode: "message_tool_only" }, + ); + + expect(text).toContain("Delivery: to send a message, use the `message` tool."); + expect(text.indexOf("Delivery:")).toBeLessThan(text.indexOf("Conversation info")); + expect(text).toContain("Conversation info (untrusted metadata):"); + }); + + it("does not add delivery guidance for automatic source delivery", () => { + const text = buildInboundUserContextPrefix( + { + ChatType: "direct", + OriginatingChannel: "telegram", + OriginatingTo: "telegram:849985193", + MessageSid: "776", + } as TemplateContext, + undefined, + { sourceReplyDeliveryMode: "automatic" }, + ); + + expect(text).not.toContain("Delivery: to send a message"); + expect(text).toContain("Conversation info (untrusted metadata):"); + }); + it("includes message identifiers for direct chats when channel is inferred from Provider", () => { const text = buildInboundUserContextPrefix({ ChatType: "direct", diff --git a/src/auto-reply/reply/inbound-meta.ts b/src/auto-reply/reply/inbound-meta.ts index 2b63c5b28411..4eda19c10f71 100644 --- a/src/auto-reply/reply/inbound-meta.ts +++ b/src/auto-reply/reply/inbound-meta.ts @@ -7,11 +7,17 @@ import { normalizeOptionalString } from "../../shared/string-coerce.js"; import { truncateUtf16Safe } from "../../utils.js"; import type { EnvelopeFormatOptions } from "../envelope.js"; import { formatEnvelopeTimestamp } from "../envelope.js"; +import type { SourceReplyDeliveryMode } from "../get-reply-options.types.js"; import type { TemplateContext } from "../templating.js"; const MAX_UNTRUSTED_JSON_STRING_CHARS = 2_000; const MAX_UNTRUSTED_HISTORY_ENTRIES = 20; const MAX_UNTRUSTED_TRANSCRIPT_FIELD_CHARS = 500; +const MESSAGE_TOOL_DELIVERY_HINT = "Delivery: to send a message, use the `message` tool."; + +type InboundUserContextPrefixOptions = { + sourceReplyDeliveryMode?: SourceReplyDeliveryMode; +}; function stripNullBytes(value: string): string { return value.replaceAll("\u0000", ""); @@ -411,8 +417,12 @@ export function buildInboundMetaSystemPrompt( export function buildInboundUserContextPrefix( ctx: TemplateContext, envelope?: EnvelopeFormatOptions, + options?: InboundUserContextPrefixOptions, ): string { const blocks: string[] = []; + if (options?.sourceReplyDeliveryMode === "message_tool_only") { + blocks.push(MESSAGE_TOOL_DELIVERY_HINT); + } const chatType = normalizeChatType(ctx.ChatType); const isDirect = !chatType || chatType === "direct"; const directChannelValue = resolveInboundChannel(ctx);