mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:10:45 +00:00
fix(reply): narrow empty-body history guard
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Google video generation: fall back to the REST `predictLongRunning` Veo endpoint for text-only SDK 404s while keeping reference image/video generation on the SDK path. Fixes #62309 and #63008. (#62343) Thanks @leoleedev.
|
||||
- MiniMax music generation: switch the bundled default model from the unsupported `music-2.5+` id to the current `music-2.6` API model. Fixes #64870 and addresses the music default from #62315. Thanks @noahclanman and @edwardzheng1.
|
||||
- Cron: hydrate flat legacy job rows with top-level `cron`, `tz`, `session`, and `message` fields into canonical schedule, target, and payload objects before startup recomputes run times. Fixes #43351.
|
||||
- Agents/replies: let pending group chat history trigger bare mentioned turns without treating metadata-only inbound context as user input. Fixes #71489. (#71520) Thanks @SymbolStar.
|
||||
- Google media generation: strip a configured trailing `/v1beta` from Google music/video provider base URLs before calling the Google GenAI SDK, preventing doubled `/v1beta/v1beta` paths. Fixes #63240. (#63258) Thanks @Hybirdss.
|
||||
- Discord: restore direct-message voice-note preflight transcription and classify URL-only Ogg/Opus voice attachments as audio while skipping partial attachments without usable URLs. Fixes #61314 and #64803.
|
||||
- Google Chat: preserve reply text when a typing indicator message is deleted or can no longer be updated, so media captions and first text chunks are resent instead of silently disappearing. (#71498) Thanks @colin-lgtm.
|
||||
|
||||
@@ -458,6 +458,90 @@ describe("runPreparedReply media-only handling", () => {
|
||||
expect(vi.mocked(runReplyAgent)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows pending inbound history to trigger a bare mention turn", async () => {
|
||||
vi.mocked(buildInboundUserContextPrefix).mockReturnValueOnce(
|
||||
[
|
||||
"Chat history since last reply (untrusted, for context):",
|
||||
"```json",
|
||||
JSON.stringify(
|
||||
[{ sender: "Alice", timestamp_ms: 1_700_000_000_000, body: "what changed?" }],
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"```",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await runPreparedReply(
|
||||
baseParams({
|
||||
ctx: {
|
||||
Body: "",
|
||||
RawBody: "",
|
||||
CommandBody: "",
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
},
|
||||
sessionCtx: {
|
||||
Body: "",
|
||||
BodyStripped: "",
|
||||
Provider: "feishu",
|
||||
OriginatingChannel: "feishu",
|
||||
OriginatingTo: "chat-1",
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
InboundHistory: [
|
||||
{ sender: "Alice", timestamp: 1_700_000_000_000, body: "what changed?" },
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toEqual({ text: "ok" });
|
||||
expect(vi.mocked(runReplyAgent)).toHaveBeenCalledOnce();
|
||||
const call = vi.mocked(runReplyAgent).mock.calls[0]?.[0];
|
||||
expect(call?.followupRun.prompt).toContain("Chat history since last reply");
|
||||
expect(call?.followupRun.prompt).toContain("what changed?");
|
||||
expect(call?.followupRun.prompt).not.toContain("[User sent media without caption]");
|
||||
});
|
||||
|
||||
it("does not treat blank pending inbound history as user input", async () => {
|
||||
vi.mocked(buildInboundUserContextPrefix).mockReturnValueOnce(
|
||||
[
|
||||
"Chat history since last reply (untrusted, for context):",
|
||||
"```json",
|
||||
JSON.stringify([{ sender: "Alice", timestamp_ms: 1_700_000_000_000, body: "" }], null, 2),
|
||||
"```",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const result = await runPreparedReply(
|
||||
baseParams({
|
||||
ctx: {
|
||||
Body: "",
|
||||
RawBody: "",
|
||||
CommandBody: "",
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
},
|
||||
sessionCtx: {
|
||||
Body: "",
|
||||
BodyStripped: "",
|
||||
Provider: "feishu",
|
||||
OriginatingChannel: "feishu",
|
||||
OriginatingTo: "chat-1",
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
InboundHistory: [{ sender: "Alice", timestamp: 1_700_000_000_000, body: "\u0000 " }],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
text: "I didn't receive any text in your message. Please resend or add a caption.",
|
||||
});
|
||||
expect(vi.mocked(runReplyAgent)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows webchat pure-image turns when image content is carried outside MediaPath", async () => {
|
||||
vi.mocked(buildInboundUserContextPrefix).mockReturnValueOnce(
|
||||
[
|
||||
|
||||
@@ -165,6 +165,13 @@ function stripPromptThinkingDirectives(body: string): string {
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function hasInboundHistoryBody(ctx: TemplateContext): boolean {
|
||||
return (
|
||||
Array.isArray(ctx.InboundHistory) &&
|
||||
ctx.InboundHistory.some((entry) => entry.body.replaceAll("\u0000", "").trim().length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
type RunPreparedReplyParams = {
|
||||
ctx: MsgContext;
|
||||
sessionCtx: TemplateContext;
|
||||
@@ -458,7 +465,7 @@ export async function runPreparedReply(
|
||||
const hasUserBody =
|
||||
baseBodyFinal.trim().length > 0 ||
|
||||
softResetTail.length > 0 ||
|
||||
(inboundUserContext != null && inboundUserContext.trim().length > 0);
|
||||
hasInboundHistoryBody(sessionCtx);
|
||||
const hasMediaAttachment = hasInboundMedia(sessionCtx) || (opts?.images?.length ?? 0) > 0;
|
||||
if (!hasUserBody && !hasMediaAttachment) {
|
||||
// Skip onReplyStart when typing is suppressed (e.g. sendPolicy deny) —
|
||||
|
||||
Reference in New Issue
Block a user