惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
G
Google Developers Blog
有赞技术团队
有赞技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Blog — PlanetScale
Blog — PlanetScale
罗磊的独立博客
博客园 - 聂微东
V
Visual Studio Blog
博客园_首页
D
DataBreaches.Net
腾讯CDC
I
InfoQ
F
Fortinet All Blogs
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
月光博客
月光博客
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Check Point Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(telegram): prevent preview duplication in partial and...
jmao0001 · 2026-06-03 · via Recent Commits to openclaw:main

@@ -3573,6 +3573,150 @@ describe("dispatchTelegramMessage draft streaming", () => {

35733573

await sidePromise;

35743574

});

357535753576+

it("does not drop the first chunk of a long final after a generic lane rotation", async () => {

3577+

const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });

3578+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

3579+

async ({ dispatcherOptions, replyOptions }) => {

3580+

await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });

3581+

await dispatcherOptions.deliver(

3582+

{ text: "A".repeat(4000) + "B".repeat(4000) },

3583+

{ kind: "final" },

3584+

);

3585+

return { queuedFinal: true };

3586+

},

3587+

);

3588+3589+

await dispatchWithContext({

3590+

context: createContext(),

3591+

textLimit: 4000,

3592+

});

3593+3594+

expect(answerDraftStream.update).toHaveBeenCalledWith("A".repeat(4000));

3595+

});

3596+3597+

it("does not suppress text-only blocks as delivered when answer draft is inactive", async () => {

3598+

setupDraftStreams({ answerMessageId: 2001 });

3599+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {

3600+

await dispatcherOptions.deliver({ text: "forced block" }, { kind: "block" });

3601+

await dispatcherOptions.deliver({ text: "final text" }, { kind: "final" });

3602+

return { queuedFinal: true };

3603+

});

3604+3605+

await dispatchWithContext({

3606+

context: createContext(),

3607+

streamMode: "partial",

3608+

telegramCfg: {

3609+

streaming: { mode: "partial", block: { enabled: true } },

3610+

} satisfies Parameters<typeof dispatchTelegramMessage>[0]["telegramCfg"],

3611+

});

3612+3613+

const deliveredTexts = deliverReplies.mock.calls.flatMap((call) =>

3614+

((call[0] as { replies?: Array<{ text?: string }> }).replies ?? []).map(

3615+

(reply) => reply.text,

3616+

),

3617+

);

3618+

expect(deliveredTexts).toContain("forced block");

3619+

});

3620+3621+

it("does not suppress text-only blocks after a tool-progress draft", async () => {

3622+

const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });

3623+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

3624+

async ({ dispatcherOptions, replyOptions }) => {

3625+

await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });

3626+

await dispatcherOptions.deliver({ text: "block after progress" }, { kind: "block" });

3627+

return { queuedFinal: true };

3628+

},

3629+

);

3630+3631+

await dispatchWithContext({

3632+

context: createContext(),

3633+

streamMode: "partial",

3634+

telegramCfg: { streaming: { mode: "partial" } },

3635+

});

3636+3637+

expect(mockCallArg(answerDraftStream.update)).toContain("Exec");

3638+

expect(answerDraftStream.update).toHaveBeenLastCalledWith("block after progress");

3639+

});

3640+3641+

it("does not suppress button-bearing blocks after answer streaming starts", async () => {

3642+

const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });

3643+

const buttons = [[{ text: "OK", callback_data: "ok" }]];

3644+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

3645+

async ({ dispatcherOptions, replyOptions }) => {

3646+

await replyOptions?.onPartialReply?.({ text: "partial answer" });

3647+

await dispatcherOptions.deliver(

3648+

{ text: "choose now", channelData: { telegram: { buttons } } },

3649+

{ kind: "block" },

3650+

);

3651+

return { queuedFinal: true };

3652+

},

3653+

);

3654+3655+

await dispatchWithContext({

3656+

context: createContext(),

3657+

streamMode: "partial",

3658+

telegramCfg: { streaming: { mode: "partial" } },

3659+

});

3660+3661+

expect(answerDraftStream.update).toHaveBeenLastCalledWith("choose now");

3662+

expectRecordFields(mockCallArg(editMessageTelegram, 0, 3), { buttons });

3663+

});

3664+3665+

it("finalizes a duplicate text-only block when no final follows", async () => {

3666+

const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });

3667+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

3668+

async ({ dispatcherOptions, replyOptions }) => {

3669+

await replyOptions?.onPartialReply?.({ text: "partial answer" });

3670+

await dispatcherOptions.deliver({ text: "partial answer" }, { kind: "block" });

3671+

return { queuedFinal: false };

3672+

},

3673+

);

3674+3675+

await dispatchWithContext({

3676+

context: createContext(),

3677+

streamMode: "partial",

3678+

telegramCfg: { streaming: { mode: "partial" } },

3679+

});

3680+3681+

expect(answerDraftStream.stop).toHaveBeenCalled();

3682+

expect(answerDraftStream.clear).not.toHaveBeenCalled();

3683+

expectRecordFields(mockCallArg(emitInternalMessageSentHook), {

3684+

content: "partial answer",

3685+

messageId: 2001,

3686+

});

3687+

expectRecordFields(mockCallArg(recordOutboundMessageForPromptContext), {

3688+

text: "partial answer",

3689+

messageId: 2001,

3690+

});

3691+

});

3692+3693+

it("materializes a pending duplicate text-only block before finalizing it", async () => {

3694+

const { answerDraftStream } = setupDraftStreams();

3695+

answerDraftStream.stop.mockImplementation(async () => {

3696+

answerDraftStream.setMessageId(2001);

3697+

});

3698+

dispatchReplyWithBufferedBlockDispatcher.mockImplementation(

3699+

async ({ dispatcherOptions, replyOptions }) => {

3700+

await replyOptions?.onPartialReply?.({ text: "pending answer" });

3701+

await dispatcherOptions.deliver({ text: "pending answer" }, { kind: "block" });

3702+

return { queuedFinal: false };

3703+

},

3704+

);

3705+3706+

await dispatchWithContext({

3707+

context: createContext(),

3708+

streamMode: "partial",

3709+

telegramCfg: { streaming: { mode: "partial" } },

3710+

});

3711+3712+

expect(answerDraftStream.stop).toHaveBeenCalled();

3713+

expect(answerDraftStream.clear).not.toHaveBeenCalled();

3714+

expectRecordFields(mockCallArg(emitInternalMessageSentHook), {

3715+

content: "pending answer",

3716+

messageId: 2001,

3717+

});

3718+

});

3719+35763720

it("keeps queued room events abortable after their source dispatch returns", async () => {

35773721

const historyKey = "telegram:group:-100123";

35783722

const groupHistories = new Map([[historyKey, []]]);