@@ -28,58 +28,15 @@ export interface SshProcessMonitorOptions {
2828 networkInfoPath : string ;
2929 proxyLogDir ?: string ;
3030 logger : Logger ;
31+ // Poll interval for SSH process and file discovery
3132 pollInterval ?: number ;
33+ // Poll interval for network info updates
3234 networkPollInterval ?: number ;
3335 // For port-based SSH process discovery
3436 codeLogDir : string ;
3537 remoteSshExtensionId : string ;
3638}
3739
38- /**
39- * Finds the Remote SSH extension's log file path.
40- */
41- async function findRemoteSshLogPath (
42- codeLogDir : string ,
43- extensionId : string ,
44- ) : Promise < string | undefined > {
45- const logsParentDir = path . dirname ( codeLogDir ) ;
46-
47- // Try extension-specific folder (for VS Code clones like Cursor, Windsurf)
48- try {
49- const extensionLogDir = path . join ( logsParentDir , extensionId ) ;
50- // Node returns these directories sorted already!
51- const files = await fs . readdir ( extensionLogDir ) ;
52- files . reverse ( ) ;
53-
54- const remoteSsh = files . find ( ( file ) => file . includes ( "Remote - SSH" ) ) ;
55- if ( remoteSsh ) {
56- return path . join ( extensionLogDir , remoteSsh ) ;
57- }
58- } catch {
59- // Extension-specific folder doesn't exist, try fallback
60- }
61-
62- try {
63- // Node returns these directories sorted already!
64- const dirs = await fs . readdir ( logsParentDir ) ;
65- dirs . reverse ( ) ;
66- const outputDirs = dirs . filter ( ( d ) => d . startsWith ( "output_logging_" ) ) ;
67-
68- if ( outputDirs . length > 0 ) {
69- const outputPath = path . join ( logsParentDir , outputDirs [ 0 ] ) ;
70- const files = await fs . readdir ( outputPath ) ;
71- const remoteSSHLog = files . find ( ( f ) => f . includes ( "Remote - SSH" ) ) ;
72- if ( remoteSSHLog ) {
73- return path . join ( outputPath , remoteSSHLog ) ;
74- }
75- }
76- } catch {
77- // output_logging folder doesn't exist
78- }
79-
80- return undefined ;
81- }
82-
8340/**
8441 * Monitors the SSH process for a Coder workspace connection and displays
8542 * network status in the VS Code status bar.
@@ -116,6 +73,7 @@ export class SshProcessMonitor implements vscode.Disposable {
11673 ...options ,
11774 proxyLogDir : options . proxyLogDir ,
11875 pollInterval : options . pollInterval ?? 1000 ,
76+ // Matches the SSH update interval
11977 networkPollInterval : options . networkPollInterval ?? 3000 ,
12078 } ;
12179 this . statusBarItem = vscode . window . createStatusBarItem (
@@ -128,7 +86,7 @@ export class SshProcessMonitor implements vscode.Disposable {
12886 * Creates and starts an SSH process monitor.
12987 * Begins searching for the SSH process in the background.
13088 */
131- static start ( options : SshProcessMonitorOptions ) : SshProcessMonitor {
89+ public static start ( options : SshProcessMonitorOptions ) : SshProcessMonitor {
13290 const monitor = new SshProcessMonitor ( options ) ;
13391 monitor . searchForProcess ( ) . catch ( ( err ) => {
13492 options . logger . error ( "Error in SSH process monitor" , err ) ;
@@ -187,16 +145,14 @@ export class SshProcessMonitor implements vscode.Disposable {
187145 while ( ! this . disposed ) {
188146 attempt ++ ;
189147
190- if ( attempt % 10 === 0 ) {
148+ if ( attempt === 1 || attempt % 10 === 0 ) {
191149 logger . debug (
192150 `SSH process search attempt ${ attempt } for host: ${ sshHost } ` ,
193151 ) ;
194152 }
195153
196- // Try port-based discovery first (unique per VS Code window)
197154 const pidByPort = await this . findSshProcessByPort ( ) ;
198155 if ( pidByPort !== undefined ) {
199- logger . info ( `Found SSH process by port (PID: ${ pidByPort } )` ) ;
200156 this . setCurrentPid ( pidByPort ) ;
201157 this . startMonitoring ( ) ;
202158 return ;
@@ -250,16 +206,16 @@ export class SshProcessMonitor implements vscode.Disposable {
250206 const previousPid = this . currentPid ;
251207 this . currentPid = pid ;
252208
253- if ( previousPid !== undefined && previousPid !== pid ) {
209+ if ( previousPid === undefined ) {
210+ this . options . logger . info ( `SSH connection established (PID: ${ pid } )` ) ;
211+ this . _onPidChange . fire ( pid ) ;
212+ } else if ( previousPid !== pid ) {
254213 this . options . logger . info (
255214 `SSH process changed from ${ previousPid } to ${ pid } ` ,
256215 ) ;
257216 this . logFilePath = undefined ;
258217 this . _onLogFilePathChange . fire ( undefined ) ;
259218 this . _onPidChange . fire ( pid ) ;
260- } else if ( previousPid === undefined ) {
261- this . options . logger . info ( `SSH connection established (PID: ${ pid } )` ) ;
262- this . _onPidChange . fire ( pid ) ;
263219 }
264220 }
265221
@@ -349,7 +305,8 @@ export class SshProcessMonitor implements vscode.Disposable {
349305
350306 const content = await fs . readFile ( networkInfoFile , "utf8" ) ;
351307 const network = JSON . parse ( content ) as NetworkInfo ;
352- this . updateStatusBar ( network ) ;
308+ const isStale = ageMs > this . options . networkPollInterval * 2 ;
309+ this . updateStatusBar ( network , isStale ) ;
353310 } catch ( error ) {
354311 logger . debug (
355312 `Failed to read network info: ${ ( error as Error ) . message } ` ,
@@ -363,7 +320,7 @@ export class SshProcessMonitor implements vscode.Disposable {
363320 /**
364321 * Updates the status bar with network information.
365322 */
366- private updateStatusBar ( network : NetworkInfo ) : void {
323+ private updateStatusBar ( network : NetworkInfo , isStale : boolean ) : void {
367324 let statusText = "$(globe) " ;
368325
369326 // Coder Connect doesn't populate any other stats
@@ -409,8 +366,56 @@ export class SshProcessMonitor implements vscode.Disposable {
409366 }
410367
411368 this . statusBarItem . tooltip = tooltip ;
412- statusText += "(" + network . latency . toFixed ( 2 ) + "ms)" ;
369+ const latencyText = isStale
370+ ? `(~${ network . latency . toFixed ( 2 ) } ms)`
371+ : `(${ network . latency . toFixed ( 2 ) } ms)` ;
372+ statusText += latencyText ;
413373 this . statusBarItem . text = statusText ;
414374 this . statusBarItem . show ( ) ;
415375 }
416376}
377+
378+ /**
379+ * Finds the Remote SSH extension's log file path.
380+ */
381+ async function findRemoteSshLogPath (
382+ codeLogDir : string ,
383+ extensionId : string ,
384+ ) : Promise < string | undefined > {
385+ const logsParentDir = path . dirname ( codeLogDir ) ;
386+
387+ // Try extension-specific folder (for VS Code clones like Cursor, Windsurf)
388+ try {
389+ const extensionLogDir = path . join ( logsParentDir , extensionId ) ;
390+ // Node returns these directories sorted already!
391+ const files = await fs . readdir ( extensionLogDir ) ;
392+ files . reverse ( ) ;
393+
394+ const remoteSsh = files . find ( ( file ) => file . includes ( "Remote - SSH" ) ) ;
395+ if ( remoteSsh ) {
396+ return path . join ( extensionLogDir , remoteSsh ) ;
397+ }
398+ } catch {
399+ // Extension-specific folder doesn't exist, try fallback
400+ }
401+
402+ try {
403+ // Node returns these directories sorted already!
404+ const dirs = await fs . readdir ( logsParentDir ) ;
405+ dirs . reverse ( ) ;
406+ const outputDirs = dirs . filter ( ( d ) => d . startsWith ( "output_logging_" ) ) ;
407+
408+ if ( outputDirs . length > 0 ) {
409+ const outputPath = path . join ( logsParentDir , outputDirs [ 0 ] ) ;
410+ const files = await fs . readdir ( outputPath ) ;
411+ const remoteSSHLog = files . find ( ( f ) => f . includes ( "Remote - SSH" ) ) ;
412+ if ( remoteSSHLog ) {
413+ return path . join ( outputPath , remoteSSHLog ) ;
414+ }
415+ }
416+ } catch {
417+ // output_logging folder doesn't exist
418+ }
419+
420+ return undefined ;
421+ }
0 commit comments