mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(outbound): report broadcast delivery truth (#122605)
Derive broadcast success from structured send outcomes instead of treating every returned send as delivered.
This commit is contained in:
committed by
GitHub
parent
5209d6cb05
commit
c97f3207a0
@@ -16,6 +16,7 @@ import {
|
||||
runMessageAction,
|
||||
setMessageActionTestPlugin as setTestPlugin,
|
||||
} from "./message-action-runner.test-helpers.js";
|
||||
import type { MessageSendResult } from "./message.js";
|
||||
|
||||
const requireLabeledRecord = createRequireRecord("record", "expected-label");
|
||||
|
||||
@@ -375,6 +376,127 @@ describe("runMessageAction plugin dispatch", () => {
|
||||
expect(mocks.executeSendAction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each<{
|
||||
name: string;
|
||||
delivery: Partial<MessageSendResult>;
|
||||
outcome: { ok: boolean; error?: string; sentBeforeError?: true };
|
||||
}>([
|
||||
{
|
||||
name: "sent",
|
||||
delivery: { deliveryStatus: "sent" },
|
||||
outcome: { ok: true },
|
||||
},
|
||||
{
|
||||
name: "suppressed",
|
||||
delivery: {
|
||||
deliveryStatus: "suppressed",
|
||||
suppressionReason: "cancelled_by_message_sending_hook",
|
||||
},
|
||||
outcome: {
|
||||
ok: false,
|
||||
error: "Broadcast send suppressed: cancelled_by_message_sending_hook.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failed",
|
||||
delivery: {
|
||||
deliveryStatus: "failed",
|
||||
error: "provider rejected the message",
|
||||
},
|
||||
outcome: { ok: false, error: "provider rejected the message" },
|
||||
},
|
||||
{
|
||||
name: "failed without an error",
|
||||
delivery: { deliveryStatus: "failed" },
|
||||
outcome: { ok: false, error: "Broadcast send failed." },
|
||||
},
|
||||
{
|
||||
name: "partial_failed",
|
||||
delivery: {
|
||||
deliveryStatus: "partial_failed",
|
||||
error: "second payload failed",
|
||||
sentBeforeError: true,
|
||||
},
|
||||
outcome: { ok: false, error: "second payload failed", sentBeforeError: true },
|
||||
},
|
||||
{
|
||||
name: "partial_failed without an error",
|
||||
delivery: { deliveryStatus: "partial_failed", sentBeforeError: true },
|
||||
outcome: {
|
||||
ok: false,
|
||||
error: "Broadcast send partially failed.",
|
||||
sentBeforeError: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "legacy result without deliveryStatus",
|
||||
delivery: {
|
||||
via: "gateway",
|
||||
result: { messageId: "legacy-message-1" },
|
||||
},
|
||||
outcome: { ok: true },
|
||||
},
|
||||
])("derives broadcast truth from a $name send result", async ({ delivery, outcome }) => {
|
||||
const nestedPayload = { ok: true, nested: "payload" };
|
||||
const sendResult = {
|
||||
channel: "gatewaychat",
|
||||
to: "user-123",
|
||||
via: "direct",
|
||||
mediaUrl: null,
|
||||
...delivery,
|
||||
} satisfies MessageSendResult;
|
||||
const gatewayPlugin = createGatewayActionPlugin({
|
||||
pluginId: "gatewaychat",
|
||||
label: "Gateway Chat",
|
||||
blurb: "Gateway Chat delivery truth test plugin.",
|
||||
actions: ["send"],
|
||||
messaging: {
|
||||
targetResolver: {
|
||||
looksLikeId: () => true,
|
||||
},
|
||||
},
|
||||
handleAction: vi.fn(async () => jsonResult({ ok: true })),
|
||||
});
|
||||
setTestPlugin(gatewayPlugin, "gatewaychat");
|
||||
mocks.executeSendAction.mockResolvedValue({
|
||||
handledBy: "core",
|
||||
payload: nestedPayload,
|
||||
sendResult,
|
||||
});
|
||||
|
||||
const result = await runMessageAction({
|
||||
cfg: {
|
||||
channels: {
|
||||
gatewaychat: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
action: "broadcast",
|
||||
params: {
|
||||
channel: "gatewaychat",
|
||||
targets: ["user-123"],
|
||||
message: "hello from broadcast",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("broadcast");
|
||||
if (result.kind !== "broadcast") {
|
||||
throw new Error("expected broadcast result");
|
||||
}
|
||||
expect(result.payload.results).toEqual([
|
||||
{
|
||||
channel: "gatewaychat",
|
||||
to: "user-123",
|
||||
...outcome,
|
||||
payload: nestedPayload,
|
||||
result: sendResult,
|
||||
},
|
||||
]);
|
||||
expect(result.payload.results[0]?.payload).toBe(nestedPayload);
|
||||
expect(result.payload.results[0]?.result).toBe(sendResult);
|
||||
});
|
||||
|
||||
it("preserves partial-delivery evidence from failed broadcast sends", async () => {
|
||||
const gatewayPlugin = createGatewayActionPlugin({
|
||||
pluginId: "gatewaychat",
|
||||
|
||||
@@ -57,6 +57,34 @@ function withSendNormalization(
|
||||
return normalization && result.kind === "send" ? { ...result, normalization } : result;
|
||||
}
|
||||
|
||||
function deriveBroadcastEntryOutcome(
|
||||
sendResult?: MessageSendResult,
|
||||
): { ok: true } | { ok: false; error: string; sentBeforeError?: true } {
|
||||
if (
|
||||
!sendResult ||
|
||||
sendResult.deliveryStatus === undefined ||
|
||||
sendResult.deliveryStatus === "sent"
|
||||
) {
|
||||
return { ok: true };
|
||||
}
|
||||
switch (sendResult.deliveryStatus) {
|
||||
case "suppressed":
|
||||
return {
|
||||
ok: false,
|
||||
error: `Broadcast send suppressed: ${sendResult.suppressionReason ?? "unknown reason"}.`,
|
||||
};
|
||||
case "failed":
|
||||
return { ok: false, error: sendResult.error ?? "Broadcast send failed." };
|
||||
case "partial_failed":
|
||||
return {
|
||||
ok: false,
|
||||
error: sendResult.error ?? "Broadcast send partially failed.",
|
||||
sentBeforeError: true,
|
||||
};
|
||||
}
|
||||
return sendResult.deliveryStatus satisfies never;
|
||||
}
|
||||
|
||||
async function handleBroadcastAction(
|
||||
input: MessageActionInput,
|
||||
params: Record<string, unknown>,
|
||||
@@ -147,7 +175,9 @@ async function handleBroadcastAction(
|
||||
results.push({
|
||||
channel: targetChannel,
|
||||
to: resolved.to,
|
||||
ok: true,
|
||||
...deriveBroadcastEntryOutcome(
|
||||
sendResult.kind === "send" ? sendResult.sendResult : undefined,
|
||||
),
|
||||
payload: sendResult.kind === "send" ? sendResult.payload : undefined,
|
||||
result: sendResult.kind === "send" ? sendResult.sendResult : undefined,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user