

























@@ -204,27 +204,31 @@ vi.mock("openclaw/plugin-sdk/reply-runtime", () => ({
204204createReplyDispatcherWithTyping: (opts: {
205205deliver: (payload: unknown, info: { kind: string }) => Promise<void> | void;
206206onReplyStart?: () => Promise<void> | void;
207-}) => ({
208-dispatcher: {
209-sendToolResult: vi.fn(() => true),
210-sendBlockReply: vi.fn((payload: unknown) => {
211-void opts.deliver(payload, { kind: "block" });
212-return true;
213-}),
214-sendFinalReply: vi.fn((payload: unknown) => {
215-void opts.deliver(payload, { kind: "final" });
216-return true;
217-}),
218-waitForIdle: vi.fn(async () => {}),
219-getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
220-markComplete: vi.fn(),
221-},
222-replyOptions: {
223-onReplyStart: opts.onReplyStart,
224-},
225-markDispatchIdle: vi.fn(),
226-markRunComplete: vi.fn(),
227-}),
207+}) => {
208+const pendingDeliveries: Promise<void>[] = [];
209+const queueDelivery = (payload: unknown, info: { kind: "block" | "final" }) => {
210+const delivery = Promise.resolve(opts.deliver(payload, info)).catch(() => undefined);
211+pendingDeliveries.push(delivery);
212+return true;
213+};
214+return {
215+dispatcher: {
216+sendToolResult: vi.fn(() => true),
217+sendBlockReply: vi.fn((payload: unknown) => queueDelivery(payload, { kind: "block" })),
218+sendFinalReply: vi.fn((payload: unknown) => queueDelivery(payload, { kind: "final" })),
219+waitForIdle: vi.fn(async () => {
220+await Promise.all(pendingDeliveries);
221+}),
222+getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
223+markComplete: vi.fn(),
224+},
225+replyOptions: {
226+onReplyStart: opts.onReplyStart,
227+},
228+markDispatchIdle: vi.fn(),
229+markRunComplete: vi.fn(),
230+};
231+},
228232}));
229233230234vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
@@ -1473,6 +1477,32 @@ describe("processDiscordMessage draft streaming", () => {
14731477expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
14741478});
147514791480+it("clears partial drafts when fallback final delivery fails before completion", async () => {
1481+const draftStream = createMockDraftStreamForTest();
1482+deliverDiscordReply.mockRejectedValueOnce(new Error("send failed"));
1483+dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
1484+await params?.replyOptions?.onPartialReply?.({ text: "partial answer..." });
1485+await params?.dispatcher.sendFinalReply({ text: "complete\nanswer" });
1486+return {
1487+queuedFinal: true,
1488+counts: { final: 1, tool: 0, block: 0 },
1489+failedCounts: { final: 1 },
1490+};
1491+});
1492+1493+const ctx = await createAutomaticSourceDeliveryContext({
1494+discordConfig: { streamMode: "partial", maxLinesPerMessage: 1 },
1495+});
1496+1497+await runProcessDiscordMessage(ctx);
1498+1499+expect(draftStream.update).toHaveBeenCalledWith("partial answer...");
1500+expect(editMessageDiscord).not.toHaveBeenCalled();
1501+expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
1502+expect(draftStream.discardPending).toHaveBeenCalled();
1503+expect(draftStream.clear).toHaveBeenCalledTimes(1);
1504+});
1505+14761506it("uses root discord maxLinesPerMessage for preview finalization when runtime config omits it", async () => {
14771507const longReply = Array.from({ length: 20 }, (_value, index) => `Line ${index + 1}`).join("\n");
14781508dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。