Managing attacks is a bit complex but don’t worry, let’s break it down:
As you inferred, whenever you request an attack, the first position and destination parameters given are the ones that will be used throughout the rest of the attack.
This behavior is intentional by design so your attacks can whiff:

However you still have some quick tools to overwrite this:

When you overwrite position, you are telling the attack to ignore any given positions and to use its own physical Node position instead, and so you can dictate your final result just by modifying its position:
func _process(_delta: float) -> void:
...
# -> Like this!
projectile_manager.global_position = end_of_gun.global_position
if charging and Input.is_action_pressed("mb_left"):
projectile_manager.request_execution(0, 0, end_of_gun.global_position, get_global_mouse_position())
...

In case you need more control or you don’t want to use the overrides, you will have to modify the behavior of your attacks:
func _ready() -> void:
# -> Here we set the methods themselves on the attack
projectile_manager.get_attack(0).set_on_charge_exit(on_charge_exit).set_on_main_enter(on_main_enter)
func on_charge_exit(attack: Attack2D) -> void:
# -> Here we are setting the final destination *only* when the charge is finished.
# -> "pi" refers to *PackedInfo*, a helper class that stores all the information about how a projectile is initialized.
attack.pi.destination = get_global_mouse_position()
attack.charge_exit()
func on_main_enter(attack: Attack2D) -> void:
# -> Here we are setting the projectile position, just before spawning.
attack.pi.position = end_of_gun.global_position
attack.main_enter()

All of this will be explained in the Documentation, again sorry it is not finished yet but I hope this clarification will help you in your project!
I’m here if you need more help. Regards.