fix(telegram): suppress NO_REPLY partial leaks and default streaming off

This commit is contained in:
Ayaan Zaidi
2026-02-21 17:38:42 +05:30
parent ed3bf4ff14
commit 36996628ac
8 changed files with 64 additions and 7 deletions

View File

@@ -28,7 +28,7 @@ import {
import { stripHeartbeatToken } from "../heartbeat.js";
import type { TemplateContext } from "../templating.js";
import type { VerboseLevel } from "../thinking.js";
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
import { isSilentReplyPrefixText, isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
import type { GetReplyOptions, ReplyPayload } from "../types.js";
import {
buildEmbeddedRunBaseParams,
@@ -138,7 +138,10 @@ export async function runAgentTurnWithFallback(params: {
}
text = stripped.text;
}
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) {
if (
isSilentReplyText(text, SILENT_REPLY_TOKEN) ||
isSilentReplyPrefixText(text, SILENT_REPLY_TOKEN)
) {
return { skip: true };
}
if (!text) {

View File

@@ -383,6 +383,26 @@ describe("runReplyAgent typing (heartbeat)", () => {
expect(typing.startTypingLoop).not.toHaveBeenCalled();
});
it("suppresses partial streaming for NO_REPLY prefixes", async () => {
const onPartialReply = vi.fn();
state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: AgentRunParams) => {
await params.onPartialReply?.({ text: "NO_" });
await params.onPartialReply?.({ text: "NO_RE" });
await params.onPartialReply?.({ text: "NO_REPLY" });
return { payloads: [{ text: "NO_REPLY" }], meta: {} };
});
const { run, typing } = createMinimalRun({
opts: { isHeartbeat: false, onPartialReply },
typingMode: "message",
});
await run();
expect(onPartialReply).not.toHaveBeenCalled();
expect(typing.startTypingOnText).not.toHaveBeenCalled();
expect(typing.startTypingLoop).not.toHaveBeenCalled();
});
it("does not start typing on assistant message start without prior text in message mode", async () => {
state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: AgentRunParams) => {
await params.onAssistantMessageStart?.();

View File

@@ -18,3 +18,17 @@ export function isSilentReplyText(
const suffix = new RegExp(`\\b${escaped}\\b\\W*$`);
return suffix.test(text);
}
export function isSilentReplyPrefixText(
text: string | undefined,
token: string = SILENT_REPLY_TOKEN,
): boolean {
if (!text) {
return false;
}
const normalized = text.trimStart().toUpperCase();
if (!normalized) {
return false;
}
return token.toUpperCase().startsWith(normalized);
}

View File

@@ -378,11 +378,11 @@ describe("legacy config detection", () => {
expect(res.config.channels?.telegram?.groupPolicy).toBe("allowlist");
}
});
it("defaults telegram.streaming to true when telegram section exists", async () => {
it("defaults telegram.streaming to false when telegram section exists", async () => {
const res = validateConfigObject({ channels: { telegram: {} } });
expect(res.ok).toBe(true);
if (res.ok) {
expect(res.config.channels?.telegram?.streaming).toBe(true);
expect(res.config.channels?.telegram?.streaming).toBe(false);
expect(res.config.channels?.telegram?.streamMode).toBeUndefined();
}
});

View File

@@ -392,7 +392,7 @@ export const FIELD_HELP: Record<string, string> = {
"channels.telegram.dmPolicy":
'Direct message access control ("pairing" recommended). "open" requires channels.telegram.allowFrom=["*"].',
"channels.telegram.streaming":
"Enable Telegram live stream preview via message edits (default: true; legacy streamMode auto-maps here).",
"Enable Telegram live stream preview via message edits (default: false; legacy streamMode auto-maps here).",
"channels.discord.streamMode":
"Live stream preview mode for Discord replies (off | partial | block). Separate from block streaming; uses sendMessage + editMessage.",
"channels.discord.draftChunk.minChars":

View File

@@ -117,7 +117,7 @@ function normalizeTelegramStreamingConfig(value: {
delete value.streamMode;
return;
}
value.streaming = true;
value.streaming = false;
}
export const TelegramAccountSchemaBase = z

View File

@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { resolveTelegramStreamMode } from "./bot/helpers.js";
describe("resolveTelegramStreamMode", () => {
it("defaults to off when telegram streaming is unset", () => {
expect(resolveTelegramStreamMode(undefined)).toBe("off");
expect(resolveTelegramStreamMode({})).toBe("off");
});
it("prefers explicit streaming boolean", () => {
expect(resolveTelegramStreamMode({ streaming: true })).toBe("partial");
expect(resolveTelegramStreamMode({ streaming: false })).toBe("off");
});
it("maps legacy streamMode values", () => {
expect(resolveTelegramStreamMode({ streamMode: "off" })).toBe("off");
expect(resolveTelegramStreamMode({ streamMode: "partial" })).toBe("partial");
expect(resolveTelegramStreamMode({ streamMode: "block" })).toBe("partial");
});
});

View File

@@ -167,7 +167,7 @@ export function resolveTelegramStreamMode(telegramCfg?: {
if (raw === "partial" || raw === "block") {
return "partial";
}
return "partial";
return "off";
}
export function buildTelegramGroupPeerId(chatId: number | string, messageThreadId?: number) {