From 7337c3a9e97642bfd7afdca8ce83c3d0d73cd197 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Mon, 11 May 2026 09:18:33 +0530 Subject: [PATCH] fix(telegram): join inline reply prompt --- src/agents/pi-embedded-runner/run/attempt.ts | 12 +++++------- src/agents/pi-embedded-runner/run/params.ts | 1 + .../run/runtime-context-prompt.test.ts | 17 +++++++++++++++++ .../run/runtime-context-prompt.ts | 14 ++++++++++++++ .../reply/get-reply-run.media-only.test.ts | 7 ++++++- src/auto-reply/reply/get-reply-run.ts | 13 +++++++++++-- src/auto-reply/reply/inbound-meta.ts | 4 ++++ 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 0412561dced..1584093d31d 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -362,7 +362,7 @@ import { shouldPreemptivelyCompactBeforePrompt, } from "./preemptive-compaction.js"; import { - buildCurrentTurnPromptContextPrefix, + buildCurrentTurnPrompt, buildRuntimeContextSystemContext, queueRuntimeContextForNextTurn, resolveRuntimeContextPromptParts, @@ -3114,12 +3114,10 @@ export async function runEmbeddedAttempt( effectivePrompt, transcriptPrompt: effectiveTranscriptPrompt, }); - const currentTurnPromptContextPrefix = promptSubmission.runtimeOnly - ? "" - : buildCurrentTurnPromptContextPrefix(params.currentTurnContext); - const promptForModel = [currentTurnPromptContextPrefix, promptSubmission.prompt] - .filter(Boolean) - .join("\n\n"); + const promptForModel = buildCurrentTurnPrompt({ + context: promptSubmission.runtimeOnly ? undefined : params.currentTurnContext, + prompt: promptSubmission.prompt, + }); const runtimeSystemContext = promptSubmission.runtimeSystemContext?.trim(); if (promptSubmission.runtimeOnly && runtimeSystemContext) { const runtimeSystemPrompt = composeSystemPromptWithHookContext({ diff --git a/src/agents/pi-embedded-runner/run/params.ts b/src/agents/pi-embedded-runner/run/params.ts index 5329c53e3f6..10c7aae99eb 100644 --- a/src/agents/pi-embedded-runner/run/params.ts +++ b/src/agents/pi-embedded-runner/run/params.ts @@ -30,6 +30,7 @@ export type EmbeddedRunTrigger = "cron" | "heartbeat" | "manual" | "memory" | "o export type CurrentTurnPromptContext = { text: string; + promptJoiner?: "\n\n" | "\n" | " "; }; export type RunEmbeddedPiAgentParams = { diff --git a/src/agents/pi-embedded-runner/run/runtime-context-prompt.test.ts b/src/agents/pi-embedded-runner/run/runtime-context-prompt.test.ts index badce722044..628c51f5546 100644 --- a/src/agents/pi-embedded-runner/run/runtime-context-prompt.test.ts +++ b/src/agents/pi-embedded-runner/run/runtime-context-prompt.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { + buildCurrentTurnPrompt, buildCurrentTurnPromptContextPrefix, buildRuntimeContextSystemContext, queueRuntimeContextForNextTurn, @@ -81,6 +82,22 @@ describe("runtime context prompt submission", () => { expect(buildCurrentTurnPromptContextPrefix({ text: " " })).toBe(""); }); + it("joins current-turn context and prompt with the requested separator", () => { + expect( + buildCurrentTurnPrompt({ + context: { text: "Current message:\n#34975 obviyus:", promptJoiner: " " }, + prompt: "What do you mean hidden?", + }), + ).toBe("Current message:\n#34975 obviyus: What do you mean hidden?"); + + expect( + buildCurrentTurnPrompt({ + context: { text: "Conversation context:" }, + prompt: "visible ask", + }), + ).toBe("Conversation context:\n\nvisible ask"); + }); + it("queues runtime context as a hidden next-turn custom message", async () => { const sentMessages: Array<{ content: string }> = []; const sendCustomMessage = vi.fn(async (message: { content: string }) => { diff --git a/src/agents/pi-embedded-runner/run/runtime-context-prompt.ts b/src/agents/pi-embedded-runner/run/runtime-context-prompt.ts index 0e4fe9e3283..aee3ddec33b 100644 --- a/src/agents/pi-embedded-runner/run/runtime-context-prompt.ts +++ b/src/agents/pi-embedded-runner/run/runtime-context-prompt.ts @@ -34,6 +34,20 @@ export function buildCurrentTurnPromptContextPrefix( return context?.text.trim() ?? ""; } +export function buildCurrentTurnPrompt(params: { + context: CurrentTurnPromptContext | undefined; + prompt: string; +}): string { + const prefix = buildCurrentTurnPromptContextPrefix(params.context); + if (!prefix) { + return params.prompt; + } + if (!params.prompt) { + return prefix; + } + return [prefix, params.prompt].join(params.context?.promptJoiner ?? "\n\n"); +} + function removeLastPromptOccurrence(text: string, prompt: string): string | null { const index = text.lastIndexOf(prompt); if (index === -1) { 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 f1490026b8f..f1d7af8978d 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 @@ -99,6 +99,7 @@ vi.mock("./groups.js", () => ({ vi.mock("./inbound-meta.js", () => ({ buildInboundMetaSystemPrompt: vi.fn().mockReturnValue(""), buildInboundUserContextPrefix: vi.fn().mockReturnValue(""), + resolveInboundUserContextPromptJoiner: vi.fn().mockReturnValue(undefined), })); vi.mock("./queue/settings-runtime.js", () => ({ @@ -133,6 +134,7 @@ let resolveTypingMode: typeof import("./typing-mode.js").resolveTypingMode; let buildDirectChatContext: typeof import("./groups.js").buildDirectChatContext; let buildGroupChatContext: typeof import("./groups.js").buildGroupChatContext; let buildInboundUserContextPrefix: typeof import("./inbound-meta.js").buildInboundUserContextPrefix; +let resolveInboundUserContextPromptJoiner: typeof import("./inbound-meta.js").resolveInboundUserContextPromptJoiner; let getActiveReplyRunCount: typeof import("./reply-run-registry.js").getActiveReplyRunCount; let replyRunTesting: typeof import("./reply-run-registry.js").__testing; let loadScopeCounter = 0; @@ -252,7 +254,8 @@ describe("runPreparedReply media-only handling", () => { ({ drainFormattedSystemEvents } = await import("./session-system-events.js")); ({ resolveTypingMode } = await import("./typing-mode.js")); ({ buildDirectChatContext, buildGroupChatContext } = await import("./groups.js")); - ({ buildInboundUserContextPrefix } = await import("./inbound-meta.js")); + ({ buildInboundUserContextPrefix, resolveInboundUserContextPromptJoiner } = + await import("./inbound-meta.js")); ({ __testing: replyRunTesting, getActiveReplyRunCount } = await import("./reply-run-registry.js")); }); @@ -1142,6 +1145,7 @@ describe("runPreparedReply media-only handling", () => { vi.mocked(buildInboundUserContextPrefix).mockReturnValueOnce( ["Current message:", '[Replying to: "quoted status body"]', "#34974 obviyus:"].join("\n"), ); + vi.mocked(resolveInboundUserContextPromptJoiner).mockReturnValueOnce(" "); await runPreparedReply( baseParams({ @@ -1172,6 +1176,7 @@ describe("runPreparedReply media-only handling", () => { expect(call?.transcriptCommandBody).toBe("what does this mean?"); expect(call?.followupRun.prompt).toContain("what does this mean?"); expect(call?.followupRun.transcriptPrompt).toBe("what does this mean?"); + expect(call?.followupRun.currentTurnContext?.promptJoiner).toBe(" "); expect(call?.followupRun.currentTurnContext?.text).toContain("Current message:"); expect(call?.followupRun.currentTurnContext?.text).toContain( '[Replying to: "quoted status body"]', diff --git a/src/auto-reply/reply/get-reply-run.ts b/src/auto-reply/reply/get-reply-run.ts index a51b0124d09..2d2d707381a 100644 --- a/src/auto-reply/reply/get-reply-run.ts +++ b/src/auto-reply/reply/get-reply-run.ts @@ -60,7 +60,11 @@ import { resolveGroupSilentReplyBehavior, } from "./groups.js"; import { hasInboundMedia } from "./inbound-media.js"; -import { buildInboundMetaSystemPrompt, buildInboundUserContextPrefix } from "./inbound-meta.js"; +import { + buildInboundMetaSystemPrompt, + buildInboundUserContextPrefix, + resolveInboundUserContextPromptJoiner, +} from "./inbound-meta.js"; import type { createModelSelectionState } from "./model-selection.js"; import { resolveOriginMessageProvider } from "./origin-routing.js"; import { buildReplyPromptBodies } from "./prompt-prelude.js"; @@ -750,7 +754,12 @@ export async function runPreparedReply( () => rebuildPromptBodies(), ); const currentTurnContext: CurrentTurnPromptContext | undefined = - !isBareSessionReset && inboundUserContext.trim() ? { text: inboundUserContext } : undefined; + !isBareSessionReset && inboundUserContext.trim() + ? { + text: inboundUserContext, + promptJoiner: resolveInboundUserContextPromptJoiner(sessionCtx), + } + : undefined; if (!resolvedThinkLevel) { resolvedThinkLevel = await modelState.resolveDefaultThinkingLevel(); } diff --git a/src/auto-reply/reply/inbound-meta.ts b/src/auto-reply/reply/inbound-meta.ts index a65e9ced166..2b63c5b2841 100644 --- a/src/auto-reply/reply/inbound-meta.ts +++ b/src/auto-reply/reply/inbound-meta.ts @@ -321,6 +321,10 @@ function formatTelegramCurrentMessageContext(ctx: TemplateContext): string | und .join("\n"); } +export function resolveInboundUserContextPromptJoiner(ctx: TemplateContext): " " | undefined { + return formatTelegramCurrentMessageContext(ctx) ? " " : undefined; +} + function formatConversationTimestamp( value: unknown, envelope?: EnvelopeFormatOptions,