@@ -18,14 +18,40 @@ const {
1818 mockReleaseExecutionSlot,
1919 mockReplaceLargeValueReferenceKeysWithClient,
2020 mockPreprocessExecution,
21+ mockExecuteWorkflowCore,
22+ mockCleanupExecutionBase64Cache,
23+ mockResetExecutionStreamBuffer,
24+ mockInitializeExecutionStreamMeta,
25+ mockEventWriter,
2126} = vi . hoisted ( ( ) => ( {
2227 mockReleaseExecutionSlot : vi . fn ( ) ,
2328 mockPreprocessExecution : vi . fn ( ) ,
2429 mockReplaceLargeValueReferenceKeysWithClient : vi . fn ( ) ,
30+ mockExecuteWorkflowCore : vi . fn ( ) ,
31+ mockCleanupExecutionBase64Cache : vi . fn ( ) ,
32+ mockResetExecutionStreamBuffer : vi . fn ( ) ,
33+ mockInitializeExecutionStreamMeta : vi . fn ( ) ,
34+ mockEventWriter : { write : vi . fn ( ) , writeTerminal : vi . fn ( ) , close : vi . fn ( ) } ,
2535} ) )
2636
2737vi . mock ( '@/lib/execution/preprocessing' , ( ) => ( { preprocessExecution : mockPreprocessExecution } ) )
2838
39+ vi . mock ( '@/lib/workflows/executor/execution-core' , ( ) => ( {
40+ executeWorkflowCore : mockExecuteWorkflowCore ,
41+ } ) )
42+
43+ vi . mock ( '@/lib/uploads/utils/user-file-base64.server' , ( ) => ( {
44+ cleanupExecutionBase64Cache : mockCleanupExecutionBase64Cache ,
45+ } ) )
46+
47+ vi . mock ( '@/lib/execution/event-buffer' , ( ) => ( {
48+ createExecutionEventWriter : vi . fn ( ( ) => mockEventWriter ) ,
49+ flushExecutionStreamReplayBuffer : vi . fn ( ) ,
50+ initializeExecutionStreamMeta : mockInitializeExecutionStreamMeta ,
51+ markExecutionStreamTerminal : vi . fn ( ) ,
52+ resetExecutionStreamBuffer : mockResetExecutionStreamBuffer ,
53+ } ) )
54+
2955vi . mock ( '@/lib/billing/calculations/usage-reservation' , ( ) => ( {
3056 releaseExecutionSlot : mockReleaseExecutionSlot ,
3157} ) )
@@ -2008,10 +2034,11 @@ describe('repeated human review pauses', () => {
20082034 resetDbChainMock ( )
20092035 } )
20102036
2011- it ( 'keeps the next pause snapshot attached to the log that admission claimed' , async ( ) => {
2012- dbChainMockFns . returning . mockResolvedValueOnce ( [ { id : 'log-1' , deploymentVersionId : null } ] )
2013- const stopAfterSnapshot = new Error ( 'stop after snapshot construction' )
2014- mockPreprocessExecution . mockRejectedValueOnce ( stopAfterSnapshot )
2037+ const runResumeExecution = Reflect . get ( PauseResumeManager , 'runResumeExecution' ) as (
2038+ args : Record < string , unknown >
2039+ ) => Promise < unknown >
2040+
2041+ function createRepeatedReviewResumeArgs ( ) : Record < string , unknown > {
20152042 const seed = createSnapshotSeed ( )
20162043 const snapshot = JSON . parse ( seed . snapshot )
20172044 snapshot . metadata = {
@@ -2023,26 +2050,30 @@ describe('repeated human review pauses', () => {
20232050 }
20242051 snapshot . workflow = { blocks : [ ] , connections : [ ] }
20252052 snapshot . state = { ...createExecutionState ( ) , dagIncomingEdges : { } }
2026- const runResumeExecution = Reflect . get ( PauseResumeManager , 'runResumeExecution' ) as (
2027- args : Record < string , unknown >
2028- ) => Promise < unknown >
2053+ return {
2054+ reservationId : 'reservation-1' ,
2055+ resumeExecutionId : 'attempt-1' ,
2056+ pausedExecution : {
2057+ id : 'pause-1' ,
2058+ workflowId : 'workflow-1' ,
2059+ executionId : 'durable-run' ,
2060+ executionSnapshot : { ...seed , snapshot : JSON . stringify ( snapshot ) } ,
2061+ pausePoints : { hitl_loop0 : { blockId : 'hitl' , pauseKind : 'human' } } ,
2062+ } ,
2063+ contextId : 'hitl_loop0' ,
2064+ resumeInput : { reply : 'partial answer' } ,
2065+ userId : 'user-1' ,
2066+ }
2067+ }
20292068
2030- await expect (
2031- runResumeExecution ( {
2032- reservationId : 'reservation-1' ,
2033- resumeExecutionId : 'attempt-1' ,
2034- pausedExecution : {
2035- id : 'pause-1' ,
2036- workflowId : 'workflow-1' ,
2037- executionId : 'durable-run' ,
2038- executionSnapshot : { ...seed , snapshot : JSON . stringify ( snapshot ) } ,
2039- pausePoints : { hitl_loop0 : { blockId : 'hitl' , pauseKind : 'human' } } ,
2040- } ,
2041- contextId : 'hitl_loop0' ,
2042- resumeInput : { reply : 'partial answer' } ,
2043- userId : 'user-1' ,
2044- } )
2045- ) . rejects . toBe ( stopAfterSnapshot )
2069+ it ( 'keeps the next pause snapshot attached to the log that admission claimed' , async ( ) => {
2070+ dbChainMockFns . returning . mockResolvedValueOnce ( [ { id : 'log-1' , deploymentVersionId : null } ] )
2071+ const stopAfterSnapshot = new Error ( 'stop after snapshot construction' )
2072+ mockPreprocessExecution . mockRejectedValueOnce ( stopAfterSnapshot )
2073+
2074+ await expect ( runResumeExecution ( createRepeatedReviewResumeArgs ( ) ) ) . rejects . toBe (
2075+ stopAfterSnapshot
2076+ )
20462077
20472078 expect ( humanInTheLoopLogger . info ) . toHaveBeenCalledWith (
20482079 'Created resume snapshot' ,
@@ -2060,6 +2091,37 @@ describe('repeated human review pauses', () => {
20602091 ) . toBe ( true )
20612092 } )
20622093
2094+ it ( 'cleans the base64 cache under the durable run that the resumed blocks wrote to' , async ( ) => {
2095+ dbChainMockFns . returning . mockResolvedValueOnce ( [ { id : 'log-1' , deploymentVersionId : null } ] )
2096+ mockPreprocessExecution . mockResolvedValueOnce ( {
2097+ success : true ,
2098+ actorUserId : 'user-1' ,
2099+ executionTimeout : { async : 60_000 } ,
2100+ } )
2101+ mockResetExecutionStreamBuffer . mockResolvedValueOnce ( true )
2102+ mockInitializeExecutionStreamMeta . mockResolvedValueOnce ( true )
2103+ mockEventWriter . write . mockResolvedValue ( { eventId : 1 } )
2104+ mockEventWriter . writeTerminal . mockResolvedValue ( { eventId : 2 } )
2105+ mockEventWriter . close . mockResolvedValue ( undefined )
2106+ mockExecuteWorkflowCore . mockResolvedValueOnce ( {
2107+ success : true ,
2108+ status : 'completed' ,
2109+ output : { } ,
2110+ logs : [ ] ,
2111+ metadata : { executionId : 'durable-run' } ,
2112+ } )
2113+
2114+ await expect ( runResumeExecution ( createRepeatedReviewResumeArgs ( ) ) ) . resolves . toMatchObject ( {
2115+ status : 'completed' ,
2116+ } )
2117+
2118+ expect ( mockExecuteWorkflowCore ) . toHaveBeenCalledWith (
2119+ expect . objectContaining ( { includeFileBase64 : true } )
2120+ )
2121+ expect ( mockCleanupExecutionBase64Cache ) . toHaveBeenCalledTimes ( 1 )
2122+ expect ( mockCleanupExecutionBase64Cache ) . toHaveBeenCalledWith ( 'durable-run' )
2123+ } )
2124+
20632125 it ( 'settles the answered context exactly once when the same run pauses again' , async ( ) => {
20642126 const runSpy = vi
20652127 . spyOn ( PauseResumeManager as unknown as PauseResumeManagerInternals , 'runResumeExecution' )
0 commit comments