Skip to content

Commit 9dcd993

Browse files
fix(discord): preserve streamed replies after tool warnings
1 parent aa9ab31 commit 9dcd993

3 files changed

Lines changed: 33 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Docs: https://docs.openclaw.ai
9292
- Providers/DeepSeek: normalize MCP tool schemas with `anyOf`/`oneOf` unions before normal and compaction requests reach DeepSeek, preventing union-shaped parameters from being rejected. (#83766) Thanks @TurboTheTurtle.
9393
- Control UI: render live tool progress from session-scoped `session.tool` Gateway events so externally started runs show their tool cards in the active session. (#83734) Thanks @TurboTheTurtle.
9494
- Outbound: resolve send-capable channel plugins from the active runtime registry when the pinned startup registry only has setup metadata. (#83733) Thanks @TurboTheTurtle.
95+
- Discord: preserve streamed reply previews when recovered tool-warning finals are delivered before or after the assistant's final reply. (#83844) Thanks @neeravmakwana.
9596
- Control UI: keep the chat delete confirmation popover clamped inside the visible viewport on small screens. (#83804) Thanks @ThiagoCAltoe.
9697
- Browser: enforce current-tab URL allowlist checks for `/act` evaluate/batch actions and `/highlight` routes while leaving tab-management actions unblocked. (#78523)
9798
- CI: require real-behavior-proof verdict markers to come from the ClawSweeper GitHub App before accepting exact-head proof. (#83692)

extensions/discord/src/monitor/message-handler.process.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,6 +2092,37 @@ describe("processDiscordMessage draft streaming", () => {
20922092
});
20932093
});
20942094

2095+
it("keeps draft previews when tool warning finals arrive before recovered replies", async () => {
2096+
const draftStream = createMockDraftStreamForTest();
2097+
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
2098+
await params?.dispatcher.sendFinalReply({
2099+
text: "⚠️ 🛠️ `run openclaw definitely-not-a-real-subcommand (agent)` failed",
2100+
isError: true,
2101+
} as never);
2102+
await params?.dispatcher.sendFinalReply({ text: "delivery recovered" });
2103+
return { queuedFinal: true, counts: { final: 2, tool: 0, block: 0 } };
2104+
});
2105+
2106+
const ctx = await createAutomaticSourceDeliveryContext({
2107+
discordConfig: { streamMode: "partial", maxLinesPerMessage: 5 },
2108+
});
2109+
2110+
await runProcessDiscordMessage(ctx);
2111+
2112+
expectPreviewEditContent("delivery recovered");
2113+
expect(draftStream.clear).not.toHaveBeenCalled();
2114+
expect(draftStream.messageId()).toBe("preview-1");
2115+
expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
2116+
expect(firstMockArg(deliverDiscordReply, "deliverDiscordReply")).toMatchObject({
2117+
replies: [
2118+
{
2119+
text: "⚠️ 🛠️ `run openclaw definitely-not-a-real-subcommand (agent)` failed",
2120+
isError: true,
2121+
},
2122+
],
2123+
});
2124+
});
2125+
20952126
it("suppresses reasoning payload delivery to Discord", async () => {
20962127
mockDispatchSingleBlockReply({ text: "thinking...", isReasoning: true });
20972128
await processStreamOffDiscordMessage();

extensions/discord/src/monitor/message-handler.process.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,6 @@ export async function processDiscordMessage(
513513
const finalPreviewFlags =
514514
(discordConfig?.suppressEmbeds ?? true) ? MessageFlags.SuppressEmbeds : undefined;
515515
let finalReplyStartNotified = false;
516-
let nonErrorFinalDeliveryStarted = false;
517516
const notifyFinalReplyStart = () => {
518517
if (finalReplyStartNotified) {
519518
return;
@@ -551,15 +550,11 @@ export async function processDiscordMessage(
551550
return;
552551
}
553552
}
554-
const followsNonErrorFinalDelivery = payload.isError && nonErrorFinalDeliveryStarted;
555-
if (isFinal && !payload.isError) {
556-
nonErrorFinalDeliveryStarted = true;
557-
}
558553
const shouldFinalizeDraftPreview =
559554
draftStream &&
560555
isFinal &&
561556
(!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted) &&
562-
!followsNonErrorFinalDelivery;
557+
!payload.isError;
563558
if (shouldFinalizeDraftPreview) {
564559
const reply = resolveSendableOutboundReplyParts(effectivePayload);
565560
const hasMedia = reply.hasMedia;

0 commit comments

Comments
 (0)