mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 22:52:03 +00:00
fix: prevent delivery-mirror re-delivery and raise Slack chunk limit (#45489)
Merged via squash.
Prepared head SHA: c7664c7b6e
Co-authored-by: theo674 <261068216+theo674@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
@@ -38,6 +38,15 @@ const stripTrailingDirective = (text: string): string => {
|
||||
return text.slice(0, openIndex);
|
||||
};
|
||||
|
||||
function isTranscriptOnlyOpenClawAssistantMessage(message: AgentMessage | undefined): boolean {
|
||||
if (!message || message.role !== "assistant") {
|
||||
return false;
|
||||
}
|
||||
const provider = typeof message.provider === "string" ? message.provider.trim() : "";
|
||||
const model = typeof message.model === "string" ? message.model.trim() : "";
|
||||
return provider === "openclaw" && (model === "delivery-mirror" || model === "gateway-injected");
|
||||
}
|
||||
|
||||
function emitReasoningEnd(ctx: EmbeddedPiSubscribeContext) {
|
||||
if (!ctx.state.reasoningStreamOpen) {
|
||||
return;
|
||||
@@ -134,7 +143,7 @@ export function handleMessageStart(
|
||||
evt: AgentEvent & { message: AgentMessage },
|
||||
) {
|
||||
const msg = evt.message;
|
||||
if (msg?.role !== "assistant") {
|
||||
if (msg?.role !== "assistant" || isTranscriptOnlyOpenClawAssistantMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,7 +162,7 @@ export function handleMessageUpdate(
|
||||
evt: AgentEvent & { message: AgentMessage; assistantMessageEvent?: unknown },
|
||||
) {
|
||||
const msg = evt.message;
|
||||
if (msg?.role !== "assistant") {
|
||||
if (msg?.role !== "assistant" || isTranscriptOnlyOpenClawAssistantMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,7 +332,7 @@ export function handleMessageEnd(
|
||||
evt: AgentEvent & { message: AgentMessage },
|
||||
) {
|
||||
const msg = evt.message;
|
||||
if (msg?.role !== "assistant") {
|
||||
if (msg?.role !== "assistant" || isTranscriptOnlyOpenClawAssistantMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,15 @@ async function emitMessageToolLifecycle(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function emitAssistantMessageEnd(emit: (evt: unknown) => void, text: string) {
|
||||
function emitAssistantMessageEnd(
|
||||
emit: (evt: unknown) => void,
|
||||
text: string,
|
||||
overrides?: Partial<AssistantMessage>,
|
||||
) {
|
||||
const assistantMessage = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
...overrides,
|
||||
} as AssistantMessage;
|
||||
emit({ type: "message_end", message: assistantMessage });
|
||||
}
|
||||
@@ -68,6 +73,7 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
result: "ok",
|
||||
});
|
||||
emitAssistantMessageEnd(emit, messageText);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onBlockReply).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -82,16 +88,44 @@ describe("subscribeEmbeddedPiSession", () => {
|
||||
result: { details: { status: "error" } },
|
||||
});
|
||||
emitAssistantMessageEnd(emit, messageText);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onBlockReply).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("clears block reply state on message_start", () => {
|
||||
|
||||
it("ignores delivery-mirror assistant messages", async () => {
|
||||
const { emit, onBlockReply } = createBlockReplyHarness("message_end");
|
||||
|
||||
emitAssistantMessageEnd(emit, "Mirrored transcript text", {
|
||||
provider: "openclaw",
|
||||
model: "delivery-mirror",
|
||||
});
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onBlockReply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores gateway-injected assistant messages", async () => {
|
||||
const { emit, onBlockReply } = createBlockReplyHarness("message_end");
|
||||
|
||||
emitAssistantMessageEnd(emit, "Injected transcript text", {
|
||||
provider: "openclaw",
|
||||
model: "gateway-injected",
|
||||
});
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onBlockReply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears block reply state on message_start", async () => {
|
||||
const { emit, onBlockReply } = createBlockReplyHarness("text_end");
|
||||
emitAssistantTextEndBlock(emit, "OK");
|
||||
await Promise.resolve();
|
||||
expect(onBlockReply).toHaveBeenCalledTimes(1);
|
||||
|
||||
// New assistant message with identical output should still emit.
|
||||
emitAssistantTextEndBlock(emit, "OK");
|
||||
await Promise.resolve();
|
||||
expect(onBlockReply).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from "node:path";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { upsertAcpSessionMeta } from "../../acp/runtime/session-meta.js";
|
||||
import * as jsonFiles from "../../infra/json-files.js";
|
||||
import * as transcriptEvents from "../../sessions/transcript-events.js";
|
||||
import type { OpenClawConfig } from "../config.js";
|
||||
import {
|
||||
clearSessionStoreCacheForTest,
|
||||
@@ -429,6 +430,42 @@ describe("appendAssistantMessageToSessionTranscript", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("emits transcript update events for delivery mirrors", async () => {
|
||||
const sessionId = "test-session-id";
|
||||
const sessionKey = "test-session";
|
||||
const store = {
|
||||
[sessionKey]: {
|
||||
sessionId,
|
||||
chatType: "direct",
|
||||
channel: "discord",
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(fixture.storePath(), JSON.stringify(store), "utf-8");
|
||||
const emitSpy = vi.spyOn(transcriptEvents, "emitSessionTranscriptUpdate");
|
||||
|
||||
await appendAssistantMessageToSessionTranscript({
|
||||
sessionKey,
|
||||
text: "Hello from delivery mirror!",
|
||||
storePath: fixture.storePath(),
|
||||
});
|
||||
|
||||
const sessionFile = resolveSessionTranscriptPathInDir(sessionId, fixture.sessionsDir());
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionFile,
|
||||
sessionKey,
|
||||
messageId: expect.any(String),
|
||||
message: expect.objectContaining({
|
||||
role: "assistant",
|
||||
provider: "openclaw",
|
||||
model: "delivery-mirror",
|
||||
content: [{ type: "text", text: "Hello from delivery mirror!" }],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
emitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("does not append a duplicate delivery mirror for the same idempotency key", async () => {
|
||||
writeTranscriptStore();
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const resolveProviderUsageAuthWithPluginMock = vi.fn();
|
||||
const resolveProviderUsageAuthWithPluginMock = vi.fn(
|
||||
async (..._args: unknown[]): Promise<unknown> => null,
|
||||
);
|
||||
|
||||
vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
resolveProviderUsageAuthWithPlugin: (...args: unknown[]) =>
|
||||
resolveProviderUsageAuthWithPluginMock(...args),
|
||||
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
|
||||
}));
|
||||
|
||||
let resolveProviderAuths: typeof import("./provider-usage.auth.js").resolveProviderAuths;
|
||||
|
||||
Reference in New Issue
Block a user