

























@@ -83,6 +83,77 @@ function createDurableSendResult(messageIds: string[]) {
8383};
8484}
858586+type DurableSendRequest = {
87+accountId?: string;
88+channel?: string;
89+durability?: string;
90+payloads?: ReplyPayload[];
91+replyToMode?: string;
92+session?: {
93+key?: string;
94+agentId?: string;
95+requesterAccountId?: string;
96+requesterSenderId?: string;
97+conversationType?: string;
98+};
99+threadId?: string | number | null;
100+to?: string;
101+};
102+103+type DurableSupportRequest = {
104+channel?: string;
105+requirements?: Record<string, boolean>;
106+};
107+108+type DeliveryResult = {
109+messageIds?: string[];
110+receipt?: { platformMessageIds?: string[] };
111+visibleReplySent?: boolean;
112+};
113+114+type FinalizeResult = {
115+admission?: unknown;
116+dispatched?: boolean;
117+routeSessionKey?: string;
118+};
119+120+type TurnLogEvent = {
121+event?: string;
122+messageId?: string;
123+stage?: string;
124+};
125+126+function latestDurableSendRequest(): DurableSendRequest {
127+const [request] = sendDurableMessageBatch.mock.calls.at(-1) as unknown as [DurableSendRequest];
128+return request;
129+}
130+131+function latestDurableSupportRequest(): DurableSupportRequest {
132+const [request] = resolveOutboundDurableFinalDeliverySupport.mock.calls.at(-1) as unknown as [
133+DurableSupportRequest,
134+];
135+return request;
136+}
137+138+function deliveryResult(value: unknown): DeliveryResult {
139+return value as DeliveryResult;
140+}
141+142+function finalizeResult(value: unknown): FinalizeResult {
143+return value as FinalizeResult;
144+}
145+146+function loggedEvents(log: ReturnType<typeof vi.fn>): TurnLogEvent[] {
147+return log.mock.calls.map(([event]) => {
148+const entry = event as TurnLogEvent;
149+return {
150+stage: entry.stage,
151+event: entry.event,
152+ ...(entry.messageId === undefined ? {} : { messageId: entry.messageId }),
153+};
154+});
155+}
156+86157describe("channel turn kernel", () => {
87158beforeEach(() => {
88159vi.clearAllMocks();
@@ -117,34 +188,30 @@ describe("channel turn kernel", () => {
117188118189expect(result.dispatched).toBe(true);
119190expect(deliver).not.toHaveBeenCalled();
120-expect(sendDurableMessageBatch).toHaveBeenCalledWith(
121-expect.objectContaining({
122-channel: "telegram",
123-to: "123",
124-accountId: "acct",
125-payloads: [expect.objectContaining({ text: "reply" })],
126-durability: "best_effort",
127-replyToMode: "first",
128-threadId: 777,
129-session: expect.objectContaining({
130-key: "agent:main:test:peer",
131-agentId: "main",
132-requesterAccountId: "acct",
133-requesterSenderId: "sender-1",
134-conversationType: "group",
135-}),
136-}),
137-);
138-expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledWith(
139-expect.objectContaining({
140-channel: "telegram",
141-requirements: {
142-text: true,
143-thread: true,
144-messageSendingHooks: true,
145-},
146-}),
147-);
191+expect(sendDurableMessageBatch).toHaveBeenCalledTimes(1);
192+const sendRequest = latestDurableSendRequest();
193+expect(sendRequest.channel).toBe("telegram");
194+expect(sendRequest.to).toBe("123");
195+expect(sendRequest.accountId).toBe("acct");
196+expect(sendRequest.payloads?.[0]?.text).toBe("reply");
197+expect(sendRequest.durability).toBe("best_effort");
198+expect(sendRequest.replyToMode).toBe("first");
199+expect(sendRequest.threadId).toBe(777);
200+expect(sendRequest.session).toEqual({
201+key: "agent:main:test:peer",
202+agentId: "main",
203+requesterAccountId: "acct",
204+requesterSenderId: "sender-1",
205+conversationType: "group",
206+});
207+expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledTimes(1);
208+const supportRequest = latestDurableSupportRequest();
209+expect(supportRequest.channel).toBe("telegram");
210+expect(supportRequest.requirements).toEqual({
211+text: true,
212+thread: true,
213+messageSendingHooks: true,
214+});
148215});
149216150217it("returns durable delivery result to the buffered dispatcher", async () => {
@@ -176,15 +243,10 @@ describe("channel turn kernel", () => {
176243delivery: { deliver: vi.fn(), durable: { replyToMode: "first" } },
177244});
178245179-expect(deliveredResult).toEqual(
180-expect.objectContaining({
181-messageIds: ["tg-1", "tg-2"],
182-receipt: expect.objectContaining({
183-platformMessageIds: ["tg-1", "tg-2"],
184-}),
185-visibleReplySent: true,
186-}),
187-);
246+const delivered = deliveryResult(deliveredResult);
247+expect(delivered.messageIds).toEqual(["tg-1", "tg-2"]);
248+expect(delivered.receipt?.platformMessageIds).toEqual(["tg-1", "tg-2"]);
249+expect(delivered.visibleReplySent).toBe(true);
188250});
189251190252it("prepares payloads before durable enqueue and observes handled delivery", async () => {
@@ -216,23 +278,18 @@ describe("channel turn kernel", () => {
216278},
217279});
218280219-expect(sendDurableMessageBatch).toHaveBeenCalledWith(
220-expect.objectContaining({
221-payloads: [expect.objectContaining({ text: "reply\n\n_[Generated by test]_" })],
222-}),
223-);
224-expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledWith(
225-expect.objectContaining({
226-requirements: {
227-text: true,
228-},
229-}),
230-);
231-expect(onDelivered).toHaveBeenCalledWith(
232-expect.objectContaining({ text: "reply\n\n_[Generated by test]_" }),
233-{ kind: "final" },
234-expect.objectContaining({ visibleReplySent: true }),
235-);
281+expect(sendDurableMessageBatch).toHaveBeenCalledTimes(1);
282+expect(latestDurableSendRequest().payloads?.[0]?.text).toBe("reply\n\n_[Generated by test]_");
283+expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledTimes(1);
284+expect(latestDurableSupportRequest().requirements).toEqual({
285+text: true,
286+});
287+expect(onDelivered).toHaveBeenCalledTimes(1);
288+const [deliveredPayload, deliveredInfo, deliveredResult] = onDelivered.mock
289+.calls[0] as unknown as [ReplyPayload, unknown, DeliveryResult];
290+expect(deliveredPayload.text).toBe("reply\n\n_[Generated by test]_");
291+expect(deliveredInfo).toEqual({ kind: "final" });
292+expect(deliveredResult.visibleReplySent).toBe(true);
236293});
237294238295it("falls back before queueing when durable outbound delivery is unsupported", async () => {
@@ -268,23 +325,18 @@ describe("channel turn kernel", () => {
268325delivery: { deliver, durable: { replyToMode: "first" } },
269326});
270327271-expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledWith(
272-expect.objectContaining({
273-channel: "telegram",
274-requirements: {
275-text: true,
276-messageSendingHooks: true,
277-},
278-}),
279-);
328+expect(resolveOutboundDurableFinalDeliverySupport).toHaveBeenCalledTimes(1);
329+const supportRequest = latestDurableSupportRequest();
330+expect(supportRequest.channel).toBe("telegram");
331+expect(supportRequest.requirements).toEqual({
332+text: true,
333+messageSendingHooks: true,
334+});
280335expect(deliverOutboundPayloads).not.toHaveBeenCalled();
281336expect(deliver).toHaveBeenCalledWith({ text: "reply" }, { kind: "final" });
282-expect(deliveredResult).toEqual(
283-expect.objectContaining({
284-messageIds: ["legacy-1"],
285-visibleReplySent: true,
286-}),
287-);
337+const delivered = deliveryResult(deliveredResult);
338+expect(delivered.messageIds).toEqual(["legacy-1"]);
339+expect(delivered.visibleReplySent).toBe(true);
288340});
289341290342it("treats durable outbound support preflight failures as terminal", async () => {
@@ -341,12 +393,9 @@ describe("channel turn kernel", () => {
341393},
342394});
343395344-expect(deliveredResult).toEqual(
345-expect.objectContaining({
346-messageIds: ["local-1"],
347-visibleReplySent: true,
348-}),
349-);
396+const delivered = deliveryResult(deliveredResult);
397+expect(delivered.messageIds).toEqual(["local-1"]);
398+expect(delivered.visibleReplySent).toBe(true);
350399});
351400352401it("does not use durable outbound delivery when durable options are omitted", async () => {
@@ -392,11 +441,13 @@ describe("channel turn kernel", () => {
392441});
393442394443expect(deliver).toHaveBeenCalledWith({ text: "reply!" }, { kind: "final" });
395-expect(onDelivered).toHaveBeenCalledWith(
396-{ text: "reply!" },
397-{ kind: "final" },
398-expect.objectContaining({ messageIds: ["local-1"], visibleReplySent: true }),
399-);
444+expect(onDelivered).toHaveBeenCalledTimes(1);
445+const [deliveredPayload, deliveredInfo, deliveredResult] = onDelivered.mock
446+.calls[0] as unknown as [ReplyPayload, unknown, DeliveryResult];
447+expect(deliveredPayload).toEqual({ text: "reply!" });
448+expect(deliveredInfo).toEqual({ kind: "final" });
449+expect(deliveredResult.messageIds).toEqual(["local-1"]);
450+expect(deliveredResult.visibleReplySent).toBe(true);
400451});
401452402453it("assembles channel message reply pipeline options inside the turn kernel", async () => {
@@ -461,12 +512,11 @@ describe("channel turn kernel", () => {
461512expect(result.dispatched).toBe(true);
462513expect(result.dispatchResult?.counts.final).toBe(1);
463514expect(events).toEqual(["record", "dispatch", "deliver"]);
464-expect(recordInboundSession).toHaveBeenCalledWith(
465-expect.objectContaining({
466-sessionKey: "agent:main:test:peer",
467-storePath: "/tmp/sessions.json",
468-}),
469-);
515+expect(recordInboundSession).toHaveBeenCalledTimes(1);
516+const [recordRequest] = (recordInboundSession as unknown as ReturnType<typeof vi.fn>).mock
517+.calls[0] as unknown as [{ sessionKey?: string; storePath?: string }];
518+expect(recordRequest.sessionKey).toBe("agent:main:test:peer");
519+expect(recordRequest.storePath).toBe("/tmp/sessions.json");
470520expect(deliver).toHaveBeenCalledWith({ text: "reply" }, { kind: "final" });
471521});
472522@@ -498,18 +548,12 @@ describe("channel turn kernel", () => {
498548499549expect(events).toEqual(["record", "dispatch"]);
500550expect(result.dispatchResult?.queuedFinal).toBe(true);
501-expect(log).toHaveBeenCalledWith(
502-expect.objectContaining({ stage: "record", event: "start", messageId: "msg-1" }),
503-);
504-expect(log).toHaveBeenCalledWith(
505-expect.objectContaining({ stage: "record", event: "done", messageId: "msg-1" }),
506-);
507-expect(log).toHaveBeenCalledWith(
508-expect.objectContaining({ stage: "dispatch", event: "start", messageId: "msg-1" }),
509-);
510-expect(log).toHaveBeenCalledWith(
511-expect.objectContaining({ stage: "dispatch", event: "done", messageId: "msg-1" }),
512-);
551+expect(loggedEvents(log)).toEqual([
552+{ stage: "record", event: "start", messageId: "msg-1" },
553+{ stage: "record", event: "done", messageId: "msg-1" },
554+{ stage: "dispatch", event: "start", messageId: "msg-1" },
555+{ stage: "dispatch", event: "done", messageId: "msg-1" },
556+]);
513557});
514558515559it("suppresses direct prepared dispatches for observe-only admission", async () => {
@@ -602,7 +646,7 @@ describe("channel turn kernel", () => {
602646expect(events).toEqual(["record", "cleanup"]);
603647expect(runDispatch).not.toHaveBeenCalled();
604648expect(onPreDispatchFailure).toHaveBeenCalledWith(recordError);
605-expect(log).toHaveBeenCalledWith(expect.objectContaining({ stage: "record", event: "error" }));
649+expect(loggedEvents(log)).toContainEqual({ stage: "record", event: "error" });
606650});
607651608652it("normalizes visible dispatch checks", () => {
@@ -720,13 +764,15 @@ describe("channel turn kernel", () => {
720764expect(result.dispatched).toBe(true);
721765expect(events).toEqual(["record", "dispatch"]);
722766expect(deliver).not.toHaveBeenCalled();
723-expect(onFinalize).toHaveBeenCalledWith(
724-expect.objectContaining({
725-admission: { kind: "observeOnly", reason: "broadcast-observer" },
726-dispatched: true,
727-routeSessionKey: "agent:observer:test:peer",
728-}),
729-);
767+expect(onFinalize).toHaveBeenCalledTimes(1);
768+const [finalized] = onFinalize.mock.calls[0] as unknown as [unknown];
769+const finalizedResult = finalizeResult(finalized);
770+expect(finalizedResult.admission).toEqual({
771+kind: "observeOnly",
772+reason: "broadcast-observer",
773+});
774+expect(finalizedResult.dispatched).toBe(true);
775+expect(finalizedResult.routeSessionKey).toBe("agent:observer:test:peer");
730776});
731777732778it("runs custom prepared dispatch from a full turn adapter", async () => {
@@ -797,12 +843,14 @@ describe("channel turn kernel", () => {
797843throw new Error("expected dispatch");
798844}
799845expect(hasFinalChannelTurnDispatch(result.dispatchResult)).toBe(false);
800-expect(onFinalize).toHaveBeenCalledWith(
801-expect.objectContaining({
802-admission: { kind: "observeOnly", reason: "broadcast-observer" },
803-dispatched: true,
804-}),
805-);
846+expect(onFinalize).toHaveBeenCalledTimes(1);
847+const [finalized] = onFinalize.mock.calls[0] as unknown as [unknown];
848+const finalizedResult = finalizeResult(finalized);
849+expect(finalizedResult.admission).toEqual({
850+kind: "observeOnly",
851+reason: "broadcast-observer",
852+});
853+expect(finalizedResult.dispatched).toBe(true);
806854});
807855808856it("finalizes failed dispatches before rethrowing", async () => {
@@ -837,12 +885,11 @@ describe("channel turn kernel", () => {
837885}),
838886).rejects.toThrow(dispatchError);
839887840-expect(onFinalize).toHaveBeenCalledWith(
841-expect.objectContaining({
842-admission: { kind: "dispatch" },
843-dispatched: false,
844-routeSessionKey: "agent:main:test:peer",
845-}),
846-);
888+expect(onFinalize).toHaveBeenCalledTimes(1);
889+const [finalized] = onFinalize.mock.calls[0] as unknown as [unknown];
890+const finalizedResult = finalizeResult(finalized);
891+expect(finalizedResult.admission).toEqual({ kind: "dispatch" });
892+expect(finalizedResult.dispatched).toBe(false);
893+expect(finalizedResult.routeSessionKey).toBe("agent:main:test:peer");
847894});
848895});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。