@@ -63,6 +63,8 @@ vi.mock("./streaming-card.js", () => ({
6363import { createFeishuReplyDispatcher } from "./reply-dispatcher.js" ;
6464
6565describe ( "createFeishuReplyDispatcher streaming behavior" , ( ) => {
66+ type ReplyDispatcherArgs = Parameters < typeof createFeishuReplyDispatcher > [ 0 ] ;
67+
6668 beforeEach ( ( ) => {
6769 vi . clearAllMocks ( ) ;
6870 streamingInstances . length = 0 ;
@@ -128,6 +130,25 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
128130 return createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
129131 }
130132
133+ function createRuntimeLogger ( ) {
134+ return { log : vi . fn ( ) , error : vi . fn ( ) } as never ;
135+ }
136+
137+ function createDispatcherHarness ( overrides : Partial < ReplyDispatcherArgs > = { } ) {
138+ const result = createFeishuReplyDispatcher ( {
139+ cfg : { } as never ,
140+ agentId : "agent" ,
141+ runtime : { } as never ,
142+ chatId : "oc_chat" ,
143+ ...overrides ,
144+ } ) ;
145+
146+ return {
147+ result,
148+ options : createReplyDispatcherWithTypingMock . mock . calls . at ( - 1 ) ?. [ 0 ] ,
149+ } ;
150+ }
151+
131152 it ( "skips typing indicator when account typingIndicator is disabled" , async ( ) => {
132153 resolveFeishuAccountMock . mockReturnValue ( {
133154 accountId : "main" ,
@@ -209,14 +230,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
209230 } ) ;
210231
211232 it ( "keeps auto mode plain text on non-streaming send path" , async ( ) => {
212- createFeishuReplyDispatcher ( {
213- cfg : { } as never ,
214- agentId : "agent" ,
215- runtime : { } as never ,
216- chatId : "oc_chat" ,
217- } ) ;
218-
219- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
233+ const { options } = createDispatcherHarness ( ) ;
220234 await options . deliver ( { text : "plain text" } , { kind : "final" } ) ;
221235
222236 expect ( streamingInstances ) . toHaveLength ( 0 ) ;
@@ -225,14 +239,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
225239 } ) ;
226240
227241 it ( "suppresses internal block payload delivery" , async ( ) => {
228- createFeishuReplyDispatcher ( {
229- cfg : { } as never ,
230- agentId : "agent" ,
231- runtime : { } as never ,
232- chatId : "oc_chat" ,
233- } ) ;
234-
235- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
242+ const { options } = createDispatcherHarness ( ) ;
236243 await options . deliver ( { text : "internal reasoning chunk" } , { kind : "block" } ) ;
237244
238245 expect ( streamingInstances ) . toHaveLength ( 0 ) ;
@@ -253,15 +260,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
253260 } ) ;
254261
255262 it ( "uses streaming session for auto mode markdown payloads" , async ( ) => {
256- createFeishuReplyDispatcher ( {
257- cfg : { } as never ,
258- agentId : "agent" ,
259- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
260- chatId : "oc_chat" ,
263+ const { options } = createDispatcherHarness ( {
264+ runtime : createRuntimeLogger ( ) ,
261265 rootId : "om_root_topic" ,
262266 } ) ;
263-
264- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
265267 await options . deliver ( { text : "```ts\nconst x = 1\n```" } , { kind : "final" } ) ;
266268
267269 expect ( streamingInstances ) . toHaveLength ( 1 ) ;
@@ -277,14 +279,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
277279 } ) ;
278280
279281 it ( "closes streaming with block text when final reply is missing" , async ( ) => {
280- createFeishuReplyDispatcher ( {
281- cfg : { } as never ,
282- agentId : "agent" ,
283- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
284- chatId : "oc_chat" ,
282+ const { options } = createDispatcherHarness ( {
283+ runtime : createRuntimeLogger ( ) ,
285284 } ) ;
286-
287- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
288285 await options . deliver ( { text : "```md\npartial answer\n```" } , { kind : "block" } ) ;
289286 await options . onIdle ?.( ) ;
290287
@@ -295,14 +292,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
295292 } ) ;
296293
297294 it ( "delivers distinct final payloads after streaming close" , async ( ) => {
298- createFeishuReplyDispatcher ( {
299- cfg : { } as never ,
300- agentId : "agent" ,
301- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
302- chatId : "oc_chat" ,
295+ const { options } = createDispatcherHarness ( {
296+ runtime : createRuntimeLogger ( ) ,
303297 } ) ;
304-
305- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
306298 await options . deliver ( { text : "```md\n完整回复第一段\n```" } , { kind : "final" } ) ;
307299 await options . deliver ( { text : "```md\n完整回复第一段 + 第二段\n```" } , { kind : "final" } ) ;
308300
@@ -316,14 +308,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
316308 } ) ;
317309
318310 it ( "skips exact duplicate final text after streaming close" , async ( ) => {
319- createFeishuReplyDispatcher ( {
320- cfg : { } as never ,
321- agentId : "agent" ,
322- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
323- chatId : "oc_chat" ,
311+ const { options } = createDispatcherHarness ( {
312+ runtime : createRuntimeLogger ( ) ,
324313 } ) ;
325-
326- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
327314 await options . deliver ( { text : "```md\n同一条回复\n```" } , { kind : "final" } ) ;
328315 await options . deliver ( { text : "```md\n同一条回复\n```" } , { kind : "final" } ) ;
329316
@@ -383,14 +370,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
383370 } ,
384371 } ) ;
385372
386- const result = createFeishuReplyDispatcher ( {
387- cfg : { } as never ,
388- agentId : "agent" ,
389- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
390- chatId : "oc_chat" ,
373+ const { result, options } = createDispatcherHarness ( {
374+ runtime : createRuntimeLogger ( ) ,
391375 } ) ;
392-
393- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
394376 await options . onReplyStart ?.( ) ;
395377 await result . replyOptions . onPartialReply ?.( { text : "hello" } ) ;
396378 await options . deliver ( { text : "lo world" } , { kind : "block" } ) ;
@@ -402,14 +384,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
402384 } ) ;
403385
404386 it ( "sends media-only payloads as attachments" , async ( ) => {
405- createFeishuReplyDispatcher ( {
406- cfg : { } as never ,
407- agentId : "agent" ,
408- runtime : { } as never ,
409- chatId : "oc_chat" ,
410- } ) ;
411-
412- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
387+ const { options } = createDispatcherHarness ( ) ;
413388 await options . deliver ( { mediaUrl : "https://example.com/a.png" } , { kind : "final" } ) ;
414389
415390 expect ( sendMediaFeishuMock ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -424,14 +399,7 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
424399 } ) ;
425400
426401 it ( "falls back to legacy mediaUrl when mediaUrls is an empty array" , async ( ) => {
427- createFeishuReplyDispatcher ( {
428- cfg : { } as never ,
429- agentId : "agent" ,
430- runtime : { } as never ,
431- chatId : "oc_chat" ,
432- } ) ;
433-
434- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
402+ const { options } = createDispatcherHarness ( ) ;
435403 await options . deliver (
436404 { text : "caption" , mediaUrl : "https://example.com/a.png" , mediaUrls : [ ] } ,
437405 { kind : "final" } ,
@@ -447,14 +415,9 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
447415 } ) ;
448416
449417 it ( "sends attachments after streaming final markdown replies" , async ( ) => {
450- createFeishuReplyDispatcher ( {
451- cfg : { } as never ,
452- agentId : "agent" ,
453- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
454- chatId : "oc_chat" ,
418+ const { options } = createDispatcherHarness ( {
419+ runtime : createRuntimeLogger ( ) ,
455420 } ) ;
456-
457- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
458421 await options . deliver (
459422 { text : "```ts\nconst x = 1\n```" , mediaUrls : [ "https://example.com/a.png" ] } ,
460423 { kind : "final" } ,
@@ -472,16 +435,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
472435 } ) ;
473436
474437 it ( "passes replyInThread to sendMessageFeishu for plain text" , async ( ) => {
475- createFeishuReplyDispatcher ( {
476- cfg : { } as never ,
477- agentId : "agent" ,
478- runtime : { } as never ,
479- chatId : "oc_chat" ,
438+ const { options } = createDispatcherHarness ( {
480439 replyToMessageId : "om_msg" ,
481440 replyInThread : true ,
482441 } ) ;
483-
484- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
485442 await options . deliver ( { text : "plain text" } , { kind : "final" } ) ;
486443
487444 expect ( sendMessageFeishuMock ) . toHaveBeenCalledWith (
@@ -504,16 +461,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
504461 } ,
505462 } ) ;
506463
507- createFeishuReplyDispatcher ( {
508- cfg : { } as never ,
509- agentId : "agent" ,
510- runtime : { } as never ,
511- chatId : "oc_chat" ,
464+ const { options } = createDispatcherHarness ( {
512465 replyToMessageId : "om_msg" ,
513466 replyInThread : true ,
514467 } ) ;
515-
516- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
517468 await options . deliver ( { text : "card text" } , { kind : "final" } ) ;
518469
519470 expect ( sendMarkdownCardFeishuMock ) . toHaveBeenCalledWith (
@@ -525,16 +476,11 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
525476 } ) ;
526477
527478 it ( "passes replyToMessageId and replyInThread to streaming.start()" , async ( ) => {
528- createFeishuReplyDispatcher ( {
529- cfg : { } as never ,
530- agentId : "agent" ,
531- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
532- chatId : "oc_chat" ,
479+ const { options } = createDispatcherHarness ( {
480+ runtime : createRuntimeLogger ( ) ,
533481 replyToMessageId : "om_msg" ,
534482 replyInThread : true ,
535483 } ) ;
536-
537- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
538484 await options . deliver ( { text : "```ts\nconst x = 1\n```" } , { kind : "final" } ) ;
539485
540486 expect ( streamingInstances ) . toHaveLength ( 1 ) ;
@@ -545,18 +491,13 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
545491 } ) ;
546492
547493 it ( "disables streaming for thread replies and keeps reply metadata" , async ( ) => {
548- createFeishuReplyDispatcher ( {
549- cfg : { } as never ,
550- agentId : "agent" ,
551- runtime : { log : vi . fn ( ) , error : vi . fn ( ) } as never ,
552- chatId : "oc_chat" ,
494+ const { options } = createDispatcherHarness ( {
495+ runtime : createRuntimeLogger ( ) ,
553496 replyToMessageId : "om_msg" ,
554497 replyInThread : false ,
555498 threadReply : true ,
556499 rootId : "om_root_topic" ,
557500 } ) ;
558-
559- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
560501 await options . deliver ( { text : "```ts\nconst x = 1\n```" } , { kind : "final" } ) ;
561502
562503 expect ( streamingInstances ) . toHaveLength ( 0 ) ;
@@ -569,16 +510,10 @@ describe("createFeishuReplyDispatcher streaming behavior", () => {
569510 } ) ;
570511
571512 it ( "passes replyInThread to media attachments" , async ( ) => {
572- createFeishuReplyDispatcher ( {
573- cfg : { } as never ,
574- agentId : "agent" ,
575- runtime : { } as never ,
576- chatId : "oc_chat" ,
513+ const { options } = createDispatcherHarness ( {
577514 replyToMessageId : "om_msg" ,
578515 replyInThread : true ,
579516 } ) ;
580-
581- const options = createReplyDispatcherWithTypingMock . mock . calls [ 0 ] ?. [ 0 ] ;
582517 await options . deliver ( { mediaUrl : "https://example.com/a.png" } , { kind : "final" } ) ;
583518
584519 expect ( sendMediaFeishuMock ) . toHaveBeenCalledWith (
0 commit comments