@@ -144,6 +144,8 @@ export interface BrowserDownloadSettings {
144144 pathExists ?: ( path : string ) => boolean | Promise < boolean >
145145 /** Overrides the move of a completed staging file to its final name. */
146146 moveFile ?: ( from : string , to : string ) => Promise < void >
147+ /** Overrides the exclusive creation of a download's destination placeholder. */
148+ claimFile ?: ( path : string ) => Promise < void >
147149}
148150
149151export interface AgentSessionEvents {
@@ -441,6 +443,11 @@ interface ActiveBrowserDownload {
441443 * program choosing a name sees it taken; the completed file replaces it.
442444 */
443445 placeholderPath ?: string
446+ /**
447+ * Set while the placeholder write is in flight. The name stays reserved in process until it
448+ * settles, so teardown cannot hand the name to a newer download that the write then beats.
449+ */
450+ claimingDestination ?: boolean
444451 /** Settles with the final destination, or null when allocation failed. */
445452 destination : Promise < string | null >
446453 /** Set once Electron reports the item done, so a late disk check never resumes or cancels it. */
@@ -716,7 +723,7 @@ function releaseActiveBrowserDownload(active: ActiveBrowserDownload): void {
716723 if ( active . terminal ) return
717724 active . terminal = true
718725 activeBrowserDownloads . delete ( active )
719- releaseActiveBrowserDownloadPath ( active )
726+ if ( ! active . claimingDestination ) releaseActiveBrowserDownloadPath ( active )
720727 // Once Electron reports the item done, the move owns the placeholder until it settles.
721728 if ( ! active . finished ) removeBrowserDownloadPlaceholder ( active )
722729}
@@ -756,12 +763,16 @@ function discardStagedBrowserDownload(active: ActiveBrowserDownload): void {
756763 removeBrowserDownloadPlaceholder ( active )
757764}
758765
766+ function createEmptyFileExclusively ( path : string ) : Promise < void > {
767+ return writeFile ( path , '' , { flag : 'wx' } )
768+ }
769+
759770/** Claims the allocated destination with an empty file; throws if anything already holds it. */
760771async function claimBrowserDownloadDestination (
761772 active : ActiveBrowserDownload ,
762773 savePath : string
763774) : Promise < void > {
764- await writeFile ( savePath , '' , { flag : 'wx' } )
775+ await ( browserDownloadSettings ?. claimFile ?? createEmptyFileExclusively ) ( savePath )
765776 active . placeholderPath = savePath
766777}
767778
@@ -1635,23 +1646,24 @@ function configureBrowserDownloads(ses: Session): void {
16351646 withBrowserScope ( scopeId , persistBrowserSession )
16361647 logger . warn ( message , { error : getErrorMessage ( error ) , filename } )
16371648 }
1638- const stagingPath = join ( directory , `.sim-download- ${ generateShortId ( ) } ` )
1649+ // Paused before the staging path is set, so a failure here leaves no file behind.
16391650 try {
1640- item . setSavePath ( stagingPath )
1651+ item . pause ( )
16411652 } catch ( error ) {
16421653 failDownloadSetup (
1643- 'Stopped: the download destination could not be prepared safely ' ,
1644- 'Could not set the staging destination for an agent browser download ' ,
1654+ 'Stopped: the download could not be paused for a disk-space safety check ' ,
1655+ 'Agent browser download could not be paused for admission ' ,
16451656 error
16461657 )
16471658 return
16481659 }
1660+ const stagingPath = join ( directory , `.sim-download-${ generateShortId ( ) } ` )
16491661 try {
1650- item . pause ( )
1662+ item . setSavePath ( stagingPath )
16511663 } catch ( error ) {
16521664 failDownloadSetup (
1653- 'Stopped: the download could not be paused for a disk-space safety check ' ,
1654- 'Agent browser download could not be paused for admission ' ,
1665+ 'Stopped: the download destination could not be prepared safely ' ,
1666+ 'Could not set the staging destination for an agent browser download ' ,
16551667 error
16561668 )
16571669 return
@@ -1768,11 +1780,17 @@ function configureBrowserDownloads(ses: Session): void {
17681780 publishActiveBrowserDownload ( active )
17691781 return null
17701782 }
1771- await claimBrowserDownloadDestination ( active , savePath )
1772- if ( active . terminal || ! activeBrowserDownloads . has ( active ) ) {
1773- removeBrowserDownloadPlaceholder ( active )
1774- return null
1783+ active . claimingDestination = true
1784+ try {
1785+ await claimBrowserDownloadDestination ( active , savePath )
1786+ } finally {
1787+ active . claimingDestination = false
1788+ if ( active . terminal || ! activeBrowserDownloads . has ( active ) ) {
1789+ removeBrowserDownloadPlaceholder ( active )
1790+ releaseActiveBrowserDownloadPath ( active , savePath )
1791+ }
17751792 }
1793+ if ( active . terminal || ! activeBrowserDownloads . has ( active ) ) return null
17761794 checkBrowserDownloadDiskSpace ( active , 'admission' )
17771795 return savePath
17781796 } )
0 commit comments