Compare commits
2 Commits
main
...
issue-3829
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d992e21a6f | ||
|
|
d65d1b166f |
@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Discord/reply normalization parity: strip `<think>/<final>` reasoning tags from final/block outbound payload text before preview-finalization and delivery so user-visible replies stay aligned with cleaned final-answer policy even when transcripts contain raw tagged content. (#38291)
|
||||||
- Onboarding/headless Linux daemon probe hardening: treat `systemctl --user is-enabled` probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.
|
- Onboarding/headless Linux daemon probe hardening: treat `systemctl --user is-enabled` probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.
|
||||||
- Memory/QMD mcporter Windows spawn hardening: when `mcporter.cmd` launch fails with `spawn EINVAL`, retry via bare `mcporter` shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.
|
- Memory/QMD mcporter Windows spawn hardening: when `mcporter.cmd` launch fails with `spawn EINVAL`, retry via bare `mcporter` shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.
|
||||||
- Tools/web_search Brave language-code validation: align `search_lang` handling with Brave-supported codes (including `zh-hans`, `zh-hant`, `en-gb`, and `pt-br`), map common alias inputs (`zh`, `ja`) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
|
- Tools/web_search Brave language-code validation: align `search_lang` handling with Brave-supported codes (including `zh-hans`, `zh-hant`, `en-gb`, and `pt-br`), map common alias inputs (`zh`, `ja`) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
|
||||||
|
|||||||
@ -46,4 +46,24 @@ describe("readLatestAssistantReply", () => {
|
|||||||
|
|
||||||
expect(result).toBe("older output");
|
expect(result).toBe("older output");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes reasoning tags when reading from transcript history", async () => {
|
||||||
|
callGatewayMock.mockResolvedValue({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: "<think>private reasoning</think><final>Clean answer</final>",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await readLatestAssistantReply({ sessionKey: "agent:main:child" });
|
||||||
|
|
||||||
|
expect(result).toBe("Clean answer");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -527,6 +527,45 @@ describe("processDiscordMessage draft streaming", () => {
|
|||||||
expect(editMessageDiscord).not.toHaveBeenCalled();
|
expect(editMessageDiscord).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("strips reasoning tags from final payload delivery", async () => {
|
||||||
|
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||||
|
await params?.dispatcher.sendFinalReply({
|
||||||
|
text: "<think>internal chain of thought</think><final>Visible answer</final>",
|
||||||
|
});
|
||||||
|
return { queuedFinal: true, counts: { final: 1, tool: 0, block: 0 } };
|
||||||
|
});
|
||||||
|
|
||||||
|
await processStreamOffDiscordMessage();
|
||||||
|
|
||||||
|
expect(deliverDiscordReply).toHaveBeenCalledTimes(1);
|
||||||
|
expect(deliverDiscordReply).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
replies: [expect.objectContaining({ text: "Visible answer" })],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not drop non-reasoning final payloads that start with Reasoning prefix text", async () => {
|
||||||
|
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||||
|
await params?.dispatcher.sendFinalReply({
|
||||||
|
text: "Reasoning:\nThis heading is intentional user-facing content",
|
||||||
|
});
|
||||||
|
return { queuedFinal: true, counts: { final: 1, tool: 0, block: 0 } };
|
||||||
|
});
|
||||||
|
|
||||||
|
await processStreamOffDiscordMessage();
|
||||||
|
|
||||||
|
expect(deliverDiscordReply).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
replies: [
|
||||||
|
expect.objectContaining({
|
||||||
|
text: "Reasoning:\nThis heading is intentional user-facing content",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("delivers non-reasoning block payloads to Discord", async () => {
|
it("delivers non-reasoning block payloads to Discord", async () => {
|
||||||
mockDispatchSingleBlockReply({ text: "hello from block stream" });
|
mockDispatchSingleBlockReply({ text: "hello from block stream" });
|
||||||
await processStreamOffDiscordMessage();
|
await processStreamOffDiscordMessage();
|
||||||
|
|||||||
@ -592,6 +592,13 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
await draftStream.flush();
|
await draftStream.flush();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sanitizeVisibleReplyText = (text?: string) => {
|
||||||
|
if (typeof text !== "string") {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
|
||||||
|
};
|
||||||
|
|
||||||
// When draft streaming is active, suppress block streaming to avoid double-streaming.
|
// When draft streaming is active, suppress block streaming to avoid double-streaming.
|
||||||
const disableBlockStreamingForDraft = draftStream ? true : undefined;
|
const disableBlockStreamingForDraft = draftStream ? true : undefined;
|
||||||
|
|
||||||
@ -609,10 +616,15 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
// Reasoning/thinking payloads should not be delivered to Discord.
|
// Reasoning/thinking payloads should not be delivered to Discord.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const visiblePayload =
|
||||||
|
typeof payload.text === "string"
|
||||||
|
? { ...payload, text: sanitizeVisibleReplyText(payload.text) }
|
||||||
|
: payload;
|
||||||
if (draftStream && isFinal) {
|
if (draftStream && isFinal) {
|
||||||
await flushDraft();
|
await flushDraft();
|
||||||
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
const hasMedia =
|
||||||
const finalText = payload.text;
|
Boolean(visiblePayload.mediaUrl) || (visiblePayload.mediaUrls?.length ?? 0) > 0;
|
||||||
|
const finalText = visiblePayload.text;
|
||||||
const previewFinalText = resolvePreviewFinalText(finalText);
|
const previewFinalText = resolvePreviewFinalText(finalText);
|
||||||
const previewMessageId = draftStream.messageId();
|
const previewMessageId = draftStream.messageId();
|
||||||
|
|
||||||
@ -622,7 +634,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
!hasMedia &&
|
!hasMedia &&
|
||||||
typeof previewFinalText === "string" &&
|
typeof previewFinalText === "string" &&
|
||||||
typeof previewMessageId === "string" &&
|
typeof previewMessageId === "string" &&
|
||||||
!payload.isError;
|
!visiblePayload.isError;
|
||||||
|
|
||||||
if (canFinalizeViaPreviewEdit) {
|
if (canFinalizeViaPreviewEdit) {
|
||||||
await draftStream.stop();
|
await draftStream.stop();
|
||||||
@ -657,7 +669,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
typeof messageIdAfterStop === "string" &&
|
typeof messageIdAfterStop === "string" &&
|
||||||
typeof previewFinalText === "string" &&
|
typeof previewFinalText === "string" &&
|
||||||
!hasMedia &&
|
!hasMedia &&
|
||||||
!payload.isError
|
!visiblePayload.isError
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await editMessageDiscord(
|
await editMessageDiscord(
|
||||||
@ -688,7 +700,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
|
|
||||||
const replyToId = replyReference.use();
|
const replyToId = replyReference.use();
|
||||||
await deliverDiscordReply({
|
await deliverDiscordReply({
|
||||||
replies: [payload],
|
replies: [visiblePayload],
|
||||||
target: deliverTarget,
|
target: deliverTarget,
|
||||||
token,
|
token,
|
||||||
accountId,
|
accountId,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user