



























@@ -296,6 +296,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
296296rootId: "om_root_topic",
297297});
298298await options.deliver({ text: "```ts\nconst x = 1\n```" }, { kind: "final" });
299+await options.onIdle?.();
299300300301expect(streamingInstances).toHaveLength(1);
301302expect(streamingInstances[0].start).toHaveBeenCalledTimes(1);
@@ -330,20 +331,17 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
330331});
331332});
332333333-it("delivers distinct final payloads after streaming close", async () => {
334+it("coalesces distinct final payloads into one streaming card until idle", async () => {
334335const { options } = createDispatcherHarness({
335336runtime: createRuntimeLogger(),
336337});
337338await options.deliver({ text: "```md\n完整回复第一段\n```" }, { kind: "final" });
338339await options.deliver({ text: "```md\n完整回复第一段 + 第二段\n```" }, { kind: "final" });
340+await options.onIdle?.();
339341340-expect(streamingInstances).toHaveLength(2);
342+expect(streamingInstances).toHaveLength(1);
341343expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
342-expect(streamingInstances[0].close).toHaveBeenCalledWith("```md\n完整回复第一段\n```", {
343-note: "Agent: agent",
344-});
345-expect(streamingInstances[1].close).toHaveBeenCalledTimes(1);
346-expect(streamingInstances[1].close).toHaveBeenCalledWith(
344+expect(streamingInstances[0].close).toHaveBeenCalledWith(
347345"```md\n完整回复第一段 + 第二段\n```",
348346{
349347note: "Agent: agent",
@@ -358,6 +356,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
358356runtime: createRuntimeLogger(),
359357});
360358await options.deliver({ text: "```md\n同一条回复\n```" }, { kind: "final" });
359+await options.onIdle?.();
361360await options.deliver({ text: "```md\n同一条回复\n```" }, { kind: "final" });
362361363362expect(streamingInstances).toHaveLength(1);
@@ -370,6 +369,17 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
370369});
371370372371it("skips final text already closed by idle streaming", async () => {
372+resolveFeishuAccountMock.mockReturnValue({
373+accountId: "main",
374+appId: "app_id",
375+appSecret: "app_secret",
376+domain: "feishu",
377+config: {
378+renderMode: "card",
379+streaming: true,
380+},
381+});
382+373383const { result, options } = createDispatcherHarness({
374384runtime: createRuntimeLogger(),
375385});
@@ -454,6 +464,33 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
454464});
455465});
456466467+it("skips block payloads that exactly repeat the latest partial snapshot", async () => {
468+resolveFeishuAccountMock.mockReturnValue({
469+accountId: "main",
470+appId: "app_id",
471+appSecret: "app_secret",
472+domain: "feishu",
473+config: {
474+renderMode: "card",
475+streaming: true,
476+},
477+});
478+479+const { result, options } = createDispatcherHarness({
480+runtime: createRuntimeLogger(),
481+});
482+await options.onReplyStart?.();
483+result.replyOptions.onPartialReply?.({ text: "```md\npartial\n```" });
484+await options.deliver({ text: "```md\npartial\n```" }, { kind: "block" });
485+await options.onIdle?.();
486+487+expect(streamingInstances).toHaveLength(1);
488+expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
489+expect(streamingInstances[0].close).toHaveBeenCalledWith("```md\npartial\n```", {
490+note: "Agent: agent",
491+});
492+});
493+457494it("sends media-only payloads as attachments", async () => {
458495const { options } = createDispatcherHarness();
459496await options.deliver({ mediaUrl: "https://example.com/a.png" }, { kind: "final" });
@@ -508,6 +545,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
508545{ text: "```ts\nconst x = 1\n```", mediaUrls: ["https://example.com/a.png"] },
509546{ kind: "final" },
510547);
548+await options.onIdle?.();
511549512550expect(streamingInstances).toHaveLength(1);
513551expect(streamingInstances[0].start).toHaveBeenCalledTimes(1);
@@ -576,6 +614,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
576614result.replyOptions.onPartialReply?.({ text: "answer part" });
577615result.replyOptions.onReasoningEnd?.();
578616await options.deliver({ text: "answer part final" }, { kind: "final" });
617+await options.onIdle?.();
579618580619expect(streamingInstances).toHaveLength(1);
581620const updateCalls = streamingInstances[0].update.mock.calls.map((c: unknown[]) =>
@@ -667,6 +706,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
667706result.replyOptions.onReasoningStream?.({ text: "" });
668707result.replyOptions.onPartialReply?.({ text: "```ts\ncode\n```" });
669708await options.deliver({ text: "```ts\ncode\n```" }, { kind: "final" });
709+await options.onIdle?.();
670710671711expect(streamingInstances).toHaveLength(1);
672712const closeArg = streamingInstances[0].close.mock.calls[0][0] as string;
@@ -684,6 +724,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
684724result.replyOptions.onReasoningStream?.({ text: "Reasoning:\n_thought_" });
685725result.replyOptions.onReasoningEnd?.();
686726await options.deliver({ text: "```ts\nfinal answer\n```" }, { kind: "final" });
727+await options.onIdle?.();
687728688729expect(streamingInstances).toHaveLength(1);
689730expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
@@ -798,6 +839,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
798839// or transient Feishu failures recover without a process restart.
799840nowSpy.mockReturnValue(62_000);
800841await options.deliver({ text: "```ts\nconst z = 3\n```" }, { kind: "final" });
842+await options.onIdle?.();
801843802844expect(streamingInstances).toHaveLength(2);
803845expect(streamingInstances[1].start).toHaveBeenCalled();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。