mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:10:52 +00:00
fix(telegram): honor removeAckAfterReply for status reactions (#68067)
Thanks @poiskgit.
This commit is contained in:
@@ -298,6 +298,19 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
};
|
||||
}
|
||||
|
||||
function createStatusReactionController() {
|
||||
return {
|
||||
setQueued: vi.fn(),
|
||||
setThinking: vi.fn(async () => {}),
|
||||
setTool: vi.fn(async () => {}),
|
||||
setCompacting: vi.fn(async () => {}),
|
||||
cancelPending: vi.fn(),
|
||||
setError: vi.fn(async () => {}),
|
||||
setDone: vi.fn(async () => {}),
|
||||
restoreInitial: vi.fn(async () => {}),
|
||||
};
|
||||
}
|
||||
|
||||
function createBot(): Bot {
|
||||
return {
|
||||
api: {
|
||||
@@ -3075,15 +3088,8 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
resolvePreviewVisible = resolve;
|
||||
});
|
||||
|
||||
const statusReactionController = {
|
||||
setQueued: vi.fn(),
|
||||
setThinking: vi.fn(async () => {}),
|
||||
setTool: vi.fn(async () => {}),
|
||||
setCompacting: vi.fn(async () => {}),
|
||||
cancelPending: vi.fn(),
|
||||
setError: vi.fn(async () => {}),
|
||||
setDone: vi.fn(async () => {}),
|
||||
};
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
const firstAnswerDraft = createTestDraftStream({
|
||||
messageId: 1001,
|
||||
onUpdate: (text) => {
|
||||
@@ -3116,6 +3122,8 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
|
||||
const firstPromise = dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: true,
|
||||
statusReactionController: statusReactionController as never,
|
||||
ctxPayload: {
|
||||
SessionKey: "s1",
|
||||
@@ -3123,6 +3131,15 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
RawBody: "earlier request",
|
||||
} as never,
|
||||
}),
|
||||
cfg: {
|
||||
messages: {
|
||||
statusReactions: {
|
||||
timing: {
|
||||
doneHoldMs: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await previewVisible;
|
||||
@@ -3147,11 +3164,23 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
});
|
||||
|
||||
releaseFirstFinal();
|
||||
await Promise.all([firstPromise, abortPromise]);
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
releaseFirstFinal();
|
||||
await Promise.all([firstPromise, abortPromise]);
|
||||
|
||||
expect(statusReactionController.setDone).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.setError).not.toHaveBeenCalled();
|
||||
expect(statusReactionController.setDone).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.setError).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(249);
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(reactionApi).toHaveBeenCalledWith(123, 456, []);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps an existing preview when abort arrives during queued draft-lane cleanup", async () => {
|
||||
@@ -3529,6 +3558,174 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("uses configured doneHoldMs when clearing Telegram status reactions after reply", async () => {
|
||||
vi.useFakeTimers();
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: true });
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
try {
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: true,
|
||||
statusReactionController: statusReactionController as never,
|
||||
}),
|
||||
cfg: {
|
||||
messages: {
|
||||
statusReactions: {
|
||||
timing: {
|
||||
doneHoldMs: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
streamMode: "off",
|
||||
});
|
||||
|
||||
expect(statusReactionController.setDone).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.restoreInitial).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(249);
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(reactionApi).toHaveBeenCalledWith(123, 456, []);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("restores the initial Telegram status reaction after reply when removeAckAfterReply is disabled", async () => {
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: true });
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: false,
|
||||
statusReactionController: statusReactionController as never,
|
||||
}),
|
||||
streamMode: "off",
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(statusReactionController.setDone).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.restoreInitial).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(statusReactionController.setError).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
});
|
||||
|
||||
it("uses configured errorHoldMs to clear Telegram status reactions after an error fallback", async () => {
|
||||
vi.useFakeTimers();
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockRejectedValue(new Error("dispatcher exploded"));
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
try {
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: true,
|
||||
statusReactionController: statusReactionController as never,
|
||||
}),
|
||||
cfg: {
|
||||
messages: {
|
||||
statusReactions: {
|
||||
timing: {
|
||||
errorHoldMs: 320,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
streamMode: "off",
|
||||
});
|
||||
|
||||
expect(statusReactionController.setError).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.setDone).not.toHaveBeenCalled();
|
||||
expect(statusReactionController.restoreInitial).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(319);
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(reactionApi).toHaveBeenCalledWith(123, 456, []);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("restores the initial Telegram status reaction after an error when no final reply is sent", async () => {
|
||||
vi.useFakeTimers();
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockRejectedValue(new Error("dispatcher exploded"));
|
||||
deliverReplies.mockResolvedValue({ delivered: false });
|
||||
|
||||
try {
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: true,
|
||||
statusReactionController: statusReactionController as never,
|
||||
}),
|
||||
cfg: {
|
||||
messages: {
|
||||
statusReactions: {
|
||||
timing: {
|
||||
errorHoldMs: 320,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
streamMode: "off",
|
||||
});
|
||||
|
||||
expect(statusReactionController.setError).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.restoreInitial).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(319);
|
||||
expect(statusReactionController.restoreInitial).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(statusReactionController.restoreInitial).toHaveBeenCalledTimes(1);
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("restores the initial Telegram status reaction after an error fallback when removeAckAfterReply is disabled", async () => {
|
||||
const reactionApi = vi.fn(async () => true);
|
||||
const statusReactionController = createStatusReactionController();
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockRejectedValue(new Error("dispatcher exploded"));
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
reactionApi: reactionApi as never,
|
||||
removeAckAfterReply: false,
|
||||
statusReactionController: statusReactionController as never,
|
||||
}),
|
||||
streamMode: "off",
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(statusReactionController.setError).toHaveBeenCalledTimes(1);
|
||||
expect(statusReactionController.restoreInitial).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(statusReactionController.setDone).not.toHaveBeenCalled();
|
||||
expect(reactionApi).not.toHaveBeenCalledWith(123, 456, []);
|
||||
});
|
||||
|
||||
it("uses resolved DM config for auto-topic-label overrides", async () => {
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: true });
|
||||
loadSessionStore.mockReturnValue({ s1: {} });
|
||||
|
||||
Reference in New Issue
Block a user