@@ -37,6 +37,11 @@ function parseArgs(argv) {
3737 . split ( ',' )
3838 . map ( ( item ) => item . trim ( ) )
3939 . filter ( Boolean ) ,
40+ closeBlockerProcesses : ( process . env . SEO_DUNGEON_DESKTOP_PROOF_CLOSE_BLOCKER_PROCESSES || '' )
41+ . split ( ',' )
42+ . map ( ( item ) => item . trim ( ) )
43+ . filter ( Boolean ) ,
44+ allowFallbackProject : process . env . SEO_DUNGEON_DESKTOP_PROOF_ALLOW_FALLBACK_PROJECT === '1' ,
4045 allowForegroundMismatch : process . env . SEO_DUNGEON_DESKTOP_PROOF_ALLOW_FOREGROUND_MISMATCH === '1' ,
4146 } ;
4247
@@ -71,6 +76,13 @@ function parseArgs(argv) {
7176 if ( ! processName ) throw new Error ( '--blocker-process cannot be empty.' ) ;
7277 options . blockerProcesses . push ( processName ) ;
7378 }
79+ else if ( token === '--close-blocker-process' ) {
80+ const processName = readValue ( ) . trim ( ) ;
81+ if ( ! processName ) throw new Error ( '--close-blocker-process cannot be empty.' ) ;
82+ options . closeBlockerProcesses . push ( processName ) ;
83+ options . blockerProcesses . push ( processName ) ;
84+ }
85+ else if ( token === '--allow-fallback-project' ) options . allowFallbackProject = true ;
7486 else if ( token === '--allow-foreground-mismatch' ) options . allowForegroundMismatch = true ;
7587 else if ( token === '--help' || token === '-h' ) options . help = true ;
7688 else throw new Error ( `Unknown option: ${ token } ` ) ;
@@ -102,7 +114,8 @@ function usage() {
102114 ' node scripts/record-desktop-proof.mjs [--output-dir path] [--domain seodungeon.com] [--project E:\\seo-dungeon-website]' ,
103115 ' [--browser-x 960] [--browser-y 0] [--browser-width 960] [--browser-height 1040] [--fake-codex|--real-codex]' ,
104116 ' [--browser-command "..."] [--codex-command "..."] [--command-timeout-ms 120000]' ,
105- ' [--minimize-known-blockers] [--hide-known-blockers] [--blocker-process CapCut] [--allow-foreground-mismatch]' ,
117+ ' [--minimize-known-blockers] [--hide-known-blockers] [--blocker-process CapCut] [--close-blocker-process Taskmgr]' ,
118+ ' [--allow-fallback-project] [--allow-foreground-mismatch]' ,
106119 '' ,
107120 'Records a full-desktop RC-008 rehearsal with SEO Dungeon in a headed browser window.' ,
108121 'This does not automate the Codex desktop UI. Put Codex on the left before running for side-by-side proof framing.' ,
@@ -480,9 +493,31 @@ if ($pids.Count -gt 0) {
480493 return result . stdout . trim ( ) . split ( / \r ? \n / ) . filter ( Boolean ) ;
481494}
482495
483- async function assertBrowserForeground ( options , logOutput = [ ] ) {
496+ async function closeBlockerProcess ( processName , pid , logOutput = [ ] ) {
497+ const safeProcessName = String ( processName || '' ) . replace ( / ' / g, "''" ) ;
498+ const safePid = Number ( pid ) ;
499+ if ( ! safeProcessName || ! Number . isInteger ( safePid ) || safePid <= 0 ) {
500+ throw new Error ( `Invalid blocker process close request: ${ processName } :${ pid } ` ) ;
501+ }
502+ const script = `
503+ $p = Get-Process -Id ${ safePid } -ErrorAction Stop
504+ if ($p.ProcessName -ne '${ safeProcessName } ') {
505+ Write-Error "PID ${ safePid } is $($p.ProcessName), not ${ safeProcessName } "
506+ exit 3
507+ }
508+ Stop-Process -Id ${ safePid } -Force
509+ Write-Output "$($p.ProcessName):$($p.Id)"
510+ ` ;
511+ const result = await runTool ( 'powershell' , [ '-NoProfile' , '-Command' , script ] , 8000 ) ;
512+ logOutput . push ( `\n[close-blocker-process] code=${ result . code } \n${ result . stdout } ${ result . stderr } \n` ) ;
513+ if ( result . code !== 0 ) throw new Error ( `Could not close blocker process:\n${ result . stderr || result . stdout } ` ) ;
514+ return result . stdout . trim ( ) . split ( / \r ? \n / ) . filter ( Boolean ) ;
515+ }
516+
517+ async function assertBrowserForeground ( options , logOutput = [ ] , blockerActions = [ ] ) {
484518 if ( options . minimizeKnownBlockers ) {
485- await minimizeKnownBlockers ( logOutput , options . hideKnownBlockers , options . blockerProcesses ) ;
519+ blockerActions . push ( ...( await minimizeKnownBlockers ( logOutput , options . hideKnownBlockers , options . blockerProcesses ) )
520+ . map ( ( target ) => ( { action : options . hideKnownBlockers ? 'hide' : 'minimize' , target } ) ) ) ;
486521 }
487522 await focusBrowserWindowByTitle ( 'SEO Dungeon Desktop Proof' , logOutput ) ;
488523 await new Promise ( ( resolve ) => setTimeout ( resolve , 350 ) ) ;
@@ -492,7 +527,18 @@ async function assertBrowserForeground(options, logOutput = []) {
492527 foregroundInfo . processName &&
493528 options . blockerProcesses . some ( ( name ) => name . toLowerCase ( ) === foregroundInfo . processName . toLowerCase ( ) )
494529 ) {
495- await minimizeKnownBlockers ( logOutput , options . hideKnownBlockers , options . blockerProcesses ) ;
530+ blockerActions . push ( ...( await minimizeKnownBlockers ( logOutput , options . hideKnownBlockers , options . blockerProcesses ) )
531+ . map ( ( target ) => ( { action : options . hideKnownBlockers ? 'hide' : 'minimize' , target } ) ) ) ;
532+ await focusBrowserWindowByTitle ( 'SEO Dungeon Desktop Proof' , logOutput ) ;
533+ await new Promise ( ( resolve ) => setTimeout ( resolve , 350 ) ) ;
534+ foregroundInfo = await getForegroundWindowInfo ( logOutput ) ;
535+ }
536+ if (
537+ foregroundInfo . processName &&
538+ options . closeBlockerProcesses . some ( ( name ) => name . toLowerCase ( ) === foregroundInfo . processName . toLowerCase ( ) )
539+ ) {
540+ blockerActions . push ( ...( await closeBlockerProcess ( foregroundInfo . processName , foregroundInfo . pid , logOutput ) )
541+ . map ( ( target ) => ( { action : 'close' , target } ) ) ) ;
496542 await focusBrowserWindowByTitle ( 'SEO Dungeon Desktop Proof' , logOutput ) ;
497543 await new Promise ( ( resolve ) => setTimeout ( resolve , 350 ) ) ;
498544 foregroundInfo = await getForegroundWindowInfo ( logOutput ) ;
@@ -579,15 +625,24 @@ async function main() {
579625 let vite ;
580626 let recorder ;
581627 let foregroundBeforeCapture = '' ;
628+ let bridgeHealth = null ;
629+ let watchedEvent = null ;
630+ let sendJson = null ;
631+ const blockerActions = [ ] ;
582632 let failureError = null ;
583633
584634 fs . mkdirSync ( outputDir , { recursive : true } ) ;
585635 fs . mkdirSync ( fallbackProject , { recursive : true } ) ;
586636 fs . writeFileSync ( path . join ( fallbackProject , 'README.md' ) , '# SEO Dungeon Desktop Proof Fallback Project\n' , 'utf8' ) ;
587637 writeFakeCodexAppServer ( fakeCodexAppServer , options ) ;
588638
589- const projectPath = fs . existsSync ( options . projectPath )
590- ? path . resolve ( options . projectPath )
639+ const requestedProjectPath = path . resolve ( options . projectPath ) ;
640+ const projectPathExists = fs . existsSync ( options . projectPath ) ;
641+ if ( ! projectPathExists && ! options . fakeCodex && ! options . allowFallbackProject ) {
642+ throw new Error ( `Project path does not exist for real-Codex proof: ${ options . projectPath } ` ) ;
643+ }
644+ const projectPath = projectPathExists
645+ ? requestedProjectPath
591646 : fallbackProject ;
592647
593648 try {
@@ -596,6 +651,7 @@ async function main() {
596651 env : {
597652 ...process . env ,
598653 SEO_DUNGEON_BRIDGE_PORT : String ( bridgePort ) ,
654+ SEO_DUNGEON_BRIDGE_STRICT_PORT : '1' ,
599655 SEO_DUNGEON_ALLOWED_ORIGINS : origin ,
600656 ...( options . fakeCodex
601657 ? {
@@ -616,7 +672,10 @@ async function main() {
616672 vite . stdout . on ( 'data' , ( chunk ) => viteOutput . push ( chunk . toString ( ) ) ) ;
617673 vite . stderr . on ( 'data' , ( chunk ) => viteOutput . push ( chunk . toString ( ) ) ) ;
618674
619- await waitForHttp ( `http://127.0.0.1:${ bridgePort } /health` , 'bridge' , bridgeOutput , bridge ) ;
675+ const bridgeHealthResponse = await waitForHttp ( `http://127.0.0.1:${ bridgePort } /health` , 'bridge' , bridgeOutput , bridge ) ;
676+ bridgeHealth = await bridgeHealthResponse . json ( ) ;
677+ assert . equal ( bridgeHealth . ok , true ) ;
678+ assert . equal ( bridgeHealth . supportsRemoteControl , true ) ;
620679 await waitForHttp ( origin , 'vite' , viteOutput , vite ) ;
621680
622681 browser = await chromium . launch ( {
@@ -649,7 +708,7 @@ async function main() {
649708 await page . locator ( '#danger-mode-toggle' ) . click ( ) ;
650709 }
651710 await positionBrowserWindow ( page , options ) ;
652- foregroundBeforeCapture = await assertBrowserForeground ( options , ffmpegOutput ) ;
711+ foregroundBeforeCapture = await assertBrowserForeground ( options , ffmpegOutput , blockerActions ) ;
653712
654713 recorder = startDesktopRecorder ( { capturePath : rawVideoPath , fps : options . fps , ffmpegOutput } ) ;
655714 await new Promise ( ( resolve ) => setTimeout ( resolve , 800 ) ) ;
@@ -680,7 +739,7 @@ async function main() {
680739 fs . writeFileSync ( watchOutputPath , watchResult . stdout , 'utf8' ) ;
681740 assert . equal ( watchResult . code , 0 , watchResult . stdout ) ;
682741 const watchLines = parseJsonLines ( watchResult . stdout ) ;
683- const watchedEvent = watchLines . find ( ( line ) => line . type === 'session-event' ) ;
742+ watchedEvent = watchLines . find ( ( line ) => line . type === 'session-event' ) ;
684743 assert . equal ( watchedEvent ?. event ?. command , options . browserCommand ) ;
685744 assert . equal ( watchedEvent ?. event ?. projectPath , projectPath ) ;
686745
@@ -694,7 +753,7 @@ async function main() {
694753 '--json' ,
695754 '--wait' ,
696755 '--timeout' ,
697- '15000' ,
756+ String ( options . commandTimeoutMs ) ,
698757 '--project' ,
699758 projectPath ,
700759 '--profile' ,
@@ -705,7 +764,7 @@ async function main() {
705764 ] , { bridgeWs, origin, timeoutMs : options . commandTimeoutMs + 3000 } ) ;
706765 fs . writeFileSync ( sendOutputPath , sendResult . stdout , 'utf8' ) ;
707766 assert . equal ( sendResult . code , 0 , sendResult . stdout ) ;
708- const sendJson = JSON . parse ( sendResult . stdout ) ;
767+ sendJson = JSON . parse ( sendResult . stdout ) ;
709768 assert . equal ( sendJson . ok , true ) ;
710769 assert . equal ( sendJson . waitEvent ?. status , 'complete' ) ;
711770 await waitForLedger ( page , new RegExp ( `Remote codex-cli: ${ escapeRegExp ( options . codexCommand ) } ` , 'i' ) , 'desktop proof helper command mirrored' , options . commandTimeoutMs ) ;
@@ -739,11 +798,19 @@ async function main() {
739798 projectPath,
740799 browserCommand : options . browserCommand ,
741800 codexCommand : options . codexCommand ,
801+ commandTimeoutMs : options . commandTimeoutMs ,
742802 usedFallbackProject : projectPath === fallbackProject ,
803+ allowFallbackProject : options . allowFallbackProject ,
743804 fakeCodex : options . fakeCodex ,
805+ codexTransport : bridgeHealth ?. defaultCodexTransport || null ,
806+ codexCliOverride : options . fakeCodex ? process . execPath : ( process . env . SEO_DUNGEON_CODEX_CLI || null ) ,
807+ codexArgsOverride : options . fakeCodex ? `"${ fakeCodexAppServer } "` : ( process . env . SEO_DUNGEON_CODEX_ARGS || null ) ,
808+ bridgeHealth,
744809 minimizedKnownBlockers : options . minimizeKnownBlockers ,
745810 hiddenKnownBlockers : options . hideKnownBlockers ,
746811 blockerProcesses : options . blockerProcesses ,
812+ closeBlockerProcesses : options . closeBlockerProcesses ,
813+ blockerActions,
747814 allowForegroundMismatch : options . allowForegroundMismatch ,
748815 foregroundBeforeCapture,
749816 browserWindow : {
@@ -792,11 +859,23 @@ async function main() {
792859 projectPath,
793860 browserCommand : options . browserCommand ,
794861 codexCommand : options . codexCommand ,
862+ commandTimeoutMs : options . commandTimeoutMs ,
795863 usedFallbackProject : projectPath === fallbackProject ,
864+ allowFallbackProject : options . allowFallbackProject ,
796865 fakeCodex : options . fakeCodex ,
866+ codexTransport : bridgeHealth ?. defaultCodexTransport || null ,
867+ codexCliOverride : options . fakeCodex ? process . execPath : ( process . env . SEO_DUNGEON_CODEX_CLI || null ) ,
868+ codexArgsOverride : options . fakeCodex ? `"${ fakeCodexAppServer } "` : ( process . env . SEO_DUNGEON_CODEX_ARGS || null ) ,
869+ bridgeHealth,
870+ watchedEvent,
871+ sendCommandId : sendJson ?. data ?. commandId || null ,
872+ sendEvent : sendJson ?. data ?. event || null ,
873+ waitEvent : sendJson ?. waitEvent || null ,
797874 minimizedKnownBlockers : options . minimizeKnownBlockers ,
798875 hiddenKnownBlockers : options . hideKnownBlockers ,
799876 blockerProcesses : options . blockerProcesses ,
877+ closeBlockerProcesses : options . closeBlockerProcesses ,
878+ blockerActions,
800879 foregroundBeforeCapture,
801880 browserWindow : {
802881 x : options . browserX ,
0 commit comments