mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
eb38d5e486
* 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
142 lines
4.9 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|