mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 02:43:42 +00:00
fix: stabilize Telegram draft boundaries and suppress NO_REPLY lead leaks (#33169)
* fix: stabilize telegram draft stream message boundaries * fix: suppress NO_REPLY lead-fragment leaks * fix: keep underscore guard for non-NO_REPLY prefixes * fix: skip assistant-start rotation only after real lane rotation * fix: preserve finalized state when pre-rotation does not force * fix: reset finalized preview state on message-start boundary * fix: document Telegram draft boundary + NO_REPLY reliability updates (#33169) (thanks @obviyus)
This commit is contained in:
@@ -10,7 +10,6 @@ function createHarness(params?: {
|
||||
answerStream?: DraftLaneState["stream"];
|
||||
answerHasStreamedMessage?: boolean;
|
||||
answerLastPartialText?: string;
|
||||
answerPreviewRevisionBaseline?: number;
|
||||
}) {
|
||||
const answer =
|
||||
params?.answerStream ?? createTestDraftStream({ messageId: params?.answerMessageId });
|
||||
@@ -20,13 +19,11 @@ function createHarness(params?: {
|
||||
stream: answer,
|
||||
lastPartialText: params?.answerLastPartialText ?? "",
|
||||
hasStreamedMessage: params?.answerHasStreamedMessage ?? false,
|
||||
previewRevisionBaseline: params?.answerPreviewRevisionBaseline ?? 0,
|
||||
},
|
||||
reasoning: {
|
||||
stream: reasoning as DraftLaneState["stream"],
|
||||
lastPartialText: "",
|
||||
hasStreamedMessage: false,
|
||||
previewRevisionBaseline: 0,
|
||||
},
|
||||
};
|
||||
const sendPayload = vi.fn().mockResolvedValue(true);
|
||||
@@ -212,10 +209,8 @@ describe("createLaneTextDeliverer", () => {
|
||||
expect(harness.log).toHaveBeenCalledWith(expect.stringContaining("preview final too long"));
|
||||
});
|
||||
|
||||
it("treats unchanged DM draft final text as already finalized", async () => {
|
||||
it("sends a final message after DM draft streaming even when text is unchanged", async () => {
|
||||
const answerStream = createTestDraftStream({ previewMode: "draft" });
|
||||
answerStream.previewRevision.mockReturnValue(7);
|
||||
answerStream.lastDeliveredText.mockReturnValue("Hello final");
|
||||
answerStream.update.mockImplementation(() => {});
|
||||
const harness = createHarness({
|
||||
answerStream: answerStream as DraftLaneState["stream"],
|
||||
@@ -230,76 +225,19 @@ describe("createLaneTextDeliverer", () => {
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(result).toBe("preview-finalized");
|
||||
expect(harness.flushDraftLane).toHaveBeenCalledTimes(1);
|
||||
expect(harness.stopDraftLane).toHaveBeenCalledTimes(1);
|
||||
expect(harness.sendPayload).not.toHaveBeenCalled();
|
||||
expect(harness.markDelivered).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("falls back once when DM draft finalization emits no update", async () => {
|
||||
const answerStream = createTestDraftStream({ previewMode: "draft" });
|
||||
answerStream.previewRevision.mockReturnValue(3);
|
||||
answerStream.update.mockImplementation(() => {});
|
||||
const harness = createHarness({
|
||||
answerStream: answerStream as DraftLaneState["stream"],
|
||||
answerHasStreamedMessage: true,
|
||||
answerLastPartialText: "Partial",
|
||||
});
|
||||
|
||||
const result = await harness.deliverLaneText({
|
||||
laneName: "answer",
|
||||
text: "Final answer",
|
||||
payload: { text: "Final answer" },
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(result).toBe("sent");
|
||||
expect(harness.flushDraftLane).toHaveBeenCalledTimes(1);
|
||||
expect(harness.stopDraftLane).toHaveBeenCalledTimes(1);
|
||||
expect(harness.sendPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Final answer" }),
|
||||
);
|
||||
expect(harness.markDelivered).not.toHaveBeenCalled();
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("draft final text not emitted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back when unchanged final text has no emitted draft preview in current lane", async () => {
|
||||
const answerStream = createTestDraftStream({ previewMode: "draft" });
|
||||
answerStream.previewRevision.mockReturnValue(7);
|
||||
answerStream.update.mockImplementation(() => {});
|
||||
const harness = createHarness({
|
||||
answerStream: answerStream as DraftLaneState["stream"],
|
||||
answerHasStreamedMessage: true,
|
||||
answerLastPartialText: "Hello final",
|
||||
answerPreviewRevisionBaseline: 7,
|
||||
});
|
||||
|
||||
const result = await harness.deliverLaneText({
|
||||
laneName: "answer",
|
||||
text: "Hello final",
|
||||
payload: { text: "Hello final" },
|
||||
infoKind: "final",
|
||||
});
|
||||
|
||||
expect(result).toBe("sent");
|
||||
expect(harness.stopDraftLane).toHaveBeenCalledTimes(1);
|
||||
expect(harness.flushDraftLane).toHaveBeenCalled();
|
||||
expect(harness.stopDraftLane).toHaveBeenCalled();
|
||||
expect(harness.sendPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Hello final" }),
|
||||
);
|
||||
expect(harness.markDelivered).not.toHaveBeenCalled();
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("draft final text not emitted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back when revision advances but final text was not emitted", async () => {
|
||||
let previewRevision = 7;
|
||||
it("sends a final message after DM draft streaming when revision changes", async () => {
|
||||
let previewRevision = 3;
|
||||
const answerStream = createTestDraftStream({ previewMode: "draft" });
|
||||
answerStream.previewRevision.mockImplementation(() => previewRevision);
|
||||
answerStream.lastDeliveredText.mockReturnValue("Older partial");
|
||||
answerStream.update.mockImplementation(() => {});
|
||||
answerStream.flush.mockImplementation(async () => {
|
||||
previewRevision += 1;
|
||||
@@ -322,9 +260,6 @@ describe("createLaneTextDeliverer", () => {
|
||||
expect.objectContaining({ text: "Final answer" }),
|
||||
);
|
||||
expect(harness.markDelivered).not.toHaveBeenCalled();
|
||||
expect(harness.log).toHaveBeenCalledWith(
|
||||
expect.stringContaining("draft final text not emitted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not use DM draft final shortcut for media payloads", async () => {
|
||||
|
||||
Reference in New Issue
Block a user