Files
openclaw/src/plugin-sdk/channel-message.test.ts
T
Peter Steinberger eb38d5e486 fix(reply): report settled delivery outcomes once (#124773)
* refactor(reply): publish settled dispatch receipts

* refactor(reply): isolate dispatch outcome accounting

* refactor(discord): infer settled dispatch result

* test(reply): model settled dispatch receipts

* test(reply): type settled receipt fixtures

* test(telegram): declare settled final receipt

* test(telegram): adapt legacy dispatch fixtures

* fix(reply): complete settled receipt compatibility

* test(channels): keep delivery suite within lint budget

* test(channels): reuse settled count fixture

* test(channels): consolidate receipt assertions

* test(channels): extract delivery receipt fixtures

* fix(qa): recover stopped clients after config restart

* fix(reply): settle deferred delivery receipts

* test(channels): share delivery turn fixtures

* fix(channels): preserve non-visible reconciliation

* fix(reply): preserve settled receipt compatibility

* refactor(reply): make settled receipts canonical

* refactor(reply): absorb settlement at dispatcher boundary

* test(matrix): prove settled receipt precedence

* refactor(sessions): extract prompt snapshot types

* fix(reply): restore queued admission counts
2026-08-17 06:38:16 -07:00

142 lines
4.9 KiB
TypeScript

/**
* Tests channel message helper behavior and mocked runtime interactions.
*/
import { beforeAll, describe, expect, it, vi } from "vitest";
import { defineChannelMessageAdapter as defineCoreChannelMessageAdapter } from "../channels/message/adapter.js";
import {
defineChannelMessageAdapter,
type ChannelMessageDurableFinalAdapter,
} from "./channel-outbound.js";
describe("defineChannelMessageAdapter", () => {
const loadPluginSdkSubpaths = async () =>
await Promise.all([
import("openclaw/plugin-sdk/channel-outbound"),
import("openclaw/plugin-sdk/channel-message"),
import("openclaw/plugin-sdk/channel-inbound"),
import("openclaw/plugin-sdk/channel-reply-pipeline"),
] as const);
let pluginSdkSubpaths: Awaited<ReturnType<typeof loadPluginSdkSubpaths>>;
beforeAll(async () => {
pluginSdkSubpaths = await loadPluginSdkSubpaths();
});
it("keeps channel plugin SDK subpaths aligned", async () => {
const [channelOutbound, channelMessage, , channelReplyPipeline] = pluginSdkSubpaths;
expect(channelOutbound.createChannelMessageReplyPipeline).toBe(
channelReplyPipeline.createChannelReplyPipeline,
);
expect(channelMessage.createChannelMessageReplyPipeline).toBe(
channelOutbound.createChannelMessageReplyPipeline,
);
expect(channelMessage.createReplyPrefixOptions).toBe(
channelReplyPipeline.createReplyPrefixOptions,
);
expect(channelMessage.createTypingCallbacks).toBe(channelReplyPipeline.createTypingCallbacks);
expect(channelOutbound.defineChannelMessageAdapter).toBe(defineCoreChannelMessageAdapter);
});
it("preserves legacy count-shaped dispatch projections", () => {
const [, channelMessage, channelInbound] = pluginSdkSubpaths;
const legacyResult = {
queuedFinal: false,
counts: { tool: 1, block: 2, final: 1 },
};
const facades = [
{
counts: channelMessage.resolveChannelTurnDispatchCounts,
visible: channelMessage.hasVisibleChannelTurnDispatch,
final: channelMessage.hasFinalChannelTurnDispatch,
},
{
counts: channelInbound.resolveInboundReplyDispatchCounts,
visible: channelInbound.hasVisibleInboundReplyDispatch,
final: channelInbound.hasFinalInboundReplyDispatch,
},
];
for (const facade of facades) {
expect(facade.counts(legacyResult)).toEqual({ tool: 1, block: 2, final: 1 });
expect(facade.visible(legacyResult)).toBe(true);
expect(facade.final(legacyResult)).toBe(true);
const receiptResult = {
...legacyResult,
queuedFinal: true,
settledReceipt: { anyVisibleDelivered: false, counts: {} },
};
expect(facade.counts(receiptResult)).toEqual({ tool: 0, block: 0, final: 0 });
expect(facade.visible(receiptResult)).toBe(false);
expect(facade.final(receiptResult)).toBe(false);
}
});
it("defaults new message adapters to plugin-owned receive acknowledgement", () => {
const adapter = defineChannelMessageAdapter({
id: "demo",
durableFinal: { capabilities: { text: true } },
send: {
text: vi.fn(async () => ({
receipt: {
primaryPlatformMessageId: "msg-1",
platformMessageIds: ["msg-1"],
parts: [],
sentAt: 123,
},
})),
},
});
expect(adapter.receive).toEqual({
defaultAckPolicy: "manual",
supportedAckPolicies: ["manual"],
});
});
it("preserves explicit receive acknowledgement policy declarations", () => {
const adapter = defineChannelMessageAdapter({
id: "demo",
receive: {
defaultAckPolicy: "after_agent_dispatch",
supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"],
},
});
expect(adapter.receive).toEqual({
defaultAckPolicy: "after_agent_dispatch",
supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"],
});
});
it("exposes the synchronous deferred-delivery admission contract", () => {
const admitDeferredDelivery = vi.fn<
NonNullable<ChannelMessageDurableFinalAdapter["admitDeferredDelivery"]>
>((ctx) =>
ctx.phase === "recovery"
? { status: "permanent_rejection", reason: "account no longer supports replay" }
: { status: "allowed" },
);
const adapter = defineChannelMessageAdapter({
id: "demo",
durableFinal: { admitDeferredDelivery },
});
const context = {
cfg: {},
channel: "demo",
to: "conversation-1",
accountId: "workspace-1",
} as Parameters<typeof admitDeferredDelivery>[0];
expect(adapter.durableFinal?.admitDeferredDelivery?.({ ...context, phase: "live" })).toEqual({
status: "allowed",
});
expect(
adapter.durableFinal?.admitDeferredDelivery?.({ ...context, phase: "recovery" }),
).toEqual({
status: "permanent_rejection",
reason: "account no longer supports replay",
});
});
});