@@ -157,6 +157,64 @@ describe('LoggingSession completion retries', () => {
157157 expect ( session . hasCompleted ( ) ) . toBe ( true )
158158 } )
159159
160+ it ( 'preserves successful final output during fallback completion' , async ( ) => {
161+ const session = new LoggingSession ( 'workflow-1' , 'execution-5' , 'api' , 'req-1' )
162+
163+ completeWorkflowExecutionMock
164+ . mockRejectedValueOnce ( new Error ( 'success finalize failed' ) )
165+ . mockResolvedValueOnce ( { } )
166+
167+ await expect (
168+ session . safeComplete ( { finalOutput : { ok : true , stage : 'done' } } )
169+ ) . resolves . toBeUndefined ( )
170+
171+ expect ( completeWorkflowExecutionMock ) . toHaveBeenLastCalledWith (
172+ expect . objectContaining ( {
173+ executionId : 'execution-5' ,
174+ finalOutput : { ok : true , stage : 'done' } ,
175+ finalizationPath : 'fallback_completed' ,
176+ } )
177+ )
178+ } )
179+
180+ it ( 'preserves accumulated cost during fallback completion' , async ( ) => {
181+ const session = new LoggingSession ( 'workflow-1' , 'execution-6' , 'api' , 'req-1' ) as any
182+
183+ session . accumulatedCost = {
184+ total : 12 ,
185+ input : 5 ,
186+ output : 7 ,
187+ tokens : { input : 11 , output : 13 , total : 24 } ,
188+ models : {
189+ 'test-model' : {
190+ input : 5 ,
191+ output : 7 ,
192+ total : 12 ,
193+ tokens : { input : 11 , output : 13 , total : 24 } ,
194+ } ,
195+ } ,
196+ }
197+ session . costFlushed = true
198+
199+ completeWorkflowExecutionMock
200+ . mockRejectedValueOnce ( new Error ( 'success finalize failed' ) )
201+ . mockResolvedValueOnce ( { } )
202+
203+ await expect ( session . safeComplete ( { finalOutput : { ok : true } } ) ) . resolves . toBeUndefined ( )
204+
205+ expect ( completeWorkflowExecutionMock ) . toHaveBeenLastCalledWith (
206+ expect . objectContaining ( {
207+ executionId : 'execution-6' ,
208+ costSummary : expect . objectContaining ( {
209+ totalCost : 12 ,
210+ totalInputCost : 5 ,
211+ totalOutputCost : 7 ,
212+ totalTokens : 24 ,
213+ } ) ,
214+ } )
215+ )
216+ } )
217+
160218 it ( 'persists failed error semantics when completeWithError receives non-error trace spans' , async ( ) => {
161219 const session = new LoggingSession ( 'workflow-1' , 'execution-4' , 'api' , 'req-1' )
162220 const traceSpans = [
@@ -294,6 +352,75 @@ describe('LoggingSession completion retries', () => {
294352 expect ( session . complete ) . toHaveBeenCalledTimes ( 1 )
295353 } )
296354
355+ it ( 'drains fire-and-forget cost flushes before terminal completion' , async ( ) => {
356+ let releaseFlush : ( ( ) => void ) | undefined
357+ const flushPromise = new Promise < void > ( ( resolve ) => {
358+ releaseFlush = resolve
359+ } )
360+
361+ const session = new LoggingSession ( 'workflow-1' , 'execution-1' , 'api' , 'req-1' ) as any
362+ session . flushAccumulatedCost = vi . fn ( ( ) => flushPromise )
363+ session . complete = vi . fn ( ) . mockResolvedValue ( undefined )
364+
365+ await session . onBlockComplete ( 'block-2' , 'Transform' , 'function' , {
366+ endedAt : '2025-01-01T00:00:01.000Z' ,
367+ output : { value : true } ,
368+ cost : { total : 1 , input : 1 , output : 0 } ,
369+ tokens : { input : 1 , output : 0 , total : 1 } ,
370+ model : 'test-model' ,
371+ } )
372+
373+ const completionPromise = session . safeComplete ( { finalOutput : { ok : true } } )
374+
375+ await Promise . resolve ( )
376+
377+ expect ( session . complete ) . not . toHaveBeenCalled ( )
378+
379+ releaseFlush ?.( )
380+
381+ await completionPromise
382+
383+ expect ( session . flushAccumulatedCost ) . toHaveBeenCalledTimes ( 1 )
384+ expect ( session . complete ) . toHaveBeenCalledTimes ( 1 )
385+ } )
386+
387+ it ( 'keeps draining when new progress writes arrive during drain' , async ( ) => {
388+ let releaseFirst : ( ( ) => void ) | undefined
389+ let releaseSecond : ( ( ) => void ) | undefined
390+ const firstPromise = new Promise < void > ( ( resolve ) => {
391+ releaseFirst = resolve
392+ } )
393+ const secondPromise = new Promise < void > ( ( resolve ) => {
394+ releaseSecond = resolve
395+ } )
396+
397+ const session = new LoggingSession ( 'workflow-1' , 'execution-1' , 'api' , 'req-1' ) as any
398+
399+ void session . trackProgressWrite ( firstPromise )
400+
401+ const drainPromise = session . drainPendingProgressWrites ( )
402+
403+ await Promise . resolve ( )
404+
405+ void session . trackProgressWrite ( secondPromise )
406+ releaseFirst ?.( )
407+
408+ await Promise . resolve ( )
409+
410+ let drained = false
411+ void drainPromise . then ( ( ) => {
412+ drained = true
413+ } )
414+
415+ await Promise . resolve ( )
416+ expect ( drained ) . toBe ( false )
417+
418+ releaseSecond ?.( )
419+ await drainPromise
420+
421+ expect ( session . pendingProgressWrites . size ) . toBe ( 0 )
422+ } )
423+
297424 it ( 'marks pause completion as terminal and prevents duplicate pause finalization' , async ( ) => {
298425 const session = new LoggingSession ( 'workflow-1' , 'execution-1' , 'api' , 'req-1' ) as any
299426 session . completeExecutionWithFinalization = vi . fn ( ) . mockResolvedValue ( undefined )
0 commit comments