mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(telegram): isolate verbose status after streamed finals (#89813)
Summary: - The branch updates Telegram dispatch so a verbose/status final arriving after a streamed final answer uses a fresh answer-lane message, with default and progress-mode regression tests. - PR surface: Source +14, Tests +52. Total +66 across 2 files. - Reproducibility: yes. The linked bug report gives a concrete Telegram `/reset`, `/v on`, short-prompt path, and source inspection shows current main can route a second final payload through the finalized answer lane. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(telegram): isolate verbose status after streamed finals Validation: - ClawSweeper review passed for head4d476a957f. - Required merge gates passed before the squash merge. Prepared head SHA:4d476a957fReview: https://github.com/openclaw/openclaw/pull/89813#issuecomment-4612006920 Co-authored-by: kesslerio <martin@kessler.io> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
co-authored by
kesslerio
clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
takhoffman
parent
9dc1694eb7
commit
07006943de
@@ -227,6 +227,7 @@ const telegramDepsForTest: TelegramBotDeps = {
|
||||
|
||||
describe("dispatchTelegramMessage draft streaming", () => {
|
||||
type TelegramMessageContext = Parameters<typeof dispatchTelegramMessage>[0]["context"];
|
||||
const trailingFinalStatusText = "Post-final plugin status";
|
||||
|
||||
beforeAll(async () => {
|
||||
({ dispatchTelegramMessage, resetTelegramReplyFenceForTests } =
|
||||
@@ -1605,6 +1606,30 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends trailing verbose status after streamed final answer without replacing the answer draft", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onPartialReply?.({ text: "Normal reply" });
|
||||
await dispatcherOptions.deliver({ text: "Normal reply" }, { kind: "final" });
|
||||
await dispatcherOptions.deliver({ text: trailingFinalStatusText }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
|
||||
await dispatchWithContext({ context: createContext() });
|
||||
|
||||
expect(answerDraftStream.update).toHaveBeenCalledTimes(3);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Normal reply");
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(2, "Normal reply");
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(3, trailingFinalStatusText);
|
||||
expect(answerDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
|
||||
expect(answerDraftStream.forceNewMessage.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
answerDraftStream.update.mock.invocationCallOrder[2],
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies partial deltas while preserving the first-preview debounce", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
@@ -2075,6 +2100,33 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(editMessageTelegram).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends trailing verbose status after a progress-mode final answer", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onToolStart?.({ name: "exec", phase: "start" });
|
||||
await dispatcherOptions.deliver({ text: "Branch is up to date" }, { kind: "final" });
|
||||
await dispatcherOptions.deliver({ text: trailingFinalStatusText }, { kind: "final" });
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext(),
|
||||
streamMode: "progress",
|
||||
telegramCfg: { streaming: { mode: "progress" } },
|
||||
});
|
||||
|
||||
expect(answerDraftStream.update).toHaveBeenCalledTimes(2);
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(1, "Cracking\n\n`🛠️ Exec`");
|
||||
expect(answerDraftStream.update).toHaveBeenNthCalledWith(2, trailingFinalStatusText);
|
||||
expect(answerDraftStream.forceNewMessage).toHaveBeenCalledTimes(2);
|
||||
expect(answerDraftStream.forceNewMessage.mock.invocationCallOrder[1]).toBeLessThan(
|
||||
answerDraftStream.update.mock.invocationCallOrder[1],
|
||||
);
|
||||
expectDeliveredReply(0, { text: "Branch is up to date" });
|
||||
});
|
||||
|
||||
it("does not stream text-only tool results into progress drafts", async () => {
|
||||
const { answerDraftStream } = setupDraftStreams({ answerMessageId: 2001 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
|
||||
@@ -1673,6 +1673,19 @@ export const dispatchTelegramMessage = async ({
|
||||
buttons?: TelegramInlineButtons,
|
||||
) => {
|
||||
const finalText = await resolveTranscriptBackedFinalText(text);
|
||||
const deliverPostFinalFollowUpText = async () => {
|
||||
await prepareAnswerLaneForText();
|
||||
return deliverLaneText({
|
||||
laneName: "answer",
|
||||
text: finalText,
|
||||
payload: answerPayload,
|
||||
infoKind: "final",
|
||||
buttons,
|
||||
});
|
||||
};
|
||||
if (finalAnswerDelivered) {
|
||||
return deliverPostFinalFollowUpText();
|
||||
}
|
||||
if (streamMode === "progress") {
|
||||
return deliverProgressModeFinalAnswer(answerPayload, finalText);
|
||||
}
|
||||
@@ -1918,6 +1931,7 @@ export const dispatchTelegramMessage = async ({
|
||||
? () =>
|
||||
enqueueDraftLaneEvent(async () => {
|
||||
reasoningStepState.resetForNextStep();
|
||||
finalAnswerDelivered = false;
|
||||
if (streamMode !== "progress") {
|
||||
resetProgressDraftState();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user