@@ -22,6 +22,7 @@ const pty = require("node-pty");
2222const os = require ( "os" ) ;
2323const path = require ( "path" ) ;
2424const which = require ( "which" ) ;
25+ const { execFile} = require ( "child_process" ) ;
2526const NodeConnector = require ( "./node-connector" ) ;
2627
2728const CONNECTOR_ID = "phoenix_terminal" ;
@@ -146,6 +147,7 @@ exports.createTerminal = async function ({id, shell, args, cwd, cols, rows, env}
146147
147148 terminals [ id ] = {
148149 pty : ptyProcess ,
150+ shellPath : shell ,
149151 buffer : "" ,
150152 flushTimer : null ,
151153 paused : false
@@ -341,6 +343,65 @@ exports.getDefaultShells = async function () {
341343 return { shells} ;
342344} ;
343345
346+ /**
347+ * On Windows, node-pty's .process returns the terminal name (e.g. "xterm-256color")
348+ * instead of the actual foreground process. This helper queries the process tree
349+ * via PowerShell's Get-CimInstance to find the deepest child process name.
350+ * Falls back gracefully if PowerShell is unavailable or returns unexpected output.
351+ * @param {number } pid - The shell PID to look up children for
352+ * @returns {Promise<string> } The leaf child process name, or empty string
353+ */
354+ function _getWindowsForegroundProcess ( pid ) {
355+ return new Promise ( ( resolve ) => {
356+ const psCommand = `Get-CimInstance Win32_Process -Filter 'ParentProcessId=${ pid } '` +
357+ ` | Select-Object Name,ProcessId | ConvertTo-Json -Compress` ;
358+ let settled = false ;
359+ function done ( val ) {
360+ if ( ! settled ) {
361+ settled = true ;
362+ resolve ( val ) ;
363+ }
364+ }
365+
366+ // Hard 2-second deadline — don't block the UI waiting for PowerShell
367+ const deadline = setTimeout ( ( ) => done ( "" ) , 2000 ) ;
368+
369+ let child ;
370+ try {
371+ child = execFile ( "powershell.exe" , [
372+ "-NoProfile" , "-NoLogo" , "-Command" , psCommand
373+ ] , { timeout : 2000 , windowsHide : true } , ( err , stdout ) => {
374+ clearTimeout ( deadline ) ;
375+ if ( err || ! stdout || ! stdout . trim ( ) ) {
376+ done ( "" ) ;
377+ return ;
378+ }
379+ try {
380+ let parsed = JSON . parse ( stdout . trim ( ) ) ;
381+ // PowerShell returns a single object if one result, an array if multiple
382+ if ( ! Array . isArray ( parsed ) ) {
383+ parsed = [ parsed ] ;
384+ }
385+ const leaf = parsed . length > 0 ? parsed [ parsed . length - 1 ] : null ;
386+ done ( leaf && typeof leaf . Name === "string" ? leaf . Name : "" ) ;
387+ } catch ( e ) {
388+ done ( "" ) ;
389+ }
390+ } ) ;
391+ } catch ( e ) {
392+ // powershell.exe not found or execFile threw synchronously
393+ clearTimeout ( deadline ) ;
394+ done ( "" ) ;
395+ return ;
396+ }
397+
398+ child . on ( "error" , ( ) => {
399+ clearTimeout ( deadline ) ;
400+ done ( "" ) ;
401+ } ) ;
402+ } ) ;
403+ }
404+
344405/**
345406 * Get foreground process info for a terminal
346407 * @param {Object } params
@@ -352,9 +413,23 @@ exports.getTerminalProcess = async function ({id}) {
352413 if ( ! term ) {
353414 throw new Error ( `Terminal ${ id } not found` ) ;
354415 }
416+
417+ // On Mac/Linux, node-pty .process returns the actual foreground process name
418+ if ( process . platform !== "win32" ) {
419+ return {
420+ process : term . pty . process ,
421+ pid : term . pty . pid
422+ } ;
423+ }
424+
425+ // On Windows, resolve the actual process from the PID tree
426+ const shellPid = term . pty . pid ;
427+ const childName = await _getWindowsForegroundProcess ( shellPid ) ;
428+ // If a child process exists, return it; otherwise return the shell executable name
429+ const processName = childName || path . basename ( term . shellPath || "" ) ;
355430 return {
356- process : term . pty . process ,
357- pid : term . pty . pid
431+ process : processName ,
432+ pid : shellPid
358433 } ;
359434} ;
360435
0 commit comments