






















@@ -122,7 +122,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
122122onReplyStart?: () => Promise<void> | void;
123123onIdle?: () => Promise<void> | void;
124124deliver: (
125-payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; audioAsVoice?: boolean },
125+payload: {
126+text?: string;
127+mediaUrl?: string;
128+mediaUrls?: string[];
129+audioAsVoice?: boolean;
130+isError?: boolean;
131+},
126132meta: { kind: string },
127133) => Promise<void> | void;
128134};
@@ -712,7 +718,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
712718});
713719});
714720715-it("coalesces distinct final payloads into one streaming card until idle", async () => {
721+it("coalesces cumulative final payloads into one streaming card until idle", async () => {
716722const { options } = createDispatcherHarness({
717723runtime: createRuntimeLogger(),
718724});
@@ -732,6 +738,76 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
732738expect(sendMarkdownCardFeishuMock).not.toHaveBeenCalled();
733739});
734740741+it("appends an independent error final without replacing the assistant answer", async () => {
742+const { options } = createDispatcherHarness({
743+runtime: createRuntimeLogger(),
744+});
745+await options.deliver({ text: "The file is ready." }, { kind: "final" });
746+await options.deliver({ text: "⚠️ Exec failed", isError: true }, { kind: "final" });
747+await options.onIdle?.();
748+749+expect(streamingInstances).toHaveLength(1);
750+expect(streamingInstances[0].close).toHaveBeenCalledTimes(1);
751+expect(streamingInstances[0].close).toHaveBeenCalledWith(
752+"The file is ready.\n\n⚠️ Exec failed",
753+{ note: "Agent: agent" },
754+);
755+expect(sendMessageFeishuMock).not.toHaveBeenCalled();
756+expect(sendStructuredCardFeishuMock).not.toHaveBeenCalled();
757+});
758+759+it("does not duplicate the answer from a cumulative error final", async () => {
760+const { options } = createDispatcherHarness({
761+runtime: createRuntimeLogger(),
762+});
763+await options.deliver({ text: "The file is ready." }, { kind: "final" });
764+await options.deliver(
765+{ text: "The file is ready.\n\n⚠️ Exec failed", isError: true },
766+{ kind: "final" },
767+);
768+await options.onIdle?.();
769+770+expect(streamingInstances).toHaveLength(1);
771+expect(streamingInstances[0].close).toHaveBeenCalledWith(
772+"The file is ready.\n\n⚠️ Exec failed",
773+{ note: "Agent: agent" },
774+);
775+});
776+777+it("replaces a partial preview when the first final is an error", async () => {
778+const { result, options } = createDispatcherHarness({
779+runtime: createRuntimeLogger(),
780+});
781+result.replyOptions.onPartialReply?.({ text: "Working on it..." });
782+await options.deliver({ text: "⚠️ Exec failed", isError: true }, { kind: "final" });
783+await options.onIdle?.();
784+785+expect(streamingInstances).toHaveLength(1);
786+expect(streamingInstances[0].close).toHaveBeenCalledWith("⚠️ Exec failed", {
787+note: "Agent: agent",
788+});
789+});
790+791+it("falls back to chunked text when an appended error exceeds the streaming limit", async () => {
792+const runtime = getFeishuRuntimeMock();
793+runtime.channel.text.resolveTextChunkLimit.mockReturnValue(20);
794+795+const { options } = createDispatcherHarness({
796+runtime: createRuntimeLogger(),
797+});
798+await options.deliver({ text: "123456789012345678" }, { kind: "final" });
799+await options.deliver({ text: "⚠️ Exec failed", isError: true }, { kind: "final" });
800+await options.onIdle?.();
801+802+expect(streamingInstances).toHaveLength(1);
803+expect(streamingInstances[0].discard).toHaveBeenCalledTimes(1);
804+expect(streamingInstances[0].close).not.toHaveBeenCalled();
805+expect(sendMessageFeishuMock).toHaveBeenCalledTimes(1);
806+expectLastMockArgFields(sendMessageFeishuMock, "message send params", {
807+text: "123456789012345678\n\n⚠️ Exec failed",
808+});
809+});
810+735811it("skips exact duplicate final text after streaming close", async () => {
736812const { options } = createDispatcherHarness({
737813runtime: createRuntimeLogger(),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。