






















@@ -19,6 +19,7 @@ const { mockCreateFeishuReplyDispatcher, mockCreateFeishuClient, mockResolveAgen
1919},
2020replyOptions: {},
2121markDispatchIdle: vi.fn(),
22+ensureNoVisibleReplyFallback: vi.fn(),
2223})),
2324mockCreateFeishuClient: vi.fn(),
2425mockResolveAgentRoute: vi.fn(),
@@ -227,6 +228,20 @@ describe("broadcast dispatch", () => {
227228lastRoutePolicy: "session",
228229matchedBy: "default",
229230});
231+mockCreateFeishuReplyDispatcher.mockReturnValue({
232+dispatcher: {
233+sendToolResult: vi.fn(),
234+sendBlockReply: vi.fn(),
235+sendFinalReply: vi.fn(),
236+waitForIdle: vi.fn(),
237+getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
238+getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
239+markComplete: vi.fn(),
240+},
241+replyOptions: {},
242+markDispatchIdle: vi.fn(),
243+ensureNoVisibleReplyFallback: vi.fn(),
244+});
230245mockCreateFeishuClient.mockReturnValue({
231246contact: {
232247user: {
@@ -329,6 +344,130 @@ describe("broadcast dispatch", () => {
329344expect(dispatcherParams?.agentId).toBe("main");
330345});
331346347+it("sends no-visible-reply fallback for active broadcast zero-final dispatch", async () => {
348+mockDispatchReplyFromConfig
349+.mockResolvedValueOnce({ queuedFinal: false, counts: { final: 1 } })
350+.mockResolvedValueOnce({
351+queuedFinal: false,
352+counts: { final: 0 },
353+noVisibleReplyFallbackEligible: true,
354+});
355+const ensureNoVisibleReplyFallback = vi.fn();
356+mockCreateFeishuReplyDispatcher.mockReturnValueOnce({
357+dispatcher: {
358+sendToolResult: vi.fn(),
359+sendBlockReply: vi.fn(),
360+sendFinalReply: vi.fn(),
361+waitForIdle: vi.fn(),
362+getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
363+getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
364+markComplete: vi.fn(),
365+},
366+replyOptions: {},
367+markDispatchIdle: vi.fn(),
368+ ensureNoVisibleReplyFallback,
369+});
370+const cfg = createBroadcastConfig();
371+const event = createBroadcastEvent({
372+messageId: "msg-broadcast-zero-final",
373+text: "hello @bot",
374+botMentioned: true,
375+});
376+377+await handleFeishuMessage({
378+ cfg,
379+ event,
380+botOpenId: "bot-open-id",
381+runtime: createRuntimeEnv(),
382+});
383+384+expect(ensureNoVisibleReplyFallback).toHaveBeenCalledWith(
385+"broadcast-dispatch-complete-no-visible-reply",
386+);
387+});
388+389+it("sends no-visible-reply fallback for active broadcast failed final delivery", async () => {
390+mockDispatchReplyFromConfig
391+.mockResolvedValueOnce({ queuedFinal: false, counts: { final: 1 } })
392+.mockResolvedValueOnce({
393+queuedFinal: true,
394+counts: { final: 1 },
395+});
396+const ensureNoVisibleReplyFallback = vi.fn();
397+mockCreateFeishuReplyDispatcher.mockReturnValueOnce({
398+dispatcher: {
399+sendToolResult: vi.fn(),
400+sendBlockReply: vi.fn(),
401+sendFinalReply: vi.fn(),
402+waitForIdle: vi.fn(),
403+getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
404+getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 1 })),
405+markComplete: vi.fn(),
406+},
407+replyOptions: {},
408+markDispatchIdle: vi.fn(),
409+ ensureNoVisibleReplyFallback,
410+});
411+const cfg = createBroadcastConfig();
412+const event = createBroadcastEvent({
413+messageId: "msg-broadcast-final-failed",
414+text: "hello @bot",
415+botMentioned: true,
416+});
417+418+await handleFeishuMessage({
419+ cfg,
420+ event,
421+botOpenId: "bot-open-id",
422+runtime: createRuntimeEnv(),
423+});
424+425+expect(ensureNoVisibleReplyFallback).toHaveBeenCalledWith(
426+"broadcast-dispatch-complete-no-visible-reply",
427+);
428+});
429+430+it("skips no-visible-reply fallback for source-suppressed active broadcast dispatch", async () => {
431+mockDispatchReplyFromConfig
432+.mockResolvedValueOnce({ queuedFinal: false, counts: { final: 1 } })
433+.mockResolvedValueOnce({
434+queuedFinal: false,
435+counts: { final: 0 },
436+sourceReplyDeliveryMode: "message_tool_only",
437+noVisibleReplyFallbackEligible: true,
438+});
439+const ensureNoVisibleReplyFallback = vi.fn();
440+mockCreateFeishuReplyDispatcher.mockReturnValueOnce({
441+dispatcher: {
442+sendToolResult: vi.fn(),
443+sendBlockReply: vi.fn(),
444+sendFinalReply: vi.fn(),
445+waitForIdle: vi.fn(),
446+getQueuedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
447+getFailedCounts: vi.fn(() => ({ tool: 0, block: 0, final: 0 })),
448+markComplete: vi.fn(),
449+},
450+replyOptions: {},
451+markDispatchIdle: vi.fn(),
452+ ensureNoVisibleReplyFallback,
453+});
454+const cfg = createBroadcastConfig();
455+const event = createBroadcastEvent({
456+messageId: "msg-broadcast-source-suppressed",
457+text: "hello @bot",
458+botMentioned: true,
459+});
460+461+await handleFeishuMessage({
462+ cfg,
463+ event,
464+botOpenId: "bot-open-id",
465+runtime: createRuntimeEnv(),
466+});
467+468+expect(ensureNoVisibleReplyFallback).not.toHaveBeenCalled();
469+});
470+332471it("skips broadcast dispatch when bot is NOT mentioned (requireMention=true)", async () => {
333472const cfg = createBroadcastConfig();
334473const event = createBroadcastEvent({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。