Skip to content

Commit a3f00d3

Browse files
hugenshenNIOcursoragentsteipete
authored
fix(telegram): keep DM topic auto-rename user message UTF-16 safe (#101781)
* fix(telegram): keep DM topic auto-rename user message UTF-16 safe Add surrogate-boundary regression for auto-topic label input truncation. Co-authored-by: Cursor <[email protected]> * test(telegram): tighten topic label boundary proof --------- Co-authored-by: NIO <[email protected]> Co-authored-by: Cursor <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent cc96100 commit a3f00d3

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

extensions/telegram/src/bot-message-dispatch.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6371,6 +6371,34 @@ describe("dispatchTelegramMessage draft streaming", () => {
63716371
expect(bot.api["editForumTopic"]).not.toHaveBeenCalled();
63726372
});
63736373

6374+
it("truncates DM topic auto-rename input on UTF-16 boundaries", async () => {
6375+
const sessionKey = "agent:default:telegram:direct:123";
6376+
loadSessionStore.mockReturnValue({
6377+
[sessionKey]: { sessionId: "s1", updatedAt: 1 },
6378+
});
6379+
dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: true });
6380+
const bot = createBot();
6381+
const base = "a".repeat(499);
6382+
const rawBody = `${base}😀tail`;
6383+
6384+
await dispatchWithContext({
6385+
bot,
6386+
context: createContext({
6387+
ctxPayload: {
6388+
SessionKey: sessionKey,
6389+
RawBody: rawBody,
6390+
} as TelegramMessageContext["ctxPayload"],
6391+
}),
6392+
telegramCfg: { autoTopicLabel: true },
6393+
});
6394+
6395+
await vi.waitFor(() => {
6396+
expect(generateTopicLabel).toHaveBeenCalled();
6397+
});
6398+
const call = generateTopicLabel.mock.calls[0]?.[0] as { userMessage: string };
6399+
expect(call.userMessage).toBe(base);
6400+
});
6401+
63746402
it("does not emit a silent-reply fallback when the dispatcher reports a queued final reply", async () => {
63756403
dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({
63766404
queuedFinal: true,

extensions/telegram/src/bot-message-dispatch.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
readLatestAssistantTextByIdentity,
5656
} from "openclaw/plugin-sdk/session-transcript-runtime";
5757
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
58+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
5859
import { resolveTelegramConfigReasoningDefault } from "./agent-config.js";
5960
import { withTelegramApiErrorLogging } from "./api-logging.js";
6061
import type { TelegramBotDeps } from "./bot-deps.js";
@@ -3074,7 +3075,7 @@ export const dispatchTelegramMessage = async ({
30743075

30753076
// Fire-and-forget: auto-rename DM topic on first message.
30763077
if (isDmTopic && isFirstTurnInSession) {
3077-
const userMessage = (ctxPayload.RawBody ?? ctxPayload.Body ?? "").slice(0, 500);
3078+
const userMessage = truncateUtf16Safe(ctxPayload.RawBody ?? ctxPayload.Body ?? "", 500);
30783079
if (userMessage.trim()) {
30793080
const agentDir = resolveAgentDir(cfg, route.agentId);
30803081
const directAutoTopicLabel =

0 commit comments

Comments
 (0)